Browse Source

Squashed commit of the following:

commit cb3e057dee1f2b29838ab654a526baac1baab7d6
Author: NuclearW <incongruency@gmail.com>
Date:   Fri Mar 1 00:43:57 2013 -0500

    1.4.00 release

commit 4f9628d2e4cde31c8946e9a911ee6f10e1fb6b35
Author: NuclearW <incongruency@gmail.com>
Date:   Fri Mar 1 00:07:30 2013 -0500

    \r -> \n

commit b2ca22e0477c747143b0f08a28a096967ee6ffd7
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 23:53:56 2013 -0500

    Commented-out code shouldn't be done like that.

commit 92f131712cc671e3e616c14a22e22769ef6d6d0b
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 23:45:36 2013 -0500

    More things we missed.

commit 408b03766f6261a03a862a1ab7f5835772feda4a
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 23:20:13 2013 -0500

    Format: util through spout and backup lib

commit d6bd2c29bbb51bee3607247468cfe145d4f38c9e
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 22:50:08 2013 -0500

    The things we missed the first time through...

commit 393f0b889aa1b7011ee81ee7b15413d8824b8cfb
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 22:05:29 2013 -0500

    Formatting: Skills

commit c097a6e188a7b760dd1b4389ed81dca417146b16
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 19:30:12 2013 -0500

    Organize imports.

commit 34c3e74be7eb5f983f21d969e30155c5d82c01c1
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 19:09:45 2013 -0500

    Fixed a missing fallthrough comment from ChatCommand

commit b4a76c9f022a2fd98bdd8f083accfea03becfd71
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 19:09:36 2013 -0500

    Formatting: datatypes.* through events.*

commit 3e57dd41d3265a7c8106c7eb026df926770a4d15
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 17:56:15 2013 -0500

    Fix issue with bad rebase

commit e8c8e06b2971555b7334e49128257e3af6f36892
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 17:35:32 2013 -0500

    Formatting: DatabaseManager, LeaderboardManager, DatabaseUpdateType, and
    PlayerStat

commit 13ecf1cc41f377a12991e357ac10abdcda24d6de
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 17:31:43 2013 -0500

    Format: listeners.* through runnables.*

commit 71686e3c0d96c2dcf25442b91703fadda1ea3bb0
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 17:13:57 2013 -0500

    Format PartyLockCommand

commit d50abed10bf94e1a88df3dc5cc07c259aea920ea
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 16:54:08 2013 -0500

    Format: base through config.*

commit 7004823eeebbae5be7728bf9cafc3b04e57b64cf
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 15:21:40 2013 -0500

    Example of using spaces to align like things

commit 534190cfe2481e466fe459d65628550458cc2993
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 15:12:19 2013 -0500

    Capitalization

commit 5b61d3ba4c8d81e6f358b0cf4f460abfe9798414
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 15:07:43 2013 -0500

    Updated readme, added standards.md

commit 5ec0df70fb82c527420a2f437f27f31bd758f884
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 14:42:16 2013 -0500

    Markdown was here, Creole is a loser

commit 70d557c59d086b6a5fb5e0e63c0c1d8eb4c8d19c
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 13:46:24 2013 -0500

    Move MCStats shading to .metrics.mcstats

commit eb9d67e66b1659d6abd2397ecf403343cfeffdda
Author: GJ <gjmcferrin@gmail.com>
Date:   Thu Feb 28 13:37:37 2013 -0500

    Move ALL the packages!

commit 8ffa9e7b75417b6c7f158613d4b4ffb783dcf2d0
Author: NuclearW <incongruency@gmail.com>
Date:   Thu Feb 28 12:37:12 2013 -0500

    /r/n -> /n
NuclearW 12 years ago
parent
commit
a542d6cf3e
100 changed files with 9114 additions and 8183 deletions
  1. 4 0
      .gitattributes
  2. 1653 1653
      Changelog.txt
  3. 0 28
      README.creole
  4. 47 0
      README.md
  5. 182 182
      pom.xml
  6. 51 51
      src/main/java/com/gmail/nossr50/api/AbilityAPI.java
  7. 5 5
      src/main/java/com/gmail/nossr50/api/ChatAPI.java
  8. 335 335
      src/main/java/com/gmail/nossr50/api/ExperienceAPI.java
  9. 174 174
      src/main/java/com/gmail/nossr50/api/PartyAPI.java
  10. 29 28
      src/main/java/com/gmail/nossr50/api/SpoutHudAPI.java
  11. 53 53
      src/main/java/com/gmail/nossr50/api/SpoutToolsAPI.java
  12. 77 73
      src/main/java/com/gmail/nossr50/chat/ChatManager.java
  13. 70 70
      src/main/java/com/gmail/nossr50/chat/ChatMode.java
  14. 80 0
      src/main/java/com/gmail/nossr50/commands/McabilityCommand.java
  15. 89 0
      src/main/java/com/gmail/nossr50/commands/McgodCommand.java
  16. 113 114
      src/main/java/com/gmail/nossr50/commands/McmmoCommand.java
  17. 33 0
      src/main/java/com/gmail/nossr50/commands/McnotifyCommand.java
  18. 80 0
      src/main/java/com/gmail/nossr50/commands/McrefreshCommand.java
  19. 69 0
      src/main/java/com/gmail/nossr50/commands/XprateCommand.java
  20. 0 154
      src/main/java/com/gmail/nossr50/commands/admin/AddlevelsCommand.java
  21. 0 151
      src/main/java/com/gmail/nossr50/commands/admin/AddxpCommand.java
  22. 0 86
      src/main/java/com/gmail/nossr50/commands/admin/HardcoreCommand.java
  23. 0 89
      src/main/java/com/gmail/nossr50/commands/admin/McgodCommand.java
  24. 0 80
      src/main/java/com/gmail/nossr50/commands/admin/McrefreshCommand.java
  25. 0 150
      src/main/java/com/gmail/nossr50/commands/admin/MmoeditCommand.java
  26. 0 169
      src/main/java/com/gmail/nossr50/commands/admin/SkillresetCommand.java
  27. 0 91
      src/main/java/com/gmail/nossr50/commands/admin/VampirismCommand.java
  28. 0 69
      src/main/java/com/gmail/nossr50/commands/admin/XprateCommand.java
  29. 26 26
      src/main/java/com/gmail/nossr50/commands/chat/AdminChatCommand.java
  30. 89 88
      src/main/java/com/gmail/nossr50/commands/chat/ChatCommand.java
  31. 48 48
      src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java
  32. 45 0
      src/main/java/com/gmail/nossr50/commands/database/McpurgeCommand.java
  33. 51 0
      src/main/java/com/gmail/nossr50/commands/database/McremoveCommand.java
  34. 51 51
      src/main/java/com/gmail/nossr50/commands/database/MmoupdateCommand.java
  35. 154 0
      src/main/java/com/gmail/nossr50/commands/experience/AddlevelsCommand.java
  36. 152 0
      src/main/java/com/gmail/nossr50/commands/experience/AddxpCommand.java
  37. 150 0
      src/main/java/com/gmail/nossr50/commands/experience/MmoeditCommand.java
  38. 169 0
      src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java
  39. 85 0
      src/main/java/com/gmail/nossr50/commands/hardcore/HardcoreCommand.java
  40. 90 0
      src/main/java/com/gmail/nossr50/commands/hardcore/VampirismCommand.java
  41. 42 0
      src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java
  42. 34 0
      src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java
  43. 49 50
      src/main/java/com/gmail/nossr50/commands/party/PartyChangePasswordCommand.java
  44. 154 0
      src/main/java/com/gmail/nossr50/commands/party/PartyCommand.java
  45. 56 0
      src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java
  46. 37 0
      src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java
  47. 55 55
      src/main/java/com/gmail/nossr50/commands/party/PartyExpShareCommand.java
  48. 30 0
      src/main/java/com/gmail/nossr50/commands/party/PartyHelpCommand.java
  49. 102 102
      src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java
  50. 72 0
      src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java
  51. 101 0
      src/main/java/com/gmail/nossr50/commands/party/PartyItemShareCommand.java
  52. 96 95
      src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java
  53. 49 0
      src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java
  54. 97 97
      src/main/java/com/gmail/nossr50/commands/party/PartyLockCommand.java
  55. 38 0
      src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java
  56. 58 0
      src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java
  57. 48 48
      src/main/java/com/gmail/nossr50/commands/party/PartySubcommandType.java
  58. 218 218
      src/main/java/com/gmail/nossr50/commands/party/PtpCommand.java
  59. 58 58
      src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java
  60. 0 80
      src/main/java/com/gmail/nossr50/commands/player/McabilityCommand.java
  61. 0 34
      src/main/java/com/gmail/nossr50/commands/player/McnotifyCommand.java
  62. 60 60
      src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java
  63. 50 50
      src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java
  64. 115 115
      src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java
  65. 105 105
      src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java
  66. 104 104
      src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java
  67. 128 128
      src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java
  68. 66 67
      src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java
  69. 123 123
      src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java
  70. 180 180
      src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java
  71. 149 148
      src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java
  72. 166 164
      src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java
  73. 159 159
      src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java
  74. 107 0
      src/main/java/com/gmail/nossr50/commands/skills/SkillGuideCommand.java
  75. 122 121
      src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java
  76. 117 117
      src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java
  77. 165 165
      src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java
  78. 146 146
      src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java
  79. 103 103
      src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java
  80. 29 29
      src/main/java/com/gmail/nossr50/commands/spout/MchudCommand.java
  81. 54 53
      src/main/java/com/gmail/nossr50/commands/spout/SpoutCommand.java
  82. 64 64
      src/main/java/com/gmail/nossr50/commands/spout/XplockCommand.java
  83. 3 3
      src/main/java/com/gmail/nossr50/config/AutoUpdateConfigLoader.java
  84. 2 2
      src/main/java/com/gmail/nossr50/config/Config.java
  85. 80 79
      src/main/java/com/gmail/nossr50/config/ConfigLoader.java
  86. 101 94
      src/main/java/com/gmail/nossr50/config/mods/CustomArmorConfig.java
  87. 120 117
      src/main/java/com/gmail/nossr50/config/mods/CustomBlockConfig.java
  88. 84 82
      src/main/java/com/gmail/nossr50/config/mods/CustomEntityConfig.java
  89. 105 101
      src/main/java/com/gmail/nossr50/config/mods/CustomToolConfig.java
  90. 10 9
      src/main/java/com/gmail/nossr50/config/party/ItemWeightConfig.java
  91. 65 0
      src/main/java/com/gmail/nossr50/config/spout/SpoutConfig.java
  92. 287 285
      src/main/java/com/gmail/nossr50/config/treasure/TreasureConfig.java
  93. 231 174
      src/main/java/com/gmail/nossr50/database/DatabaseManager.java
  94. 0 8
      src/main/java/com/gmail/nossr50/database/DatabaseUpdate.java
  95. 47 45
      src/main/java/com/gmail/nossr50/database/LeaderboardManager.java
  96. 0 45
      src/main/java/com/gmail/nossr50/database/commands/McpurgeCommand.java
  97. 0 51
      src/main/java/com/gmail/nossr50/database/commands/McremoveCommand.java
  98. 0 21
      src/main/java/com/gmail/nossr50/database/runnables/SQLReconnect.java
  99. 8 0
      src/main/java/com/gmail/nossr50/datatypes/database/DatabaseUpdateType.java
  100. 11 11
      src/main/java/com/gmail/nossr50/datatypes/database/PlayerStat.java

+ 4 - 0
.gitattributes

@@ -0,0 +1,4 @@
+* text
+
+*.png binary
+*.wav binary

+ 1653 - 1653
Changelog.txt

@@ -1,1661 +1,1661 @@
-Changelog:
-Versions without changelogs probably had very small misc fixes, like tweaks to the source code
-
-Key:
-  + Addition
-  = Fix
-  ! Change
-  - Removal
-
-Version 1.4.00-dev
- + Added new Child Skill - Smelting!
- + Added a version check, admins will get notified when a new version is available!
- + Added new cancellable McMMOPlayerDisarmEvent for Citizens compatibility - fires whenever a player is disarmed.
- + Added config options for Hylian Luck skill
- + Added display values to Unarmed command for Iron Grip
- + Added '/party create <name>' command, use this to create a party
- + Added '/party disband' command, kicks out all members and deletes the party
- + Added '/ptp toggle' command, to disable party teleportation.
- + Added '/ptp accept' and '/ptp acceptall' commands
- + Added an automatic party kick when a party member has been offline for 7 days (default)
- + Added a permission to allow friendly fire in parties, both attacker and defender must have it for friendly fire to occur
- + Added timeout on party teleport requests
- + Added XP bonus for Archery based on distance from shooter to target
- + Added ability to config Hylian Luck drops through treasures.yml
- + Added party XP sharing, when more party members are near the share bonus increases.
- + Added vanilla XP boost for Fishing - includes permissions, config options, etc
- + Added particle effect for bleeding
- + Added methods to check if a player is in party or admin chat to the ChatAPI
- + Added /mcpurge functionality for Flatfile users
- + Added basic support for Mo' Creatures (and other entity mods) - specify mob info in entities.yml
- + Added Shears, Buckets, Fishing Rods, Flint & Steel, Carrot Sticks, and Bows to the list of items that can be Salvaged
- + Added the "wait" music disc to the default fishing treasures
- + Added "Chinese (Taiwan)" localization files (zh_TW)
- + Added '/hardcore' and '/vampirism' commands for toggling these modes on or off.
- + Added Block Cracker to Unarmed's Berserk, turn smooth brick into cracked smooth brick
- + Added config option to disable automatic zip backups.
- + Added particle effects for many abilities.
- + Added '/mcnotify' command to toggle ability notifications on/off
- + Added ability for config files to automatically update with new keys, and prune out old ones
- + Added config option to make .new config files instead over writing over old ones when updating
- + Added "Holy Hound" ability to Taming
- + Added "Shroom Thumb" ability to Herbalism
- + Added child.yml config file to choose parents for child skills
- + Added '/party itemshare <NONE | EQUAL | RANDOM>' command to choose party item share mode
- + Added '/party itemshare <loot | mining | herbalism | woodcutting> <true | false>' command to control items that are shared
- + Added itemweights.yml file to determine which items are more valuable for party itemshare
- = Fixed Green Thumb on wheat not working properly at rank 4
- = Fixed Green Thumb and Green Terra consuming twice the amount of seed needed
- = Fixed Green Terra not also checking Green Thumb permissions
- = Fixed bug where splash potions could raise a player's unarmed level
- = Fixed bug where fired arrows could raise skill levels other than Archery
- = Fixed /ptp telporting the target to the player, rather than the other way around.
- = Fixed Impact reducing the durability of non-armor equipped blocks
- = Fixed Impact reducing improperly the durability of armors (as a consequence it is now more effective)
- = Fixed multiple commands not working properly on offline players
- = Fixed /mmoedit not giving feedback when modifying another players stats
- = Fixed the guide usage string showing up every time /skillname was called
- = Fixed Spout not being able to precache our resources properly, and therefore making our XP bars fail
- = Fixed Spout config files loading / generating when they shouldn't have
- = Fixed mod config files loading / generating when they shouldn't have
+Changelog:
+Versions without changelogs probably had very small misc fixes, like tweaks to the source code
+
+Key:
+  + Addition
+  = Fix
+  ! Change
+  - Removal
+
+Version 1.4.00
+ + Added new Child Skill - Smelting!
+ + Added a version check, admins will get notified when a new version is available!
+ + Added new cancellable McMMOPlayerDisarmEvent for Citizens compatibility - fires whenever a player is disarmed.
+ + Added config options for Hylian Luck skill
+ + Added display values to Unarmed command for Iron Grip
+ + Added '/party create <name>' command, use this to create a party
+ + Added '/party disband' command, kicks out all members and deletes the party
+ + Added '/ptp toggle' command, to disable party teleportation.
+ + Added '/ptp accept' and '/ptp acceptall' commands
+ + Added an automatic party kick when a party member has been offline for 7 days (default)
+ + Added a permission to allow friendly fire in parties, both attacker and defender must have it for friendly fire to occur
+ + Added timeout on party teleport requests
+ + Added XP bonus for Archery based on distance from shooter to target
+ + Added ability to config Hylian Luck drops through treasures.yml
+ + Added party XP sharing, when more party members are near the share bonus increases.
+ + Added vanilla XP boost for Fishing - includes permissions, config options, etc
+ + Added particle effect for bleeding
+ + Added methods to check if a player is in party or admin chat to the ChatAPI
+ + Added /mcpurge functionality for Flatfile users
+ + Added basic support for Mo' Creatures (and other entity mods) - specify mob info in entities.yml
+ + Added Shears, Buckets, Fishing Rods, Flint & Steel, Carrot Sticks, and Bows to the list of items that can be Salvaged
+ + Added the "wait" music disc to the default fishing treasures
+ + Added "Chinese (Taiwan)" localization files (zh_TW)
+ + Added '/hardcore' and '/vampirism' commands for toggling these modes on or off.
+ + Added Block Cracker to Unarmed's Berserk, turn smooth brick into cracked smooth brick
+ + Added config option to disable automatic zip backups.
+ + Added particle effects for many abilities.
+ + Added '/mcnotify' command to toggle ability notifications on/off
+ + Added ability for config files to automatically update with new keys, and prune out old ones
+ + Added config option to make .new config files instead over writing over old ones when updating
+ + Added "Holy Hound" ability to Taming
+ + Added "Shroom Thumb" ability to Herbalism
+ + Added child.yml config file to choose parents for child skills
+ + Added '/party itemshare <NONE | EQUAL | RANDOM>' command to choose party item share mode
+ + Added '/party itemshare <loot | mining | herbalism | woodcutting> <true | false>' command to control items that are shared
+ + Added itemweights.yml file to determine which items are more valuable for party itemshare
+ = Fixed Green Thumb on wheat not working properly at rank 4
+ = Fixed Green Thumb and Green Terra consuming twice the amount of seed needed
+ = Fixed Green Terra not also checking Green Thumb permissions
+ = Fixed bug where splash potions could raise a player's unarmed level
+ = Fixed bug where fired arrows could raise skill levels other than Archery
+ = Fixed /ptp telporting the target to the player, rather than the other way around.
+ = Fixed Impact reducing the durability of non-armor equipped blocks
+ = Fixed Impact reducing improperly the durability of armors (as a consequence it is now more effective)
+ = Fixed multiple commands not working properly on offline players
+ = Fixed /mmoedit not giving feedback when modifying another players stats
+ = Fixed the guide usage string showing up every time /skillname was called
+ = Fixed Spout not being able to precache our resources properly, and therefore making our XP bars fail
+ = Fixed Spout config files loading / generating when they shouldn't have
+ = Fixed mod config files loading / generating when they shouldn't have
  = Fixed bug where Green Terra could activate on crops that weren't fully grown.
- = Fixed several typos relating to locale string display
+ = Fixed several typos relating to locale string display
  = Fixed bug where all skill guide headers appeared as "Skillname Guide Guide"
- = Fixed bug where Impact was applied incorrectly due to an inverted method call
- = Fixed bug where Impact improperly determined the defender's armor
- = Fixed a bug which made it impossible to join other players' parties
- = Fixed ArrayIndexOutOfBoundsException resulting from being unranked in a skill when using FlatFile
+ = Fixed bug where Impact was applied incorrectly due to an inverted method call
+ = Fixed bug where Impact improperly determined the defender's armor
+ = Fixed a bug which made it impossible to join other players' parties
+ = Fixed ArrayIndexOutOfBoundsException resulting from being unranked in a skill when using FlatFile
  = Fixed Woodcutting accidentally using Mining double drop values.
- = Fixed Hylian Luck not removing the block-placed flag from flowers.
- = Fixed Hylian Luck not checking the block-placed flag on flowers.
- = Fixed Leaf Blower not respecting the unlock level set in advanced.yml
- = Fixed abilities activating with the wrong tool in hand
- = Fixed Experience.Gains.Mobspawners.Enabled not being used correctly (the check was inverted)
- = Fixed bug where Iron Grip was using the attacker's skill values rather than the defender's.
- = Fixed a bug where /party kick would trigger the PartyChangeEvent for the wrong player
- = Fixed /party kick not working on offline players
- = Fixed a bug where party join messages weren't displayed
- = Fixed a bug where a new party leader wasn't appointed, after the previous party leader left
- = Fixed a bug where Disarm and Deflect had wrong values
- = Fixed Magic Hunter (Fishing ability) favoring certain enchants
- ! Changed our custom chat events to be async
- ! Changed some config value key names regarding double drops and XP - make sure you copy any custom values to your new config after updating.
- ! Changed Green Terra blocks to be determined via permissions instead of the config file
- ! Config files are now backed up even when running in SQL mode
- ! Changed /p and /a to use /partychat and /adminchat as the default command name. The use of /p, /pc, /a, and /ac is still supported.
- ! We're now using Bukkit sounds instead of Spout sounds.
+ = Fixed Hylian Luck not removing the block-placed flag from flowers.
+ = Fixed Hylian Luck not checking the block-placed flag on flowers.
+ = Fixed Leaf Blower not respecting the unlock level set in advanced.yml
+ = Fixed abilities activating with the wrong tool in hand
+ = Fixed Experience.Gains.Mobspawners.Enabled not being used correctly (the check was inverted)
+ = Fixed bug where Iron Grip was using the attacker's skill values rather than the defender's.
+ = Fixed a bug where /party kick would trigger the PartyChangeEvent for the wrong player
+ = Fixed /party kick not working on offline players
+ = Fixed a bug where party join messages weren't displayed
+ = Fixed a bug where a new party leader wasn't appointed, after the previous party leader left
+ = Fixed a bug where Disarm and Deflect had wrong values
+ = Fixed Magic Hunter (Fishing ability) favoring certain enchants
+ ! Changed our custom chat events to be async
+ ! Changed some config value key names regarding double drops and XP - make sure you copy any custom values to your new config after updating.
+ ! Changed Green Terra blocks to be determined via permissions instead of the config file
+ ! Config files are now backed up even when running in SQL mode
+ ! Changed /p and /a to use /partychat and /adminchat as the default command name. The use of /p, /pc, /a, and /ac is still supported.
+ ! We're now using Bukkit sounds instead of Spout sounds.
  ! It is now possible to use a negative number for Max_Level in treasures.yml to not use a maximum level, changed default file accordingly
  ! A Fishing catch will now always contains a fish even if a treasure is found
- ! Changed how Berserk handles not picking up items to avoid listening to PlayerPickupItemEvent
+ ! Changed how Berserk handles not picking up items to avoid listening to PlayerPickupItemEvent
  ! Moved Hylian Luck into a separate listener since it actually cancels the event and shouldn't just be on MONITOR.
- ! Changed how Tree Feller is handled, it should now put less stress on the CPU
- ! Changed Tree Feller to work on huge mushrooms
- ! Changed Fisherman's Diet and Farmer's Diet to use two seperate config values
- ! Major refactoring - please take note, this WILL break any mcMMO-related plugin not properly hooking into the API.
- ! Changed the way party commands work, use /party ? to check how to use the new commands
- ! Changed McMMOChatEvent to contain the plugin that the event originated from.
- ! Changed Excavation to have individual XP values for each block type, rather than a base XP value.
- ! Changed the way party teleportation works. When using /ptp, the target player needs to confirm the teleport before it takes place. (Configurable)
- ! Changed BeastLore: Now also displays offline player names
- ! Changed backup task to include ALL config files
- ! Deprecated most functions in ExperienceAPI, replaced them with identical versions that use a String for the SkillName rather than the SkillType enum values
- ! Changed Super Breaker & Giga Drill Breaker to be an enchantment-based boost, rather than an instabreak. Option exists in hidden.yml to change this to an potion-based buff.
- ! Changed locales to fall back on English when translated strings cannot be found.
- - Removed Party "master/apprentice" system. Replaced with the new party XP share feature.
- - Removed unused "healthbar" files from the resources
- - Removed config options for disabling commands from the config.yml. This should instead be done through permissions.
- - Removed /mcc command. Replaced with /mcmmo [?|help|commands]
+ ! Changed how Tree Feller is handled, it should now put less stress on the CPU
+ ! Changed Tree Feller to work on huge mushrooms
+ ! Changed Fisherman's Diet and Farmer's Diet to use two seperate config values
+ ! Major refactoring - please take note, this WILL break any mcMMO-related plugin not properly hooking into the API.
+ ! Changed the way party commands work, use /party ? to check how to use the new commands
+ ! Changed McMMOChatEvent to contain the plugin that the event originated from.
+ ! Changed Excavation to have individual XP values for each block type, rather than a base XP value.
+ ! Changed the way party teleportation works. When using /ptp, the target player needs to confirm the teleport before it takes place. (Configurable)
+ ! Changed BeastLore: Now also displays offline player names
+ ! Changed backup task to include ALL config files
+ ! Deprecated most functions in ExperienceAPI, replaced them with identical versions that use a String for the SkillName rather than the SkillType enum values
+ ! Changed Super Breaker & Giga Drill Breaker to be an enchantment-based boost, rather than an instabreak. Option exists in hidden.yml to change this to an potion-based buff.
+ ! Changed locales to fall back on English when translated strings cannot be found.
+ - Removed Party "master/apprentice" system. Replaced with the new party XP share feature.
+ - Removed unused "healthbar" files from the resources
+ - Removed config options for disabling commands from the config.yml. This should instead be done through permissions.
+ - Removed /mcc command. Replaced with /mcmmo [?|help|commands]
  - Removed options to allow Mining & Excavation without a tool due to the changes to their abilities
-
-Version 1.3.14
- + Added new Hylian Luck skill to Herbalism.
+
+Version 1.3.14
+ + Added new Hylian Luck skill to Herbalism.
  = Fixed a memory leak involving mob tracking
- - Removed extra durability loss from Leaf Blower
-
-Version 1.3.13
- + Added task & command to prune old and powerless users from the SQL database.
- + Added Craftbukkit 1.4.6 / 1.4.7 compatibility
- + Added new /mcrank command for showing a players leader board ranking for all skills in one place
- + Added a configurable durability cap for ArmorImpact to advanced.yml
- + Added the version number to /mcmmo
- + Added bats, giants, witches, withers, and wither skeletons to the mcMMO combat experience list, and makes their experience drops configurable
- + Added the ability to track mobs spawned by mob spawners or the Taming ability when the chunks they are in unload and reload
- + Added wooden button to the list of items that shouldn't trigger abilities
- + Added a new feature to fishing. Players will have +10% chance of finding enchanted items when fishing while it's raining
- + Added displaying bonus perks on skill commands
- + Added config option to disable gaining Acrobatics XP from dodging lightning
- + Added missing skill guides. They're finally here!
- + Added more localization 
- + Added a very secret easter egg
- = Fix issue with Sand/Gravel tracking
- = Fix possible NPE when using the PartyAPI to add a player to a party that doesn't exist.
- = Fix mcremove command for mySQL
- = Fix a java.io.FileNotFoundException when using SQL
- = Impact now works with mobs wearing armor
- = Fixed issue with Tree Feller dropping player-placed blocks
- = Fixed issue with missing default cases from several switch/case statements
- = Fixed issue with Mining using actual skill level rather than max skill level
- = Fixed some issues with static access
- = Fixed ItemStack deprecation issues
- = Fixed Async deprecation issues
- = Fixed a bug with MySQL databases (non-alphanumeric characters preventing MySQL access)
- = Fixed a bug where the /skillreset command was broken
- = Fixed a bug where skill commands displaying .x% instead of 0.x%
- = Fixed a bug Unbreaking enchantments being ignored when using Treefelling and when hit by Armor Impact
- = Fixed a bug where only 1 diamond was needed to fully repair a broken item: Repaired the Repair skill!
- = Fixed a bug where a infinite loop of errors caused by mySQL database could cause the server to crash
- = Fixed a bug where PartyChangeEvent was fired even when a player isn't able to change parties
- = Fixed a bug which caused advanced.yml not to work for Swords
- = Fixed a bug which caused advanced.yml not to respect every MaxChance node
- = Fixed a bug where GreenThumb_StageChange wasn't read from advanced.yml
- = Fixed a bug where Repair would remove enchantments but the glow effect remained
- = Fixed a bug where dropped items did not retain custom NBT data
- = Fixed a bug which caused a potentially infinite recursion in a btree structure
- = Fixed a NPE with custom blocks
- = Fixed a bug with Blast Mining never dropping debris blocks
- = Fixed a bug with Blast Mining incorrectly handling reduced TNT damage
- = Fixed a bug with conflicting fishing enchantments
- = Fixed a bug where triple drops wouldn't happen
- = Fixed a bug which caused fishing to ignore max/min levels in treasures.yml
- = Fixed a bug where treefeller affected player-placed blocks
- = Fixed bug where Skull Splitter would be applied twice.
- ! GJ stopped being a lazy slacker and got stuff done
- ! Nossr50 actually committed something
- ! Changed code that uses SpoutPlugin to make it compatible with the latest version
- ! Reimplemented skill level and power level caps.
- ! Moved Arcane Forging and Fishing setting from config.yml to advanced.yml
- ! Overall SQL query improvements
- ! Reduced number of SQL queries for mcTop command from 11 to 1, speeding it up immensely
- ! Changed FFS Leaderboards to hold information in memory rather than doing IO work (optimizations)
- ! Improved chunk conversion (less errors)
- ! Changed Fishing Treasure Hunter, chance has increased and now actually is level dependent
- ! Indexed most used mySQL columns for faster queries
- - Removed dead code relating to null profiles
- - Removed unused imports
- - Removed ChunkletUnloader and dependents, since they are no longer necessary.
-
-Version 1.3.12
- + Added Craftbukkit 1.4.5 compatibility
- + Added the new 1.3.2 items, xp and double drops for Cocoa beans & Emeralds, EnderChest to the list of blocks that shouldn't trigger abilities
- + Added new items from Minecraft 1.4 to Herbalism (potatoes & carrots)
- + Added new configuration file for advanced users.
- + Added new permission nodes to greenthumb for the 1.4 items
- + Added new mobs from Minecraft 1.4 checks for every ability
- + Added new active ability for Repair: Salvage
- + Added options to 'config.yml' configure shake chance
- + Added the option to negate experience earned for Herbalism while in a minecart to prevent afk leveling
- + Added Green thumb now converts cobble walls to mossy cobble walls
- + Added beacons and anvils to list of blocks that don't trigger abilities
- + Added a configuration option to disable experience gains when in a minecraft for Acrobatics and Herbalism, to prevent AFK leveling
- + Added a new passive ability for Fishing, Fishermans diet. Increases hunger restored from fish
- + Added a feature to display all active perks on login
- ! Changed Fishing, Shake drops changed from guaranteed to based upon fishing level and perks
- ! Changed Woodcutting, the amount of experience earned when using Tree Feller on jungle trees has increased
- ! Changed Herbalism double drop rates for melons and netherwart
- ! Changed filesystem usage, it's reduced a lot. Should help reduce lag on larger servers
- ! Changed database connection handling. Support for aggressive connection timeouts, with exponential backoff for multiple failures
- ! Changed Cobblestone walls are now mossy-able with Greenthumb
- ! Changed the skull drop rates of the shake ability to 3%
- = Fixed a NPE when Citizens perform certain tasks
- = Fixed a NPE with Woodcutting, excessive null chunk before earning Woodcutting experience
- = Fixed a NPE related to skill cooldowns
- = Fixed a NPE when a players profile was null
- = Fixed a NPE involving certain explosions
- = Fixed a dupe bug when for players who were using a 'nuker' client
- = Fixed a dupe bug where pistons were used to dupe ores
- = Fixed a dupe bug with Salvage when players were in Creative mode
- = Fixed a bug where the player was displayed an incorrect cooldown time
- = Fixed a bug where players could earn experience when they were dealing 0 damage
- = Fixed a bug where players could get double drops from mossified Cobblestone
- = Fixed a bug where Herablism magically converted potatoes to carrots
- = Fixed a bug where you couldn't modify the stats of offline players
- = Fixed a bug where treefeller didn't work properly on tree's with side-way logs
- = Fixed a bug where the Arcane forging downgrade chance should've been 0, but actually wasn't
- = Fixed a bug where Fishing would sometimes give items with empty enchantments
- = Fixed a bug where the lucky perk for Fishing was actually an unlucky perk
- - Removed nothing
-
-Version 1.3.11
- ! Changed axes to start with 1 durability damage instead of 5, gain 1 durability damage every 50 levels instead of 30, and only have a 25% chance on hit to damage armor (per armor piece)
- + Added compatibility with bow-wielding NPCs from Citizens/NPC mods
- + Added compatibility for pvp-prevention plugins for Serrated Strikes
- = Fixed bug where mcMMO could throw NPE errors if trees cut down were from a custom mod and had an id of 17
- = Fixed dupe bug where mcMMO would ignore other block-protection plugins for various abilities
- = Fixed NPE with hardcore mode's vampirism
- 
-Version 1.3.10
- + Added 1.3.1 compatibility
- + Added permission node for Iron Grip ability (mcmmo.ability.unarmed.irongrip)
- + Added ability for custom blocks to drop a range of items.
- + Added Ability API functions
- + Added 50% & 150% XP boost perks
- + Added "lucky" perk for donors
- = Fixed /inspect not working on offline players
- = Fixed custom blocks, tools and armors not loading properly
- = Fixed duplication bug with sticky pistons
- = Fixed "GenericLabel belonging to mcMMO..." message
- = Fixed menu exit button not working
- = Fixed Repair enchant downgrade not working
- = Fixed NPE caused by Spout players after a /reload
- = Fixed ConcurrentModificationException on world unload
- = Fixed players never being removed from memory (memory leak)
- = Fixed admin chat being seen by everyone
- = Fixed issue with UTFDataFormatException occurring on occasion when trying to load Chunklets
- = Fixed ArrayIndexOutOfBounds error caused when trying to use /xplock after logging in but before gaining XP
- = Fixed custom tools not properly respecting the Ability_Enabled flag.
- = Fixed "lower tool" messages still being displayed even when ability messages are disabled.
- = Fixed custom blocks not dropping the proper item with Super Breaker when Silk Touch is used
- = Fixed custom woodcutting blocks throwing errors.
- = Fixed possible ClassCastException from catching something other than a mob when using the Shake Mob skill
- ! Changed the format by which Chunklets are stored to be much smaller, and much faster to load
- ! Optimized how player placed blocks are tracked
-
-Version 1.3.09
- + Added compatibility with AntiCheat (Which I highly recommend to prevent cheating)
- + Added several permission nodes to give individual users special perks (Double/Triple/Quadruple XP)
- + Added reduced cooldown permission nodes as special perks (1/4, 1/3, 1/2 cooldown)
- + Added increased activation time permissions nodes as special perks (+4, +8, and +12 seconds)
- + Added API for plugins to add custom tools directly via Spout - repair / abilities do not work ATM
- + Added offline party members to the list displayed by /party
- + Added possibility to kick offline members from parties
- = Fixed bug that would cause a NPE for players that had no parties
- = Fixed Vampirism not notifying the correct amount of stolen levels
- = Fixed bug with Acrobatics not saving you from deadly falls
- = Fixed /mcremove being applied only after a reload
- = Fixed Archery PVE disablement not working properly
- = Fixed possible NPE when a projectile is shot by a dispenser or doesn't have any shooter
- = Fixed issue with NoCheatPlus and Serrated Strikes / Skull Splitter (fight.noswing)
- = Fixed tiny memory leak concerning Archery
- = Fixed bug where you could receive Archery XP from Potions
- = Fixed bug where Chunklets for the < 64 y coordinates would not be properly loaded
- = Fixed exploit with block duplication via piston pushing
- = Fixed bug with falling sand/gravel not being tracked
- = Fixed bug with Tree Feller not working with custom axes
- = Fixed bug with locale strings when trying to teleport to a non-existent player
- = Fixed bug with Tree Feller changing durability before checking for axe splintering
- = Fixed bug with Repair Mastery permission due to typo
- = Fixed bug with repairing items that use metadata
- = Fixed bug with Chunklets not being reloaded on /reload
- = Fixed possible NPE when falling with no item in hand
- ! API methods can now only be used in a static way
- ! Arrows shot from a bow having the Infinity enchantment can no longer be retrieved
- ! Arrows that aren't shot by an entity are now able to be dodged (currently only from dispensers)
- ! Changed Spout settings to be in their own config file (spout.yml)
- ! Changed file format for parties (parties.yml), previous files are no longer used
- ! Changed mcMMO to inform on corrupt Chunklets and make new ones
-
-Version 1.3.08
- + Added more notifications about Vampirism and Hardcore mode on player death
- + Added information about Hardcore mode when joining a server running Hardcore mode
- + Added new hidden.yml inside the jar for very sensitive config options for advanced users
- + Added option to disable Chunklets for servers which do not have doubledrops and do not care about xp farming
- + Added new "Max_Seconds" setting in config.yml to limit the max time of abilities
- + Added new repair configs to allow customization of the repair skill
- + Added message to inform users about hardcore mode on login
- = Fixed exploit where you could gain tons of Acrobatics XP from spamming Ender Pearls
- = Fixed normal pistons marking a block as user-placed on retract if it wasn't a sticky piston (thanks turt2live!)
- = Fixed handling of the Unbreaking enchantment so that tools are actually damaged as they should now
- = Fixed hurting pet cats with serrated strikes
- ! Changed Hardcore Vampirism to require the victim to have at least half the skill level of the killer in order for vampirism to proc (this is to avoid exploitation)
- ! Changed Hardcore Vampirism to steal a minimum of 1 skill level from a player no matter the percentage
- ! Changed Hardcore & Vampirism to not be executed if percentages were set to zero or below
- ! Changed Vampirism to actually remove stats from the victim
- ! Changed Vampirism to inform the victim of their stat loss
- ! Changed Mining to allow Silk Touch to work again since the dupe exploit has been fixed.
- ! Changed Metrics to also report if the server uses plugin profiling
- - Removed level and item settings from Repair skill in config.yml
-
-Version 1.3.07
- + Added ability to gain XP from custom blocks. Enable custom blocks in the config file, then enter the data in the blocks.yml file.
- + Added ability to gain XP with custom tools. Enable custom tools in the config file, then enter the data in the tools.yml file.
- + Added ability to repair custom tools. Enable custom tools in the config file, then enter the data in the tools.yml file.
- + Added ability to repair custom armor. Enable custom armor in the config file, then enter the data in the armor.yml file.
- + Added functionality which makes a new folder in all world files "mcmmo_data" to store player placed block information in
- + Added new configurable Hardcore mode functionality to mcMMO
- + Added new configurable Vampirism PVP stat leech for Hardcore mode
- + Added new bypass permission node for the negative penalties of Hardcore mode 'mcmmo.bypass.hardcoremode'
- + Added configurable level curve multiplier which allows for tweaking the steepness of the XP needed to level formula
- + Added a permission node for Archery bonus damage
- + Added a permission node for Greater Impact ability
- + Added permission nodes for Treasure & Magic Hunter for Fishing
- + Added a permission node for Farmer's Diet
- + Added config options for enabling/disabling specific double drops
- + Added automatic zip backup of flatfile database & config files
- + Added config options to enable/disable specific skills for PVP & PVE
- = Fixed bug where Tree Feller was looking at the wrong blocks for determining how much to take down.
- = Fixed bug where Green Terra consumed seeds even on Mossy Stone Brick
- = Fixed bug where the client didn't reflect the Stone Brick to Mossy Stone Brick change
- = Fixed bug where an arrow could bounce off entities on daze proc
- = Fixed bug where a player could gain Acrobatics experience while riding a cart
- = Fixed /party not working properly with 2 arguments
- = Fixed /party not showing properly the member list
- = Fixed /ability not checking the right permission
- = Fixed rare NPE on /party command
- = Fixed Arrow Retrieval dropping only one arrow
- = Fixed /p and /a incompatibilities with bChatManager
- = Fixed Iron Grip working reversely
- = Fixed NPE when user clicked the HUD button with Spout
- = Fixed bug where the permission node for Impact didn't work
- = Fixed some bypass nodes defaulting true for Ops
- = Fixed bug with trying to use Chimera Wing while standing on a half-block
- = Fixed duplication bug when a placed block was mined after a server restart
- = Fixed exploit where shooting yourself with an arrow gave Archery XP
- ! Changed the mcMMO motd to link to the new website rather than the wiki
- ! Changed bleeding ticks damage to 1 from 2
- ! Changed Mining to ignore blocks when the pick is enchanted with Silk Touch
- ! Changed Super Breaker to be non-functional when used with a Silk Touch enchanted pick
- ! Changed MySQL to save player information 50ms apart from each other to reduce the load on the MySQL server
- ! Changed the permission node for Blast Mining detonation to mcmmo.ability.blastmining.detonate (was mcmmo.skills.blastmining) for the sake of consistency
- ! Changed skill commands to only display what you have permissions for
- ! Changed mcMMO to use a new storage system for player placed blocks
- - Removed some unused permission nodes
- - Removed a few config options in favor of permissions nodes (Hunger Bonus, Armor/Tool Repair, Instant Wheat Regrowth)
- - Removed level requirement for repairing string tools from the config file
-
-Version 1.3.06
- + Added Iron Golem XP for aggressive golems
- + Added permissions check to skill functions
- + Added API functions for obtaining offline profiles & profiles via player names
- + Added API functions for admin & party chat
- + Added Iron Grip skill to Unarmed which gives players an chance to keep from being disarmed.
- + Added some new languages to the locale files.
- = Fixed Green Thumb consuming 2 seeds instead of 1
- = Fixed exploit where you could teleport to yourself with PTP to prevent things like fall damage
- = Fixed NPE error with Metrics on startup
- = Fixed bug where Herbalism required double drops permission to give XP
- = Fixed bug where {0} would be displayed in front of your power level in mcstats
- = Fixed mmoupdate not being useable from console
- = Fixed bug with repairing wooden tools
- = Fixed bug with Nether Wart not awarding XP
- = Fixed bug with fishing treasures when treasures list is empty
- = Fixed bug with only getting one level when there was enough XP for multiple levels.
- = Fixed bugs with the way /mctop displayed
- = Fixed issues with custom characters & locale files.
- = Fixed double explosion for Blast Mining
- = Fixed Blast Mining not giving triple drops when it should
- ! Changed Bleeding to now stack to a finite number on Monsters and will wear off eventually
- ! Changed how we handled the config file to prevent any bugs when returning values
- ! Changed locale files to use a new naming scheme. This breaks ALL old locale files. If you want to assist with re-translating anything, go to getlocalization.com/mcMMO
- ! Changed /mcremove to check for users in the MySQL DB before sending queries to remove them
- ! Changed how the tree feller threshold worked for the better
- ! Changed /mcremove to no longer kick players when they are removed from database
- ! Changed /mcremove to work on offline users for FlatFile
- ! Changed PlayerProfile constructor to always take a boolean
- ! Changed getPlayerProfile function to work for online & offline users
- ! Changed Archery's Daze to deal 4 DMG on proc (2 Hearts)
- ! Changed /addlevel command to work for offline users
- ! Changed party & admin chat handling to be nicer to developers
- ! Changed /mcrefresh to work from console
- ! Changed /mcrefresh to work for offline players
- ! Changed UpdateXPBar function to hopefully avoid errors
- ! Changed /party to show offline party members
- ! Changed Blast Mining requirements, now asks for the player to be crouching
-
-Version 1.3.05
- + Added Skill Shot to Archery which increases damage dealt by 10% every 50 skill levels (caps at 200%)
- + Added ExperienceAPI and PartyAPI classes for developer use
- + Added ability to cap overall power level
- + Added showing powerlevel below a persons name if you run Spout (optional)
- = Fixed errors when Spout would disable itself after start-up
- = Fixed XP bar not updating when XP was gained
- = Fixed bug with repairing wooden tools
- = Fixed bug where spawned wolves only had 8 health.
- = Fixed bug where rare Treasures from Excavation were dropping too often
- = Fixed bug where Skull Splitter & Serrated Strikes could be used without permissions.
- = Fixed bug where API functions were set to static
- = Fixed bug where mmoedit threw errors when modifying an offline user
- = Fixed dupe exploit with Blast Mining
- ! Changed Tree Feller to account for ability durability loss but not leaves.
- ! Changed bypass node for Arcane Forging to not default to true for OPs
- - Removed Ignition from Archery
- - Removed McMMOPlayerRepairEvent - was basically a duplicate of McMMOPlayerRepairCheck but couldn't be cancelled.
-
-Version 1.3.04
- + Added McMMOPlayerRepairEvent for API usage - fires after completion of a repair.
- + Added McMMOPlayerRepairCheckEvent for API usage - fires before repair process begins, can be cancelled.
- + Added ability to get skill level from McMMOPlayerExperience events
- + Added McMMOPartyTeleportEvent for API usage - fires before a successful teleportation would occur.
- + Added McMMOPartyChangeEvent for API usage - fires whenever a player joins or leaves a party
- = Fixed Shake ability dropping bonemeal instead of ink for squids.
- = Fixed Green Terra & Super Breaker awarding 4x drops at high levels.
- = Fixed summoned ocelots never changing skins.
- = Fixed bug with Disarm not working
- = Fixed some API functions not being visible
- = Fixed bug where /ptp worked on dead party members
- ! Changed MySQL to reload all player information on reconnection
- ! Changed event package structure - be sure to update these if you're using the API in your plugin
-
-Version 1.3.03
- + Added Ocelots to Taming XP tables
- + Added ability to summon Ocelots with Call of the Wild
- + Added offline user functionality to mmoedit
- + Added bookshelves to list of blocks that don't trigger abilities.
- + Added 'mcmmo.repair.arcanebypass' permission node to bypass Arcane Repair and keep your enchantments
- + Added config option to disable Herbalism's instant wheat replanting
- + Added LOTS of new permissions nodes. *CHECK PLUGIN.YML FOR UPDATES*
- + Added Italian locale file - thanks Luxius96!
- + Added ability to inspect Ocelots with Beast Lore
- + Added console functionality to mctop
- = Fixed Green Terra not awarding Triple Drops
- = Fixed ClassCastException from Taming preventDamage checks
- = Fixed issue with Blast Mining not seeing TNT for detonation due to snow
- = Fixed issue with block interaction returning NPEs
- = Fixed issue where every block broken had a mining check applied
- = Fixed issue where every block broken had a herbalism check applied
- = Fixed issue where blocks weren't being removed from the watchlist
- = Fixed exploit where you could use /ptp to teleport to anyone
- = Fixed bug where Green Terra didn't work on Stone Brick
- = Fixed bug where Tree Feller could be used without permissions
- = Fixed exploit where falling sand & gravel weren't tracked
- = Fixed exploit where Acrobatics could be leveled via Dodge on party members.
- = Fixed exploit where you could gain combat XP on animals summoned by Call of the Wild
- ! Changed mcMMO to save profiles only when the profile is about to be discarded rather than on player quit
- ! Changed MySQL to try to reconnect every 60 seconds rather than infinitely which caused server hangs
- ! Changed mcMMO to be better about saving player information on server shutdown
- ! Changed PTP to prevent teleporting if you've been hurt in the last 30 seconds (configurable)
- ! Changed Chimera Wing failure check to use the maxWorldHeight.
- ! Changed inspect failed message to say inspect rather than whois
- ! Changed Call of the Wild to activate on left-click rather than right-click
- ! Changed Blast Mining to track based on Entity ID vs. Location
- ! Changed mmoedit to save a profile when used (this will make mctop update)
- ! Changed a few Runnable tasks to have their own classes
- ! Changed parties so that a player will leave their existing party if they enter a world where they don't have party permissions.
- ! Changed Call of the Wild to summon animals already tamed.
- ! Changed mob spawner tracking to use new Metadata API
- ! Changed block watch list to use new Metadata API
- ! Changed around a few config options, including the ones for mySQL. *YOU NEED TO REDO YOUR CONFIG FILE*
- - Removed 'true/false' debug message from Inspect command
-
-Version 1.3.02
- + Added in game guides for Mining, Excavation, and Acrobatics. Simply type /skillname ? to access them
- ! Changed Tree Feller to hand out 1/4 of normal XP for each JUNGLE LOG block it fells
- ! Changed Tree Feller to only fell trees if you have enough durability
- ! Changed Tree Feller to cause injury if your axe splinters from a failed Tree Feller attempt
- ! Changed invincibility checks in EntityDamage listeners to accommodate for vanilla MC behaviour
- ! Changed Ignition to add fire ticks rather than replacing them.
- ! Changed Axes critical to have a max critical rate of 37.5% down from 75%
- = Fixed bug where Taming defensive checks got called twice
- = Fixed Shake not working correctly
- = Fixed bug with Axes command displaying wrong Greater Impact bonus damage
- = Fixed bug where Impact didn't apply bonus damage
- = Fixed Impact proccing multiple times in a row
- = Fixed bug where PVE skills didn't level
-
-Version 1.3.01
- = Fixed bug where Tree Feller had no cooldown
- = Fixed bug with activating Skull Splitter after using Tree Feller
- 
-Version 1.3.00
- + Added ability to customize drops for Excavation skill (treasures.yml)
- + Added ability to customize drops for Fishing skill (treasures.yml)
- + Added messages to nearby players when your abilities wear off
- + Added jungle trees to Woodcutting XP tables
- + Added player notification for when they stop Bleeding
- + Added configuration option to control mcMMO reporting damage events
- + Added hunger regain bonuses to Herbalism skill
- + Added Blast Mining subskills to Mining
- + Added Fast Food Service subskill to Taming
- + Added size limit to Tree Feller in config as Tree Feller Threshold
- + Added /inspect (works on offline players)
- + Added /addlevels commands
- + Added /mcremove command which lets you remove users from MySQL or FlatFile
- + Added config values for XP multipliers for different hostile mobs
- + Added 'mcmmo.commands.inspect' permission node for the new /inspect command
- + Added Impact & Greater Impact subskills to Axes
- + Re-added mcMMO reporting damage events
- = Fixed bug with updating MySQL tables to include fishing on servers using custom table prefixes
- = Fixed bug where Disarm didn't work at all ever
- = Fixed bug where Swords command showed Bleed Length twice instead of Bleed Chance
- = Fixed bug where Tree Feller wasn't checking for Tree Feller permission
- = Fixed bug where Leaf Blower required Tree Feller permissions rather than Woodcutting permissions
- = Fixed Leaf Blower preventing the use of shears to collect leaves
- = Fixed Green Thumb & Green Terra not consuming or requiring seeds to replant Wheat
- = Fixed Super Breaker & Giga Drill Breaker failing to damage tools
- = Fixed Tree Feller not giving proper XP for different kinds of trees
- = Fixed Skill Abilities conflicting with NoCheat
- = Fixed memory leak with mob spawner tracking
- = Fixed /mcability not respecting permissions
- = Prettied up new config files
- = Various skill function optimizations
- ! Changed how mcMMO calculates distance between two points (optimizations)
- ! Changed how mcMMO handles MySQL connections (optimizations)
- ! Changed the description /mcmmo provides to be more up to date and relevant
- ! Changed mcMMO user information to be stored for 2 minutes after log out to reduce lag on rejoins
- ! Changed Food+ to be named Farmer's Diet in Herbalism
- ! Changed default values of Woodcutting XP tables
- ! Changed 'Pine' to be renamed 'Oak' in Woodcutting XP tables
- ! Changed the name of Unarmed Apprentice/Mastery to Iron Arm Style
- ! Changed Axes to gain bonus damage every 50 skill levels
- ! Changed Unarmed to start with a +3 DMG (1 Heart = 2 DMG) bonus from Iron Arm Style to make leveling it more viable
- ! Changed Unarmed to gain bonus damage every 50 skill levels
- ! Changed Unarmed to gain more bonus damage total than before
- ! Changed Unarmed to have a max disarm chance of 33.3% rather than 25%
- ! Changed Tree Feller to take down entire trees
- ! Changed mob spawn tracking to use Unique Entity ID instead of Entity Object
- ! Changed stats command name to mcstats for better plugin compatibility
- ! Changed god mode to turn off if player enters world where he does not have mcgod permission
- ! Changed Taming to also gain XP from animal taming
- ! Changed Swords Bleeding effect to never kill
- ! Changed Bleeding to never go beyond 10 ticks
- ! Changed to use Bukkit's built-in ignoreCancelledEvents system
- ! Changed chat logging for /p & /a
- ! Changed Tree Feller to use per-use ArrayList
- - Removed /mcstats console functionality
- - Removed /whois command (replaced with /inspect which has similar functionality)
- - Removed Master/Apprentice chat notifications to reduce spam
- - Removed MySpawn system (You can still use Chimaera Wings) due to being outdated and unwanted
- - Removed duplicate settings in config.yml
- - Removed unused settings from config.yml (HP Regen)
- - Removed Nether Brick from Mining XP Tables
- - Removed Stone Brick from Mining XP Tables
- 
-Version 1.2.12
- - Fixed issue that caused terrible MySQL performance and negative XP on levelup (Issue #134)
- - Fixed addxp command taking xprate and skill modifiers into account
- - Added anonymous usage statistics (you can opt out in plugins/PluginMetrics/config.yml)
- - Modified onEntityDamage priority to have better compatibility with other plugins (Factions, WorldGuard, etc...)
- - Fixed /mcgod & /mmoedit permissions defaulting to true
- - Fixed Fishing not working or handing out XP
- - Fixed error with Skull Splitter / Serrated Strikes that caused server instability and log spam
- - Fixed config.yml not having values for End Stone & other new mining blocks
- - Fixed Green Thumb/Green Terra not correctly planting wheat (Issue #133)
-
-Version 1.2.11
- - Removed legacy Permission & PEX dependency. (PEX still works fine with mcMMO)
- - Made Smooth Brick to Mossy Brick and Dirt to Grass for green thumb configurable (Issue #120)
- - Added MagmaCube to XP tables
- - Made optimizations to Skull Splitter/Serrated Strikes
- - Made it so players take damage if they try to log out with Serrated Strikes stacked onto them (Issue #131)
- - Changed mcMMO to save data periodically to optimize performance with FlatFile & MySQL (Issue #138)
- - Added a configurable save interval for the new save system
- - Fixed a bug with the odds calculations for Serrated Strikes
- - Fixed several commands not working from console (mmoedit, etc..) (Issue #150)
- - Added a success message when executing xprate from console
-
-Version 1.2.10
- - Fixed issue with receiving Woodcutting XP for all blocks broken (Issue #103)
- - Fixed issue with Spout images & sounds not working (Issue #93)
- - Fixed typo with repairing Leather Armor & Bows
- - Fixed issue with Silk Touch & Double/Triple drops not working properly
- - Fixed conflict with NoCheat plugin & Super Breaker/Giga Drill Breaker/Berserk/Leaf Blower (Issue #104)
- - Fixed counter-attacking non-LivingEntity (Issue #100 & Issue #107)
- - Fixed various bugs with Leaf Blower
- - Added Monitor & ignoreCancelledEvents to onBlockPlace
- - Made Anvil ID configurable
-
-Version 1.2.09
- - Fixed issue with Repair Mastery (Issue #47)
- - Made Arcane Forging fully configurable (Pull Request #52)
- - Made Fishing configurable (Pull Request #60)
- - Changed timer to be a bit more efficient (Issue #19)
- - Changed to fire EntityDamageEvents for all damage done by mcMMO
- - New custom event for developers McMMOPlayerLevelUpEvent
- - New custom event for developers McMMOItemSpawnEvent
- - Changed LoadProperties from the old Configuration to FileConfiguration
- - Removed aliasing from config.yml
- - Fixed mining procs from Super Break & Silk Touch
- - Unused smelt property removed
- - Minor optimizations
- - Fix for setting properly block damage values
- - Initial skill level capping added
- - Initial command alias framework added
- - Fixed abilities not handling Unbreaking items
- - Fix for treefeller glitch
- - Super secret anniversary easter egg!
-
-Version 1.2.08
- - Changed Bukkit events to new event system
- - Changed aliasing to send both the mcmmo command and the command used.
- - Changes in combat exp (Pull Request #49)
- - Repair for bows & leather armor (Pull Request #46)
- - Fixed missing images (Pull Request #45)
- - POM Changes for dependencies (Pull Request #36)
- - Fishing & Repair fixes (Pull Request #31)
- - Fixed CraftOfflinePlayer issue (Issue #212) errors for offline wolf owners
- - Pull in commit from @NuclearW for issue from previous commit
-
-Version 1.2.07
-Fixed mctop not working at all (whoops!)
-Fixed problem with multithreading in mcMMO causing errors
-Fixed bug with Repair where it would remove the enchantments if you could not even repair the item
-The command mmoupdate now runs in its own thread to ease the burden on the server
-
-Version 1.2.06
-German translation has been updated
-Fixed fishing not being applied to MySQL DB when converting from Flat File -> MySQL
-Fixed mctop not taking Fishing into account some of the time
-Fixed bug where Taming would try to grab the name of offline players
-Fixed bug where Fishing would try to add an enchantment level not normally possible
-Fixed another bug with mmoedit and Fishing
-Added option to only allow tools to ready when you are sneaking, this is off by default
-Added Brewing Stand & Enchanters table to the list of blocks that won't cause you to ready your tool on right click
-
-Version 1.2.05
-Fixed my fix of not being able to place blocks near/on Anvils
-Fixed resources in inventory not correctly updating after Repair
-
-Version 1.2.04
-Fixed bug where you could not place blocks near/on the Anvil
-
-Version 1.2.03
-skills2 and experience2 will be removed from MySQL DB automagically when this version first runs
-Fishing is now stored in skills and experience tables on the MySQL DB as it should have been
-Fishing will now save properly for MySQL
-Fishing will now work properly with mctop for those using MySQL
-Fixed problems with mmoedit and fishing
-
-Version 1.2.02
-Added measures to prevent easy xp from hacks that cause a ridiculous amount of clicks per second
-Fixed bug where Call Of The Wild used only 1 bone to summon
-Reduced Endermen XP from 3x to 2x
-The number of bonus fish caught from fishing has been reduced
-Fishing XP reduced from 1500 to 800
-Fishing XP is now configurable in the config file
-
-Version 1.2.01
-Added a setting to turn off abilities completely from config
-Added a setting to just turn off ability messages from config
-Fixed the bug with sword repair
-Fixed mcMMO not working properly with Spout
-Added Fishing XP icon to Normal/Retro HUDs for Spout
-Added icons to Spout notifications for leveling Fishing
-Added Fishing Retro XP bar color customization to config file
-The number of bones required to use Call of The Wild is now configurable
-Reduced the XP animals would give from 1.5x to 1x
-Removed current durability value message from Repairing
-Fixed bug where Arcane Forging failed to display messages
-Fixed bug where Arcane Forging tries to downgrade level 1 enchants
-Fixed bug where Arcane Forging always kept enchantments if you had under 100 Repair skill
-
-Version 1.2.00
-Added Fishing Skill
-Added Call Of The Wild to Taming
-Added Arcane Forging to Repair
-Added new mobs to XP tables
-Added Master/Apprentice system to the Party system
-Animals now give xp to combat skills (those poor sheep ;_;)
-Removed unnecessary sorcery permissions from plugin.yml
-
-Version 1.1.17
-XP gained in combat is now softcapped by the remaining health of the entity you are damaging, preventing many exploits.
-Players in Creative mode no longer gain XP
-Compiled against latest Spout & CraftBukkit
-Added World PVP check to Ignition, should no longer ignore PVP settings
-Enemies should no longer grant XP when hit during their death
-Fixed an exploit that led to unlimited ability use
-Possibly fixed a bug where the same player would be listed multiple times in mctop
-Added author and description to plugin.yml
-
-/mmoedit and /addxp are useable from the console now
-Swearword's statistics tracking removed (He stopped the service, so its gone now.. On a positive note, I did find out 1000-1500 servers installed mcMMO a day)
-
-Version 1.1.16
-Added Melons to Herbalism xp tables
-Endermen added to Combat skill xp tables
-Silverfish added to Combat skill xp tables
-CaveSpider added to Combat skill xp tables
-
-Version 1.1.15
-Smooth Brick added to Green Terra
-Green thumb can be used to spread moss to Smooth Brick now
-Implemented a ghetto fix for the sword durability bug (real fix sometime soon)
-Added Spain Spanish localization (es_es)
-
-Version 1.1.14
-[1.8] Removed the Archery fire rate limiter as its no longer necesarry due to changes in game mechanics
-[1.8] Removed the bonus damage from Archery (I'll rework this skill soon)
-[1.8] Removed the food bonuses to healing Herbalism provided due to the change of eating in game mechanics
-[1.8] Swords no longer parry, no need to compete with in game mechanics
-[1.8] mcMMO no longer has an HP Regen system, no need to compete with in game mechanics
-[SPOUT] mcMMO now transfers files between [MC Server] -> [Client] rather than [Webserver] -> [Client]
-[SPOUT] Temporarily disabled the PartyHUD due to some performance issues
-[SPOUT/CONFIG] mcMMO now allows for disabling of the party HUD with the node Spout.Party.HUD.Enabled
-[BUG] Fixed a few problems with readying abilities for Woodcutting/Axes
-[MYSQL] Improvements have been made to the performance of MySQL thanks to krinsdeath
-[CONFIG] Spout.Party.HP tree removed, replaced with Spout.Party.HUD
-[CONFIG] Added an option for Excavation to require use of a shovel, on by default
-[COMPATIBILITY] Changed the listener priority for OnEntityDamage from High to Monitor (Should make mcMMO compatible with Worldguards pvp regions among other things)
-[COMPATIBILITY] Made party/admin chat modes more compatible with chat plugins (vChat)
-[API] Added addXpOverride for modders, this will ignore skill modifiers
-[SPOUT] The option to change the weburl of mcMMO Images/Sounds has been removed, if you want to customize mcMMO images/sounds you can open mcMMO.jar and replace them there
-[LOCALE] Portuguese Brazil locale added (Code: pt_br)
-[MISC] Added some experimental usage tracking, you can opt out of this in /plugins/stats/config.yml (Once its generated, may require 2 restarts)
-
-Version 1.1.13
-Pets are removed from party bars
-
-Version 1.1.12
-mcMMO now downloads files when you join the server to provide the best experience
-mcMMO now uses a brand new Party HUD by Rycochet (from his mmoParty plugin)
-Fixed the xpbar and xpicon settings in config to work properly
-Fixed infinite HP exploit with Herbalism
-Fixed bug where herbalism would heal out of the players normal health range
-Fixed bug where entering ':' into your party name caused stat loss among other things
-Fixed issue with block break listener priority
-
-Version 1.1.11
-mcMMO now properly cancels its Async taks when disabled
-Fixed newly generated configs using 2 instead of 1 for skill multipliers
-
-Version 1.1.10
-Added default hud setting to config
-Fixed bug where newly generated configs used old xp gain numbers
-
-Version 1.1.09
-Fixed mcMMO to run fine without Spout :)
-
-Version 1.1.08
-Fixed repair being 10x slower to level than normal
-
-Version 1.1.07
-Fixed the default HUD being set to RETRO instead of STANDARD
-
-Version 1.1.06
-mcMMO menu implemented! Default is 'M', change this in config
-Retro HUD implemented!
-Retro XP fill color is completely customizable on a per skill basis
-New levelup sound thanks to @Rustydaggers !
-With the help of Randomage the XP Formulas have been vastly changed for flexibility
-Global modifiers and skill modifiers now support decimals
-Global formula modifier dropped from config
-GigaDrillBreaker/Berserk doesn't drop clay blocks anymore
-Fixed bug where Herbalism didn't heal more for bread/stew when right clicking a block
-Fixed bug where Wheat did not use the values form the config file
-Fixed bug where Archery gave xp for inflicting self injury
-Watch added to clay loot tables and maps remove from clay loot tables
-
-Version 1.1.05
-Maps dropped from excavation are created correctly, and represent the area they are found in
-Fixed an exploit with clay and excavation
-Fixed a NPE with locking xp bars
-Fixed the !AdeptDiamond! localization error when repairing diamond with a skill below 50
-
-Version 1.1.04
-Removed URL settings for XPBAR/XPICON/HPBAR
-Added single URL setting for mcMMO
-Changed default host from Dropbox to Rycochet's webserver (with apparently unlimited bandwidth!, thanks Rycochet)
-Fixed Repair noise not getting played
-Fixed a small memory leak with party health bars
-
-Version 1.1.03
-Fixed a few images being hard-coded still rather than configurable
-
-Version 1.1.02
-Fixed bug where toggle for xpicon didn't work
-Fixed bug where Excavation gave gravel drops to grass
-Excavation now uses more enums
-
-Version 1.1.01
-Fixed toggles for hpbar/xpbar not working
-
-Version 1.1.0
-Brand new XP Bars, Health bars, and Skill Icons designed by BrandonXP
-Added /xplock <skillname> to lock the xp bar to a skill
-Repairing metal now has a sound effect
-Shears added to Repair
-MySpawn now works correctly when you are in the nether
-MySpawn message when you right click a bed is now squelched
-Intervals at which players renegerate hp have doubled in length (making it take 100% longer to regenerate than before)
-Rewrote many variables stored per player to be integer instead of long, reducing overall memory usage of mcMMO
-Rewrote the Timer mcMMO relies on to instead use the BukkitScheduler for performance
-Fixed the party member list of /party
-Fixed bug where Swords would counter-attack Projectiles
-Removed a debug message when repairing diamond armor
-Changed chat to use getDisplayName() instead of getName()
-Changed chat priority from lowest to highest
-Added Clay to excavation
-Added new items to Clay's loot tables
-Archery now works with the latest CB
-
-Version 1.0.50
-New /xprate command for those with mcmmo.admin permissions!
-mcMMO now uses Spout instead of BukkitContrib
-BukkitContrib support dropped
-XP Formula is now 100+(skill level value * skill modifier * global modifier) thanks to suggestion
-Fixed bug where /mmoupdate used the old directory instead of the new one to find the flat file
-Fixed bug where Unarmed Mastery damage bonus only did as much as Unarmed Apprentice
-Fixed bug where Pumpkins did not give out XP
-Coordinates removed from /whois as they didn't really fit
-/mcgod and /mmoedit now require permissions to be setup in some shape or form to be used
-Lapus renamed to Lapis in config
-
-Version 1.0.49
-Updated German locale
-Fixed bug where using the party system on a MySQL setup caused errors when writing to non-existent files
-Fixed bug where using /accept caused a NPE (hopefully)
-Fixed a few missing descriptions for commands
-
-Version 1.0.48
-Updated French Translation
-Updated German Translation
-Updated Polish Translation
-Placed Coal Ore and Redstone Ore won't give XP anymore
-Fixed unusually high memory usage at startup
-Added many features to the party system written by NuclearW
-
-Version 1.0.47
-Fixed another BukkitContrib error for servers not running BukkitContrib
-
-Version 1.0.46
-Fixed bug preventing Excavation from gaining skill
-
-Version 1.0.45
-Corrected /stats showing Repair XP as Level for Repair
-Corrected /repair showing Repair XP as Level for Repair
-Corrected /whois showing Repair XP as Level for Repair
-
-Version 1.0.44
-Fixed my 'fix' of BukkitContrib errors with Tree Feller
-
-Version 1.0.43
-Stopped things from being auto-smelt'd
-
-Version 1.0.42
-Corrected 2 more errors involving not running BukkitContrib
-
-Version 1.0.41
-Fixed errors using Tree Feller if your server wasn't running BukkitContrib (sorry!)
-Fixed some more leftover stuff involving the new half-finished mining skill
-Fixed excavation's Giga Drill Breaker not working on placed blocks
-
-Version 1.0.40
-Fixed errors if your server wasn't running BukkitContrib
-
-Version 1.0.39
-mcMMO won't auto-download and auto-run BukkitContrib anymore
-
-Version 1.0.38
-Commented code for the half-finished Infernal Pick subskill (Whoops)
-
-Version 1.0.37
-The donation message in /mcmmo is now toggle-able
-The anvil message now only gets shown the first time you place an anvil (after login)
-Reworked /mcmmo (an improvement I would say)
-Added /mcmmo text to localization file
-Archery fire rate now configurable
-Berserk mode stops items from being collected
-Taming no longer receives xp from wolves being harmed
-Fixed bug where /stats required Tree Feller permission to show Woodcutting skill
-Fixed bug where players with mcgod could be harmed by AoE
-Fixed bug where modifying a skill also modified the xp to the same amount (when it should be zero)
-
-BukkitContrib Stuff
-Added a pop-up when placing an Anvil
-Added pop-ups on levelup
-Added basic sound effects to various abilities (Berserk, Tree Feller, Super Breaker, Leaf Blower, etc...)
-
-Code Stuff
-Added checkXp(SkillType, Player) for plugin devs (use this after modifying XP to check for levels)
-Added getPlayerProfile() which returns a PlayerProfile object for plugin devs (You can do almost everything with this object)
-100% more enums
-Changed how checking skill xp worked to be more efficient
-
-Version 1.0.36
-mcMMO now properly supports Bukkit/PEX/Permissions for Permissions
-Config.yml will no longer generate Performance Debugging nodes
-Registered permission nodes to plugin.yml
-Some more changes to Permissions code
-Fixed bug where Super Breaker activated where it shouldn't
-Fixed bug with enabling/disabling mcgod in config.yml
-Fixed bug with Excavation not kicking in until 1 level higher
-
-Version 1.0.35
-Added a Toggle for Chimaera Wing in config.yml
-Added customization of what item is used for Chimaera Wing in config.yml
-Fixed bug with randomly receiving Taming XP
-mcmmo.users file moved into /plugins/mcMMO/FlatFileStuff/
-Leaderboard files now moved into /plugins/mcMMO/FlatFileStuff/Leaderboards
-Locale files now have the prefix locale_ instead of messages_
-Locale files are now located inside com/gmail/nossr50/locale/ instead of com/gmail/nossr50/
-Updated the code that handles permissions (this may mean 3.1.6 will finally play well!)
-Some more source code organization
-Fixed warnings for compiler
-Removed dependencies on CraftBukkit
-Registered commands to OnCommand
-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
-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
-/mining now shows mining values instead of taming values
-
-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)
-MySQL version now makes requests to the MySQL server less frequently (should help performance)
-Fixed bug with Skull Splitter hitting the user
-
-Version 1.0.32
-Added "General.Performance.Print_Reports" node to config.yml to help identify causes of performance issues
-Fixed bug of swords users hurting themselves with serrated strikes
-
-Version 1.0.31
-Fixed bug of trying to cast Animals to non-animals
-
-Version 1.0.30
-Mobs that spawn from spawners no longer give XP (for reals this time)
-
-Version 1.0.29
-Mobs that spawn from spawners no longer give XP (again)
-Fixed bug where Serrated Strikes did not Bleed additional targets
-Identified and solved a potential memory leak in Bleed Simulation
-Renamed the Object Config to Misc and rewrote parts of it
-Rewrote Party/Admin/God toggles
-Added Polish language support (pl)
-
-Version 1.0.28
-Actually fixed /stats showing excavation values for swords
-Made some improvements to how Bleed Simulation was handled for different entity types
-Obsidian now does normal durability damage during Super Breaker
-
-Version 1.0.27
-Fixed /stats showing excavation values for swords
-Hopefully fixed a wide range of NPE errors
-Updated German (de) localization
-
-Version 1.0.26
-Fixed accidentally making power levels go above 9,000
-
-Version 1.0.25
-Compatible with the latest CB
-Beast Lore now functions correctly
-Wolves are no longer invincible to players
-Changed the look of Beast Lore
-Skill info pages now show your stat in that skill (if you have permission)
-/stats and /whois has been alphabetized and divided into three categories (Gathering/Combat/Misc)
-Abilities will not trigger on Trap Doors
-
-Version 1.0.24
-Now compatible with latest RB (928)
-Taming now receives XP from your wolves harming foes
-Taming is now easier to level
-Green Thumb now drops seeds when harvesting Wheat
-
-Version 1.0.23
-Modified Bleed Simulation to fix performance problems
-Rewrote MySpawn to be more efficient when calculating time left
-Rewrote Skills to be more efficient when calculating time left
-
-Version 1.0.22
-Added 'Name' nodes to commands for renaming them
-
-Version 1.0.21
-Fixed Skull Splitter length in /axes displaying incorrectly
-Fire rate limiter now correctly uses the value in the config file
-Stone XP now correctly uses the value in the config file
-Cobble -> Mossy now correctly uses the value in the config file
-Removed setmyspawn from config file as it serves no purpose
-All commands now have an 'Enabled' node in the config file that when set to false disables the command completely
-Fixed color scheme inconsistency for Mining in /whois results
-
-Version 1.0.20
-Fixed Array Index Out of Bounds error
-
-Version 1.0.19
-Removed a failsafe for the Timer that is no longer necessary (should improve performance)
-Fixed /myspawn not working by rewriting it :3
-Fixed exploit where players could break a freshly placed mushroom for XP
-MySQL User Passwords can now be blank (Although you really should have a password...)
-Fixed a few NPE errors
-
-Version 1.0.18
-Fixed MySQL default TablePrefix
-Fixed Wheat not being configurable
-
-Version 1.0.17
-Brand new YAML Configuration file
-Ability to configure XP for all gathering skills in config file
-German Language added to mcMMO
-French Language added to mcMMO
-MySpawn will no longer heal players
-/<skillname> commands now also check for their localized names for displaying help
-Added many more Strings to localization files
-Added more safeguards to MySpawn for NPE
-Fixed bug where Tree Feller Radius depended on WoodCutting XP rather than Skill Level
-Fixed bug where Readying a Hoe returned a missing localization string
-Added some safeguards into Bleed Simulation to prevent possible memory leaks
-Performance improvements to storing/calling Skill/XP Values
-Plugged a potential memory leak with PlayerProfiles not being removed correctly
-Disabled the mob spawner camping anti-exploit in favor of performance
-
-Version 1.0.16
-Fixed bug where localization file failed to load
-Changed en_US to lowercase
-mcMMO now requires locale files to be in lowercase
-Fixed a few strings missing from the localization file
-
-Version 1.0.15
-Removed leftover code that spammed SQL errors
-
-Version 1.0.14
-Added many missed strings into localization
-Finnish Localization updated for the new strings
-Green Thumb should respect Block Protection plugins now
-Fixed Number Format Exception when loading a PlayerProfile
-
-Version 1.0.13
-Fixed bug/NPE where stats would not load and therefore 'reset' for players
-Fixed NPE involving /ptp
-Added "enableMOTD" setting to properties file
-
-Version 1.0.12
-Fixed another NPE error
-Non-Gathering skills should correctly gain XP if PVP is set to false now
-Localization will now support language codes that do not have two parts like "fi"
-Fixed bug where Wiki MOTD message would not be loaded from localization file
-
-Version 1.0.11
-Fixed bug where players could not gain experience in several skills
-Removed PVP flag from mcmmo.properties as its not needed anymore
-Fixed a few NPE errors
-Mushroom XP reduced from 25 to 15
-Fixed an exploit where players who just logged in could be farmed for experience because they were invulnerable
-
-Version 1.0.10
-Added Localization/String Customization
-Mushroom XP reduced from 40 to 25
-Removed "clears inventory" warning in /mcc for /myspawn since this no longer happens
-
-Version 1.0.09
-Fixed the NPE that occurs when players gain experience (Sorry!)
-Fixed bug where /myspawn & /clearmyspawn would work if MySpawn was disabled in the properties file
-Changed strings containing "MMO" to read "mcMMO"
-Removed a lot of unused or unnecessary variables from the PlayerProfiles in mcMMO, this should lower the memory footprint
-Added getXpToLevel() for modders
-
-Version 1.0.08
-Added removeXP() for modders
-Fixed bug where stone swords only repaired by 33% instead of 50%
-Fixed bug where stone/wooden hoes wouldn't repair
-Big overhaul to how skill values and xp values were handled in the code
-Modifying the players skill levels now sets the corresponding skill xp to zero
-Using Serrated Strikes/Skull Splitter on mobs should no longer harm nearby players when PVP is disabled
-Switching to another weapon after firing your bow should no longer trigger procs for that weapon when the arrow hits
-Slimes/Ghasts now give XP for combat skills
-Added "EnableHpRegeneration" property setting
-Added "EnableMySpawn" property setting
-
-Version 1.0.07
-Added more repair customization by solarcloud7
-Leaderboards ignore players with the respective stat at 0
-Reconnecting to MySQL will reload player data
-Fixed a NPE with MySQL's Leaderboards
-Removed "Loop iteration" debug message from mcMMO
-
-Version 1.0.06
-MySQL will attempt to reconnect if the connection is closed
-Breaking the bottom block of Cactus/Reeds will award the correct experience and double drops
-Added support for Minecraft Statistics
-Fixed NPE with /myspawn command
-
-Version 1.0.05
-PVP interactions now check for permissions before handing out any experience
-Many skill abilities now check for permissions correctly
-All interactions with Taming now check for permissions
-mcMMO now checks for its pvp flag being true before handling pvp interactions
-
-Version 1.0.04
-Fixed bug where players would be informed incorrectly when their cooldowns refreshed
-Fixed exploit where players could reconnect to reset their cooldowns
-Added new "cooldowns" table to MySQL
-Berserk now breaks through snow
-Lightning no longer gives Taming XP
-Shortened /mcc to fit the screen
-
-Version 1.0.03
-Bleed will no longer trigger on friendly wolves
-Axes criticals will no longer trigger on friendly wolves
-
-Version 1.0.02
-Fixed bug where the Timer would start before everything else was ready
-Fixed bug where mcrefresh also required mcability permission node
-Fixed bug where Unarmed was not checking for disarm procs
-Green Thumb now checks for herbalism permissions
-Added "enableGreenThumbCobbleToMossy" to config file, this also changes Green Terra
-AoE abilities now harm wolves
-
-Version 1.0.01
-Removed debug message when wolves are struck
-Fixed issue with reloading mcMMO when MySQL was enabled
-Fixed a NPE with MySpawn
-Fixed a NPE with removing users from PlayerProfile
-Unarmed no longer starts with a damage bonus
-Unarmed apprentice DMG bonus changed from 3 to 2
-
-Version 1.0
-Players can now repair Stone/Wood tools
-Fixed duping bug with WG/Block Protection Plugins
-Added Leaf Blower to WoodCutting
-Different Trees give different WoodCutting XP
-Water changing Gravel to Clay removed
-Code Organized/Optimized further
-MySQL Support
-Taming Skill
-Leaderboards
-Players won't hand out XP if they died within the last 5 seconds
-
-Version 0.9.29
-Fixes critical bug involving water turning anything into clay
-
-Version 0.9.28
-Green thumb can now spread grass to dirt using seeds
-Adding XP will check for level ups again
-Acrobatics won't hand out XP on death anymore
-Acrobatics will check plugins for the event being cancelled before handing out XP
-
-Version 0.9.27
-Fixed Herbalism not properly receiving Triple Drops from Green Terra
-Fixed Herbalism not handing out any XP outside of Green Terra
-Fixed Herbalism asking for seeds on things that did not require it
-
-Version 0.9.26
-Fixed Green Terra going off without readiness
-Fixed Hoe trying to ready when tilling Grass
-
-Version 0.9.25
-Fixed issue with anti-exploits and Herbalism
-MySpawn works like a hearthstone now, no inv pentality, 1hr cooldown
-Added Green Terra Ability to Herbalism
-Added Green Thumb ability to Herbalism
-Fixed Repair not working for Iron Tools
-Fixed bug where Axes Ability checked for Unarmed Ability Permission
-Added Cocoa Beans to Excavation XP/Loot Tables, Found in Grass/Dirt
-Using Super Breaker on Obsidian significantly damages it compared to other materials
-Added Obsidian to Mining XP Table/Super Breaker
-Added Pumpkins/Reeds/Cactus to Herbalism XP Tables/Double Drops
-Corrected "mcMMMO" to "mcMMO" in MOTD
-
-Version 0.9.24
-PLAYER_BED_ENTER removed due to its unusual issues
-Added info about the Wiki to the motd
-/mcrefresh will reset if you were recently hurt (Chimaera Wing/HP Regen)
-Fixed Armor Repair not adding XP
-Boosted Repair XP of Armor to match Tools
-Repairing Armor won't trigger Super Repair twice anymore
-Setting your MySpawn now just requires right clicking a bed (still requires the setmyspawn permission node)
-
-Version 0.9.23
-Players will now announce ability usage within a short distance to nearby players
-Chimaera Wing now takes the world into account
-Acrobatics won't give XP on death, and will fail if you would've died after the damage reduction
-Added yet another check to see if a Player is not in the Users system for NPC mod compatibility
-
-
-Version 0.9.22
-Fixed bug where chimaera wing was unusable after being hurt even after the cooldown
-
-Version 0.9.21
-/mcrefresh fixed to work properly with the new ability monitoring system
-Ability lengths are now based on your skill level directly rather than a tiered system
-Chimaera Wings won't trigger on things they shouldn't (Doors, Chests, ETC)
-Chimaera Wings will properly tell you how long you have to wait to use it if you've been recently hurt
-
-Version 0.9.20
-Fixed Tree Feller not checking if their cooldown was refreshed and always activating
-/stats and /whois will now show the powerlevel based on permissions
-Shovels will no longer say you've lowered your axe
-/myspawn will no longer say your inventory has been cleared if the server settings disable this feature
-
-
-Version 0.9.19
-Fixed Anti-Exploit XP stuff not working
-
-Version 0.9.18
-Added failsafe to prevent abilities from going on forever, abilities will check if they should've expired when being used in case the Timer fails
-Archery Spam has been nerf'd, you can only fire once per second now (Toggle-able in config file)
-Fixed bug when just having the Admin Chat permission wouldn't allow you to see Admin Chat
-Fixed bug where Axes ability could be used without permission
-Abilities are monitored with Timestamps rather than a Timer monitored tick rate
-When players were last hurt is now monitored with Timestamps rather than a Timer monitored tick rate
-Made Anti XP-Exploits more Robust
-Repair XP is now based on durability restored
-Acrobatics rolling will now reduce damage if you go over the damage threshold
-Acrobatics rolling damage threshold lowered to 10 from 20
-Added Graceful Roll to Acrobatics, hold Shift when falling to do a Graceful Roll
-mcMMO now checks for the blockBreak and EntityDamage events being canceled before proceeding
-Dodge notification shortened
-Dodge won't negate damage completely anymore
-Added 3 more functions for plugin authors to call, getPartyName(Player player), inParty(Player player), and getParties()
-
-Version 0.9.17
-Players now set their MySpawn by entering a bed, it requires the setmyspawn permission node
-/setmyspawn has been removed
-Compatible with CB 670
-Fixed errors related to Repair
-Abilities will no longer trigger from Bed interactions
-/unarmed will now tell the player when they will receive unarmed master (if they have apprentice)
-
-Version 0.9.16
-Logs placed by the player won't grant XP/Double Drops anymore
-Added more functions plugin authors can call
-Acrobatics roll has a damage threshold of 20, going above this means a failed Roll
-
-
-Version 0.9.15
-Acrobatics will now behave properly
-AoE Abilities ignore wolves (temp fix)
-Added "all" parameter to /mmoedit & /addxp
-After giving XP to a player it will now check for level ups in skills
-
-Version 0.9.14
-mcMMO checks for abilities being active before sending the fake block break event
-
-Version 0.9.13
-Fixed excavation ignoring the xpGainMultiplier
-Now compatible with CB 600+
-Fixed bug where Dodge acted maxed out no matter your skill level
-
-Version 0.9.12
-mcMMO now fakes a block break event for abilities to maximize plugin compatibility
-/herbalism will return the correct values now
-New /addxp command
-
-Version 0.9.11
-PVE Combat Skills experience is now based on damage dealt
-The Timer will no longer break from Bleed Simulation
-Tree feller no longer "damages" saplings
-Bleed+ (Serrated Strikes) lasts 5 ticks down from 12
-Bleed/Bleed+ now do 2 damage instead of 1
-Power Level is now based on permissions
-Counter Attack added to swords
-Parry is now based directly on Swords skill level
-Parry maximum proc chance raised to 30% from 20%
-Serrated Strikes now properly applies Bleed+ to targets
-Players who parry can no longer be disarmed
-Acrobatics now has a Dodge passive skill reducing damage
-Repair skill now effects how much durability is restored
-Super repair now doubles the repair amount on proc
-Unarmed now starts with a bonus to damage to encourage use
-Unarmed now has two steps to damage scaling, Appentice, and Mastery
-Unarmed disarm now caps at 25% for 1000 skill
-Fixed problem where Archery skill procs would ignore other plugins
-Ignition changed to 25% chance
-Ignition length will be based on archery skill level
-/myspawn now has a warning about the inventory loss penalty in /mcc
-mcMMO Timer now runs in 1 second intervals rather than 2
-
-Version 0.9.10
-Party invites now show who they are from
-Mushrooms added to Dirt/Grass excavation loot tables, drops with 500+ skill
-mcMMO configuration files property setting names have been changed for readability
-Fixed bug where Gold and Iron wouldn't drop anything during Super Breaker
-Added /mcability info to /mcc
-Potentially fixed NPE error when checking players for being in same party for PVP XP
-Removed sand specific diamond drop from sand excavation loot table, Diamonds can still drop globally for sand
-Added a global XP gain multiplier, increase it to increase XP gained
-Reduced PVE XP for Unarmed, now identical to Axes/Swords
-Changed Chat priority in mcMMO to be higher, this should help plugin conflicts
-Mushroom XP raised to 40 from 10
-Flower XP raised to 10 from 3
-
-Version 0.9.9
-Fixed problem where entities never got removed from the arrow retrieval list of entities
-
-Version 0.9.8
-EntityLiving shouldn't be cast to entities that are not an instance of EntityLiving
-Added a null check in the timer for players being null before proceeding
-
-Version 0.9.7
-Procs/XP Gain will no longer happen when the Entity is immune to damage (Thanks EdwardHand!)
-Axes critical damage versus players reduced to 150% damage from 200% damage
-Fixed bug where Daze might not proc
-Changed archery Daze to follow smooth transition
-Added archery Daze chance info to /archery
-Cooldown lengths are now customizable, they are in seconds and multiplied by 2 by mcMMO
-
-Version 0.9.6
-Timer checks for player being null before adding them to the mcUsers system
-Cooldowns will now show how much time is remaining when trying to use their respective abilities
-SkullSpliiter will now correctly inform the player when they are too tired to use it
-Acrobatics will no longer give XP if the event was cancelled by another plugin
-Version 0.9.5
-Super Breaker now gives a chance for Triple Drops based on mining skill
-Ability durability loss down from 15 to 2
-Ability durability loss is now toggle-able
-Ability durability loss can be adjusted in the configuration file
-Mining Picks are no longer lowered after activating Super Breaker
-
-Version 0.9.4
-Flowers won't drop wheat anymore
-Signs won't trigger ability readiness anymore
-Version 0.9.3
-Bug stopping abilities from never wearing of may have been fixed
-Changed color of "X Ability has worn off" to RED from GRAY
-Super Breaker, Giga Drill Breaker, and Tree Feller now damage the tool significantly during use
-Netherrack and Glowstone now give Mining XP
-Netherrack and Glowstone are now effected by Super Breaker
-Abilities will no longer be readied when you right click signs or beds
-Chimaera Wings won't activate on blocks you can interact with and signs
-Abilities now adjust their effects depending on tool quality
-Superbreaker won't break things that tool couldn't normally break
-Giga Drill Breaker will only give triple xp and triple drops for diamond tools, with a reduced effect for lesser tools
-Skull Splitter now has a limit of opponents nearby it will strike based on your tool quality
-Serrated Strikes now has a limit of opponents nearby it will strike based on your tool quality
-Modified /mcmmo description to be a little bit more relevant.
-
-Version 0.9.2
-Changed priority of some of the mcMMO listeners
-Now when certain abilities are activated it shouldn't say "You lower your x"
-
-Version 0.9.1
-Fixed "Unknown console command" errors with CB 556
-Added /mcability command to toggle being able to trigger abilities with right click
-Added some more nullchecks for people reporting NPE errors
-Compatibility with NPC mods improved (Mainly for archery!)
-Other plugins can now call inSameParty() from mcMMO to increase compatibility
-
-Version 0.9
---NEW CONTENT--
-Woodcutting now has the "Tree Feller" Ability
-Unarmed now has the "Berserk" Ability
-Swords now has the "Serrated Strikes" Ability
-Mining now has the "Super Breaker" Ability
-Axes now has the "Skull Splitter" Ability
-Excavation now has the "Giga Drill Breaker" Ability
-Added /mcrefresh <playername> - tool for refreshing cooldowns
-Unarmed now has the "Deflect Arrows" passive skill
-Chimaera Wing Item Added
-
---CHANGES--
-HP Regen & Bleed are back
-Woodcutting will drop the appropriate log on double drop procs
-Herbalism now applies double drops to herbs
-/<skillname> now shows much more information to the player regarding their stats
-Axes skill Critical Strikes are now based directly on your skill level
-Swords skill Bleed chance is now based directly on your skill level
-Unarmed disarm chance is now based directly on your skill level
-Acrobatics now gives XP when you roll
-
---BUGFIXES--
-Memory Leak Fixed
-Axes not doing critical strikes
-Gold Armor repair
-Capped skills now have the correct proc chance
-/mmoedit is no longer case sensitive
-More NPE errors fixed
-Many bugs I forgot to write down
-
---PLUGIN COMPATABILITY FIXES--
-If combat interactions are cancelled by other plugins mcMMO should ignore the event
-If block damage interactions are cancelled by other plugins mcMMO should ignore the event
-
-Version 0.8.22
-	Fixed bug where Axes did less damage than normal
-	Acrobatic rolls now give XP
-	Acrobatics XP increased for non-rolls
-Version 0.8.21
-	Fixed bug where axe criticals would dupe items
-Version 0.8.20
-	99.99% sure I fixed anvils that suddenly stop working
-Version 0.8.19
-	Fixed being able to excavate placed blocks
-	Added toggle option to mining requiring a pickaxe
-	Added toggle option to woodcutting requiring an axe
-	PVP interactions now reward XP based on the damage caused (this is effected by skills)
-	PVP XP gain can be disabled in the configuration file
-	PVP XP has a modifier, increase the modifier for higher XP rewards from PVP combat
-Version 0.8.18
-	Fixed sandstone not being watched for exploitation
-Version 0.8.17
-	mcmmo.users moved to plugins/mcMMO/
-	Snowballs and Eggs will no longer trigger Ignition
-	Loot tables for excavation adjusted
-	Mining benefits now require the player to be holding a mining pick
-	Woodcutting benefits now require the player to be holding an axe
-Version 0.8.16
-	Moved configuration file to /plugins/mcMMO
-	Arrows now have a chance to Ignite enemiesw
-	Fixed arrows not being retrievable from corpses
-	Added info about ignition to /archery
-Version 0.8.14
-	Mining, Woodcutting, Herbalism, and Acrobatics proc rates now are based on your skill level directly rather than tiers you unlock via skill levels
-	Archery's ability to retrieve arrows from corpses now is based on your skill level directly rather than tiers you unlock via skill levels
-	Mining, Woodcutting, Herbalism, Archery, and Acrobatics now show their proc % relative to your skill if you type /<skillname>
-	You can now adjust what level is required to repair diamond in the configuration file
-	Changed mining XP rates to be a tad higher for some things
-	You can now get XP from sandstone
-	XP rates increased for gathering glowstone with excavation
-	XP rates increased a bit for excavation
-	Skill info is now a bit more detailed for certain skills
-	Added info about arrow retrieval to /archery
-Version 0.8.13
-	Enemies no longer look like they have frozen when they die
-	Item duping fix
-Version 0.8.11
-	Performance improvements
-	Memory leak fixed
-	NPE error with MySpawn really fixed
-Version 0.8.9
-	Fixed NPE for My Spawn
-	Fixed NPE for onBlockDamage
-	Bleed proc now correctly checks for Swords permissions
-Version 0.8.8
-	Gold can now be repaired
-	Tweaked Mining XP gains
-	Reorganized code
-	Added /mcgod godmode command
-	Fixed the pvp toggle in the settings file
-Version 0.8.7
-	Removed packet-sending stuff wasn't working anyways
-	Fixed another NPE with the TimerTask
-	Skills now only show up in /stats if you have permissions for them
-Version 0.8.6
-	Added a null check in bleed simulation to prevent a NPE
-Version 0.8.5
-	Players are now added to files when they connect (to fix a NPE)
-	onPlayerCommand stuff moved into onPlayerCommandPreprocess
-Version 0.8.4
-	Fixed another nullpointer error for TimerTask
-	Fixed bug making regeneration take twice as long to kick in after combat
-Version 0.8.3
-	Modified the timer intervals (from 1 second to 2)
-	All skills now have an individual modifier (Set by default to 2)
-	There is now a global XP modifier (Set by default to 1)
-	Herbalism now correctly follows its skill curve
-	Unarmed no longer gives experience for harming other players
-	Players can no longer exploit mob spawners for experience
-Version 0.8.2
-	Fixed Concurrent Modification Exception
-	Fixed some incorrect skill descriptions
-	First tier of HP Regeneration is now available from the start
-	Fixed bleed proc rate for very high skill levels
-	Changed regeneration permissions to 'mcmmo.regeneration'
-Version 0.8
-	Archery skill now lets players recover arrows from downed foes
-	Health regenerates based on power level
-	Added toggle to myspawn clearing player inventory in settings file
-	Swords now have a bleed effect
-	Rewrote Skill descriptions to be more informative/better
-Version 0.7.9
-	XP Curve now follows a new formula
-	Acrobatics XP gains changed
-	Compiled against permissions 2.1
-Version 0.7.8
-	Massive tweaks to XP gain for Archery, Swords, Axes, Unarmed
-Version 0.7.7
-	Minor tweak to how players are added to the flat file
-	Fixed some nullpointer exceptions when players die
-Version 0.7.6
-	Fixed being able to repair diamond armor with below 50 skill
-	Myspawn now supports multiple worlds, clearing myspawn will set it to the first world created by the server
-Version 0.7.5
-	Removed random checks for herbalism XP
-	Herbalism is now called properly (This should fix gaining no xp or double drops)
-Version 0.7.4
-	Work around for a bukkit bug that broke my onBlockDamage event
-	Added /clearmyspawn
-Version 0.7.3
-	Fixed to work with build 424 of CB
-	Lowered the XP of gold due to it not being that rare anymore
-Version 0.7.2
-	Fixed security flaw where players could access /mmoedit if the server was not running permissions
-	Reduced XP gain of woodcutting a bit
-Version 0.7
-	Completely rewrote the XP system
-	Added an XP skillrate modifier to the settings file
-
-Version 0.6.2
-	Axes now do critical strikes against farm animals
-	Removed the "Stupidly Long Constructor"
-	Now compatible with the latest CB builds
-Version 0.6.1
-	Customizable command names
-	Axes can now be modified with /mmoedit
-	Party members are now correctly informed when you leave the party
-	Fixed incorrect commands in /mcc
-Version 0.5.17
-
-	Changed namespaces to fit bukkits new standard
-	Adjusted excavation proc rates
-	Modified excavation loot tables
-	Added Party Invite System
-
-Version 0.5.16
-
-	Fixed unarmed not checking for permissions when hitting players
-
-Version 0.5.15
-	Fixed stone swords not being recognized as swords
-	Fixed /a not working if you were an op but did not have permissions
-
-Version 0.5.14
-	Added permissions for skills
-
-Version 0.5.13
-
-	Removed skillgain from succesful parries
-	Repair now refreshed the inventory
-
-Version 0.5.12
-
-	Fixed being able to hurt party members with the bow and arrow
-
-Version 0.5.11
-
-	Added /mmoedit command
-	Fixed bug preventing player versus player damage
-	Fixed bug preventing damage from scaling with unarmed & bows
-	Fixed disarm proc making the opponent dupe his/her items
-	Added mcmmo.tools.mmoedit permission
-	Added mcmmo.commands.setmyspawn permission
-	Added totalskill to /stats
-	Changed the look of /stats
-
-Version 0.5.10
-
-    Fixed trying to set health to an invalid value
-
-Version 0.5.9
-
-    Fixed duping inventories on death
-
-Version 0.5.8
-
-    Fixed bug where players inventories would dupe during combat
-    
-Version 0.5.7
-
-    Fixed monsters instant killing players
-    Misc fixes
-Version 0.5.4
-
-    Changed herbalism skill gain from wheat to be WAAAAY slower
-
-Version 0.5.3
-
-    Players will now correctly drop their inventories when killed by a monster
-
-Version 0.5.2
-
-    Fixed MAJOR bug preventing swords skill from gaining through combat
-
-Version 0.5
-
-    Archery Added
-    Swords Added
-    Acrobatics Added
-    Logging for Party/Admin chat added
-    Fixed whois to show correct values for Excavation
-    Made death messages much much more specific
-
-Version 0.4.4
-
-    Fixed being able to repair full durability iron tools
-    Fixed herbalism benefits not behaving properly
-    Fixed removing 1 diamond from every stack of diamond when repairing diamond
-
-Version 0.4.2
-
-    Removed myspawn from the motd
-
-Version 0.4.1
-
-    Fixed /mcc showing incorrect command for herbalism
-    Changed unarmed skillrate to be much slower than before
-    Modified a few skill descriptions
-    Added permission for /whois
-    Players can now use admin chat without being op as long as they have the correct permission (requires Permissions)
-
-Version 0.4
-
-    Permissions support
-    Removed OPs having different names than normal players
-    Removed /setspawn & /spawn
-    Slowed down excavation skill rate
-    Fixed excavation coal drop being too rare
-
-Version 0.3.4
-
-    Creepers now give double xp for unarmed
-    Iron armor can now be repaired!
-    Fixed bug stopping items from being repaired
-
-Version 0.3.3
-
-    Yet another herbalism skill gain tweak
-
-Version 0.3.2
-
-    Changed excavation loot tables to be more rewarding
-    Changed sand to give normal excavation xp instead of double xp
-    Fixed herbalism skill exploit
-    Mobs killed with unarmed now drop loot properly
-    Unarmed xp rate depends on mob (zombies lowest fyi)
-    Huge player crashing bug fix on disarm!
-
-Version 0.3.1
-
-    Fixed excavation not saving properly
-    Fixed repair using excavation values
-
-Version 0.3
-
-    Unarmed skill
-    Herbalism skill
-    Excavation skill
-    Many bugfixes (thanks for reporting them!)
-    /<skillname> - Detailed information about skills in game
-
-Version 0.2.1
-
-    Misc bugfixes
-
-Version 0.2
-
-    Repair ability added
-    Repair skill added
-    Iron Armor repair temporarily disabled
-    Anvils (Iron Block) added
-    /mcmmo & /mcc added
-    Misc changes to existing commands
-    Misc bug fixes
-
-Version 0.1
-
-    Releasing my awesome plugin
-
+ - Removed extra durability loss from Leaf Blower
+
+Version 1.3.13
+ + Added task & command to prune old and powerless users from the SQL database.
+ + Added Craftbukkit 1.4.6 / 1.4.7 compatibility
+ + Added new /mcrank command for showing a players leader board ranking for all skills in one place
+ + Added a configurable durability cap for ArmorImpact to advanced.yml
+ + Added the version number to /mcmmo
+ + Added bats, giants, witches, withers, and wither skeletons to the mcMMO combat experience list, and makes their experience drops configurable
+ + Added the ability to track mobs spawned by mob spawners or the Taming ability when the chunks they are in unload and reload
+ + Added wooden button to the list of items that shouldn't trigger abilities
+ + Added a new feature to fishing. Players will have +10% chance of finding enchanted items when fishing while it's raining
+ + Added displaying bonus perks on skill commands
+ + Added config option to disable gaining Acrobatics XP from dodging lightning
+ + Added missing skill guides. They're finally here!
+ + Added more localization 
+ + Added a very secret easter egg
+ = Fix issue with Sand/Gravel tracking
+ = Fix possible NPE when using the PartyAPI to add a player to a party that doesn't exist.
+ = Fix mcremove command for mySQL
+ = Fix a java.io.FileNotFoundException when using SQL
+ = Impact now works with mobs wearing armor
+ = Fixed issue with Tree Feller dropping player-placed blocks
+ = Fixed issue with missing default cases from several switch/case statements
+ = Fixed issue with Mining using actual skill level rather than max skill level
+ = Fixed some issues with static access
+ = Fixed ItemStack deprecation issues
+ = Fixed Async deprecation issues
+ = Fixed a bug with MySQL databases (non-alphanumeric characters preventing MySQL access)
+ = Fixed a bug where the /skillreset command was broken
+ = Fixed a bug where skill commands displaying .x% instead of 0.x%
+ = Fixed a bug Unbreaking enchantments being ignored when using Treefelling and when hit by Armor Impact
+ = Fixed a bug where only 1 diamond was needed to fully repair a broken item: Repaired the Repair skill!
+ = Fixed a bug where a infinite loop of errors caused by mySQL database could cause the server to crash
+ = Fixed a bug where PartyChangeEvent was fired even when a player isn't able to change parties
+ = Fixed a bug which caused advanced.yml not to work for Swords
+ = Fixed a bug which caused advanced.yml not to respect every MaxChance node
+ = Fixed a bug where GreenThumb_StageChange wasn't read from advanced.yml
+ = Fixed a bug where Repair would remove enchantments but the glow effect remained
+ = Fixed a bug where dropped items did not retain custom NBT data
+ = Fixed a bug which caused a potentially infinite recursion in a btree structure
+ = Fixed a NPE with custom blocks
+ = Fixed a bug with Blast Mining never dropping debris blocks
+ = Fixed a bug with Blast Mining incorrectly handling reduced TNT damage
+ = Fixed a bug with conflicting fishing enchantments
+ = Fixed a bug where triple drops wouldn't happen
+ = Fixed a bug which caused fishing to ignore max/min levels in treasures.yml
+ = Fixed a bug where treefeller affected player-placed blocks
+ = Fixed bug where Skull Splitter would be applied twice.
+ ! GJ stopped being a lazy slacker and got stuff done
+ ! Nossr50 actually committed something
+ ! Changed code that uses SpoutPlugin to make it compatible with the latest version
+ ! Reimplemented skill level and power level caps.
+ ! Moved Arcane Forging and Fishing setting from config.yml to advanced.yml
+ ! Overall SQL query improvements
+ ! Reduced number of SQL queries for mcTop command from 11 to 1, speeding it up immensely
+ ! Changed FFS Leaderboards to hold information in memory rather than doing IO work (optimizations)
+ ! Improved chunk conversion (less errors)
+ ! Changed Fishing Treasure Hunter, chance has increased and now actually is level dependent
+ ! Indexed most used mySQL columns for faster queries
+ - Removed dead code relating to null profiles
+ - Removed unused imports
+ - Removed ChunkletUnloader and dependents, since they are no longer necessary.
+
+Version 1.3.12
+ + Added Craftbukkit 1.4.5 compatibility
+ + Added the new 1.3.2 items, xp and double drops for Cocoa beans & Emeralds, EnderChest to the list of blocks that shouldn't trigger abilities
+ + Added new items from Minecraft 1.4 to Herbalism (potatoes & carrots)
+ + Added new configuration file for advanced users.
+ + Added new permission nodes to greenthumb for the 1.4 items
+ + Added new mobs from Minecraft 1.4 checks for every ability
+ + Added new active ability for Repair: Salvage
+ + Added options to 'config.yml' configure shake chance
+ + Added the option to negate experience earned for Herbalism while in a minecart to prevent afk leveling
+ + Added Green thumb now converts cobble walls to mossy cobble walls
+ + Added beacons and anvils to list of blocks that don't trigger abilities
+ + Added a configuration option to disable experience gains when in a minecraft for Acrobatics and Herbalism, to prevent AFK leveling
+ + Added a new passive ability for Fishing, Fishermans diet. Increases hunger restored from fish
+ + Added a feature to display all active perks on login
+ ! Changed Fishing, Shake drops changed from guaranteed to based upon fishing level and perks
+ ! Changed Woodcutting, the amount of experience earned when using Tree Feller on jungle trees has increased
+ ! Changed Herbalism double drop rates for melons and netherwart
+ ! Changed filesystem usage, it's reduced a lot. Should help reduce lag on larger servers
+ ! Changed database connection handling. Support for aggressive connection timeouts, with exponential backoff for multiple failures
+ ! Changed Cobblestone walls are now mossy-able with Greenthumb
+ ! Changed the skull drop rates of the shake ability to 3%
+ = Fixed a NPE when Citizens perform certain tasks
+ = Fixed a NPE with Woodcutting, excessive null chunk before earning Woodcutting experience
+ = Fixed a NPE related to skill cooldowns
+ = Fixed a NPE when a players profile was null
+ = Fixed a NPE involving certain explosions
+ = Fixed a dupe bug when for players who were using a 'nuker' client
+ = Fixed a dupe bug where pistons were used to dupe ores
+ = Fixed a dupe bug with Salvage when players were in Creative mode
+ = Fixed a bug where the player was displayed an incorrect cooldown time
+ = Fixed a bug where players could earn experience when they were dealing 0 damage
+ = Fixed a bug where players could get double drops from mossified Cobblestone
+ = Fixed a bug where Herablism magically converted potatoes to carrots
+ = Fixed a bug where you couldn't modify the stats of offline players
+ = Fixed a bug where treefeller didn't work properly on tree's with side-way logs
+ = Fixed a bug where the Arcane forging downgrade chance should've been 0, but actually wasn't
+ = Fixed a bug where Fishing would sometimes give items with empty enchantments
+ = Fixed a bug where the lucky perk for Fishing was actually an unlucky perk
+ - Removed nothing
+
+Version 1.3.11
+ ! Changed axes to start with 1 durability damage instead of 5, gain 1 durability damage every 50 levels instead of 30, and only have a 25% chance on hit to damage armor (per armor piece)
+ + Added compatibility with bow-wielding NPCs from Citizens/NPC mods
+ + Added compatibility for pvp-prevention plugins for Serrated Strikes
+ = Fixed bug where mcMMO could throw NPE errors if trees cut down were from a custom mod and had an id of 17
+ = Fixed dupe bug where mcMMO would ignore other block-protection plugins for various abilities
+ = Fixed NPE with hardcore mode's vampirism
+ 
+Version 1.3.10
+ + Added 1.3.1 compatibility
+ + Added permission node for Iron Grip ability (mcmmo.ability.unarmed.irongrip)
+ + Added ability for custom blocks to drop a range of items.
+ + Added Ability API functions
+ + Added 50% & 150% XP boost perks
+ + Added "lucky" perk for donors
+ = Fixed /inspect not working on offline players
+ = Fixed custom blocks, tools and armors not loading properly
+ = Fixed duplication bug with sticky pistons
+ = Fixed "GenericLabel belonging to mcMMO..." message
+ = Fixed menu exit button not working
+ = Fixed Repair enchant downgrade not working
+ = Fixed NPE caused by Spout players after a /reload
+ = Fixed ConcurrentModificationException on world unload
+ = Fixed players never being removed from memory (memory leak)
+ = Fixed admin chat being seen by everyone
+ = Fixed issue with UTFDataFormatException occurring on occasion when trying to load Chunklets
+ = Fixed ArrayIndexOutOfBounds error caused when trying to use /xplock after logging in but before gaining XP
+ = Fixed custom tools not properly respecting the Ability_Enabled flag.
+ = Fixed "lower tool" messages still being displayed even when ability messages are disabled.
+ = Fixed custom blocks not dropping the proper item with Super Breaker when Silk Touch is used
+ = Fixed custom woodcutting blocks throwing errors.
+ = Fixed possible ClassCastException from catching something other than a mob when using the Shake Mob skill
+ ! Changed the format by which Chunklets are stored to be much smaller, and much faster to load
+ ! Optimized how player placed blocks are tracked
+
+Version 1.3.09
+ + Added compatibility with AntiCheat (Which I highly recommend to prevent cheating)
+ + Added several permission nodes to give individual users special perks (Double/Triple/Quadruple XP)
+ + Added reduced cooldown permission nodes as special perks (1/4, 1/3, 1/2 cooldown)
+ + Added increased activation time permissions nodes as special perks (+4, +8, and +12 seconds)
+ + Added API for plugins to add custom tools directly via Spout - repair / abilities do not work ATM
+ + Added offline party members to the list displayed by /party
+ + Added possibility to kick offline members from parties
+ = Fixed bug that would cause a NPE for players that had no parties
+ = Fixed Vampirism not notifying the correct amount of stolen levels
+ = Fixed bug with Acrobatics not saving you from deadly falls
+ = Fixed /mcremove being applied only after a reload
+ = Fixed Archery PVE disablement not working properly
+ = Fixed possible NPE when a projectile is shot by a dispenser or doesn't have any shooter
+ = Fixed issue with NoCheatPlus and Serrated Strikes / Skull Splitter (fight.noswing)
+ = Fixed tiny memory leak concerning Archery
+ = Fixed bug where you could receive Archery XP from Potions
+ = Fixed bug where Chunklets for the < 64 y coordinates would not be properly loaded
+ = Fixed exploit with block duplication via piston pushing
+ = Fixed bug with falling sand/gravel not being tracked
+ = Fixed bug with Tree Feller not working with custom axes
+ = Fixed bug with locale strings when trying to teleport to a non-existent player
+ = Fixed bug with Tree Feller changing durability before checking for axe splintering
+ = Fixed bug with Repair Mastery permission due to typo
+ = Fixed bug with repairing items that use metadata
+ = Fixed bug with Chunklets not being reloaded on /reload
+ = Fixed possible NPE when falling with no item in hand
+ ! API methods can now only be used in a static way
+ ! Arrows shot from a bow having the Infinity enchantment can no longer be retrieved
+ ! Arrows that aren't shot by an entity are now able to be dodged (currently only from dispensers)
+ ! Changed Spout settings to be in their own config file (spout.yml)
+ ! Changed file format for parties (parties.yml), previous files are no longer used
+ ! Changed mcMMO to inform on corrupt Chunklets and make new ones
+
+Version 1.3.08
+ + Added more notifications about Vampirism and Hardcore mode on player death
+ + Added information about Hardcore mode when joining a server running Hardcore mode
+ + Added new hidden.yml inside the jar for very sensitive config options for advanced users
+ + Added option to disable Chunklets for servers which do not have doubledrops and do not care about xp farming
+ + Added new "Max_Seconds" setting in config.yml to limit the max time of abilities
+ + Added new repair configs to allow customization of the repair skill
+ + Added message to inform users about hardcore mode on login
+ = Fixed exploit where you could gain tons of Acrobatics XP from spamming Ender Pearls
+ = Fixed normal pistons marking a block as user-placed on retract if it wasn't a sticky piston (thanks turt2live!)
+ = Fixed handling of the Unbreaking enchantment so that tools are actually damaged as they should now
+ = Fixed hurting pet cats with serrated strikes
+ ! Changed Hardcore Vampirism to require the victim to have at least half the skill level of the killer in order for vampirism to proc (this is to avoid exploitation)
+ ! Changed Hardcore Vampirism to steal a minimum of 1 skill level from a player no matter the percentage
+ ! Changed Hardcore & Vampirism to not be executed if percentages were set to zero or below
+ ! Changed Vampirism to actually remove stats from the victim
+ ! Changed Vampirism to inform the victim of their stat loss
+ ! Changed Mining to allow Silk Touch to work again since the dupe exploit has been fixed.
+ ! Changed Metrics to also report if the server uses plugin profiling
+ - Removed level and item settings from Repair skill in config.yml
+
+Version 1.3.07
+ + Added ability to gain XP from custom blocks. Enable custom blocks in the config file, then enter the data in the blocks.yml file.
+ + Added ability to gain XP with custom tools. Enable custom tools in the config file, then enter the data in the tools.yml file.
+ + Added ability to repair custom tools. Enable custom tools in the config file, then enter the data in the tools.yml file.
+ + Added ability to repair custom armor. Enable custom armor in the config file, then enter the data in the armor.yml file.
+ + Added functionality which makes a new folder in all world files "mcmmo_data" to store player placed block information in
+ + Added new configurable Hardcore mode functionality to mcMMO
+ + Added new configurable Vampirism PVP stat leech for Hardcore mode
+ + Added new bypass permission node for the negative penalties of Hardcore mode 'mcmmo.bypass.hardcoremode'
+ + Added configurable level curve multiplier which allows for tweaking the steepness of the XP needed to level formula
+ + Added a permission node for Archery bonus damage
+ + Added a permission node for Greater Impact ability
+ + Added permission nodes for Treasure & Magic Hunter for Fishing
+ + Added a permission node for Farmer's Diet
+ + Added config options for enabling/disabling specific double drops
+ + Added automatic zip backup of flatfile database & config files
+ + Added config options to enable/disable specific skills for PVP & PVE
+ = Fixed bug where Tree Feller was looking at the wrong blocks for determining how much to take down.
+ = Fixed bug where Green Terra consumed seeds even on Mossy Stone Brick
+ = Fixed bug where the client didn't reflect the Stone Brick to Mossy Stone Brick change
+ = Fixed bug where an arrow could bounce off entities on daze proc
+ = Fixed bug where a player could gain Acrobatics experience while riding a cart
+ = Fixed /party not working properly with 2 arguments
+ = Fixed /party not showing properly the member list
+ = Fixed /ability not checking the right permission
+ = Fixed rare NPE on /party command
+ = Fixed Arrow Retrieval dropping only one arrow
+ = Fixed /p and /a incompatibilities with bChatManager
+ = Fixed Iron Grip working reversely
+ = Fixed NPE when user clicked the HUD button with Spout
+ = Fixed bug where the permission node for Impact didn't work
+ = Fixed some bypass nodes defaulting true for Ops
+ = Fixed bug with trying to use Chimera Wing while standing on a half-block
+ = Fixed duplication bug when a placed block was mined after a server restart
+ = Fixed exploit where shooting yourself with an arrow gave Archery XP
+ ! Changed the mcMMO motd to link to the new website rather than the wiki
+ ! Changed bleeding ticks damage to 1 from 2
+ ! Changed Mining to ignore blocks when the pick is enchanted with Silk Touch
+ ! Changed Super Breaker to be non-functional when used with a Silk Touch enchanted pick
+ ! Changed MySQL to save player information 50ms apart from each other to reduce the load on the MySQL server
+ ! Changed the permission node for Blast Mining detonation to mcmmo.ability.blastmining.detonate (was mcmmo.skills.blastmining) for the sake of consistency
+ ! Changed skill commands to only display what you have permissions for
+ ! Changed mcMMO to use a new storage system for player placed blocks
+ - Removed some unused permission nodes
+ - Removed a few config options in favor of permissions nodes (Hunger Bonus, Armor/Tool Repair, Instant Wheat Regrowth)
+ - Removed level requirement for repairing string tools from the config file
+
+Version 1.3.06
+ + Added Iron Golem XP for aggressive golems
+ + Added permissions check to skill functions
+ + Added API functions for obtaining offline profiles & profiles via player names
+ + Added API functions for admin & party chat
+ + Added Iron Grip skill to Unarmed which gives players an chance to keep from being disarmed.
+ + Added some new languages to the locale files.
+ = Fixed Green Thumb consuming 2 seeds instead of 1
+ = Fixed exploit where you could teleport to yourself with PTP to prevent things like fall damage
+ = Fixed NPE error with Metrics on startup
+ = Fixed bug where Herbalism required double drops permission to give XP
+ = Fixed bug where {0} would be displayed in front of your power level in mcstats
+ = Fixed mmoupdate not being useable from console
+ = Fixed bug with repairing wooden tools
+ = Fixed bug with Nether Wart not awarding XP
+ = Fixed bug with fishing treasures when treasures list is empty
+ = Fixed bug with only getting one level when there was enough XP for multiple levels.
+ = Fixed bugs with the way /mctop displayed
+ = Fixed issues with custom characters & locale files.
+ = Fixed double explosion for Blast Mining
+ = Fixed Blast Mining not giving triple drops when it should
+ ! Changed Bleeding to now stack to a finite number on Monsters and will wear off eventually
+ ! Changed how we handled the config file to prevent any bugs when returning values
+ ! Changed locale files to use a new naming scheme. This breaks ALL old locale files. If you want to assist with re-translating anything, go to getlocalization.com/mcMMO
+ ! Changed /mcremove to check for users in the MySQL DB before sending queries to remove them
+ ! Changed how the tree feller threshold worked for the better
+ ! Changed /mcremove to no longer kick players when they are removed from database
+ ! Changed /mcremove to work on offline users for FlatFile
+ ! Changed PlayerProfile constructor to always take a boolean
+ ! Changed getPlayerProfile function to work for online & offline users
+ ! Changed Archery's Daze to deal 4 DMG on proc (2 Hearts)
+ ! Changed /addlevel command to work for offline users
+ ! Changed party & admin chat handling to be nicer to developers
+ ! Changed /mcrefresh to work from console
+ ! Changed /mcrefresh to work for offline players
+ ! Changed UpdateXPBar function to hopefully avoid errors
+ ! Changed /party to show offline party members
+ ! Changed Blast Mining requirements, now asks for the player to be crouching
+
+Version 1.3.05
+ + Added Skill Shot to Archery which increases damage dealt by 10% every 50 skill levels (caps at 200%)
+ + Added ExperienceAPI and PartyAPI classes for developer use
+ + Added ability to cap overall power level
+ + Added showing powerlevel below a persons name if you run Spout (optional)
+ = Fixed errors when Spout would disable itself after start-up
+ = Fixed XP bar not updating when XP was gained
+ = Fixed bug with repairing wooden tools
+ = Fixed bug where spawned wolves only had 8 health.
+ = Fixed bug where rare Treasures from Excavation were dropping too often
+ = Fixed bug where Skull Splitter & Serrated Strikes could be used without permissions.
+ = Fixed bug where API functions were set to static
+ = Fixed bug where mmoedit threw errors when modifying an offline user
+ = Fixed dupe exploit with Blast Mining
+ ! Changed Tree Feller to account for ability durability loss but not leaves.
+ ! Changed bypass node for Arcane Forging to not default to true for OPs
+ - Removed Ignition from Archery
+ - Removed McMMOPlayerRepairEvent - was basically a duplicate of McMMOPlayerRepairCheck but couldn't be cancelled.
+
+Version 1.3.04
+ + Added McMMOPlayerRepairEvent for API usage - fires after completion of a repair.
+ + Added McMMOPlayerRepairCheckEvent for API usage - fires before repair process begins, can be cancelled.
+ + Added ability to get skill level from McMMOPlayerExperience events
+ + Added McMMOPartyTeleportEvent for API usage - fires before a successful teleportation would occur.
+ + Added McMMOPartyChangeEvent for API usage - fires whenever a player joins or leaves a party
+ = Fixed Shake ability dropping bonemeal instead of ink for squids.
+ = Fixed Green Terra & Super Breaker awarding 4x drops at high levels.
+ = Fixed summoned ocelots never changing skins.
+ = Fixed bug with Disarm not working
+ = Fixed some API functions not being visible
+ = Fixed bug where /ptp worked on dead party members
+ ! Changed MySQL to reload all player information on reconnection
+ ! Changed event package structure - be sure to update these if you're using the API in your plugin
+
+Version 1.3.03
+ + Added Ocelots to Taming XP tables
+ + Added ability to summon Ocelots with Call of the Wild
+ + Added offline user functionality to mmoedit
+ + Added bookshelves to list of blocks that don't trigger abilities.
+ + Added 'mcmmo.repair.arcanebypass' permission node to bypass Arcane Repair and keep your enchantments
+ + Added config option to disable Herbalism's instant wheat replanting
+ + Added LOTS of new permissions nodes. *CHECK PLUGIN.YML FOR UPDATES*
+ + Added Italian locale file - thanks Luxius96!
+ + Added ability to inspect Ocelots with Beast Lore
+ + Added console functionality to mctop
+ = Fixed Green Terra not awarding Triple Drops
+ = Fixed ClassCastException from Taming preventDamage checks
+ = Fixed issue with Blast Mining not seeing TNT for detonation due to snow
+ = Fixed issue with block interaction returning NPEs
+ = Fixed issue where every block broken had a mining check applied
+ = Fixed issue where every block broken had a herbalism check applied
+ = Fixed issue where blocks weren't being removed from the watchlist
+ = Fixed exploit where you could use /ptp to teleport to anyone
+ = Fixed bug where Green Terra didn't work on Stone Brick
+ = Fixed bug where Tree Feller could be used without permissions
+ = Fixed exploit where falling sand & gravel weren't tracked
+ = Fixed exploit where Acrobatics could be leveled via Dodge on party members.
+ = Fixed exploit where you could gain combat XP on animals summoned by Call of the Wild
+ ! Changed mcMMO to save profiles only when the profile is about to be discarded rather than on player quit
+ ! Changed MySQL to try to reconnect every 60 seconds rather than infinitely which caused server hangs
+ ! Changed mcMMO to be better about saving player information on server shutdown
+ ! Changed PTP to prevent teleporting if you've been hurt in the last 30 seconds (configurable)
+ ! Changed Chimera Wing failure check to use the maxWorldHeight.
+ ! Changed inspect failed message to say inspect rather than whois
+ ! Changed Call of the Wild to activate on left-click rather than right-click
+ ! Changed Blast Mining to track based on Entity ID vs. Location
+ ! Changed mmoedit to save a profile when used (this will make mctop update)
+ ! Changed a few Runnable tasks to have their own classes
+ ! Changed parties so that a player will leave their existing party if they enter a world where they don't have party permissions.
+ ! Changed Call of the Wild to summon animals already tamed.
+ ! Changed mob spawner tracking to use new Metadata API
+ ! Changed block watch list to use new Metadata API
+ ! Changed around a few config options, including the ones for mySQL. *YOU NEED TO REDO YOUR CONFIG FILE*
+ - Removed 'true/false' debug message from Inspect command
+
+Version 1.3.02
+ + Added in game guides for Mining, Excavation, and Acrobatics. Simply type /skillname ? to access them
+ ! Changed Tree Feller to hand out 1/4 of normal XP for each JUNGLE LOG block it fells
+ ! Changed Tree Feller to only fell trees if you have enough durability
+ ! Changed Tree Feller to cause injury if your axe splinters from a failed Tree Feller attempt
+ ! Changed invincibility checks in EntityDamage listeners to accommodate for vanilla MC behaviour
+ ! Changed Ignition to add fire ticks rather than replacing them.
+ ! Changed Axes critical to have a max critical rate of 37.5% down from 75%
+ = Fixed bug where Taming defensive checks got called twice
+ = Fixed Shake not working correctly
+ = Fixed bug with Axes command displaying wrong Greater Impact bonus damage
+ = Fixed bug where Impact didn't apply bonus damage
+ = Fixed Impact proccing multiple times in a row
+ = Fixed bug where PVE skills didn't level
+
+Version 1.3.01
+ = Fixed bug where Tree Feller had no cooldown
+ = Fixed bug with activating Skull Splitter after using Tree Feller
+ 
+Version 1.3.00
+ + Added ability to customize drops for Excavation skill (treasures.yml)
+ + Added ability to customize drops for Fishing skill (treasures.yml)
+ + Added messages to nearby players when your abilities wear off
+ + Added jungle trees to Woodcutting XP tables
+ + Added player notification for when they stop Bleeding
+ + Added configuration option to control mcMMO reporting damage events
+ + Added hunger regain bonuses to Herbalism skill
+ + Added Blast Mining subskills to Mining
+ + Added Fast Food Service subskill to Taming
+ + Added size limit to Tree Feller in config as Tree Feller Threshold
+ + Added /inspect (works on offline players)
+ + Added /addlevels commands
+ + Added /mcremove command which lets you remove users from MySQL or FlatFile
+ + Added config values for XP multipliers for different hostile mobs
+ + Added 'mcmmo.commands.inspect' permission node for the new /inspect command
+ + Added Impact & Greater Impact subskills to Axes
+ + Re-added mcMMO reporting damage events
+ = Fixed bug with updating MySQL tables to include fishing on servers using custom table prefixes
+ = Fixed bug where Disarm didn't work at all ever
+ = Fixed bug where Swords command showed Bleed Length twice instead of Bleed Chance
+ = Fixed bug where Tree Feller wasn't checking for Tree Feller permission
+ = Fixed bug where Leaf Blower required Tree Feller permissions rather than Woodcutting permissions
+ = Fixed Leaf Blower preventing the use of shears to collect leaves
+ = Fixed Green Thumb & Green Terra not consuming or requiring seeds to replant Wheat
+ = Fixed Super Breaker & Giga Drill Breaker failing to damage tools
+ = Fixed Tree Feller not giving proper XP for different kinds of trees
+ = Fixed Skill Abilities conflicting with NoCheat
+ = Fixed memory leak with mob spawner tracking
+ = Fixed /mcability not respecting permissions
+ = Prettied up new config files
+ = Various skill function optimizations
+ ! Changed how mcMMO calculates distance between two points (optimizations)
+ ! Changed how mcMMO handles MySQL connections (optimizations)
+ ! Changed the description /mcmmo provides to be more up to date and relevant
+ ! Changed mcMMO user information to be stored for 2 minutes after log out to reduce lag on rejoins
+ ! Changed Food+ to be named Farmer's Diet in Herbalism
+ ! Changed default values of Woodcutting XP tables
+ ! Changed 'Pine' to be renamed 'Oak' in Woodcutting XP tables
+ ! Changed the name of Unarmed Apprentice/Mastery to Iron Arm Style
+ ! Changed Axes to gain bonus damage every 50 skill levels
+ ! Changed Unarmed to start with a +3 DMG (1 Heart = 2 DMG) bonus from Iron Arm Style to make leveling it more viable
+ ! Changed Unarmed to gain bonus damage every 50 skill levels
+ ! Changed Unarmed to gain more bonus damage total than before
+ ! Changed Unarmed to have a max disarm chance of 33.3% rather than 25%
+ ! Changed Tree Feller to take down entire trees
+ ! Changed mob spawn tracking to use Unique Entity ID instead of Entity Object
+ ! Changed stats command name to mcstats for better plugin compatibility
+ ! Changed god mode to turn off if player enters world where he does not have mcgod permission
+ ! Changed Taming to also gain XP from animal taming
+ ! Changed Swords Bleeding effect to never kill
+ ! Changed Bleeding to never go beyond 10 ticks
+ ! Changed to use Bukkit's built-in ignoreCancelledEvents system
+ ! Changed chat logging for /p & /a
+ ! Changed Tree Feller to use per-use ArrayList
+ - Removed /mcstats console functionality
+ - Removed /whois command (replaced with /inspect which has similar functionality)
+ - Removed Master/Apprentice chat notifications to reduce spam
+ - Removed MySpawn system (You can still use Chimaera Wings) due to being outdated and unwanted
+ - Removed duplicate settings in config.yml
+ - Removed unused settings from config.yml (HP Regen)
+ - Removed Nether Brick from Mining XP Tables
+ - Removed Stone Brick from Mining XP Tables
+ 
+Version 1.2.12
+ - Fixed issue that caused terrible MySQL performance and negative XP on levelup (Issue #134)
+ - Fixed addxp command taking xprate and skill modifiers into account
+ - Added anonymous usage statistics (you can opt out in plugins/PluginMetrics/config.yml)
+ - Modified onEntityDamage priority to have better compatibility with other plugins (Factions, WorldGuard, etc...)
+ - Fixed /mcgod & /mmoedit permissions defaulting to true
+ - Fixed Fishing not working or handing out XP
+ - Fixed error with Skull Splitter / Serrated Strikes that caused server instability and log spam
+ - Fixed config.yml not having values for End Stone & other new mining blocks
+ - Fixed Green Thumb/Green Terra not correctly planting wheat (Issue #133)
+
+Version 1.2.11
+ - Removed legacy Permission & PEX dependency. (PEX still works fine with mcMMO)
+ - Made Smooth Brick to Mossy Brick and Dirt to Grass for green thumb configurable (Issue #120)
+ - Added MagmaCube to XP tables
+ - Made optimizations to Skull Splitter/Serrated Strikes
+ - Made it so players take damage if they try to log out with Serrated Strikes stacked onto them (Issue #131)
+ - Changed mcMMO to save data periodically to optimize performance with FlatFile & MySQL (Issue #138)
+ - Added a configurable save interval for the new save system
+ - Fixed a bug with the odds calculations for Serrated Strikes
+ - Fixed several commands not working from console (mmoedit, etc..) (Issue #150)
+ - Added a success message when executing xprate from console
+
+Version 1.2.10
+ - Fixed issue with receiving Woodcutting XP for all blocks broken (Issue #103)
+ - Fixed issue with Spout images & sounds not working (Issue #93)
+ - Fixed typo with repairing Leather Armor & Bows
+ - Fixed issue with Silk Touch & Double/Triple drops not working properly
+ - Fixed conflict with NoCheat plugin & Super Breaker/Giga Drill Breaker/Berserk/Leaf Blower (Issue #104)
+ - Fixed counter-attacking non-LivingEntity (Issue #100 & Issue #107)
+ - Fixed various bugs with Leaf Blower
+ - Added Monitor & ignoreCancelledEvents to onBlockPlace
+ - Made Anvil ID configurable
+
+Version 1.2.09
+ - Fixed issue with Repair Mastery (Issue #47)
+ - Made Arcane Forging fully configurable (Pull Request #52)
+ - Made Fishing configurable (Pull Request #60)
+ - Changed timer to be a bit more efficient (Issue #19)
+ - Changed to fire EntityDamageEvents for all damage done by mcMMO
+ - New custom event for developers McMMOPlayerLevelUpEvent
+ - New custom event for developers McMMOItemSpawnEvent
+ - Changed LoadProperties from the old Configuration to FileConfiguration
+ - Removed aliasing from config.yml
+ - Fixed mining procs from Super Break & Silk Touch
+ - Unused smelt property removed
+ - Minor optimizations
+ - Fix for setting properly block damage values
+ - Initial skill level capping added
+ - Initial command alias framework added
+ - Fixed abilities not handling Unbreaking items
+ - Fix for treefeller glitch
+ - Super secret anniversary easter egg!
+
+Version 1.2.08
+ - Changed Bukkit events to new event system
+ - Changed aliasing to send both the mcmmo command and the command used.
+ - Changes in combat exp (Pull Request #49)
+ - Repair for bows & leather armor (Pull Request #46)
+ - Fixed missing images (Pull Request #45)
+ - POM Changes for dependencies (Pull Request #36)
+ - Fishing & Repair fixes (Pull Request #31)
+ - Fixed CraftOfflinePlayer issue (Issue #212) errors for offline wolf owners
+ - Pull in commit from @NuclearW for issue from previous commit
+
+Version 1.2.07
+Fixed mctop not working at all (whoops!)
+Fixed problem with multithreading in mcMMO causing errors
+Fixed bug with Repair where it would remove the enchantments if you could not even repair the item
+The command mmoupdate now runs in its own thread to ease the burden on the server
+
+Version 1.2.06
+German translation has been updated
+Fixed fishing not being applied to MySQL DB when converting from Flat File -> MySQL
+Fixed mctop not taking Fishing into account some of the time
+Fixed bug where Taming would try to grab the name of offline players
+Fixed bug where Fishing would try to add an enchantment level not normally possible
+Fixed another bug with mmoedit and Fishing
+Added option to only allow tools to ready when you are sneaking, this is off by default
+Added Brewing Stand & Enchanters table to the list of blocks that won't cause you to ready your tool on right click
+
+Version 1.2.05
+Fixed my fix of not being able to place blocks near/on Anvils
+Fixed resources in inventory not correctly updating after Repair
+
+Version 1.2.04
+Fixed bug where you could not place blocks near/on the Anvil
+
+Version 1.2.03
+skills2 and experience2 will be removed from MySQL DB automagically when this version first runs
+Fishing is now stored in skills and experience tables on the MySQL DB as it should have been
+Fishing will now save properly for MySQL
+Fishing will now work properly with mctop for those using MySQL
+Fixed problems with mmoedit and fishing
+
+Version 1.2.02
+Added measures to prevent easy xp from hacks that cause a ridiculous amount of clicks per second
+Fixed bug where Call Of The Wild used only 1 bone to summon
+Reduced Endermen XP from 3x to 2x
+The number of bonus fish caught from fishing has been reduced
+Fishing XP reduced from 1500 to 800
+Fishing XP is now configurable in the config file
+
+Version 1.2.01
+Added a setting to turn off abilities completely from config
+Added a setting to just turn off ability messages from config
+Fixed the bug with sword repair
+Fixed mcMMO not working properly with Spout
+Added Fishing XP icon to Normal/Retro HUDs for Spout
+Added icons to Spout notifications for leveling Fishing
+Added Fishing Retro XP bar color customization to config file
+The number of bones required to use Call of The Wild is now configurable
+Reduced the XP animals would give from 1.5x to 1x
+Removed current durability value message from Repairing
+Fixed bug where Arcane Forging failed to display messages
+Fixed bug where Arcane Forging tries to downgrade level 1 enchants
+Fixed bug where Arcane Forging always kept enchantments if you had under 100 Repair skill
+
+Version 1.2.00
+Added Fishing Skill
+Added Call Of The Wild to Taming
+Added Arcane Forging to Repair
+Added new mobs to XP tables
+Added Master/Apprentice system to the Party system
+Animals now give xp to combat skills (those poor sheep ;_;)
+Removed unnecessary sorcery permissions from plugin.yml
+
+Version 1.1.17
+XP gained in combat is now softcapped by the remaining health of the entity you are damaging, preventing many exploits.
+Players in Creative mode no longer gain XP
+Compiled against latest Spout & CraftBukkit
+Added World PVP check to Ignition, should no longer ignore PVP settings
+Enemies should no longer grant XP when hit during their death
+Fixed an exploit that led to unlimited ability use
+Possibly fixed a bug where the same player would be listed multiple times in mctop
+Added author and description to plugin.yml
+
+/mmoedit and /addxp are useable from the console now
+Swearword's statistics tracking removed (He stopped the service, so its gone now.. On a positive note, I did find out 1000-1500 servers installed mcMMO a day)
+
+Version 1.1.16
+Added Melons to Herbalism xp tables
+Endermen added to Combat skill xp tables
+Silverfish added to Combat skill xp tables
+CaveSpider added to Combat skill xp tables
+
+Version 1.1.15
+Smooth Brick added to Green Terra
+Green thumb can be used to spread moss to Smooth Brick now
+Implemented a ghetto fix for the sword durability bug (real fix sometime soon)
+Added Spain Spanish localization (es_es)
+
+Version 1.1.14
+[1.8] Removed the Archery fire rate limiter as its no longer necesarry due to changes in game mechanics
+[1.8] Removed the bonus damage from Archery (I'll rework this skill soon)
+[1.8] Removed the food bonuses to healing Herbalism provided due to the change of eating in game mechanics
+[1.8] Swords no longer parry, no need to compete with in game mechanics
+[1.8] mcMMO no longer has an HP Regen system, no need to compete with in game mechanics
+[SPOUT] mcMMO now transfers files between [MC Server] -> [Client] rather than [Webserver] -> [Client]
+[SPOUT] Temporarily disabled the PartyHUD due to some performance issues
+[SPOUT/CONFIG] mcMMO now allows for disabling of the party HUD with the node Spout.Party.HUD.Enabled
+[BUG] Fixed a few problems with readying abilities for Woodcutting/Axes
+[MYSQL] Improvements have been made to the performance of MySQL thanks to krinsdeath
+[CONFIG] Spout.Party.HP tree removed, replaced with Spout.Party.HUD
+[CONFIG] Added an option for Excavation to require use of a shovel, on by default
+[COMPATIBILITY] Changed the listener priority for OnEntityDamage from High to Monitor (Should make mcMMO compatible with Worldguards pvp regions among other things)
+[COMPATIBILITY] Made party/admin chat modes more compatible with chat plugins (vChat)
+[API] Added addXpOverride for modders, this will ignore skill modifiers
+[SPOUT] The option to change the weburl of mcMMO Images/Sounds has been removed, if you want to customize mcMMO images/sounds you can open mcMMO.jar and replace them there
+[LOCALE] Portuguese Brazil locale added (Code: pt_br)
+[MISC] Added some experimental usage tracking, you can opt out of this in /plugins/stats/config.yml (Once its generated, may require 2 restarts)
+
+Version 1.1.13
+Pets are removed from party bars
+
+Version 1.1.12
+mcMMO now downloads files when you join the server to provide the best experience
+mcMMO now uses a brand new Party HUD by Rycochet (from his mmoParty plugin)
+Fixed the xpbar and xpicon settings in config to work properly
+Fixed infinite HP exploit with Herbalism
+Fixed bug where herbalism would heal out of the players normal health range
+Fixed bug where entering ':' into your party name caused stat loss among other things
+Fixed issue with block break listener priority
+
+Version 1.1.11
+mcMMO now properly cancels its Async taks when disabled
+Fixed newly generated configs using 2 instead of 1 for skill multipliers
+
+Version 1.1.10
+Added default hud setting to config
+Fixed bug where newly generated configs used old xp gain numbers
+
+Version 1.1.09
+Fixed mcMMO to run fine without Spout :)
+
+Version 1.1.08
+Fixed repair being 10x slower to level than normal
+
+Version 1.1.07
+Fixed the default HUD being set to RETRO instead of STANDARD
+
+Version 1.1.06
+mcMMO menu implemented! Default is 'M', change this in config
+Retro HUD implemented!
+Retro XP fill color is completely customizable on a per skill basis
+New levelup sound thanks to @Rustydaggers !
+With the help of Randomage the XP Formulas have been vastly changed for flexibility
+Global modifiers and skill modifiers now support decimals
+Global formula modifier dropped from config
+GigaDrillBreaker/Berserk doesn't drop clay blocks anymore
+Fixed bug where Herbalism didn't heal more for bread/stew when right clicking a block
+Fixed bug where Wheat did not use the values form the config file
+Fixed bug where Archery gave xp for inflicting self injury
+Watch added to clay loot tables and maps remove from clay loot tables
+
+Version 1.1.05
+Maps dropped from excavation are created correctly, and represent the area they are found in
+Fixed an exploit with clay and excavation
+Fixed a NPE with locking xp bars
+Fixed the !AdeptDiamond! localization error when repairing diamond with a skill below 50
+
+Version 1.1.04
+Removed URL settings for XPBAR/XPICON/HPBAR
+Added single URL setting for mcMMO
+Changed default host from Dropbox to Rycochet's webserver (with apparently unlimited bandwidth!, thanks Rycochet)
+Fixed Repair noise not getting played
+Fixed a small memory leak with party health bars
+
+Version 1.1.03
+Fixed a few images being hard-coded still rather than configurable
+
+Version 1.1.02
+Fixed bug where toggle for xpicon didn't work
+Fixed bug where Excavation gave gravel drops to grass
+Excavation now uses more enums
+
+Version 1.1.01
+Fixed toggles for hpbar/xpbar not working
+
+Version 1.1.0
+Brand new XP Bars, Health bars, and Skill Icons designed by BrandonXP
+Added /xplock <skillname> to lock the xp bar to a skill
+Repairing metal now has a sound effect
+Shears added to Repair
+MySpawn now works correctly when you are in the nether
+MySpawn message when you right click a bed is now squelched
+Intervals at which players renegerate hp have doubled in length (making it take 100% longer to regenerate than before)
+Rewrote many variables stored per player to be integer instead of long, reducing overall memory usage of mcMMO
+Rewrote the Timer mcMMO relies on to instead use the BukkitScheduler for performance
+Fixed the party member list of /party
+Fixed bug where Swords would counter-attack Projectiles
+Removed a debug message when repairing diamond armor
+Changed chat to use getDisplayName() instead of getName()
+Changed chat priority from lowest to highest
+Added Clay to excavation
+Added new items to Clay's loot tables
+Archery now works with the latest CB
+
+Version 1.0.50
+New /xprate command for those with mcmmo.admin permissions!
+mcMMO now uses Spout instead of BukkitContrib
+BukkitContrib support dropped
+XP Formula is now 100+(skill level value * skill modifier * global modifier) thanks to suggestion
+Fixed bug where /mmoupdate used the old directory instead of the new one to find the flat file
+Fixed bug where Unarmed Mastery damage bonus only did as much as Unarmed Apprentice
+Fixed bug where Pumpkins did not give out XP
+Coordinates removed from /whois as they didn't really fit
+/mcgod and /mmoedit now require permissions to be setup in some shape or form to be used
+Lapus renamed to Lapis in config
+
+Version 1.0.49
+Updated German locale
+Fixed bug where using the party system on a MySQL setup caused errors when writing to non-existent files
+Fixed bug where using /accept caused a NPE (hopefully)
+Fixed a few missing descriptions for commands
+
+Version 1.0.48
+Updated French Translation
+Updated German Translation
+Updated Polish Translation
+Placed Coal Ore and Redstone Ore won't give XP anymore
+Fixed unusually high memory usage at startup
+Added many features to the party system written by NuclearW
+
+Version 1.0.47
+Fixed another BukkitContrib error for servers not running BukkitContrib
+
+Version 1.0.46
+Fixed bug preventing Excavation from gaining skill
+
+Version 1.0.45
+Corrected /stats showing Repair XP as Level for Repair
+Corrected /repair showing Repair XP as Level for Repair
+Corrected /whois showing Repair XP as Level for Repair
+
+Version 1.0.44
+Fixed my 'fix' of BukkitContrib errors with Tree Feller
+
+Version 1.0.43
+Stopped things from being auto-smelt'd
+
+Version 1.0.42
+Corrected 2 more errors involving not running BukkitContrib
+
+Version 1.0.41
+Fixed errors using Tree Feller if your server wasn't running BukkitContrib (sorry!)
+Fixed some more leftover stuff involving the new half-finished mining skill
+Fixed excavation's Giga Drill Breaker not working on placed blocks
+
+Version 1.0.40
+Fixed errors if your server wasn't running BukkitContrib
+
+Version 1.0.39
+mcMMO won't auto-download and auto-run BukkitContrib anymore
+
+Version 1.0.38
+Commented code for the half-finished Infernal Pick subskill (Whoops)
+
+Version 1.0.37
+The donation message in /mcmmo is now toggle-able
+The anvil message now only gets shown the first time you place an anvil (after login)
+Reworked /mcmmo (an improvement I would say)
+Added /mcmmo text to localization file
+Archery fire rate now configurable
+Berserk mode stops items from being collected
+Taming no longer receives xp from wolves being harmed
+Fixed bug where /stats required Tree Feller permission to show Woodcutting skill
+Fixed bug where players with mcgod could be harmed by AoE
+Fixed bug where modifying a skill also modified the xp to the same amount (when it should be zero)
+
+BukkitContrib Stuff
+Added a pop-up when placing an Anvil
+Added pop-ups on levelup
+Added basic sound effects to various abilities (Berserk, Tree Feller, Super Breaker, Leaf Blower, etc...)
+
+Code Stuff
+Added checkXp(SkillType, Player) for plugin devs (use this after modifying XP to check for levels)
+Added getPlayerProfile() which returns a PlayerProfile object for plugin devs (You can do almost everything with this object)
+100% more enums
+Changed how checking skill xp worked to be more efficient
+
+Version 1.0.36
+mcMMO now properly supports Bukkit/PEX/Permissions for Permissions
+Config.yml will no longer generate Performance Debugging nodes
+Registered permission nodes to plugin.yml
+Some more changes to Permissions code
+Fixed bug where Super Breaker activated where it shouldn't
+Fixed bug with enabling/disabling mcgod in config.yml
+Fixed bug with Excavation not kicking in until 1 level higher
+
+Version 1.0.35
+Added a Toggle for Chimaera Wing in config.yml
+Added customization of what item is used for Chimaera Wing in config.yml
+Fixed bug with randomly receiving Taming XP
+mcmmo.users file moved into /plugins/mcMMO/FlatFileStuff/
+Leaderboard files now moved into /plugins/mcMMO/FlatFileStuff/Leaderboards
+Locale files now have the prefix locale_ instead of messages_
+Locale files are now located inside com/gmail/nossr50/locale/ instead of com/gmail/nossr50/
+Updated the code that handles permissions (this may mean 3.1.6 will finally play well!)
+Some more source code organization
+Fixed warnings for compiler
+Removed dependencies on CraftBukkit
+Registered commands to OnCommand
+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
+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
+/mining now shows mining values instead of taming values
+
+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)
+MySQL version now makes requests to the MySQL server less frequently (should help performance)
+Fixed bug with Skull Splitter hitting the user
+
+Version 1.0.32
+Added "General.Performance.Print_Reports" node to config.yml to help identify causes of performance issues
+Fixed bug of swords users hurting themselves with serrated strikes
+
+Version 1.0.31
+Fixed bug of trying to cast Animals to non-animals
+
+Version 1.0.30
+Mobs that spawn from spawners no longer give XP (for reals this time)
+
+Version 1.0.29
+Mobs that spawn from spawners no longer give XP (again)
+Fixed bug where Serrated Strikes did not Bleed additional targets
+Identified and solved a potential memory leak in Bleed Simulation
+Renamed the Object Config to Misc and rewrote parts of it
+Rewrote Party/Admin/God toggles
+Added Polish language support (pl)
+
+Version 1.0.28
+Actually fixed /stats showing excavation values for swords
+Made some improvements to how Bleed Simulation was handled for different entity types
+Obsidian now does normal durability damage during Super Breaker
+
+Version 1.0.27
+Fixed /stats showing excavation values for swords
+Hopefully fixed a wide range of NPE errors
+Updated German (de) localization
+
+Version 1.0.26
+Fixed accidentally making power levels go above 9,000
+
+Version 1.0.25
+Compatible with the latest CB
+Beast Lore now functions correctly
+Wolves are no longer invincible to players
+Changed the look of Beast Lore
+Skill info pages now show your stat in that skill (if you have permission)
+/stats and /whois has been alphabetized and divided into three categories (Gathering/Combat/Misc)
+Abilities will not trigger on Trap Doors
+
+Version 1.0.24
+Now compatible with latest RB (928)
+Taming now receives XP from your wolves harming foes
+Taming is now easier to level
+Green Thumb now drops seeds when harvesting Wheat
+
+Version 1.0.23
+Modified Bleed Simulation to fix performance problems
+Rewrote MySpawn to be more efficient when calculating time left
+Rewrote Skills to be more efficient when calculating time left
+
+Version 1.0.22
+Added 'Name' nodes to commands for renaming them
+
+Version 1.0.21
+Fixed Skull Splitter length in /axes displaying incorrectly
+Fire rate limiter now correctly uses the value in the config file
+Stone XP now correctly uses the value in the config file
+Cobble -> Mossy now correctly uses the value in the config file
+Removed setmyspawn from config file as it serves no purpose
+All commands now have an 'Enabled' node in the config file that when set to false disables the command completely
+Fixed color scheme inconsistency for Mining in /whois results
+
+Version 1.0.20
+Fixed Array Index Out of Bounds error
+
+Version 1.0.19
+Removed a failsafe for the Timer that is no longer necessary (should improve performance)
+Fixed /myspawn not working by rewriting it :3
+Fixed exploit where players could break a freshly placed mushroom for XP
+MySQL User Passwords can now be blank (Although you really should have a password...)
+Fixed a few NPE errors
+
+Version 1.0.18
+Fixed MySQL default TablePrefix
+Fixed Wheat not being configurable
+
+Version 1.0.17
+Brand new YAML Configuration file
+Ability to configure XP for all gathering skills in config file
+German Language added to mcMMO
+French Language added to mcMMO
+MySpawn will no longer heal players
+/<skillname> commands now also check for their localized names for displaying help
+Added many more Strings to localization files
+Added more safeguards to MySpawn for NPE
+Fixed bug where Tree Feller Radius depended on WoodCutting XP rather than Skill Level
+Fixed bug where Readying a Hoe returned a missing localization string
+Added some safeguards into Bleed Simulation to prevent possible memory leaks
+Performance improvements to storing/calling Skill/XP Values
+Plugged a potential memory leak with PlayerProfiles not being removed correctly
+Disabled the mob spawner camping anti-exploit in favor of performance
+
+Version 1.0.16
+Fixed bug where localization file failed to load
+Changed en_US to lowercase
+mcMMO now requires locale files to be in lowercase
+Fixed a few strings missing from the localization file
+
+Version 1.0.15
+Removed leftover code that spammed SQL errors
+
+Version 1.0.14
+Added many missed strings into localization
+Finnish Localization updated for the new strings
+Green Thumb should respect Block Protection plugins now
+Fixed Number Format Exception when loading a PlayerProfile
+
+Version 1.0.13
+Fixed bug/NPE where stats would not load and therefore 'reset' for players
+Fixed NPE involving /ptp
+Added "enableMOTD" setting to properties file
+
+Version 1.0.12
+Fixed another NPE error
+Non-Gathering skills should correctly gain XP if PVP is set to false now
+Localization will now support language codes that do not have two parts like "fi"
+Fixed bug where Wiki MOTD message would not be loaded from localization file
+
+Version 1.0.11
+Fixed bug where players could not gain experience in several skills
+Removed PVP flag from mcmmo.properties as its not needed anymore
+Fixed a few NPE errors
+Mushroom XP reduced from 25 to 15
+Fixed an exploit where players who just logged in could be farmed for experience because they were invulnerable
+
+Version 1.0.10
+Added Localization/String Customization
+Mushroom XP reduced from 40 to 25
+Removed "clears inventory" warning in /mcc for /myspawn since this no longer happens
+
+Version 1.0.09
+Fixed the NPE that occurs when players gain experience (Sorry!)
+Fixed bug where /myspawn & /clearmyspawn would work if MySpawn was disabled in the properties file
+Changed strings containing "MMO" to read "mcMMO"
+Removed a lot of unused or unnecessary variables from the PlayerProfiles in mcMMO, this should lower the memory footprint
+Added getXpToLevel() for modders
+
+Version 1.0.08
+Added removeXP() for modders
+Fixed bug where stone swords only repaired by 33% instead of 50%
+Fixed bug where stone/wooden hoes wouldn't repair
+Big overhaul to how skill values and xp values were handled in the code
+Modifying the players skill levels now sets the corresponding skill xp to zero
+Using Serrated Strikes/Skull Splitter on mobs should no longer harm nearby players when PVP is disabled
+Switching to another weapon after firing your bow should no longer trigger procs for that weapon when the arrow hits
+Slimes/Ghasts now give XP for combat skills
+Added "EnableHpRegeneration" property setting
+Added "EnableMySpawn" property setting
+
+Version 1.0.07
+Added more repair customization by solarcloud7
+Leaderboards ignore players with the respective stat at 0
+Reconnecting to MySQL will reload player data
+Fixed a NPE with MySQL's Leaderboards
+Removed "Loop iteration" debug message from mcMMO
+
+Version 1.0.06
+MySQL will attempt to reconnect if the connection is closed
+Breaking the bottom block of Cactus/Reeds will award the correct experience and double drops
+Added support for Minecraft Statistics
+Fixed NPE with /myspawn command
+
+Version 1.0.05
+PVP interactions now check for permissions before handing out any experience
+Many skill abilities now check for permissions correctly
+All interactions with Taming now check for permissions
+mcMMO now checks for its pvp flag being true before handling pvp interactions
+
+Version 1.0.04
+Fixed bug where players would be informed incorrectly when their cooldowns refreshed
+Fixed exploit where players could reconnect to reset their cooldowns
+Added new "cooldowns" table to MySQL
+Berserk now breaks through snow
+Lightning no longer gives Taming XP
+Shortened /mcc to fit the screen
+
+Version 1.0.03
+Bleed will no longer trigger on friendly wolves
+Axes criticals will no longer trigger on friendly wolves
+
+Version 1.0.02
+Fixed bug where the Timer would start before everything else was ready
+Fixed bug where mcrefresh also required mcability permission node
+Fixed bug where Unarmed was not checking for disarm procs
+Green Thumb now checks for herbalism permissions
+Added "enableGreenThumbCobbleToMossy" to config file, this also changes Green Terra
+AoE abilities now harm wolves
+
+Version 1.0.01
+Removed debug message when wolves are struck
+Fixed issue with reloading mcMMO when MySQL was enabled
+Fixed a NPE with MySpawn
+Fixed a NPE with removing users from PlayerProfile
+Unarmed no longer starts with a damage bonus
+Unarmed apprentice DMG bonus changed from 3 to 2
+
+Version 1.0
+Players can now repair Stone/Wood tools
+Fixed duping bug with WG/Block Protection Plugins
+Added Leaf Blower to WoodCutting
+Different Trees give different WoodCutting XP
+Water changing Gravel to Clay removed
+Code Organized/Optimized further
+MySQL Support
+Taming Skill
+Leaderboards
+Players won't hand out XP if they died within the last 5 seconds
+
+Version 0.9.29
+Fixes critical bug involving water turning anything into clay
+
+Version 0.9.28
+Green thumb can now spread grass to dirt using seeds
+Adding XP will check for level ups again
+Acrobatics won't hand out XP on death anymore
+Acrobatics will check plugins for the event being cancelled before handing out XP
+
+Version 0.9.27
+Fixed Herbalism not properly receiving Triple Drops from Green Terra
+Fixed Herbalism not handing out any XP outside of Green Terra
+Fixed Herbalism asking for seeds on things that did not require it
+
+Version 0.9.26
+Fixed Green Terra going off without readiness
+Fixed Hoe trying to ready when tilling Grass
+
+Version 0.9.25
+Fixed issue with anti-exploits and Herbalism
+MySpawn works like a hearthstone now, no inv pentality, 1hr cooldown
+Added Green Terra Ability to Herbalism
+Added Green Thumb ability to Herbalism
+Fixed Repair not working for Iron Tools
+Fixed bug where Axes Ability checked for Unarmed Ability Permission
+Added Cocoa Beans to Excavation XP/Loot Tables, Found in Grass/Dirt
+Using Super Breaker on Obsidian significantly damages it compared to other materials
+Added Obsidian to Mining XP Table/Super Breaker
+Added Pumpkins/Reeds/Cactus to Herbalism XP Tables/Double Drops
+Corrected "mcMMMO" to "mcMMO" in MOTD
+
+Version 0.9.24
+PLAYER_BED_ENTER removed due to its unusual issues
+Added info about the Wiki to the motd
+/mcrefresh will reset if you were recently hurt (Chimaera Wing/HP Regen)
+Fixed Armor Repair not adding XP
+Boosted Repair XP of Armor to match Tools
+Repairing Armor won't trigger Super Repair twice anymore
+Setting your MySpawn now just requires right clicking a bed (still requires the setmyspawn permission node)
+
+Version 0.9.23
+Players will now announce ability usage within a short distance to nearby players
+Chimaera Wing now takes the world into account
+Acrobatics won't give XP on death, and will fail if you would've died after the damage reduction
+Added yet another check to see if a Player is not in the Users system for NPC mod compatibility
+
+
+Version 0.9.22
+Fixed bug where chimaera wing was unusable after being hurt even after the cooldown
+
+Version 0.9.21
+/mcrefresh fixed to work properly with the new ability monitoring system
+Ability lengths are now based on your skill level directly rather than a tiered system
+Chimaera Wings won't trigger on things they shouldn't (Doors, Chests, ETC)
+Chimaera Wings will properly tell you how long you have to wait to use it if you've been recently hurt
+
+Version 0.9.20
+Fixed Tree Feller not checking if their cooldown was refreshed and always activating
+/stats and /whois will now show the powerlevel based on permissions
+Shovels will no longer say you've lowered your axe
+/myspawn will no longer say your inventory has been cleared if the server settings disable this feature
+
+
+Version 0.9.19
+Fixed Anti-Exploit XP stuff not working
+
+Version 0.9.18
+Added failsafe to prevent abilities from going on forever, abilities will check if they should've expired when being used in case the Timer fails
+Archery Spam has been nerf'd, you can only fire once per second now (Toggle-able in config file)
+Fixed bug when just having the Admin Chat permission wouldn't allow you to see Admin Chat
+Fixed bug where Axes ability could be used without permission
+Abilities are monitored with Timestamps rather than a Timer monitored tick rate
+When players were last hurt is now monitored with Timestamps rather than a Timer monitored tick rate
+Made Anti XP-Exploits more Robust
+Repair XP is now based on durability restored
+Acrobatics rolling will now reduce damage if you go over the damage threshold
+Acrobatics rolling damage threshold lowered to 10 from 20
+Added Graceful Roll to Acrobatics, hold Shift when falling to do a Graceful Roll
+mcMMO now checks for the blockBreak and EntityDamage events being canceled before proceeding
+Dodge notification shortened
+Dodge won't negate damage completely anymore
+Added 3 more functions for plugin authors to call, getPartyName(Player player), inParty(Player player), and getParties()
+
+Version 0.9.17
+Players now set their MySpawn by entering a bed, it requires the setmyspawn permission node
+/setmyspawn has been removed
+Compatible with CB 670
+Fixed errors related to Repair
+Abilities will no longer trigger from Bed interactions
+/unarmed will now tell the player when they will receive unarmed master (if they have apprentice)
+
+Version 0.9.16
+Logs placed by the player won't grant XP/Double Drops anymore
+Added more functions plugin authors can call
+Acrobatics roll has a damage threshold of 20, going above this means a failed Roll
+
+
+Version 0.9.15
+Acrobatics will now behave properly
+AoE Abilities ignore wolves (temp fix)
+Added "all" parameter to /mmoedit & /addxp
+After giving XP to a player it will now check for level ups in skills
+
+Version 0.9.14
+mcMMO checks for abilities being active before sending the fake block break event
+
+Version 0.9.13
+Fixed excavation ignoring the xpGainMultiplier
+Now compatible with CB 600+
+Fixed bug where Dodge acted maxed out no matter your skill level
+
+Version 0.9.12
+mcMMO now fakes a block break event for abilities to maximize plugin compatibility
+/herbalism will return the correct values now
+New /addxp command
+
+Version 0.9.11
+PVE Combat Skills experience is now based on damage dealt
+The Timer will no longer break from Bleed Simulation
+Tree feller no longer "damages" saplings
+Bleed+ (Serrated Strikes) lasts 5 ticks down from 12
+Bleed/Bleed+ now do 2 damage instead of 1
+Power Level is now based on permissions
+Counter Attack added to swords
+Parry is now based directly on Swords skill level
+Parry maximum proc chance raised to 30% from 20%
+Serrated Strikes now properly applies Bleed+ to targets
+Players who parry can no longer be disarmed
+Acrobatics now has a Dodge passive skill reducing damage
+Repair skill now effects how much durability is restored
+Super repair now doubles the repair amount on proc
+Unarmed now starts with a bonus to damage to encourage use
+Unarmed now has two steps to damage scaling, Appentice, and Mastery
+Unarmed disarm now caps at 25% for 1000 skill
+Fixed problem where Archery skill procs would ignore other plugins
+Ignition changed to 25% chance
+Ignition length will be based on archery skill level
+/myspawn now has a warning about the inventory loss penalty in /mcc
+mcMMO Timer now runs in 1 second intervals rather than 2
+
+Version 0.9.10
+Party invites now show who they are from
+Mushrooms added to Dirt/Grass excavation loot tables, drops with 500+ skill
+mcMMO configuration files property setting names have been changed for readability
+Fixed bug where Gold and Iron wouldn't drop anything during Super Breaker
+Added /mcability info to /mcc
+Potentially fixed NPE error when checking players for being in same party for PVP XP
+Removed sand specific diamond drop from sand excavation loot table, Diamonds can still drop globally for sand
+Added a global XP gain multiplier, increase it to increase XP gained
+Reduced PVE XP for Unarmed, now identical to Axes/Swords
+Changed Chat priority in mcMMO to be higher, this should help plugin conflicts
+Mushroom XP raised to 40 from 10
+Flower XP raised to 10 from 3
+
+Version 0.9.9
+Fixed problem where entities never got removed from the arrow retrieval list of entities
+
+Version 0.9.8
+EntityLiving shouldn't be cast to entities that are not an instance of EntityLiving
+Added a null check in the timer for players being null before proceeding
+
+Version 0.9.7
+Procs/XP Gain will no longer happen when the Entity is immune to damage (Thanks EdwardHand!)
+Axes critical damage versus players reduced to 150% damage from 200% damage
+Fixed bug where Daze might not proc
+Changed archery Daze to follow smooth transition
+Added archery Daze chance info to /archery
+Cooldown lengths are now customizable, they are in seconds and multiplied by 2 by mcMMO
+
+Version 0.9.6
+Timer checks for player being null before adding them to the mcUsers system
+Cooldowns will now show how much time is remaining when trying to use their respective abilities
+SkullSpliiter will now correctly inform the player when they are too tired to use it
+Acrobatics will no longer give XP if the event was cancelled by another plugin
+Version 0.9.5
+Super Breaker now gives a chance for Triple Drops based on mining skill
+Ability durability loss down from 15 to 2
+Ability durability loss is now toggle-able
+Ability durability loss can be adjusted in the configuration file
+Mining Picks are no longer lowered after activating Super Breaker
+
+Version 0.9.4
+Flowers won't drop wheat anymore
+Signs won't trigger ability readiness anymore
+Version 0.9.3
+Bug stopping abilities from never wearing of may have been fixed
+Changed color of "X Ability has worn off" to RED from GRAY
+Super Breaker, Giga Drill Breaker, and Tree Feller now damage the tool significantly during use
+Netherrack and Glowstone now give Mining XP
+Netherrack and Glowstone are now effected by Super Breaker
+Abilities will no longer be readied when you right click signs or beds
+Chimaera Wings won't activate on blocks you can interact with and signs
+Abilities now adjust their effects depending on tool quality
+Superbreaker won't break things that tool couldn't normally break
+Giga Drill Breaker will only give triple xp and triple drops for diamond tools, with a reduced effect for lesser tools
+Skull Splitter now has a limit of opponents nearby it will strike based on your tool quality
+Serrated Strikes now has a limit of opponents nearby it will strike based on your tool quality
+Modified /mcmmo description to be a little bit more relevant.
+
+Version 0.9.2
+Changed priority of some of the mcMMO listeners
+Now when certain abilities are activated it shouldn't say "You lower your x"
+
+Version 0.9.1
+Fixed "Unknown console command" errors with CB 556
+Added /mcability command to toggle being able to trigger abilities with right click
+Added some more nullchecks for people reporting NPE errors
+Compatibility with NPC mods improved (Mainly for archery!)
+Other plugins can now call inSameParty() from mcMMO to increase compatibility
+
+Version 0.9
+--NEW CONTENT--
+Woodcutting now has the "Tree Feller" Ability
+Unarmed now has the "Berserk" Ability
+Swords now has the "Serrated Strikes" Ability
+Mining now has the "Super Breaker" Ability
+Axes now has the "Skull Splitter" Ability
+Excavation now has the "Giga Drill Breaker" Ability
+Added /mcrefresh <playername> - tool for refreshing cooldowns
+Unarmed now has the "Deflect Arrows" passive skill
+Chimaera Wing Item Added
+
+--CHANGES--
+HP Regen & Bleed are back
+Woodcutting will drop the appropriate log on double drop procs
+Herbalism now applies double drops to herbs
+/<skillname> now shows much more information to the player regarding their stats
+Axes skill Critical Strikes are now based directly on your skill level
+Swords skill Bleed chance is now based directly on your skill level
+Unarmed disarm chance is now based directly on your skill level
+Acrobatics now gives XP when you roll
+
+--BUGFIXES--
+Memory Leak Fixed
+Axes not doing critical strikes
+Gold Armor repair
+Capped skills now have the correct proc chance
+/mmoedit is no longer case sensitive
+More NPE errors fixed
+Many bugs I forgot to write down
+
+--PLUGIN COMPATABILITY FIXES--
+If combat interactions are cancelled by other plugins mcMMO should ignore the event
+If block damage interactions are cancelled by other plugins mcMMO should ignore the event
+
+Version 0.8.22
+	Fixed bug where Axes did less damage than normal
+	Acrobatic rolls now give XP
+	Acrobatics XP increased for non-rolls
+Version 0.8.21
+	Fixed bug where axe criticals would dupe items
+Version 0.8.20
+	99.99% sure I fixed anvils that suddenly stop working
+Version 0.8.19
+	Fixed being able to excavate placed blocks
+	Added toggle option to mining requiring a pickaxe
+	Added toggle option to woodcutting requiring an axe
+	PVP interactions now reward XP based on the damage caused (this is effected by skills)
+	PVP XP gain can be disabled in the configuration file
+	PVP XP has a modifier, increase the modifier for higher XP rewards from PVP combat
+Version 0.8.18
+	Fixed sandstone not being watched for exploitation
+Version 0.8.17
+	mcmmo.users moved to plugins/mcMMO/
+	Snowballs and Eggs will no longer trigger Ignition
+	Loot tables for excavation adjusted
+	Mining benefits now require the player to be holding a mining pick
+	Woodcutting benefits now require the player to be holding an axe
+Version 0.8.16
+	Moved configuration file to /plugins/mcMMO
+	Arrows now have a chance to Ignite enemiesw
+	Fixed arrows not being retrievable from corpses
+	Added info about ignition to /archery
+Version 0.8.14
+	Mining, Woodcutting, Herbalism, and Acrobatics proc rates now are based on your skill level directly rather than tiers you unlock via skill levels
+	Archery's ability to retrieve arrows from corpses now is based on your skill level directly rather than tiers you unlock via skill levels
+	Mining, Woodcutting, Herbalism, Archery, and Acrobatics now show their proc % relative to your skill if you type /<skillname>
+	You can now adjust what level is required to repair diamond in the configuration file
+	Changed mining XP rates to be a tad higher for some things
+	You can now get XP from sandstone
+	XP rates increased for gathering glowstone with excavation
+	XP rates increased a bit for excavation
+	Skill info is now a bit more detailed for certain skills
+	Added info about arrow retrieval to /archery
+Version 0.8.13
+	Enemies no longer look like they have frozen when they die
+	Item duping fix
+Version 0.8.11
+	Performance improvements
+	Memory leak fixed
+	NPE error with MySpawn really fixed
+Version 0.8.9
+	Fixed NPE for My Spawn
+	Fixed NPE for onBlockDamage
+	Bleed proc now correctly checks for Swords permissions
+Version 0.8.8
+	Gold can now be repaired
+	Tweaked Mining XP gains
+	Reorganized code
+	Added /mcgod godmode command
+	Fixed the pvp toggle in the settings file
+Version 0.8.7
+	Removed packet-sending stuff wasn't working anyways
+	Fixed another NPE with the TimerTask
+	Skills now only show up in /stats if you have permissions for them
+Version 0.8.6
+	Added a null check in bleed simulation to prevent a NPE
+Version 0.8.5
+	Players are now added to files when they connect (to fix a NPE)
+	onPlayerCommand stuff moved into onPlayerCommandPreprocess
+Version 0.8.4
+	Fixed another nullpointer error for TimerTask
+	Fixed bug making regeneration take twice as long to kick in after combat
+Version 0.8.3
+	Modified the timer intervals (from 1 second to 2)
+	All skills now have an individual modifier (Set by default to 2)
+	There is now a global XP modifier (Set by default to 1)
+	Herbalism now correctly follows its skill curve
+	Unarmed no longer gives experience for harming other players
+	Players can no longer exploit mob spawners for experience
+Version 0.8.2
+	Fixed Concurrent Modification Exception
+	Fixed some incorrect skill descriptions
+	First tier of HP Regeneration is now available from the start
+	Fixed bleed proc rate for very high skill levels
+	Changed regeneration permissions to 'mcmmo.regeneration'
+Version 0.8
+	Archery skill now lets players recover arrows from downed foes
+	Health regenerates based on power level
+	Added toggle to myspawn clearing player inventory in settings file
+	Swords now have a bleed effect
+	Rewrote Skill descriptions to be more informative/better
+Version 0.7.9
+	XP Curve now follows a new formula
+	Acrobatics XP gains changed
+	Compiled against permissions 2.1
+Version 0.7.8
+	Massive tweaks to XP gain for Archery, Swords, Axes, Unarmed
+Version 0.7.7
+	Minor tweak to how players are added to the flat file
+	Fixed some nullpointer exceptions when players die
+Version 0.7.6
+	Fixed being able to repair diamond armor with below 50 skill
+	Myspawn now supports multiple worlds, clearing myspawn will set it to the first world created by the server
+Version 0.7.5
+	Removed random checks for herbalism XP
+	Herbalism is now called properly (This should fix gaining no xp or double drops)
+Version 0.7.4
+	Work around for a bukkit bug that broke my onBlockDamage event
+	Added /clearmyspawn
+Version 0.7.3
+	Fixed to work with build 424 of CB
+	Lowered the XP of gold due to it not being that rare anymore
+Version 0.7.2
+	Fixed security flaw where players could access /mmoedit if the server was not running permissions
+	Reduced XP gain of woodcutting a bit
+Version 0.7
+	Completely rewrote the XP system
+	Added an XP skillrate modifier to the settings file
+
+Version 0.6.2
+	Axes now do critical strikes against farm animals
+	Removed the "Stupidly Long Constructor"
+	Now compatible with the latest CB builds
+Version 0.6.1
+	Customizable command names
+	Axes can now be modified with /mmoedit
+	Party members are now correctly informed when you leave the party
+	Fixed incorrect commands in /mcc
+Version 0.5.17
+
+	Changed namespaces to fit bukkits new standard
+	Adjusted excavation proc rates
+	Modified excavation loot tables
+	Added Party Invite System
+
+Version 0.5.16
+
+	Fixed unarmed not checking for permissions when hitting players
+
+Version 0.5.15
+	Fixed stone swords not being recognized as swords
+	Fixed /a not working if you were an op but did not have permissions
+
+Version 0.5.14
+	Added permissions for skills
+
+Version 0.5.13
+
+	Removed skillgain from succesful parries
+	Repair now refreshed the inventory
+
+Version 0.5.12
+
+	Fixed being able to hurt party members with the bow and arrow
+
+Version 0.5.11
+
+	Added /mmoedit command
+	Fixed bug preventing player versus player damage
+	Fixed bug preventing damage from scaling with unarmed & bows
+	Fixed disarm proc making the opponent dupe his/her items
+	Added mcmmo.tools.mmoedit permission
+	Added mcmmo.commands.setmyspawn permission
+	Added totalskill to /stats
+	Changed the look of /stats
+
+Version 0.5.10
+
+    Fixed trying to set health to an invalid value
+
+Version 0.5.9
+
+    Fixed duping inventories on death
+
+Version 0.5.8
+
+    Fixed bug where players inventories would dupe during combat
+    
+Version 0.5.7
+
+    Fixed monsters instant killing players
+    Misc fixes
+Version 0.5.4
+
+    Changed herbalism skill gain from wheat to be WAAAAY slower
+
+Version 0.5.3
+
+    Players will now correctly drop their inventories when killed by a monster
+
+Version 0.5.2
+
+    Fixed MAJOR bug preventing swords skill from gaining through combat
+
+Version 0.5
+
+    Archery Added
+    Swords Added
+    Acrobatics Added
+    Logging for Party/Admin chat added
+    Fixed whois to show correct values for Excavation
+    Made death messages much much more specific
+
+Version 0.4.4
+
+    Fixed being able to repair full durability iron tools
+    Fixed herbalism benefits not behaving properly
+    Fixed removing 1 diamond from every stack of diamond when repairing diamond
+
+Version 0.4.2
+
+    Removed myspawn from the motd
+
+Version 0.4.1
+
+    Fixed /mcc showing incorrect command for herbalism
+    Changed unarmed skillrate to be much slower than before
+    Modified a few skill descriptions
+    Added permission for /whois
+    Players can now use admin chat without being op as long as they have the correct permission (requires Permissions)
+
+Version 0.4
+
+    Permissions support
+    Removed OPs having different names than normal players
+    Removed /setspawn & /spawn
+    Slowed down excavation skill rate
+    Fixed excavation coal drop being too rare
+
+Version 0.3.4
+
+    Creepers now give double xp for unarmed
+    Iron armor can now be repaired!
+    Fixed bug stopping items from being repaired
+
+Version 0.3.3
+
+    Yet another herbalism skill gain tweak
+
+Version 0.3.2
+
+    Changed excavation loot tables to be more rewarding
+    Changed sand to give normal excavation xp instead of double xp
+    Fixed herbalism skill exploit
+    Mobs killed with unarmed now drop loot properly
+    Unarmed xp rate depends on mob (zombies lowest fyi)
+    Huge player crashing bug fix on disarm!
+
+Version 0.3.1
+
+    Fixed excavation not saving properly
+    Fixed repair using excavation values
+
+Version 0.3
+
+    Unarmed skill
+    Herbalism skill
+    Excavation skill
+    Many bugfixes (thanks for reporting them!)
+    /<skillname> - Detailed information about skills in game
+
+Version 0.2.1
+
+    Misc bugfixes
+
+Version 0.2
+
+    Repair ability added
+    Repair skill added
+    Iron Armor repair temporarily disabled
+    Anvils (Iron Block) added
+    /mcmmo & /mcc added
+    Misc changes to existing commands
+    Misc bug fixes
+
+Version 0.1
+
+    Releasing my awesome plugin
+

+ 0 - 28
README.creole

@@ -1,28 +0,0 @@
-== mcMMO
-**The RPG lovers mod**
-
-=== Dev builds
-http://ci.mcmmo.info Download the latest dev build of mcMMO here.
-
-=== Brief Description
-mcMMO takes core Minecraft game mechanics and expands them to add an extensive RPG experience, the goal of the project has always been a quality RPG experience. Everything in mcMMO is carefully thought out and is constantly improving. mcMMO adds eleven skills to train in and level in, while also offering a high level of customization for server admins. There are countless features, including custom sounds, graphical elements, and more added when running mcMMO in conjunction with Spout. I carefully read feedback and evaluate the mechanics of mcMMO in every update to provide an ever-evolving experience.
-
-If you want an original RPG experience like no other mod out there, mcMMO is for you.
-
-=== About the Developer
-I've always wanted to make games and in the last year I decided to take a swing at developing Minecraft mods as a platform to teach myself programming, the biggest project I have made to date is mcMMO. I went from knowing nothing about Java to what I know now purely from modding Minecraft, and I plan to move onto game development in the not so distant future.
-
-I take design very seriously, I am not the kind of person who can be satisfied giving a project anything less than my all. As you will see reflected in the quality of the mods I make, I take great care in my work.
-
-Hearing that people enjoy mcMMO and seeing the daily youtube videos about my mod has become a joy, I really can't believe how popular my mod has gotten!
-
-=== Compiling
-
-Required Libraries:
-* Spout API
-* JUnit
-
-Required to Run:
-* Bukkit
-
-http://dev.bukkit.org/server-mods/mcmmo for more up to date information.

+ 47 - 0
README.md

@@ -0,0 +1,47 @@
+# mcMMO
+## The RPG lovers mod
+
+### Dev builds
+Our latest development builds are available [here](http://ci.mcmmo.info).
+
+### Brief Description
+mcMMO takes core Minecraft game mechanics and expands them to add an extensive RPG experience, the goal of the project has always been a quality RPG experience. Everything in mcMMO is carefully thought out and is constantly improving. mcMMO adds eleven skills to train in and level in, while also offering a high level of customization for server admins. There are countless features, including custom sounds, graphical elements, and more added when running mcMMO in conjunction with Spout. I carefully read feedback and evaluate the mechanics of mcMMO in every update to provide an ever-evolving experience.
+
+If you want an original RPG experience like no other mod out there, mcMMO is for you.
+
+## About the Team
+
+mcMMO is currently developed by a team of individuals from all over the world.
+### Glorious Leader
+[![gmcferrin](http://www.gravatar.com/avatar/b64c52daf25d206b27650788b5813b7b.png)]
+(https://github.com/gmcferrin)
+
+### Developers
+[![bm01](http://www.gravatar.com/avatar/ec8146f5358177f12e9a252271bbc391.png)]
+(https://github.com/bm01)
+[![Glitchfinder](http://www.gravatar.com/avatar/5aa4cce22f72ae9c002ecec30f061d00.png)]
+(https://github.com/Glitchfinder)
+[![nossr50](http://www.gravatar.com/avatar/f2ee41eedfd645fb4a3a2c8f6cb1b18c.png)]
+(https://github.com/nossr50)
+[![NuclearW](http://www.gravatar.com/avatar/90926bdcf1c8a75918df5ea5fa801ce6.png)]
+(https://github.com/NuclearW)
+[![shatteredbeam](http://www.gravatar.com/avatar/cad3b5d7d39cf5387afb87f494389610.png)]
+(https://github.com/shatteredbeam)
+[![TfT_02](http://www.gravatar.com/avatar/b8914f9970e1f6ffd5281ce4770e20a7.png)]
+(https://github.com/TfT-02)
+[![t00thpick1](http://www.gravatar.com/avatar/??.png)]
+(https://github.com/t00thpick1)
+
+## Compiling
+
+mcMMO uses Maven 3 to manage dependancies, packaging, and shading of necessary classes; Maven 3 is required to compile mcMMO.
+
+The typical command used to build mcMMO is: mvn clean package install
+
+Required Libraries:
+* Spout API
+* JUnit
+* Metrics
+* Bukkit
+
+http://dev.bukkit.org/server-mods/mcmmo for more up to date information.

+ 182 - 182
pom.xml

@@ -1,182 +1,182 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>com.gmail.nossr50.mcMMO</groupId>
-    <artifactId>mcMMO</artifactId>
-    <version>1.4.00-dev5</version>
-    <name>mcMMO</name>
-    <url>https://github.com/mcMMO-Dev/mcMMO</url>
-    <issueManagement>
-        <url>https://github.com/mcMMO-Dev/mcMMO/issues</url>
-        <system>GitHub</system>
-    </issueManagement>
-    <build>
-        <finalName>mcMMO</finalName>
-        <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
-        <resources>
-            <resource>
-                <targetPath>.</targetPath>
-                <filtering>true</filtering>
-                <directory>${basedir}/src/main/resources/</directory>
-                <includes>
-                    <include>*.yml</include>
-                    <include>.jenkins</include>
-                </includes>
-            </resource>
-            <resource>
-                <targetPath>resources</targetPath>
-                <filtering>false</filtering>
-                <directory>${basedir}/src/main/resources/xpbar/</directory>
-                <includes>
-                    <include>xpbar*.png</include>
-                </includes>
-            </resource>
-            <resource>
-                <targetPath>resources</targetPath>
-                <filtering>false</filtering>
-                <directory>${basedir}/src/main/resources/healthbar/</directory>
-                <includes>
-                    <include>health*.png</include>
-                </includes>
-            </resource>
-            <resource>
-                <targetPath>resources</targetPath>
-                <filtering>false</filtering>
-                <directory>${basedir}/src/main/resources/skillicon/</directory>
-                <includes>
-                    <include>*.png</include>
-                </includes>
-            </resource>
-            <resource>
-                <targetPath>resources</targetPath>
-                <filtering>false</filtering>
-                <directory>${basedir}/src/main/resources/sound/</directory>
-                <includes>
-                    <include>*.wav</include>
-                </includes>
-            </resource>
-            <resource>
-                <targetPath>com/gmail/nossr50/locale</targetPath>
-                <filtering>true</filtering>
-                <directory>${basedir}/src/main/resources/locale/</directory>
-                <includes>
-                    <include>locale*.properties</include>
-                </includes>
-            </resource>
-        </resources>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.3.2</version>
-                <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
-                    <excludes>
-                    </excludes>
-                </configuration>
-            </plugin>
-            <plugin>
-                <artifactId>maven-assembly-plugin</artifactId>
-                <configuration>
-                    <descriptors>
-                        <descriptor>src/main/assembly/package.xml</descriptor>
-                    </descriptors>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>build</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-shade-plugin</artifactId>
-                <version>1.5</version>
-                <configuration>
-                    <artifactSet>
-                        <includes>
-                            <include>com.turt2live.metrics:MetricsExtension</include>
-                        </includes>
-                    </artifactSet>
-                    <relocations>
-                        <relocation>
-                            <pattern>com.turt2live.metrics</pattern>
-                            <shadedPattern>com.gmail.nossr50.util.mcstats</shadedPattern>
-                        </relocation>
-                    </relocations>
-                </configuration>
-                <executions>
-                    <execution>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>shade</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-        <extensions>
-            <extension>
-                <groupId>org.apache.maven.wagon</groupId>
-                <artifactId>wagon-file</artifactId>
-                <version>2.2</version>
-            </extension>
-        </extensions>
-    </build>
-    <repositories>
-        <repository>
-            <id>bukkit-repo</id>
-            <url>http://repo.bukkit.org/content/groups/public/</url>
-        </repository>
-        <repository>
-            <id>spout-repo</id>
-            <url>http://nexus.spout.org/content/groups/public/</url>
-        </repository>
-        <repository>
-            <id>Plugin MetricsExtension</id>
-            <url>http://repo.turt2live.com</url>
-        </repository>
-    </repositories>
-    <dependencies>
-        <dependency>
-            <groupId>org.bukkit</groupId>
-            <artifactId>bukkit</artifactId>
-            <version>LATEST</version>
-            <type>jar</type>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.getspout</groupId>
-            <artifactId>spoutplugin</artifactId>
-            <version>LATEST</version>
-            <type>jar</type>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit-dep</artifactId>
-            <version>4.10</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.turt2live.metrics</groupId>
-            <artifactId>MetricsExtension</artifactId>
-            <version>0.0.2-SNAPSHOT</version>
-            <type>jar</type>
-            <scope>compile</scope>
-        </dependency>
-    </dependencies>
-    <distributionManagement>
-        <repository>
-            <id>mcmmo-repo</id>
-            <url>file:///var/lib/jenkins/repo</url>
-        </repository>
-    </distributionManagement>
-    <properties>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    </properties>
-</project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.gmail.nossr50.mcMMO</groupId>
+    <artifactId>mcMMO</artifactId>
+    <version>1.4.00</version>
+    <name>mcMMO</name>
+    <url>https://github.com/mcMMO-Dev/mcMMO</url>
+    <issueManagement>
+        <url>https://github.com/mcMMO-Dev/mcMMO/issues</url>
+        <system>GitHub</system>
+    </issueManagement>
+    <build>
+        <finalName>mcMMO</finalName>
+        <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
+        <resources>
+            <resource>
+                <targetPath>.</targetPath>
+                <filtering>true</filtering>
+                <directory>${basedir}/src/main/resources/</directory>
+                <includes>
+                    <include>*.yml</include>
+                    <include>.jenkins</include>
+                </includes>
+            </resource>
+            <resource>
+                <targetPath>resources</targetPath>
+                <filtering>false</filtering>
+                <directory>${basedir}/src/main/resources/xpbar/</directory>
+                <includes>
+                    <include>xpbar*.png</include>
+                </includes>
+            </resource>
+            <resource>
+                <targetPath>resources</targetPath>
+                <filtering>false</filtering>
+                <directory>${basedir}/src/main/resources/healthbar/</directory>
+                <includes>
+                    <include>health*.png</include>
+                </includes>
+            </resource>
+            <resource>
+                <targetPath>resources</targetPath>
+                <filtering>false</filtering>
+                <directory>${basedir}/src/main/resources/skillicon/</directory>
+                <includes>
+                    <include>*.png</include>
+                </includes>
+            </resource>
+            <resource>
+                <targetPath>resources</targetPath>
+                <filtering>false</filtering>
+                <directory>${basedir}/src/main/resources/sound/</directory>
+                <includes>
+                    <include>*.wav</include>
+                </includes>
+            </resource>
+            <resource>
+                <targetPath>com/gmail/nossr50/locale</targetPath>
+                <filtering>true</filtering>
+                <directory>${basedir}/src/main/resources/locale/</directory>
+                <includes>
+                    <include>locale*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                    <excludes>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>build</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>1.5</version>
+                <configuration>
+                    <artifactSet>
+                        <includes>
+                            <include>com.turt2live.metrics:MetricsExtension</include>
+                        </includes>
+                    </artifactSet>
+                    <relocations>
+                        <relocation>
+                            <pattern>com.turt2live.metrics</pattern>
+                            <shadedPattern>com.gmail.nossr50.metrics.mcstats</shadedPattern>
+                        </relocation>
+                    </relocations>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <extensions>
+            <extension>
+                <groupId>org.apache.maven.wagon</groupId>
+                <artifactId>wagon-file</artifactId>
+                <version>2.2</version>
+            </extension>
+        </extensions>
+    </build>
+    <repositories>
+        <repository>
+            <id>bukkit-repo</id>
+            <url>http://repo.bukkit.org/content/groups/public/</url>
+        </repository>
+        <repository>
+            <id>spout-repo</id>
+            <url>http://nexus.spout.org/content/groups/public/</url>
+        </repository>
+        <repository>
+            <id>Plugin MetricsExtension</id>
+            <url>http://repo.turt2live.com</url>
+        </repository>
+    </repositories>
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>bukkit</artifactId>
+            <version>LATEST</version>
+            <type>jar</type>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.getspout</groupId>
+            <artifactId>spoutplugin</artifactId>
+            <version>LATEST</version>
+            <type>jar</type>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit-dep</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.turt2live.metrics</groupId>
+            <artifactId>MetricsExtension</artifactId>
+            <version>0.0.2-SNAPSHOT</version>
+            <type>jar</type>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+    <distributionManagement>
+        <repository>
+            <id>mcmmo-repo</id>
+            <url>file:///var/lib/jenkins/repo</url>
+        </repository>
+    </distributionManagement>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+</project>

+ 51 - 51
src/main/java/com/gmail/nossr50/api/AbilityAPI.java

@@ -1,51 +1,51 @@
-package com.gmail.nossr50.api;
-
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.skills.utilities.AbilityType;
-import com.gmail.nossr50.util.Users;
-
-public final class AbilityAPI {
-    private AbilityAPI() {}
-
-    public static boolean berserkEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.BERSERK);
-    }
-
-    public static boolean gigaDrillBreakerEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.GIGA_DRILL_BREAKER);
-    }
-
-    public static boolean greenTerraEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.GREEN_TERRA);
-    }
-
-    public static boolean serratedStrikesEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.SERRATED_STRIKES);
-    }
-
-    public static boolean skullSplitterEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.SKULL_SPLITTER);
-    }
-
-    public static boolean superBreakerEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.SUPER_BREAKER);
-    }
-
-    public static boolean treeFellerEnabled(Player player) {
-        return Users.getPlayer(player).getProfile().getAbilityMode(AbilityType.TREE_FELLER);
-    }
-
-    public static boolean isAnyAbilityEnabled(Player player) {
-        PlayerProfile profile = Users.getPlayer(player).getProfile();
-
-        for (AbilityType ability : AbilityType.values()) {
-            if (profile.getAbilityMode(ability)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
+package com.gmail.nossr50.api;
+
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.AbilityType;
+import com.gmail.nossr50.util.player.UserManager;
+
+public final class AbilityAPI {
+    private AbilityAPI() {}
+
+    public static boolean berserkEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.BERSERK);
+    }
+
+    public static boolean gigaDrillBreakerEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.GIGA_DRILL_BREAKER);
+    }
+
+    public static boolean greenTerraEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.GREEN_TERRA);
+    }
+
+    public static boolean serratedStrikesEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.SERRATED_STRIKES);
+    }
+
+    public static boolean skullSplitterEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.SKULL_SPLITTER);
+    }
+
+    public static boolean superBreakerEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.SUPER_BREAKER);
+    }
+
+    public static boolean treeFellerEnabled(Player player) {
+        return UserManager.getPlayer(player).getProfile().getAbilityMode(AbilityType.TREE_FELLER);
+    }
+
+    public static boolean isAnyAbilityEnabled(Player player) {
+        PlayerProfile profile = UserManager.getPlayer(player).getProfile();
+
+        for (AbilityType ability : AbilityType.values()) {
+            if (profile.getAbilityMode(ability)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 5 - 5
src/main/java/com/gmail/nossr50/api/ChatAPI.java

@@ -5,7 +5,7 @@ import org.bukkit.plugin.Plugin;
 
 import com.gmail.nossr50.chat.ChatManager;
 import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.util.Users;
+import com.gmail.nossr50.util.player.UserManager;
 
 public final class ChatAPI {
     private ChatAPI() {}
@@ -104,7 +104,7 @@ public final class ChatAPI {
      * @return true if the player is using party chat, false otherwise
      */
     public static boolean isUsingPartyChat(Player player) {
-        return Users.getPlayer(player).getPartyChatMode();
+        return UserManager.getPlayer(player).getPartyChatMode();
     }
 
     /**
@@ -114,7 +114,7 @@ public final class ChatAPI {
      * @return true if the player is using party chat, false otherwise
      */
     public static boolean isUsingPartyChat(String playerName) {
-        return Users.getPlayer(playerName).getPartyChatMode();
+        return UserManager.getPlayer(playerName).getPartyChatMode();
     }
 
     /**
@@ -124,7 +124,7 @@ public final class ChatAPI {
      * @return true if the player is using admin chat, false otherwise
      */
     public static boolean isUsingAdminChat(Player player) {
-        return Users.getPlayer(player).getAdminChatMode();
+        return UserManager.getPlayer(player).getAdminChatMode();
     }
 
     /**
@@ -134,6 +134,6 @@ public final class ChatAPI {
      * @return true if the player is using admin chat, false otherwise
      */
     public static boolean isUsingAdminChat(String playerName) {
-        return Users.getPlayer(playerName).getAdminChatMode();
+        return UserManager.getPlayer(playerName).getAdminChatMode();
     }
 }

+ 335 - 335
src/main/java/com/gmail/nossr50/api/ExperienceAPI.java

@@ -1,335 +1,335 @@
-package com.gmail.nossr50.api;
-
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Users;
-
-public final class ExperienceAPI {
-    private ExperienceAPI() {}
-
-    /**
-     * Check the XP of a player. This should be called after giving XP to process level-ups.
-     *
-     * @param player The player to check
-     * @param skillType The skill to check
-     * @deprecated Calling this function is no longer needed and should be avoided
-     */
-    @Deprecated
-    private static void checkXP(Player player, SkillType skillType) {
-        SkillTools.xpCheckSkill(skillType, player, Users.getProfile(player));
-    }
-
-    /**
-     * Adds raw XP to the player.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     * @deprecated Use {@link #addRawXP(Player, String, int)} instead
-     */
-   @Deprecated
-    public static void addRawXP(Player player, SkillType skillType, int XP) {
-        Users.getPlayer(player).applyXpGain(skillType, XP);
-    }
-
-    /**
-     * Adds raw XP to the player.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     */
-    public static void addRawXP(Player player, String skillType, int XP) {
-        Users.getPlayer(player).applyXpGain(SkillType.getSkill(skillType), XP);
-    }
-
-    /**
-     * Adds XP to the player, calculates for XP Rate only.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     * @deprecated Use {@link #addMultipliedXP(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void addMultipliedXP(Player player, SkillType skillType, int XP) {
-        Users.getPlayer(player).applyXpGain(skillType, (int) (XP * Config.getInstance().getExperienceGainsGlobalMultiplier()));
-    }
-
-    /**
-     * Adds XP to the player, calculates for XP Rate only.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     */
-    public static void addMultipliedXP(Player player, String skillType, int XP) {
-        Users.getPlayer(player).applyXpGain(SkillType.getSkill(skillType), (int) (XP * Config.getInstance().getExperienceGainsGlobalMultiplier()));
-    }
-
-    /**
-     * Adds XP to the player, calculates for XP Rate, skill modifiers and perks. May be shared with the party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     * @deprecated Use {@link #addXP(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void addXP(Player player, SkillType skillType, int XP) {
-        Users.getPlayer(player).beginXpGain(skillType, XP);
-    }
-
-    /**
-     * Adds XP to the player, calculates for XP Rate, skill modifiers and perks. May be shared with the party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add XP to
-     * @param skillType The skill to add XP to
-     * @param XP The amount of XP to add
-     */
-    public static void addXP(Player player, String skillType, int XP) {
-        Users.getPlayer(player).beginXpGain(SkillType.getSkill(skillType), XP);
-    }
-
-    /**
-     * Get the amount of XP a player has in a specific skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get XP for
-     * @param skillType The skill to get XP for
-     * @return the amount of XP in a given skill
-     * @deprecated Use {@link #getXP(Player, String)} instead
-     */
-    @Deprecated
-    public static int getXP(Player player, SkillType skillType) {
-        return Users.getPlayer(player).getProfile().getSkillXpLevel(skillType);
-    }
-
-    /**
-     * Get the amount of XP a player has in a specific skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get XP for
-     * @param skillType The skill to get XP for
-     * @return the amount of XP in a given skill
-     */
-    public static int getXP(Player player, String skillType) {
-        return Users.getPlayer(player).getProfile().getSkillXpLevel(SkillType.getSkill(skillType));
-    }
-
-    /**
-     * Get the amount of XP left before leveling up.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get the XP amount for
-     * @param skillType The skill to get the XP amount for
-     * @return the amount of XP left before leveling up a specifc skill
-     * @deprecated Use {@link #getXPToNextLevel(Player, String)} instead
-     */
-    @Deprecated
-    public static int getXPToNextLevel(Player player, SkillType skillType) {
-        return Users.getPlayer(player).getProfile().getXpToLevel(skillType);
-    }
-
-    /**
-     * Get the amount of XP left before leveling up.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get the XP amount for
-     * @param skillType The skill to get the XP amount for
-     * @return the amount of XP left before leveling up a specifc skill
-     */
-    public static int getXPToNextLevel(Player player, String skillType) {
-        return Users.getPlayer(player).getProfile().getXpToLevel(SkillType.getSkill(skillType));
-    }
-
-    /**
-     * Add levels to a skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add levels to
-     * @param skillType Type of skill to add levels to
-     * @param levels Number of levels to add
-     * @param notify Unused argument
-     * @deprecated Use addLevel(Player, SKillType, int) instead
-     */
-    public static void addLevel(Player player, SkillType skillType, int levels, boolean notify) {
-        Users.getProfile(player).addLevels(skillType, levels);
-
-        if (notify) {
-            checkXP(player, skillType);
-        }
-    }
-
-    /**
-     * Add levels to a skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add levels to
-     * @param skillType Type of skill to add levels to
-     * @param levels Number of levels to add
-     * @deprecated Use {@link #addLevel(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void addLevel(Player player, SkillType skillType, int levels) {
-        Users.getPlayer(player).getProfile().addLevels(skillType, levels);
-    }
-
-    /**
-     * Add levels to a skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add levels to
-     * @param skillType Type of skill to add levels to
-     * @param levels Number of levels to add
-     */
-    public static void addLevel(Player player, String skillType, int levels) {
-        Users.getPlayer(player).getProfile().addLevels(SkillType.getSkill(skillType), levels);
-    }
-
-    /**
-     * Get the level a player has in a specific skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get the level for
-     * @param skillType The skill to get the level for
-     * @return the level of a given skill
-     * @deprecated Use {@link #getLevel(Player, String)} instead
-     */
-    @Deprecated
-    public static int getLevel(Player player, SkillType skillType) {
-        return Users.getPlayer(player).getProfile().getSkillLevel(skillType);
-    }
-
-    /**
-     * Get the level a player has in a specific skill.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get the level for
-     * @param skillType The skill to get the level for
-     * @return the level of a given skill
-     */
-    public static int getLevel(Player player, String skillType) {
-        return Users.getPlayer(player).getProfile().getSkillLevel(SkillType.getSkill(skillType));
-    }
-
-    /**
-     * Gets the power level of a player.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to get the power level for
-     * @return the power level of the player
-     */
-    public static int getPowerLevel(Player player) {
-        return Users.getPlayer(player).getPowerLevel();
-    }
-
-    /**
-     * Sets the level of a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to set the level of
-     * @param skillType The skill to set the level for
-     * @param skillLevel The value to set the level to
-     * @deprecated Use {@link #setLevel(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void setLevel(Player player, SkillType skillType, int skillLevel) {
-        Users.getPlayer(player).getProfile().modifySkill(skillType, skillLevel);
-    }
-
-    /**
-     * Sets the level of a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to set the level of
-     * @param skillType The skill to set the level for
-     * @param skillLevel The value to set the level to
-     */
-    public static void setLevel(Player player, String skillType, int skillLevel) {
-        Users.getPlayer(player).getProfile().modifySkill(SkillType.getSkill(skillType), skillLevel);
-    }
-
-    /**
-     * Sets the XP of a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to set the XP of
-     * @param skillType The skill to set the XP for
-     * @param newValue The value to set the XP to
-     * @deprecated Use {@link #setXP(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void setXP(Player player, SkillType skillType, int newValue) {
-        Users.getPlayer(player).getProfile().setSkillXpLevel(skillType, newValue);
-    }
-
-    /**
-     * Sets the XP of a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to set the XP of
-     * @param skillType The skill to set the XP for
-     * @param newValue The value to set the XP to
-     */
-    public static void setXP(Player player, String skillType, int newValue) {
-        Users.getPlayer(player).getProfile().setSkillXpLevel(SkillType.getSkill(skillType), newValue);
-    }
-
-    /**
-     * Removes XP from a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to change the XP of
-     * @param skillType The skill to change the XP for
-     * @param xp The amount of XP to remove
-     * @deprecated Use {@link #removeXP(Player, String, int)} instead
-     */
-    @Deprecated
-    public static void removeXP(Player player, SkillType skillType, int xp) {
-        Users.getPlayer(player).getProfile().removeXp(skillType, xp);
-    }
-
-    /**
-     * Removes XP from a player in a specific skill type.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to change the XP of
-     * @param skillType The skill to change the XP for
-     * @param xp The amount of XP to remove
-     */
-    public static void removeXP(Player player, String skillType, int xp) {
-        Users.getPlayer(player).getProfile().removeXp(SkillType.getSkill(skillType), xp);
-    }
-}
+package com.gmail.nossr50.api;
+
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public final class ExperienceAPI {
+    private ExperienceAPI() {}
+
+    /**
+     * Adds raw XP to the player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     * @deprecated Use {@link #addRawXP(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void addRawXP(Player player, SkillType skillType, int XP) {
+        UserManager.getPlayer(player).applyXpGain(skillType, XP);
+    }
+
+    /**
+     * Adds raw XP to the player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     */
+    public static void addRawXP(Player player, String skillType, int XP) {
+        UserManager.getPlayer(player).applyXpGain(SkillType.getSkill(skillType), XP);
+    }
+
+    /**
+     * Adds XP to the player, calculates for XP Rate only.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     * @deprecated Use {@link #addMultipliedXP(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void addMultipliedXP(Player player, SkillType skillType, int XP) {
+        UserManager.getPlayer(player).applyXpGain(skillType, (int) (XP * Config.getInstance().getExperienceGainsGlobalMultiplier()));
+    }
+
+    /**
+     * Adds XP to the player, calculates for XP Rate only.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     */
+    public static void addMultipliedXP(Player player, String skillType, int XP) {
+        UserManager.getPlayer(player).applyXpGain(SkillType.getSkill(skillType), (int) (XP * Config.getInstance().getExperienceGainsGlobalMultiplier()));
+    }
+
+    /**
+     * Adds XP to the player, calculates for XP Rate, skill modifiers and perks. May be shared with the party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     * @deprecated Use {@link #addXP(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void addXP(Player player, SkillType skillType, int XP) {
+        UserManager.getPlayer(player).beginXpGain(skillType, XP);
+    }
+
+    /**
+     * Adds XP to the player, calculates for XP Rate, skill modifiers and perks. May be shared with the party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     */
+    public static void addXP(Player player, String skillType, int XP) {
+        UserManager.getPlayer(player).beginXpGain(SkillType.getSkill(skillType), XP);
+    }
+
+    /**
+     * Get the amount of XP a player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the amount of XP in a given skill
+     * @deprecated Use {@link #getXP(Player, String)} instead
+     */
+    @Deprecated
+    public static int getXP(Player player, SkillType skillType) {
+        return UserManager.getPlayer(player).getProfile().getSkillXpLevel(skillType);
+    }
+
+    /**
+     * Get the amount of XP a player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the amount of XP in a given skill
+     */
+    public static int getXP(Player player, String skillType) {
+        return UserManager.getPlayer(player).getProfile().getSkillXpLevel(SkillType.getSkill(skillType));
+    }
+
+    /**
+     * Get the amount of XP left before leveling up.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get the XP amount for
+     * @param skillType The skill to get the XP amount for
+     * @return the amount of XP left before leveling up a specifc skill
+     * @deprecated Use {@link #getXPToNextLevel(Player, String)} instead
+     */
+    @Deprecated
+    public static int getXPToNextLevel(Player player, SkillType skillType) {
+        return UserManager.getPlayer(player).getProfile().getXpToLevel(skillType);
+    }
+
+    /**
+     * Get the amount of XP left before leveling up.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get the XP amount for
+     * @param skillType The skill to get the XP amount for
+     * @return the amount of XP left before leveling up a specifc skill
+     */
+    public static int getXPToNextLevel(Player player, String skillType) {
+        return UserManager.getPlayer(player).getProfile().getXpToLevel(SkillType.getSkill(skillType));
+    }
+
+    /**
+     * Add levels to a skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add levels to
+     * @param skillType Type of skill to add levels to
+     * @param levels Number of levels to add
+     * @param notify Unused argument
+     * @deprecated Use addLevel(Player, SKillType, int) instead
+     */
+    public static void addLevel(Player player, SkillType skillType, int levels, boolean notify) {
+        UserManager.getProfile(player).addLevels(skillType, levels);
+
+        if (notify) {
+            checkXP(player, skillType);
+        }
+    }
+
+    /**
+     * Add levels to a skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add levels to
+     * @param skillType Type of skill to add levels to
+     * @param levels Number of levels to add
+     * @deprecated Use {@link #addLevel(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void addLevel(Player player, SkillType skillType, int levels) {
+        UserManager.getPlayer(player).getProfile().addLevels(skillType, levels);
+    }
+
+    /**
+     * Add levels to a skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add levels to
+     * @param skillType Type of skill to add levels to
+     * @param levels Number of levels to add
+     */
+    public static void addLevel(Player player, String skillType, int levels) {
+        UserManager.getPlayer(player).getProfile().addLevels(SkillType.getSkill(skillType), levels);
+    }
+
+    /**
+     * Get the level a player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get the level for
+     * @param skillType The skill to get the level for
+     * @return the level of a given skill
+     * @deprecated Use {@link #getLevel(Player, String)} instead
+     */
+    @Deprecated
+    public static int getLevel(Player player, SkillType skillType) {
+        return UserManager.getPlayer(player).getProfile().getSkillLevel(skillType);
+    }
+
+    /**
+     * Get the level a player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get the level for
+     * @param skillType The skill to get the level for
+     * @return the level of a given skill
+     */
+    public static int getLevel(Player player, String skillType) {
+        return UserManager.getPlayer(player).getProfile().getSkillLevel(SkillType.getSkill(skillType));
+    }
+
+    /**
+     * Gets the power level of a player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to get the power level for
+     * @return the power level of the player
+     */
+    public static int getPowerLevel(Player player) {
+        return UserManager.getPlayer(player).getPowerLevel();
+    }
+
+    /**
+     * Sets the level of a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to set the level of
+     * @param skillType The skill to set the level for
+     * @param skillLevel The value to set the level to
+     * @deprecated Use {@link #setLevel(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void setLevel(Player player, SkillType skillType, int skillLevel) {
+        UserManager.getPlayer(player).getProfile().modifySkill(skillType, skillLevel);
+    }
+
+    /**
+     * Sets the level of a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to set the level of
+     * @param skillType The skill to set the level for
+     * @param skillLevel The value to set the level to
+     */
+    public static void setLevel(Player player, String skillType, int skillLevel) {
+        UserManager.getPlayer(player).getProfile().modifySkill(SkillType.getSkill(skillType), skillLevel);
+    }
+
+    /**
+     * Sets the XP of a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to set the XP of
+     * @param skillType The skill to set the XP for
+     * @param newValue The value to set the XP to
+     * @deprecated Use {@link #setXP(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void setXP(Player player, SkillType skillType, int newValue) {
+        UserManager.getPlayer(player).getProfile().setSkillXpLevel(skillType, newValue);
+    }
+
+    /**
+     * Sets the XP of a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to set the XP of
+     * @param skillType The skill to set the XP for
+     * @param newValue The value to set the XP to
+     */
+    public static void setXP(Player player, String skillType, int newValue) {
+        UserManager.getPlayer(player).getProfile().setSkillXpLevel(SkillType.getSkill(skillType), newValue);
+    }
+
+    /**
+     * Removes XP from a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to change the XP of
+     * @param skillType The skill to change the XP for
+     * @param xp The amount of XP to remove
+     * @deprecated Use {@link #removeXP(Player, String, int)} instead
+     */
+    @Deprecated
+    public static void removeXP(Player player, SkillType skillType, int xp) {
+        UserManager.getPlayer(player).getProfile().removeXp(skillType, xp);
+    }
+
+    /**
+     * Removes XP from a player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to change the XP of
+     * @param skillType The skill to change the XP for
+     * @param xp The amount of XP to remove
+     */
+    public static void removeXP(Player player, String skillType, int xp) {
+        UserManager.getPlayer(player).getProfile().removeXp(SkillType.getSkill(skillType), xp);
+    }
+
+    /**
+     * Check the XP of a player. This should be called after giving XP to process level-ups.
+     *
+     * @param player The player to check
+     * @param skillType The skill to check
+     * @deprecated Calling this function is no longer needed and should be avoided
+     */
+    @Deprecated
+    private static void checkXP(Player player, SkillType skillType) {
+        SkillUtils.xpCheckSkill(skillType, player, UserManager.getProfile(player));
+    }
+}

+ 174 - 174
src/main/java/com/gmail/nossr50/api/PartyAPI.java

@@ -1,174 +1,174 @@
-package com.gmail.nossr50.api;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.bukkit.OfflinePlayer;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.util.Users;
-
-public final class PartyAPI {
-    private PartyAPI() {}
-
-    /**
-     * Get the name of the party a player is in.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to check the party name of
-     * @return the name of the player's party
-     */
-    public static String getPartyName(Player player) {
-        return Users.getPlayer(player).getParty().getName();
-    }
-
-    /**
-     * Checks if a player is in a party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to check
-     * @return true if the player is in a party, false otherwise
-     */
-    public static boolean inParty(Player player) {
-        return Users.getPlayer(player).inParty();
-    }
-
-    /**
-     * Check if two players are in the same party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param playera The first player to check
-     * @param playerb The second player to check
-     * @return true if the two players are in the same party, false otherwise
-     */
-    public static boolean inSameParty(Player playera, Player playerb) {
-        return PartyManager.inSameParty(playera, playerb);
-    }
-
-    /**
-     * Get a list of all current parties.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @return the list of parties.
-     */
-    public static List<Party> getParties() {
-        return PartyManager.getParties();
-    }
-
-    /**
-     * Add a player to a party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to add to the party
-     * @param partyName The party to add the player to
-     */
-    public static void addToParty(Player player, String partyName) {
-        Party party = PartyManager.getParty(partyName);
-
-        if (party == null) {
-            party = new Party();
-            party.setName(partyName);
-            party.setLeader(player.getName());
-        }
-
-        PartyManager.addToParty(player, Users.getPlayer(player), party);
-    }
-
-    /**
-     * Remove a player from a party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to remove
-     */
-    public static void removeFromParty(Player player) {
-        PartyManager.removeFromParty(player, Users.getPlayer(player).getParty());
-    }
-
-    /**
-     * Get the leader of a party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param partyName The party name
-     * @return the leader of the party
-     */
-    public static String getPartyLeader(String partyName) {
-        return PartyManager.getPartyLeader(partyName);
-    }
-
-    /**
-     * Set the leader of a party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param partyName The name of the party to set the leader of
-     * @param player The player to set as leader
-     */
-    public static void setPartyLeader(String partyName, String player) {
-        PartyManager.setPartyLeader(player, PartyManager.getParty(partyName));
-    }
-
-    /**
-     * Get a list of all players in this player's party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to check
-     * @return all the players in the player's party
-     * @deprecated
-     */
-    @Deprecated
-    public static List<String> getAllMembers(Player player) {
-        List<String> memberNames = new ArrayList<String>();
-
-        for (OfflinePlayer member : PartyManager.getAllMembers(player)) {
-            memberNames.add(member.getName());
-        }
-
-        return memberNames;
-    }
-
-    /**
-     * Get a list of all players in this player's party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to check
-     * @return all the players in the player's party
-     */
-    public static List<OfflinePlayer> getOnlineAndOfflineMembers(Player player) {
-        return PartyManager.getAllMembers(player);
-    }
-
-    /**
-     * Get a list of all online players in this party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param partyName The party to check
-     * @return all online players in this party
-     */
-    public static List<Player> getOnlineMembers(String partyName) {
-        return PartyManager.getOnlineMembers(partyName);
-    }
-
-    /**
-     * Get a list of all online players in this player's party.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param player The player to check
-     * @return all online players in the player's party
-     */
-    public static List<Player> getOnlineMembers(Player player) {
-        return PartyManager.getOnlineMembers(player);
-    }
-}
+package com.gmail.nossr50.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public final class PartyAPI {
+    private PartyAPI() {}
+
+    /**
+     * Get the name of the party a player is in.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to check the party name of
+     * @return the name of the player's party
+     */
+    public static String getPartyName(Player player) {
+        return UserManager.getPlayer(player).getParty().getName();
+    }
+
+    /**
+     * Checks if a player is in a party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to check
+     * @return true if the player is in a party, false otherwise
+     */
+    public static boolean inParty(Player player) {
+        return UserManager.getPlayer(player).inParty();
+    }
+
+    /**
+     * Check if two players are in the same party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param playera The first player to check
+     * @param playerb The second player to check
+     * @return true if the two players are in the same party, false otherwise
+     */
+    public static boolean inSameParty(Player playera, Player playerb) {
+        return PartyManager.inSameParty(playera, playerb);
+    }
+
+    /**
+     * Get a list of all current parties.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @return the list of parties.
+     */
+    public static List<Party> getParties() {
+        return PartyManager.getParties();
+    }
+
+    /**
+     * Add a player to a party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to add to the party
+     * @param partyName The party to add the player to
+     */
+    public static void addToParty(Player player, String partyName) {
+        Party party = PartyManager.getParty(partyName);
+
+        if (party == null) {
+            party = new Party();
+            party.setName(partyName);
+            party.setLeader(player.getName());
+        }
+
+        PartyManager.addToParty(player, UserManager.getPlayer(player), party);
+    }
+
+    /**
+     * Remove a player from a party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to remove
+     */
+    public static void removeFromParty(Player player) {
+        PartyManager.removeFromParty(player, UserManager.getPlayer(player).getParty());
+    }
+
+    /**
+     * Get the leader of a party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param partyName The party name
+     * @return the leader of the party
+     */
+    public static String getPartyLeader(String partyName) {
+        return PartyManager.getPartyLeader(partyName);
+    }
+
+    /**
+     * Set the leader of a party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param partyName The name of the party to set the leader of
+     * @param player The player to set as leader
+     */
+    public static void setPartyLeader(String partyName, String player) {
+        PartyManager.setPartyLeader(player, PartyManager.getParty(partyName));
+    }
+
+    /**
+     * Get a list of all players in this player's party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to check
+     * @return all the players in the player's party
+     * @deprecated
+     */
+    @Deprecated
+    public static List<String> getAllMembers(Player player) {
+        List<String> memberNames = new ArrayList<String>();
+
+        for (OfflinePlayer member : PartyManager.getAllMembers(player)) {
+            memberNames.add(member.getName());
+        }
+
+        return memberNames;
+    }
+
+    /**
+     * Get a list of all players in this player's party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to check
+     * @return all the players in the player's party
+     */
+    public static List<OfflinePlayer> getOnlineAndOfflineMembers(Player player) {
+        return PartyManager.getAllMembers(player);
+    }
+
+    /**
+     * Get a list of all online players in this party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param partyName The party to check
+     * @return all online players in this party
+     */
+    public static List<Player> getOnlineMembers(String partyName) {
+        return PartyManager.getOnlineMembers(partyName);
+    }
+
+    /**
+     * Get a list of all online players in this player's party.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param player The player to check
+     * @return all online players in the player's party
+     */
+    public static List<Player> getOnlineMembers(Player player) {
+        return PartyManager.getOnlineMembers(player);
+    }
+}

+ 29 - 28
src/main/java/com/gmail/nossr50/api/SpoutHudAPI.java

@@ -1,28 +1,29 @@
-package com.gmail.nossr50.api;
-
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.spout.SpoutConfig;
-import com.gmail.nossr50.spout.huds.HudType;
-import com.gmail.nossr50.util.Users;
-
-public class SpoutHudAPI {
-
-    /**
-     * Disable the mcMMO XP bar for a player.
-     * </br>
-     * This function is designed for API usage.
-     */
-    public static void disableXpBar(Player player) {
-        Users.getPlayer(player).getProfile().setHudType(HudType.DISABLED);
-    }
-
-    /**
-     * Disable the mcMMO XP bar for the server.
-     * </br>
-     * This function is designed for API usage.
-     */
-    public static void disableXpBar() {
-        SpoutConfig.getInstance().setXPBarEnabled(false);
-    }
-}
+package com.gmail.nossr50.api;
+
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.spout.SpoutConfig;
+import com.gmail.nossr50.datatypes.spout.huds.HudType;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class SpoutHudAPI {
+    private SpoutHudAPI() {}
+
+    /**
+     * Disable the mcMMO XP bar for a player.
+     * </br>
+     * This function is designed for API usage.
+     */
+    public static void disableXpBar(Player player) {
+        UserManager.getPlayer(player).getProfile().setHudType(HudType.DISABLED);
+    }
+
+    /**
+     * Disable the mcMMO XP bar for the server.
+     * </br>
+     * This function is designed for API usage.
+     */
+    public static void disableXpBar() {
+        SpoutConfig.getInstance().setXPBarEnabled(false);
+    }
+}

+ 53 - 53
src/main/java/com/gmail/nossr50/api/SpoutToolsAPI.java

@@ -1,53 +1,53 @@
-package com.gmail.nossr50.api;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.bukkit.inventory.ItemStack;
-
-import com.gmail.nossr50.skills.utilities.ToolType;
-
-public final class SpoutToolsAPI {
-    public static final List<ItemStack> spoutSwords = new ArrayList<ItemStack>();
-    public static final List<ItemStack> spoutAxes = new ArrayList<ItemStack>();
-    public static final List<ItemStack> spoutPickaxes = new ArrayList<ItemStack>();
-    public static final List<ItemStack> spoutHoes = new ArrayList<ItemStack>();
-    public static final List<ItemStack> spoutShovels = new ArrayList<ItemStack>();
-
-    private SpoutToolsAPI() {}
-
-    /**
-     * Add a custom Spout tool to mcMMO for XP gain & ability use.
-     * </br>
-     * This function is designed for API usage.
-     *
-     * @param spoutTool The tool to add
-     * @param type The type of tool to add
-     */
-    public static void addCustomTool(ItemStack spoutTool, ToolType type) {
-        switch (type) {
-        case AXE:
-            spoutAxes.add(spoutTool);
-            break;
-
-        case HOE:
-            spoutHoes.add(spoutTool);
-            break;
-
-        case PICKAXE:
-            spoutPickaxes.add(spoutTool);
-            break;
-
-        case SHOVEL:
-            spoutShovels.add(spoutTool);
-            break;
-
-        case SWORD:
-            spoutSwords.add(spoutTool);
-            break;
-
-        default:
-            break;
-        }
-    }
-}
+package com.gmail.nossr50.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bukkit.inventory.ItemStack;
+
+import com.gmail.nossr50.datatypes.skills.ToolType;
+
+public final class SpoutToolsAPI {
+    public static final List<ItemStack> spoutSwords   = new ArrayList<ItemStack>();
+    public static final List<ItemStack> spoutAxes     = new ArrayList<ItemStack>();
+    public static final List<ItemStack> spoutPickaxes = new ArrayList<ItemStack>();
+    public static final List<ItemStack> spoutHoes     = new ArrayList<ItemStack>();
+    public static final List<ItemStack> spoutShovels  = new ArrayList<ItemStack>();
+
+    private SpoutToolsAPI() {}
+
+    /**
+     * Add a custom Spout tool to mcMMO for XP gain & ability use.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param spoutTool The tool to add
+     * @param type The type of tool to add
+     */
+    public static void addCustomTool(ItemStack spoutTool, ToolType type) {
+        switch (type) {
+            case AXE:
+                spoutAxes.add(spoutTool);
+                break;
+
+            case HOE:
+                spoutHoes.add(spoutTool);
+                break;
+
+            case PICKAXE:
+                spoutPickaxes.add(spoutTool);
+                break;
+
+            case SHOVEL:
+                spoutShovels.add(spoutTool);
+                break;
+
+            case SWORD:
+                spoutSwords.add(spoutTool);
+                break;
+
+            default:
+                break;
+        }
+    }
+}

+ 77 - 73
src/main/java/com/gmail/nossr50/chat/ChatManager.java

@@ -1,74 +1,78 @@
-package com.gmail.nossr50.chat;
-
-import org.bukkit.ChatColor;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.events.chat.McMMOAdminChatEvent;
-import com.gmail.nossr50.events.chat.McMMOPartyChatEvent;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-
-public final class ChatManager {
-    public ChatManager () {}
-
-    public static void handleAdminChat(Plugin plugin, String playerName, String displayName, String message, boolean isAsync) {
-        McMMOAdminChatEvent chatEvent = new McMMOAdminChatEvent(plugin, playerName, displayName, message, isAsync);
-        mcMMO.p.getServer().getPluginManager().callEvent(chatEvent);
-
-        if (chatEvent.isCancelled()) {
-            return;
-        }
-
-        if(Config.getInstance().getAdminDisplayNames())
-            displayName = chatEvent.getDisplayName();
-        else
-            displayName = chatEvent.getSender();
-
-        String adminMessage = chatEvent.getMessage();
-
-        mcMMO.p.getServer().broadcast(LocaleLoader.getString("Commands.AdminChat.Prefix", displayName) + adminMessage, "mcmmo.chat.adminchat");
-    }
-
-    public static void handleAdminChat(Plugin plugin, String senderName, String message) {
-        handleAdminChat(plugin, senderName, senderName, message);
-    }
-
-    public static void handleAdminChat(Plugin plugin, String playerName, String displayName, String message) {
-        handleAdminChat(plugin, playerName, displayName, message, false);
-    }
-
-    public static void handlePartyChat(Plugin plugin, Party party, String playerName, String displayName, String message, boolean isAsync) {
-        String partyName = party.getName();
-
-        McMMOPartyChatEvent chatEvent = new McMMOPartyChatEvent(plugin, playerName, displayName, partyName, message, isAsync);
-        mcMMO.p.getServer().getPluginManager().callEvent(chatEvent);
-
-        if (chatEvent.isCancelled()) {
-            return;
-        }
-
-        if(Config.getInstance().getPartyDisplayNames())
-            displayName = chatEvent.getDisplayName();
-        else
-            displayName = chatEvent.getSender();
-
-        String partyMessage = chatEvent.getMessage();
-
-        for (Player member : party.getOnlineMembers()) {
-            member.sendMessage(LocaleLoader.getString("Commands.Party.Chat.Prefix", displayName) + partyMessage);
-        }
-
-        mcMMO.p.getLogger().info("[P](" + partyName + ")" + "<" + ChatColor.stripColor(displayName) + "> " + partyMessage);
-    }
-
-    public static void handlePartyChat(Plugin plugin, Party party, String senderName, String message) {
-        handlePartyChat(plugin, party, senderName, senderName, message);
-    }
-
-    public static void handlePartyChat(Plugin plugin, Party party, String playerName, String displayName, String message) {
-        handlePartyChat(plugin, party, playerName, displayName, message, false);
-    }
+package com.gmail.nossr50.chat;
+
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.events.chat.McMMOAdminChatEvent;
+import com.gmail.nossr50.events.chat.McMMOPartyChatEvent;
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public final class ChatManager {
+    public ChatManager () {}
+
+    public static void handleAdminChat(Plugin plugin, String playerName, String displayName, String message, boolean isAsync) {
+        McMMOAdminChatEvent chatEvent = new McMMOAdminChatEvent(plugin, playerName, displayName, message, isAsync);
+        mcMMO.p.getServer().getPluginManager().callEvent(chatEvent);
+
+        if (chatEvent.isCancelled()) {
+            return;
+        }
+
+        if (Config.getInstance().getAdminDisplayNames()) {
+            displayName = chatEvent.getDisplayName();
+        }
+        else {
+            displayName = chatEvent.getSender();
+        }
+
+        String adminMessage = chatEvent.getMessage();
+
+        mcMMO.p.getServer().broadcast(LocaleLoader.getString("Commands.AdminChat.Prefix", displayName) + adminMessage, "mcmmo.chat.adminchat");
+    }
+
+    public static void handleAdminChat(Plugin plugin, String senderName, String message) {
+        handleAdminChat(plugin, senderName, senderName, message);
+    }
+
+    public static void handleAdminChat(Plugin plugin, String playerName, String displayName, String message) {
+        handleAdminChat(plugin, playerName, displayName, message, false);
+    }
+
+    public static void handlePartyChat(Plugin plugin, Party party, String playerName, String displayName, String message, boolean isAsync) {
+        String partyName = party.getName();
+
+        McMMOPartyChatEvent chatEvent = new McMMOPartyChatEvent(plugin, playerName, displayName, partyName, message, isAsync);
+        mcMMO.p.getServer().getPluginManager().callEvent(chatEvent);
+
+        if (chatEvent.isCancelled()) {
+            return;
+        }
+
+        if (Config.getInstance().getPartyDisplayNames()) {
+            displayName = chatEvent.getDisplayName();
+        }
+        else {
+            displayName = chatEvent.getSender();
+        }
+
+        String partyMessage = chatEvent.getMessage();
+
+        for (Player member : party.getOnlineMembers()) {
+            member.sendMessage(LocaleLoader.getString("Commands.Party.Chat.Prefix", displayName) + partyMessage);
+        }
+
+        mcMMO.p.getLogger().info("[P](" + partyName + ")" + "<" + ChatColor.stripColor(displayName) + "> " + partyMessage);
+    }
+
+    public static void handlePartyChat(Plugin plugin, Party party, String senderName, String message) {
+        handlePartyChat(plugin, party, senderName, senderName, message);
+    }
+
+    public static void handlePartyChat(Plugin plugin, Party party, String playerName, String displayName, String message) {
+        handlePartyChat(plugin, party, playerName, displayName, message, false);
+    }
 }

+ 70 - 70
src/main/java/com/gmail/nossr50/chat/ChatMode.java

@@ -1,70 +1,70 @@
-package com.gmail.nossr50.chat;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.locale.LocaleLoader;
-
-public enum ChatMode {
-    ADMIN(LocaleLoader.getString("Commands.AdminChat.On"), LocaleLoader.getString("Commands.AdminChat.Off")),
-    PARTY(LocaleLoader.getString("Commands.Party.Chat.On"), LocaleLoader.getString("Commands.Party.Chat.Off"));
-
-    private String enabledMessage;
-    private String disabledMessage;
-
-    private ChatMode (String enabledMessage, String disabledMessage) {
-        this.enabledMessage = enabledMessage;
-        this.disabledMessage = disabledMessage;
-    }
-
-    public boolean isEnabled(McMMOPlayer mcMMOPlayer) {
-        switch (this) {
-        case ADMIN:
-            return mcMMOPlayer.getAdminChatMode();
-
-        case PARTY:
-            return mcMMOPlayer.getPartyChatMode();
-
-        default:
-            return false;
-        }
-    }
-
-    public void disable(McMMOPlayer mcMMOPlayer) {
-        switch (this) {
-        case ADMIN:
-            mcMMOPlayer.setAdminChat(false);
-            return;
-
-        case PARTY:
-            mcMMOPlayer.setPartyChat(false);
-            return;
-
-        default:
-            return;
-        }
-    }
-
-    public void enable(McMMOPlayer mcMMOPlayer) {
-        switch (this) {
-        case ADMIN:
-            mcMMOPlayer.setAdminChat(true);
-            mcMMOPlayer.setPartyChat(false);
-            return;
-
-        case PARTY:
-            mcMMOPlayer.setPartyChat(true);
-            mcMMOPlayer.setAdminChat(false);
-            return;
-
-        default:
-            return;
-        }
-    }
-
-    public String getEnabledMessage() {
-        return enabledMessage;
-    }
-
-    public String getDisabledMessage() {
-        return disabledMessage;
-    }
-}
+package com.gmail.nossr50.chat;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public enum ChatMode {
+    ADMIN(LocaleLoader.getString("Commands.AdminChat.On"), LocaleLoader.getString("Commands.AdminChat.Off")),
+    PARTY(LocaleLoader.getString("Commands.Party.Chat.On"), LocaleLoader.getString("Commands.Party.Chat.Off"));
+
+    private String enabledMessage;
+    private String disabledMessage;
+
+    private ChatMode(String enabledMessage, String disabledMessage) {
+        this.enabledMessage  = enabledMessage;
+        this.disabledMessage = disabledMessage;
+    }
+
+    public boolean isEnabled(McMMOPlayer mcMMOPlayer) {
+        switch (this) {
+            case ADMIN:
+                return mcMMOPlayer.getAdminChatMode();
+
+            case PARTY:
+                return mcMMOPlayer.getPartyChatMode();
+
+            default:
+                return false;
+        }
+    }
+
+    public void disable(McMMOPlayer mcMMOPlayer) {
+        switch (this) {
+            case ADMIN:
+                mcMMOPlayer.setAdminChat(false);
+                return;
+
+            case PARTY:
+                mcMMOPlayer.setPartyChat(false);
+                return;
+
+            default:
+                return;
+        }
+    }
+
+    public void enable(McMMOPlayer mcMMOPlayer) {
+        switch (this) {
+            case ADMIN:
+                mcMMOPlayer.setAdminChat(true);
+                mcMMOPlayer.setPartyChat(false);
+                return;
+
+            case PARTY:
+                mcMMOPlayer.setPartyChat(true);
+                mcMMOPlayer.setAdminChat(false);
+                return;
+
+            default:
+                return;
+        }
+    }
+
+    public String getEnabledMessage() {
+        return enabledMessage;
+    }
+
+    public String getDisabledMessage() {
+        return disabledMessage;
+    }
+}

+ 80 - 0
src/main/java/com/gmail/nossr50/commands/McabilityCommand.java

@@ -0,0 +1,80 @@
+package com.gmail.nossr50.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class McabilityCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+
+        switch (args.length) {
+            case 0:
+                if (!Permissions.mcability(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (profile.getAbilityUse()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Ability.Off"));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Ability.On"));
+                }
+
+                profile.toggleAbilityUse();
+                return true;
+
+            case 1:
+                if (!Permissions.mcabilityOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+
+                Player player = mcMMOPlayer.getPlayer();
+                profile = mcMMOPlayer.getProfile();
+
+                if (!player.isOnline()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+
+                if (profile.getAbilityUse()) {
+                    player.sendMessage(LocaleLoader.getString("Commands.Ability.Off"));
+                }
+                else {
+                    player.sendMessage(LocaleLoader.getString("Commands.Ability.On"));
+                }
+
+                profile.toggleAbilityUse();
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 89 - 0
src/main/java/com/gmail/nossr50/commands/McgodCommand.java

@@ -0,0 +1,89 @@
+package com.gmail.nossr50.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class McgodCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+
+        switch (args.length) {
+            case 0:
+                if (!Permissions.mcgod(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (profile == null) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                    return true;
+                }
+
+                if (profile.getGodMode()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.GodMode.Disabled"));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.GodMode.Enabled"));
+                }
+
+                profile.toggleGodMode();
+                return true;
+
+            case 1:
+                if (!Permissions.mcgodOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+
+                profile = mcMMOPlayer.getProfile();
+                Player player = mcMMOPlayer.getPlayer();
+
+                if (!player.isOnline()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+
+                if (profile.getGodMode()) {
+                    player.sendMessage(LocaleLoader.getString("Commands.GodMode.Disabled"));
+                }
+                else {
+                    player.sendMessage(LocaleLoader.getString("Commands.GodMode.Enabled"));
+                }
+
+                profile.toggleGodMode();
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 113 - 114
src/main/java/com/gmail/nossr50/commands/player/McmmoCommand.java → src/main/java/com/gmail/nossr50/commands/McmmoCommand.java

@@ -1,114 +1,113 @@
-package com.gmail.nossr50.commands.player;
-
-import org.bukkit.ChatColor;
-import org.bukkit.Material;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.getspout.spoutapi.player.SpoutPlayer;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.commands.PartySubcommandType;
-import com.gmail.nossr50.util.Permissions;
-
-public class McmmoCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-
-        switch (args.length) {
-        case 0:
-            if (!Permissions.mcmmoDescription(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            String description = LocaleLoader.getString("mcMMO.Description");
-            String[] mcSplit = description.split(",");
-            sender.sendMessage(mcSplit);
-
-            if (Config.getInstance().getDonateMessageEnabled()) {
-                if (mcMMO.spoutEnabled && sender instanceof SpoutPlayer) {
-                    SpoutPlayer spoutPlayer = (SpoutPlayer) sender;
-                    spoutPlayer.sendNotification(LocaleLoader.getString("Spout.Donate"), ChatColor.GREEN + "gjmcferrin@gmail.com", Material.DIAMOND);
-                }
-
-                sender.sendMessage(LocaleLoader.getString("MOTD.Donate"));
-                sender.sendMessage(ChatColor.GOLD + " - " + ChatColor.GREEN + "gjmcferrin@gmail.com" + ChatColor.GOLD + " Paypal");
-            }
-
-            sender.sendMessage(LocaleLoader.getString("MOTD.Version", mcMMO.p.getDescription().getVersion()));
-            return true;
-
-        case 1:
-            if (args[0].equalsIgnoreCase("?") || args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("commands")) {
-                if (!Permissions.mcmmoHelp(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.mcc.Header"));
-                displayPartyCommands(sender);
-                displayOtherCommands(sender);
-
-            }
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    private void displayPartyCommands(CommandSender sender) {
-        if (Permissions.party(sender)) {
-            sender.sendMessage(LocaleLoader.getString("Commands.Party.Commands"));
-            sender.sendMessage("/party create <" + LocaleLoader.getString("Commands.Usage.PartyName") + "> " + LocaleLoader.getString("Commands.Party1"));
-            sender.sendMessage("/party join <" + LocaleLoader.getString("Commands.Usage.Player") + "> " + LocaleLoader.getString("Commands.Party2"));
-            sender.sendMessage("/party quit " + LocaleLoader.getString("Commands.Party.Quit"));
-
-            if (Permissions.partyChat(sender)) {
-                sender.sendMessage("/party chat " + LocaleLoader.getString("Commands.Party.Toggle"));
-            }
-
-            sender.sendMessage("/party invite <" + LocaleLoader.getString("Commands.Usage.Player") + "> " + LocaleLoader.getString("Commands.Party.Invite"));
-            sender.sendMessage("/party accept " + LocaleLoader.getString("Commands.Party.Accept"));
-
-            if (Permissions.partySubcommand(sender, PartySubcommandType.TELEPORT)) {
-                sender.sendMessage("/party teleport " + LocaleLoader.getString("Commands.Party.Teleport"));
-            }
-        }
-    }
-
-    private void displayOtherCommands(CommandSender sender) {
-        sender.sendMessage(LocaleLoader.getString("Commands.Other"));
-        sender.sendMessage("/mcstats " + LocaleLoader.getString("Commands.Stats"));
-        sender.sendMessage("/mctop " + LocaleLoader.getString("Commands.Leaderboards"));
-
-        if (Permissions.skillreset(sender)) {
-            sender.sendMessage("/skillreset <skill|all> " + LocaleLoader.getString("Commands.Reset"));
-        }
-
-        if (Permissions.mcability(sender)) {
-            sender.sendMessage("/mcability " + LocaleLoader.getString("Commands.ToggleAbility"));
-        }
-
-        if (Permissions.adminChat(sender)) {
-            sender.sendMessage("/adminchat " + LocaleLoader.getString("Commands.AdminToggle"));
-        }
-
-        if (Permissions.inspect(sender)) {
-            sender.sendMessage("/inspect " + LocaleLoader.getString("Commands.Inspect"));
-        }
-
-        if (Permissions.mmoedit(sender)) {
-            sender.sendMessage("/mmoedit " + LocaleLoader.getString("Commands.mmoedit"));
-        }
-
-        if (Permissions.mcgod(sender)) {
-            sender.sendMessage("/mcgod " + LocaleLoader.getString("Commands.mcgod"));
-        }
-
-        sender.sendMessage(LocaleLoader.getString("Commands.SkillInfo"));
-    }
-}
+package com.gmail.nossr50.commands;
+
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.getspout.spoutapi.player.SpoutPlayer;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.commands.party.PartySubcommandType;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+
+public class McmmoCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 0:
+                if (!Permissions.mcmmoDescription(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                String description = LocaleLoader.getString("mcMMO.Description");
+                String[] mcSplit = description.split(",");
+                sender.sendMessage(mcSplit);
+
+                if (Config.getInstance().getDonateMessageEnabled()) {
+                    if (mcMMO.spoutEnabled && sender instanceof SpoutPlayer) {
+                        SpoutPlayer spoutPlayer = (SpoutPlayer) sender;
+                        spoutPlayer.sendNotification(LocaleLoader.getString("Spout.Donate"), ChatColor.GREEN + "gjmcferrin@gmail.com", Material.DIAMOND);
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("MOTD.Donate"));
+                    sender.sendMessage(ChatColor.GOLD + " - " + ChatColor.GREEN + "gjmcferrin@gmail.com" + ChatColor.GOLD + " Paypal");
+                }
+
+                sender.sendMessage(LocaleLoader.getString("MOTD.Version", mcMMO.p.getDescription().getVersion()));
+                return true;
+
+            case 1:
+                if (args[0].equalsIgnoreCase("?") || args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("commands")) {
+                    if (!Permissions.mcmmoHelp(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.mcc.Header"));
+                    displayPartyCommands(sender);
+                    displayOtherCommands(sender);
+
+                }
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    private void displayPartyCommands(CommandSender sender) {
+        if (Permissions.party(sender)) {
+            sender.sendMessage(LocaleLoader.getString("Commands.Party.Commands"));
+            sender.sendMessage("/party create <" + LocaleLoader.getString("Commands.Usage.PartyName") + "> " + LocaleLoader.getString("Commands.Party1"));
+            sender.sendMessage("/party join <" + LocaleLoader.getString("Commands.Usage.Player") + "> " + LocaleLoader.getString("Commands.Party2"));
+            sender.sendMessage("/party quit " + LocaleLoader.getString("Commands.Party.Quit"));
+
+            if (Permissions.partyChat(sender)) {
+                sender.sendMessage("/party chat " + LocaleLoader.getString("Commands.Party.Toggle"));
+            }
+
+            sender.sendMessage("/party invite <" + LocaleLoader.getString("Commands.Usage.Player") + "> " + LocaleLoader.getString("Commands.Party.Invite"));
+            sender.sendMessage("/party accept " + LocaleLoader.getString("Commands.Party.Accept"));
+
+            if (Permissions.partySubcommand(sender, PartySubcommandType.TELEPORT)) {
+                sender.sendMessage("/party teleport " + LocaleLoader.getString("Commands.Party.Teleport"));
+            }
+        }
+    }
+
+    private void displayOtherCommands(CommandSender sender) {
+        sender.sendMessage(LocaleLoader.getString("Commands.Other"));
+        sender.sendMessage("/mcstats " + LocaleLoader.getString("Commands.Stats"));
+        sender.sendMessage("/mctop " + LocaleLoader.getString("Commands.Leaderboards"));
+
+        if (Permissions.skillreset(sender)) {
+            sender.sendMessage("/skillreset <skill|all> " + LocaleLoader.getString("Commands.Reset"));
+        }
+
+        if (Permissions.mcability(sender)) {
+            sender.sendMessage("/mcability " + LocaleLoader.getString("Commands.ToggleAbility"));
+        }
+
+        if (Permissions.adminChat(sender)) {
+            sender.sendMessage("/adminchat " + LocaleLoader.getString("Commands.AdminToggle"));
+        }
+
+        if (Permissions.inspect(sender)) {
+            sender.sendMessage("/inspect " + LocaleLoader.getString("Commands.Inspect"));
+        }
+
+        if (Permissions.mmoedit(sender)) {
+            sender.sendMessage("/mmoedit " + LocaleLoader.getString("Commands.mmoedit"));
+        }
+
+        if (Permissions.mcgod(sender)) {
+            sender.sendMessage("/mcgod " + LocaleLoader.getString("Commands.mcgod"));
+        }
+
+        sender.sendMessage(LocaleLoader.getString("Commands.SkillInfo"));
+    }
+}

+ 33 - 0
src/main/java/com/gmail/nossr50/commands/McnotifyCommand.java

@@ -0,0 +1,33 @@
+package com.gmail.nossr50.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class McnotifyCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 0:
+                PlayerProfile profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (profile.useChatNotifications()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Notifications.Off"));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Notifications.On"));
+                }
+
+                profile.toggleChatNotifications();
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 80 - 0
src/main/java/com/gmail/nossr50/commands/McrefreshCommand.java

@@ -0,0 +1,80 @@
+package com.gmail.nossr50.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class McrefreshCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+
+        switch (args.length) {
+            case 0:
+                if (!Permissions.mcrefresh(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                profile = UserManager.getPlayer(sender.getName()).getProfile();
+
+                profile.setRecentlyHurt(0);
+                profile.resetCooldowns();
+                profile.resetToolPrepMode();
+                profile.resetAbilityMode();
+
+                sender.sendMessage(LocaleLoader.getString("Ability.Generic.Refresh"));
+                return true;
+
+            case 1:
+                if (!Permissions.mcrefreshOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+                profile = mcMMOPlayer.getProfile();
+                Player player = mcMMOPlayer.getPlayer();
+
+                if (!player.isOnline()) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
+                    return true;
+                }
+
+                profile.setRecentlyHurt(0);
+                profile.resetCooldowns();
+                profile.resetToolPrepMode();
+                profile.resetAbilityMode();
+
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Refresh"));
+                sender.sendMessage(LocaleLoader.getString("Commands.mcrefresh.Success", args[0]));
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 69 - 0
src/main/java/com/gmail/nossr50/commands/XprateCommand.java

@@ -0,0 +1,69 @@
+package com.gmail.nossr50.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+
+public class XprateCommand implements CommandExecutor {
+    private static double originalRate = Config.getInstance().getExperienceGainsGlobalMultiplier();
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 1:
+                if (!args[0].equalsIgnoreCase("reset")) {
+                    return false;
+                }
+
+                if (!Permissions.xprateReset(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (mcMMO.p.isXPEventEnabled()) {
+                    mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.over"));
+                    mcMMO.p.toggleXpEventEnabled();
+                }
+
+                Config.getInstance().setExperienceGainsGlobalMultiplier(originalRate);
+                return true;
+
+            case 2:
+                if (!StringUtils.isInt(args[0])) {
+                    return false;
+                }
+
+                if (!Permissions.xprateSet(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!args[1].equalsIgnoreCase("true") && !args[1].equalsIgnoreCase("false")) {
+                    return false;
+                }
+
+                mcMMO.p.setXPEventEnabled(Boolean.valueOf(args[1]));
+                int newXpRate = Integer.parseInt(args[0]);
+                Config.getInstance().setExperienceGainsGlobalMultiplier(newXpRate);
+
+                if (mcMMO.p.isXPEventEnabled()) {
+                    mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.started.0"));
+                    mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.started.1", newXpRate));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.xprate.modified", newXpRate));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 0 - 154
src/main/java/com/gmail/nossr50/commands/admin/AddlevelsCommand.java

@@ -1,154 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.Users;
-
-public class AddlevelsCommand implements CommandExecutor{
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-        int levels;
-        boolean allSkills = false;
-        SkillType skill = null;
-
-        switch (args.length) {
-        case 2:
-            if (!Permissions.addlevels(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            if (args[0].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[0])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[1])) {
-                return false;
-            }
-
-            levels = Integer.parseInt(args[1]);
-            profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (allSkills) {
-                for (SkillType skillType : SkillType.values()) {
-                    if (skillType.isChildSkill()) {
-                        continue;
-                    }
-
-                    profile.addLevels(skillType, levels);
-                }
-            }
-            else {
-                skill = SkillType.getSkill(args[0]);
-                profile.addLevels(skill, levels);
-            }
-
-            if (allSkills) {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.1", levels));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.1", levels, SkillTools.getSkillName(skill)));
-            }
-
-            return true;
-
-        case 3:
-            if (!Permissions.addlevelsOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (args[1].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[1])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[2])) {
-                return false;
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-            levels = Integer.parseInt(args[2]);
-
-            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        profile.addLevels(skillType, levels);
-                    }
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    profile.addLevels(skill, levels);
-                }
-
-                profile.save(); // Since this is a temporary profile, we save it here.
-            }
-            else {
-                profile = mcMMOPlayer.getProfile();
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        profile.addLevels(skillType, levels);
-                    }
-
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.1", levels));
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    profile.addLevels(skill, levels);
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.1", levels, SkillTools.getSkillName(skill)));
-                }
-            }
-
-            if (allSkills) {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.2", SkillTools.getSkillName(skill), args[0]));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 151
src/main/java/com/gmail/nossr50/commands/admin/AddxpCommand.java

@@ -1,151 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.Users;
-
-public class AddxpCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        int xp;
-        McMMOPlayer mcMMOPlayer;
-        PlayerProfile profile;
-        boolean allSkills = false;
-        SkillType skill = null;
-
-        switch (args.length) {
-        case 2:
-            if (!Permissions.addxp(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            if (args[0].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[0])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[1])) {
-                return false;
-            }
-
-            xp = Integer.parseInt(args[1]);
-            mcMMOPlayer = Users.getPlayer((Player) sender);
-            profile = mcMMOPlayer.getProfile();
-
-            if (allSkills) {
-                for (SkillType skillType : SkillType.values()) {
-                    if (skillType.isChildSkill()) {
-                        continue;
-                    }
-
-                    mcMMOPlayer.applyXpGain(skillType, xp);
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.addxp.AwardAll", xp));
-            }
-            else {
-                skill = SkillType.getSkill(args[0]);
-
-                mcMMOPlayer.applyXpGain(skill, xp);
-                sender.sendMessage(LocaleLoader.getString("Commands.addxp.AwardSkill", xp, SkillTools.getSkillName(skill)));
-            }
-
-            return true;
-
-        case 3:
-            if (!Permissions.addxpOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (args[1].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[1])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[2])) {
-                return false;
-            }
-
-            mcMMOPlayer = Users.getPlayer(args[0]);
-            xp = Integer.parseInt(args[2]);
-
-            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                // TODO: Currently the offline player doesn't level up automatically
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        profile.setSkillXpLevel(skillType, xp);
-                    }
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    profile.setSkillXpLevel(skill, xp);
-                }
-
-                profile.save(); // Since this is a temporary profile, we save it here.
-            }
-            else {
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        mcMMOPlayer.applyXpGain(skillType, xp);
-                    }
-
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addxp.AwardAll", xp));
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    mcMMOPlayer.applyXpGain(skill, xp);
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addxp.AwardSkill", xp, SkillTools.getSkillName(skill)));
-                }
-            }
-
-            if (allSkills) {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.2", SkillTools.getSkillName(skill), args[0]));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 86
src/main/java/com/gmail/nossr50/commands/admin/HardcoreCommand.java

@@ -1,86 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import java.text.DecimalFormat;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-
-public class HardcoreCommand implements CommandExecutor{
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        switch (args.length) {
-        case 0:
-            if (!Permissions.hardcoreToggle(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (Config.getInstance().getHardcoreEnabled()) {
-                disableHardcore();
-            }
-            else {
-                enableHardcore();
-            }
-
-            return true;
-
-        case 1:
-            if (args[0].equalsIgnoreCase("on") || args[0].equalsIgnoreCase("true") || args[0].equalsIgnoreCase("enabled")) {
-                if (!Permissions.hardcoreToggle(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                enableHardcore();
-                return true;
-            }
-
-            if (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("false") || args[0].equalsIgnoreCase("disabled")) {
-                if (!Permissions.hardcoreToggle(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                disableHardcore();
-                return true;
-            }
-
-            if (!StringUtils.isDouble(args[0])) {
-                return false;
-            }
-
-            if (!Permissions.hardcoreModify(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            DecimalFormat percent = new DecimalFormat("##0.00%");
-            double newPercent = Double.parseDouble(args[0]);
-
-            Config.getInstance().setHardcoreDeathStatPenaltyPercentage(newPercent);
-            sender.sendMessage(LocaleLoader.getString("Hardcore.PercentageChanged", percent.format(newPercent / 100D)));
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    private void disableHardcore() {
-        Config.getInstance().setHardcoreEnabled(false);
-        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Hardcore.Disabled"));
-    }
-
-    private void enableHardcore() {
-        Config.getInstance().setHardcoreEnabled(true);
-        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Hardcore.Enabled"));
-    }
-}

+ 0 - 89
src/main/java/com/gmail/nossr50/commands/admin/McgodCommand.java

@@ -1,89 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class McgodCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-
-        switch (args.length) {
-        case 0:
-            if (!Permissions.mcgod(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (profile == null) {
-                sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                return true;
-            }
-
-            if (profile.getGodMode()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.GodMode.Disabled"));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.GodMode.Enabled"));
-            }
-
-            profile.toggleGodMode();
-            return true;
-
-        case 1:
-            if (!Permissions.mcgodOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-
-            profile = mcMMOPlayer.getProfile();
-            Player player = mcMMOPlayer.getPlayer();
-
-            if (!player.isOnline()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-
-            if (profile.getGodMode()) {
-                player.sendMessage(LocaleLoader.getString("Commands.GodMode.Disabled"));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Commands.GodMode.Enabled"));
-            }
-
-            profile.toggleGodMode();
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 80
src/main/java/com/gmail/nossr50/commands/admin/McrefreshCommand.java

@@ -1,80 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class McrefreshCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-
-        switch (args.length) {
-        case 0:
-            if (!Permissions.mcrefresh(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            profile = Users.getPlayer(sender.getName()).getProfile();
-
-            profile.setRecentlyHurt(0);
-            profile.resetCooldowns();
-            profile.resetToolPrepMode();
-            profile.resetAbilityMode();
-
-            sender.sendMessage(LocaleLoader.getString("Ability.Generic.Refresh"));
-            return true;
-
-        case 1:
-            if (!Permissions.mcrefreshOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-            profile = mcMMOPlayer.getProfile();
-            Player player = mcMMOPlayer.getPlayer();
-
-            if (!player.isOnline()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-
-            profile.setRecentlyHurt(0);
-            profile.resetCooldowns();
-            profile.resetToolPrepMode();
-            profile.resetAbilityMode();
-
-            player.sendMessage(LocaleLoader.getString("Ability.Generic.Refresh"));
-            sender.sendMessage(LocaleLoader.getString("Commands.mcrefresh.Success", args[0]));
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 150
src/main/java/com/gmail/nossr50/commands/admin/MmoeditCommand.java

@@ -1,150 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.Users;
-
-public class MmoeditCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-        int newValue;
-        boolean allSkills = false;
-        SkillType skill = null;
-
-        switch (args.length) {
-        case 2:
-            if (!Permissions.mmoedit(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            if (args[0].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[0])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[1])) {
-                return false;
-            }
-
-            newValue = Integer.parseInt(args[1]);
-            profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (allSkills) {
-                for (SkillType skillType : SkillType.values()) {
-                    if (skillType.isChildSkill()) {
-                        continue;
-                    }
-
-                    profile.modifySkill(skillType, newValue);
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.AllSkills.1", newValue));
-            }
-            else {
-                skill = SkillType.getSkill(args[0]);
-                profile.modifySkill(skill, newValue);
-                sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.1", SkillTools.getSkillName(skill), newValue));
-            }
-
-            return true;
-
-        case 3:
-            if (!Permissions.mmoeditOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (args[1].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[1])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!StringUtils.isInt(args[2])) {
-                return false;
-            }
-
-            newValue = Integer.parseInt(args[2]);
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        profile.modifySkill(skillType, newValue);
-                    }
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    profile.modifySkill(skill, newValue);
-                }
-
-                profile.save(); // Since this is a temporary profile, we save it here.
-            }
-            else {
-                profile = mcMMOPlayer.getProfile();
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        profile.modifySkill(skillType, newValue);
-                    }
-
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.mmoedit.AllSkills.1", newValue));
-                }
-                else {
-                    skill = SkillType.getSkill(args[1]);
-                    profile.modifySkill(skill, newValue);
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.1",  SkillTools.getSkillName(skill), newValue));
-                }
-            }
-
-            if (allSkills) {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.2", SkillTools.getSkillName(skill), args[0]));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 169
src/main/java/com/gmail/nossr50/commands/admin/SkillresetCommand.java

@@ -1,169 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class SkillresetCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-        boolean allSkills = false;
-        SkillType skill = null;
-        String skillName = "";
-
-        switch (args.length) {
-        case 1:
-            if (!Permissions.skillreset(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            if (args[0].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[0])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (allSkills) {
-                for (SkillType skillType : SkillType.values()) {
-                    if (skillType.isChildSkill()) {
-                        continue;
-                    }
-
-                    if (!Permissions.skillreset(sender, skillType)) {
-                        sender.sendMessage(command.getPermissionMessage());
-                        continue;
-                    }
-
-                    profile.modifySkill(skillType, 0);
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.Reset.All"));
-            }
-            else {
-                skill = SkillType.getSkill(args[0]);
-                skillName = SkillTools.getSkillName(skill);
-
-                if (!Permissions.skillreset(sender, skill)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                profile.modifySkill(skill, 0);
-                sender.sendMessage(LocaleLoader.getString("Commands.Reset.Single", skillName));
-            }
-
-            return true;
-
-        case 2:
-            if (!Permissions.skillresetOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (args[1].equalsIgnoreCase("all")) {
-                allSkills = true;
-            }
-            else if (!SkillTools.isSkill(args[1])) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-                return true;
-            }
-
-            if (!allSkills) {
-                skill = SkillType.getSkill(args[1]);
-                skillName = SkillTools.getSkillName(skill);
-
-                if (!Permissions.skillresetOthers(sender, skill)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        if (!Permissions.skillresetOthers(sender, skill)) {
-                            sender.sendMessage(command.getPermissionMessage());
-                            continue;
-                        }
-
-                        profile.modifySkill(skillType, 0);
-                    }
-                }
-                else {
-                    profile.modifySkill(skill, 0);
-                }
-
-                profile.save(); // Since this is a temporary profile, we save it here.
-            }
-            else {
-                profile = mcMMOPlayer.getProfile();
-
-                if (allSkills) {
-                    for (SkillType skillType : SkillType.values()) {
-                        if (skillType.isChildSkill()) {
-                            continue;
-                        }
-
-                        if (!Permissions.skillresetOthers(sender, skillType)) {
-                            sender.sendMessage(command.getPermissionMessage());
-                            continue;
-                        }
-
-                        profile.modifySkill(skillType, 0);
-                    }
-
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.Reset.All"));
-                }
-                else {
-                    profile.modifySkill(skill, 0);
-                    mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.Reset.Single", skillName));
-                }
-            }
-
-            if (allSkills) {
-                sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.2", skillName, args[0]));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 91
src/main/java/com/gmail/nossr50/commands/admin/VampirismCommand.java

@@ -1,91 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import java.text.DecimalFormat;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-
-public class VampirismCommand implements CommandExecutor {
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (!Config.getInstance().getHardcoreEnabled()) {
-            sender.sendMessage(LocaleLoader.getString("Hardcore.Disabled"));
-            return true;
-        }
-
-        switch (args.length) {
-        case 0:
-            if (!Permissions.vampirismToggle(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (Config.getInstance().getHardcoreVampirismEnabled()) {
-                disableVampirism();
-            }
-            else {
-                enableVampirism();
-            }
-
-            return true;
-
-        case 1:
-            if (args[0].equalsIgnoreCase("on") || args[0].equalsIgnoreCase("true") || args[0].equalsIgnoreCase("enabled")) {
-                if (!Permissions.vampirismToggle(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                enableVampirism();
-                return true;
-            }
-
-            if (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("false") || args[0].equalsIgnoreCase("disabled")) {
-                if (!Permissions.vampirismToggle(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                disableVampirism();
-                return true;
-            }
-
-            if (!StringUtils.isDouble(args[0])) {
-                return false;
-            }
-
-            if (!Permissions.vampirismModify(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            DecimalFormat percent = new DecimalFormat("##0.00%");
-            double newPercent = Double.parseDouble(args[0]);
-
-            Config.getInstance().setHardcoreVampirismStatLeechPercentage(newPercent);
-            sender.sendMessage(LocaleLoader.getString("Vampirism.PercentageChanged", percent.format(newPercent / 100D)));
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    private void disableVampirism() {
-        Config.getInstance().setHardcoreVampirismEnabled(false);
-        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Vampirism.Disabled"));
-    }
-
-    private void enableVampirism() {
-        Config.getInstance().setHardcoreVampirismEnabled(true);
-        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Vampirism.Enabled"));
-    }
-}

+ 0 - 69
src/main/java/com/gmail/nossr50/commands/admin/XprateCommand.java

@@ -1,69 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-
-public class XprateCommand implements CommandExecutor {
-    private static double originalRate = Config.getInstance().getExperienceGainsGlobalMultiplier();
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        switch (args.length) {
-        case 1:
-            if (!args[0].equalsIgnoreCase("reset")) {
-                return false;
-            }
-
-            if (!Permissions.xprateReset(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (mcMMO.p.isXPEventEnabled()) {
-                mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.over"));
-                mcMMO.p.toggleXpEventEnabled();
-            }
-
-            Config.getInstance().setExperienceGainsGlobalMultiplier(originalRate);
-            return true;
-
-        case 2:
-            if (!StringUtils.isInt(args[0])) {
-                return false;
-            }
-
-            if (!Permissions.xprateSet(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!args[1].equalsIgnoreCase("true") && !args[1].equalsIgnoreCase("false")) {
-                return false;
-            }
-
-            mcMMO.p.setXPEventEnabled(Boolean.valueOf(args[1]));
-            int newXpRate = Integer.parseInt(args[0]);
-            Config.getInstance().setExperienceGainsGlobalMultiplier(newXpRate);
-
-            if (mcMMO.p.isXPEventEnabled()) {
-                mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.started.0"));
-                mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Commands.xprate.started.1", newXpRate));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.xprate.modified", newXpRate));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 26 - 26
src/main/java/com/gmail/nossr50/chat/commands/AdminChatCommand.java → src/main/java/com/gmail/nossr50/commands/chat/AdminChatCommand.java

@@ -1,26 +1,26 @@
-package com.gmail.nossr50.chat.commands;
-
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.chat.ChatManager;
-import com.gmail.nossr50.chat.ChatMode;
-import com.gmail.nossr50.locale.LocaleLoader;
-
-public class AdminChatCommand extends ChatCommand {
-    public AdminChatCommand() {
-        super(ChatMode.ADMIN);
-    }
-
-    @Override
-    protected void handleChatSending(CommandSender sender, String[] args) {
-        if (sender instanceof Player) {
-            Player player = (Player) sender;
-            ChatManager.handleAdminChat(mcMMO.p, player.getName(), player.getDisplayName(), buildChatMessage(args, 0));
-        }
-        else {
-            ChatManager.handleAdminChat(mcMMO.p, LocaleLoader.getString("Commands.Chat.Console"), buildChatMessage(args, 0));
-        }
-    }
-}
+package com.gmail.nossr50.commands.chat;
+
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.chat.ChatManager;
+import com.gmail.nossr50.chat.ChatMode;
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public class AdminChatCommand extends ChatCommand {
+    public AdminChatCommand() {
+        super(ChatMode.ADMIN);
+    }
+
+    @Override
+    protected void handleChatSending(CommandSender sender, String[] args) {
+        if (sender instanceof Player) {
+            Player player = (Player) sender;
+            ChatManager.handleAdminChat(mcMMO.p, player.getName(), player.getDisplayName(), buildChatMessage(args, 0));
+        }
+        else {
+            ChatManager.handleAdminChat(mcMMO.p, LocaleLoader.getString("Commands.Chat.Console"), buildChatMessage(args, 0));
+        }
+    }
+}

+ 89 - 88
src/main/java/com/gmail/nossr50/chat/commands/ChatCommand.java → src/main/java/com/gmail/nossr50/commands/chat/ChatCommand.java

@@ -1,88 +1,89 @@
-package com.gmail.nossr50.chat.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.chat.ChatMode;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.util.Users;
-
-public abstract class ChatCommand implements CommandExecutor {
-    protected McMMOPlayer mcMMOPlayer;
-    protected ChatMode chatMode;
-
-    public ChatCommand (ChatMode chatMode) {
-        this.chatMode = chatMode;
-    }
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        switch (args.length) {
-        case 0:
-            if (!(sender instanceof Player)) {
-                return false;
-            }
-
-            mcMMOPlayer = Users.getPlayer((Player) sender);
-
-            if (chatMode.isEnabled(mcMMOPlayer)) {
-                disableChatMode(sender);
-            }
-            else {
-                enableChatMode(sender);
-            }
-
-            return true;
-
-        default:
-            if (args.length == 1) {
-                if (args[0].equalsIgnoreCase("on")) {
-                    if (!(sender instanceof Player)) {
-                        return false;
-                    }
-
-                    enableChatMode(sender);
-                    return true;
-                }
-
-                if (args[0].equalsIgnoreCase("off")) {
-                    if (!(sender instanceof Player)) {
-                        return false;
-                    }
-
-                    disableChatMode(sender);
-                    return true;
-                }
-            }
-
-            handleChatSending(sender, args);
-            return true;
-        }
-    }
-
-    private void enableChatMode(CommandSender sender) {
-        chatMode.enable(mcMMOPlayer);
-        sender.sendMessage(chatMode.getEnabledMessage());
-    }
-
-    private void disableChatMode(CommandSender sender) {
-        chatMode.disable(mcMMOPlayer);
-        sender.sendMessage(chatMode.getDisabledMessage());
-    }
-
-    protected String buildChatMessage(String[] args, int index) {
-        StringBuilder builder = new StringBuilder();
-        builder.append(args[index]);
-
-        for (int i = index + 1; i < args.length; i++) {
-            builder.append(" ");
-            builder.append(args[i]);
-        }
-
-        return builder.toString();
-    }
-
-    protected abstract void handleChatSending(CommandSender sender, String[] args);
-}
+package com.gmail.nossr50.commands.chat;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.chat.ChatMode;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.util.player.UserManager;
+
+public abstract class ChatCommand implements CommandExecutor {
+    protected McMMOPlayer mcMMOPlayer;
+    protected ChatMode chatMode;
+
+    public ChatCommand(ChatMode chatMode) {
+        this.chatMode = chatMode;
+    }
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 0:
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                mcMMOPlayer = UserManager.getPlayer((Player) sender);
+
+                if (chatMode.isEnabled(mcMMOPlayer)) {
+                    disableChatMode(sender);
+                }
+                else {
+                    enableChatMode(sender);
+                }
+
+                return true;
+
+            case 1:
+                if (args[0].equalsIgnoreCase("on")) {
+                    if (!(sender instanceof Player)) {
+                        return false;
+                    }
+
+                    enableChatMode(sender);
+                    return true;
+                }
+
+                if (args[0].equalsIgnoreCase("off")) {
+                    if (!(sender instanceof Player)) {
+                        return false;
+                    }
+
+                    disableChatMode(sender);
+                    return true;
+                }
+
+                // Fallthrough
+
+            default:
+                handleChatSending(sender, args);
+                return true;
+        }
+    }
+
+    protected String buildChatMessage(String[] args, int index) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(args[index]);
+
+        for (int i = index + 1; i < args.length; i++) {
+            builder.append(" ");
+            builder.append(args[i]);
+        }
+
+        return builder.toString();
+    }
+
+    protected abstract void handleChatSending(CommandSender sender, String[] args);
+
+    private void enableChatMode(CommandSender sender) {
+        chatMode.enable(mcMMOPlayer);
+        sender.sendMessage(chatMode.getEnabledMessage());
+    }
+
+    private void disableChatMode(CommandSender sender) {
+        chatMode.disable(mcMMOPlayer);
+        sender.sendMessage(chatMode.getDisabledMessage());
+    }
+}

+ 48 - 48
src/main/java/com/gmail/nossr50/chat/commands/PartyChatCommand.java → src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java

@@ -1,48 +1,48 @@
-package com.gmail.nossr50.chat.commands;
-
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.chat.ChatManager;
-import com.gmail.nossr50.chat.ChatMode;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.util.Users;
-
-public class PartyChatCommand extends ChatCommand {
-    public PartyChatCommand() {
-        super(ChatMode.PARTY);
-    }
-
-    @Override
-    protected void handleChatSending(CommandSender sender, String[] args) {
-        if (sender instanceof Player) {
-            Player player = (Player) sender;
-            Party party = Users.getPlayer(player).getParty();
-
-            if (party == null) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Party.None"));
-                return;
-            }
-
-            ChatManager.handlePartyChat(mcMMO.p, party, player.getName(), player.getDisplayName(), buildChatMessage(args, 0));
-        }
-        else {
-            if (args.length < 2) {
-                sender.sendMessage(LocaleLoader.getString("Party.Specify"));
-                return;
-            }
-
-            Party party = PartyManager.getParty(args[0]);
-
-            if (party == null) {
-                sender.sendMessage(LocaleLoader.getString("Party.InvalidName"));
-                return;
-            }
-
-            ChatManager.handlePartyChat(mcMMO.p, party, LocaleLoader.getString("Commands.Chat.Console"), buildChatMessage(args, 1));
-        }
-    }
-}
+package com.gmail.nossr50.commands.chat;
+
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.chat.ChatManager;
+import com.gmail.nossr50.chat.ChatMode;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyChatCommand extends ChatCommand {
+    public PartyChatCommand() {
+        super(ChatMode.PARTY);
+    }
+
+    @Override
+    protected void handleChatSending(CommandSender sender, String[] args) {
+        if (sender instanceof Player) {
+            Player player = (Player) sender;
+            Party party = UserManager.getPlayer(player).getParty();
+
+            if (party == null) {
+                sender.sendMessage(LocaleLoader.getString("Commands.Party.None"));
+                return;
+            }
+
+            ChatManager.handlePartyChat(mcMMO.p, party, player.getName(), player.getDisplayName(), buildChatMessage(args, 0));
+        }
+        else {
+            if (args.length < 2) {
+                sender.sendMessage(LocaleLoader.getString("Party.Specify"));
+                return;
+            }
+
+            Party party = PartyManager.getParty(args[0]);
+
+            if (party == null) {
+                sender.sendMessage(LocaleLoader.getString("Party.InvalidName"));
+                return;
+            }
+
+            ChatManager.handlePartyChat(mcMMO.p, party, LocaleLoader.getString("Commands.Chat.Console"), buildChatMessage(args, 1));
+        }
+    }
+}

+ 45 - 0
src/main/java/com/gmail/nossr50/commands/database/McpurgeCommand.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.commands.database;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+
+public class McpurgeCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Permissions.mcpurge(sender)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        switch (args.length) {
+            case 0:
+                if (Config.getInstance().getUseMySQL()) {
+                    DatabaseManager.purgePowerlessSQL();
+
+                    if (Config.getInstance().getOldUsersCutoff() != -1) {
+                        DatabaseManager.purgeOldSQL();
+                    }
+                }
+                else {
+                    LeaderboardManager.purgePowerlessFlatfile();
+
+                    if (Config.getInstance().getOldUsersCutoff() != -1) {
+                        LeaderboardManager.purgeOldFlatfile();
+                    }
+                }
+
+                sender.sendMessage(LocaleLoader.getString("Commands.mcpurge.Success"));
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 51 - 0
src/main/java/com/gmail/nossr50/commands/database/McremoveCommand.java

@@ -0,0 +1,51 @@
+package com.gmail.nossr50.commands.database;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+
+public class McremoveCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Permissions.mcremove(sender)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        switch (args.length) {
+            case 1:
+                /* MySQL */
+                if (Config.getInstance().getUseMySQL()) {
+                    String tablePrefix = Config.getInstance().getMySQLTablePrefix();
+
+                    if (DatabaseManager.update("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.user = '" + args[0] + "'") != 0) {
+                        DatabaseManager.profileCleanup(args[0]);
+                        sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
+                    }
+                    else {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                    }
+                }
+                else {
+                    if (LeaderboardManager.removeFlatFileUser(args[0])) {
+                        DatabaseManager.profileCleanup(args[0]);
+                        sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
+                    }
+                    else {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                    }
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 51 - 51
src/main/java/com/gmail/nossr50/database/commands/MmoupdateCommand.java → src/main/java/com/gmail/nossr50/commands/database/MmoupdateCommand.java

@@ -1,51 +1,51 @@
-package com.gmail.nossr50.database.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.runnables.SQLConversionTask;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class MmoupdateCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (!Permissions.mmoupdate(sender)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return true;
-        }
-
-        switch (args.length) {
-        case 0:
-            sender.sendMessage(LocaleLoader.getString("Commands.mmoupdate.Start"));
-            Users.clearAll();
-            convertToMySQL();
-
-            for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                Users.addUser(player);
-            }
-
-            sender.sendMessage(LocaleLoader.getString("Commands.mmoupdate.Finish"));
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    /**
-     * Convert FlatFile data to MySQL data.
-     */
-    private void convertToMySQL() {
-        if (!Config.getInstance().getUseMySQL()) {
-            return;
-        }
-
-        mcMMO.p.getServer().getScheduler().runTaskLaterAsynchronously(mcMMO.p, new SQLConversionTask(), 1);
-    }
-}
+package com.gmail.nossr50.commands.database;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.runnables.database.SQLConversionTask;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class MmoupdateCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Permissions.mmoupdate(sender)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        switch (args.length) {
+            case 0:
+                sender.sendMessage(LocaleLoader.getString("Commands.mmoupdate.Start"));
+                UserManager.clearAll();
+                convertToMySQL();
+
+                for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
+                    UserManager.addUser(player);
+                }
+
+                sender.sendMessage(LocaleLoader.getString("Commands.mmoupdate.Finish"));
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Convert FlatFile data to MySQL data.
+     */
+    private void convertToMySQL() {
+        if (!Config.getInstance().getUseMySQL()) {
+            return;
+        }
+
+        mcMMO.p.getServer().getScheduler().runTaskLaterAsynchronously(mcMMO.p, new SQLConversionTask(), 1);
+    }
+}

+ 154 - 0
src/main/java/com/gmail/nossr50/commands/experience/AddlevelsCommand.java

@@ -0,0 +1,154 @@
+package com.gmail.nossr50.commands.experience;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class AddlevelsCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+        int levels;
+        boolean allSkills = false;
+        SkillType skill = null;
+
+        switch (args.length) {
+            case 2:
+                if (!Permissions.addlevels(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                if (args[0].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[0])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[1])) {
+                    return false;
+                }
+
+                levels = Integer.parseInt(args[1]);
+                profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (allSkills) {
+                    for (SkillType skillType : SkillType.values()) {
+                        if (skillType.isChildSkill()) {
+                            continue;
+                        }
+
+                        profile.addLevels(skillType, levels);
+                    }
+                }
+                else {
+                    skill = SkillType.getSkill(args[0]);
+                    profile.addLevels(skill, levels);
+                }
+
+                if (allSkills) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.1", levels));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.1", levels, SkillUtils.getSkillName(skill)));
+                }
+
+                return true;
+
+            case 3:
+                if (!Permissions.addlevelsOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (args[1].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[1])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[2])) {
+                    return false;
+                }
+
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+                levels = Integer.parseInt(args[2]);
+
+                // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            profile.addLevels(skillType, levels);
+                        }
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        profile.addLevels(skill, levels);
+                    }
+
+                    profile.save(); // Since this is a temporary profile, we save it here.
+                }
+                else {
+                    profile = mcMMOPlayer.getProfile();
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            profile.addLevels(skillType, levels);
+                        }
+
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.1", levels));
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        profile.addLevels(skill, levels);
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.1", levels, SkillUtils.getSkillName(skill)));
+                    }
+                }
+
+                if (allSkills) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.2", SkillUtils.getSkillName(skill), args[0]));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 152 - 0
src/main/java/com/gmail/nossr50/commands/experience/AddxpCommand.java

@@ -0,0 +1,152 @@
+package com.gmail.nossr50.commands.experience;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class AddxpCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        int xp;
+        McMMOPlayer mcMMOPlayer;
+        PlayerProfile profile;
+        boolean allSkills = false;
+        SkillType skill = null;
+
+        switch (args.length) {
+            case 2:
+                if (!Permissions.addxp(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                if (args[0].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[0])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[1])) {
+                    return false;
+                }
+
+                xp = Integer.parseInt(args[1]);
+                mcMMOPlayer = UserManager.getPlayer((Player) sender);
+                profile = mcMMOPlayer.getProfile();
+
+                if (allSkills) {
+                    for (SkillType skillType : SkillType.values()) {
+                        if (skillType.isChildSkill()) {
+                            continue;
+                        }
+
+                        mcMMOPlayer.applyXpGain(skillType, xp);
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.addxp.AwardAll", xp));
+                }
+                else {
+                    skill = SkillType.getSkill(args[0]);
+
+                    mcMMOPlayer.applyXpGain(skill, xp);
+                    sender.sendMessage(LocaleLoader.getString("Commands.addxp.AwardSkill", xp, SkillUtils.getSkillName(skill)));
+                }
+
+                return true;
+
+            case 3:
+                if (!Permissions.addxpOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (args[1].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[1])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[2])) {
+                    return false;
+                }
+
+                mcMMOPlayer = UserManager.getPlayer(args[0]);
+                xp = Integer.parseInt(args[2]);
+
+                // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    // TODO: Currently the offline player doesn't level up automatically
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            profile.setSkillXpLevel(skillType, xp);
+                        }
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        profile.setSkillXpLevel(skill, xp);
+                    }
+
+                    profile.save(); // Since this is a temporary profile, we save it here.
+                }
+                else {
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            mcMMOPlayer.applyXpGain(skillType, xp);
+                        }
+
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addxp.AwardAll", xp));
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        mcMMOPlayer.applyXpGain(skill, xp);
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.addxp.AwardSkill", xp, SkillUtils.getSkillName(skill)));
+                    }
+                }
+
+                if (allSkills) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardSkill.2", SkillUtils.getSkillName(skill), args[0]));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 150 - 0
src/main/java/com/gmail/nossr50/commands/experience/MmoeditCommand.java

@@ -0,0 +1,150 @@
+package com.gmail.nossr50.commands.experience;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class MmoeditCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+        int newValue;
+        boolean allSkills = false;
+        SkillType skill = null;
+
+        switch (args.length) {
+            case 2:
+                if (!Permissions.mmoedit(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                if (args[0].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[0])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[1])) {
+                    return false;
+                }
+
+                newValue = Integer.parseInt(args[1]);
+                profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (allSkills) {
+                    for (SkillType skillType : SkillType.values()) {
+                        if (skillType.isChildSkill()) {
+                            continue;
+                        }
+
+                        profile.modifySkill(skillType, newValue);
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.AllSkills.1", newValue));
+                }
+                else {
+                    skill = SkillType.getSkill(args[0]);
+                    profile.modifySkill(skill, newValue);
+                    sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.1", SkillUtils.getSkillName(skill), newValue));
+                }
+
+                return true;
+
+            case 3:
+                if (!Permissions.mmoeditOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (args[1].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[1])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!StringUtils.isInt(args[2])) {
+                    return false;
+                }
+
+                newValue = Integer.parseInt(args[2]);
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            profile.modifySkill(skillType, newValue);
+                        }
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        profile.modifySkill(skill, newValue);
+                    }
+
+                    profile.save(); // Since this is a temporary profile, we save it here.
+                }
+                else {
+                    profile = mcMMOPlayer.getProfile();
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            profile.modifySkill(skillType, newValue);
+                        }
+
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.mmoedit.AllSkills.1", newValue));
+                    }
+                    else {
+                        skill = SkillType.getSkill(args[1]);
+                        profile.modifySkill(skill, newValue);
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.1", SkillUtils.getSkillName(skill), newValue));
+                    }
+                }
+
+                if (allSkills) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.2", SkillUtils.getSkillName(skill), args[0]));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 169 - 0
src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java

@@ -0,0 +1,169 @@
+package com.gmail.nossr50.commands.experience;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class SkillresetCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        PlayerProfile profile;
+        boolean allSkills = false;
+        SkillType skill = null;
+        String skillName = "";
+
+        switch (args.length) {
+            case 1:
+                if (!Permissions.skillreset(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
+
+                if (args[0].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[0])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                profile = UserManager.getPlayer((Player) sender).getProfile();
+
+                if (allSkills) {
+                    for (SkillType skillType : SkillType.values()) {
+                        if (skillType.isChildSkill()) {
+                            continue;
+                        }
+
+                        if (!Permissions.skillreset(sender, skillType)) {
+                            sender.sendMessage(command.getPermissionMessage());
+                            continue;
+                        }
+
+                        profile.modifySkill(skillType, 0);
+                    }
+
+                    sender.sendMessage(LocaleLoader.getString("Commands.Reset.All"));
+                }
+                else {
+                    skill = SkillType.getSkill(args[0]);
+                    skillName = SkillUtils.getSkillName(skill);
+
+                    if (!Permissions.skillreset(sender, skill)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    profile.modifySkill(skill, 0);
+                    sender.sendMessage(LocaleLoader.getString("Commands.Reset.Single", skillName));
+                }
+
+                return true;
+
+            case 2:
+                if (!Permissions.skillresetOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (args[1].equalsIgnoreCase("all")) {
+                    allSkills = true;
+                }
+                else if (!SkillUtils.isSkill(args[1])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                    return true;
+                }
+
+                if (!allSkills) {
+                    skill = SkillType.getSkill(args[1]);
+                    skillName = SkillUtils.getSkillName(skill);
+
+                    if (!Permissions.skillresetOthers(sender, skill)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+                }
+
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false);
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            if (!Permissions.skillresetOthers(sender, skill)) {
+                                sender.sendMessage(command.getPermissionMessage());
+                                continue;
+                            }
+
+                            profile.modifySkill(skillType, 0);
+                        }
+                    }
+                    else {
+                        profile.modifySkill(skill, 0);
+                    }
+
+                    profile.save(); // Since this is a temporary profile, we save it here.
+                }
+                else {
+                    profile = mcMMOPlayer.getProfile();
+
+                    if (allSkills) {
+                        for (SkillType skillType : SkillType.values()) {
+                            if (skillType.isChildSkill()) {
+                                continue;
+                            }
+
+                            if (!Permissions.skillresetOthers(sender, skillType)) {
+                                sender.sendMessage(command.getPermissionMessage());
+                                continue;
+                            }
+
+                            profile.modifySkill(skillType, 0);
+                        }
+
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.Reset.All"));
+                    }
+                    else {
+                        profile.modifySkill(skill, 0);
+                        mcMMOPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.Reset.Single", skillName));
+                    }
+                }
+
+                if (allSkills) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.addlevels.AwardAll.2", args[0]));
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.mmoedit.Modified.2", skillName, args[0]));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 85 - 0
src/main/java/com/gmail/nossr50/commands/hardcore/HardcoreCommand.java

@@ -0,0 +1,85 @@
+package com.gmail.nossr50.commands.hardcore;
+
+import java.text.DecimalFormat;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+
+public class HardcoreCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 0:
+                if (!Permissions.hardcoreToggle(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (Config.getInstance().getHardcoreEnabled()) {
+                    disableHardcore();
+                }
+                else {
+                    enableHardcore();
+                }
+
+                return true;
+
+            case 1:
+                if (args[0].equalsIgnoreCase("on") || args[0].equalsIgnoreCase("true") || args[0].equalsIgnoreCase("enabled")) {
+                    if (!Permissions.hardcoreToggle(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    enableHardcore();
+                    return true;
+                }
+
+                if (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("false") || args[0].equalsIgnoreCase("disabled")) {
+                    if (!Permissions.hardcoreToggle(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    disableHardcore();
+                    return true;
+                }
+
+                if (!StringUtils.isDouble(args[0])) {
+                    return false;
+                }
+
+                if (!Permissions.hardcoreModify(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                DecimalFormat percent = new DecimalFormat("##0.00%");
+                double newPercent = Double.parseDouble(args[0]);
+
+                Config.getInstance().setHardcoreDeathStatPenaltyPercentage(newPercent);
+                sender.sendMessage(LocaleLoader.getString("Hardcore.PercentageChanged", percent.format(newPercent / 100D)));
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    private void disableHardcore() {
+        Config.getInstance().setHardcoreEnabled(false);
+        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Hardcore.Disabled"));
+    }
+
+    private void enableHardcore() {
+        Config.getInstance().setHardcoreEnabled(true);
+        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Hardcore.Enabled"));
+    }
+}

+ 90 - 0
src/main/java/com/gmail/nossr50/commands/hardcore/VampirismCommand.java

@@ -0,0 +1,90 @@
+package com.gmail.nossr50.commands.hardcore;
+
+import java.text.DecimalFormat;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+
+public class VampirismCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Config.getInstance().getHardcoreEnabled()) {
+            sender.sendMessage(LocaleLoader.getString("Hardcore.Disabled"));
+            return true;
+        }
+
+        switch (args.length) {
+            case 0:
+                if (!Permissions.vampirismToggle(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                if (Config.getInstance().getHardcoreVampirismEnabled()) {
+                    disableVampirism();
+                }
+                else {
+                    enableVampirism();
+                }
+
+                return true;
+
+            case 1:
+                if (args[0].equalsIgnoreCase("on") || args[0].equalsIgnoreCase("true") || args[0].equalsIgnoreCase("enabled")) {
+                    if (!Permissions.vampirismToggle(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    enableVampirism();
+                    return true;
+                }
+
+                if (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("false") || args[0].equalsIgnoreCase("disabled")) {
+                    if (!Permissions.vampirismToggle(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    disableVampirism();
+                    return true;
+                }
+
+                if (!StringUtils.isDouble(args[0])) {
+                    return false;
+                }
+
+                if (!Permissions.vampirismModify(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
+
+                DecimalFormat percent = new DecimalFormat("##0.00%");
+                double newPercent = Double.parseDouble(args[0]);
+
+                Config.getInstance().setHardcoreVampirismStatLeechPercentage(newPercent);
+                sender.sendMessage(LocaleLoader.getString("Vampirism.PercentageChanged", percent.format(newPercent / 100D)));
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    private void disableVampirism() {
+        Config.getInstance().setHardcoreVampirismEnabled(false);
+        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Vampirism.Disabled"));
+    }
+
+    private void enableVampirism() {
+        Config.getInstance().setHardcoreVampirismEnabled(true);
+        mcMMO.p.getServer().broadcastMessage(LocaleLoader.getString("Vampirism.Enabled"));
+    }
+}

+ 42 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java

@@ -0,0 +1,42 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyAcceptCommand implements CommandExecutor {
+    private McMMOPlayer mcMMOPlayer;
+    private Player player;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 1:
+                player = (Player) sender;
+                mcMMOPlayer = UserManager.getPlayer(player);
+
+                if (!mcMMOPlayer.hasPartyInvite()) {
+                    sender.sendMessage(LocaleLoader.getString("mcMMO.NoInvites"));
+                    return true;
+                }
+
+                // Changing parties
+                if (!PartyManager.changeOrJoinParty(mcMMOPlayer, player, mcMMOPlayer.getParty(), mcMMOPlayer.getPartyInvite().getName())) {
+                    return true;
+                }
+
+                PartyManager.joinInvitedParty(player, mcMMOPlayer);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "accept"));
+                return true;
+        }
+    }
+}

+ 34 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java

@@ -0,0 +1,34 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyChangeOwnerCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 2:
+                Party playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+                if (!playerParty.getMembers().contains(mcMMO.p.getServer().getOfflinePlayer(args[1]))) {
+                    sender.sendMessage(LocaleLoader.getString("Party.NotInYourParty", args[1]));
+                    return true;
+                }
+
+                PartyManager.setPartyLeader(args[1], playerParty);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "owner", "<" + LocaleLoader.getString("Commands.Usage.Player") + ">"));
+                return true;
+        }
+    }
+}

+ 49 - 50
src/main/java/com/gmail/nossr50/party/commands/PartyChangePasswordCommand.java → src/main/java/com/gmail/nossr50/commands/party/PartyChangePasswordCommand.java

@@ -1,50 +1,49 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.util.Users;
-
-public class PartyChangePasswordCommand implements CommandExecutor {
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        Party playerParty = Users.getPlayer((Player) sender).getParty();
-
-        switch (args.length) {
-        case 1:
-            unprotectParty(sender, playerParty);
-            return true;
-
-        case 2:
-            if (args[1].equalsIgnoreCase("clear") || args[1].equalsIgnoreCase("reset")) {
-                unprotectParty(sender, playerParty);
-                return true;
-            }
-
-            protectParty(sender, playerParty, args[1]);
-            return true;
-
-        default:
-            sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "password", "[clear|reset]"));
-            sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "password", "<" + LocaleLoader.getString("Commands.Usage.Password") + ">"));
-            return true;
-        }
-    }
-
-    private void unprotectParty(CommandSender sender, Party playerParty) {
-        playerParty.setLocked(true);
-        playerParty.setPassword(null);
-        sender.sendMessage(LocaleLoader.getString("Party.Password.Removed"));
-    }
-
-    private void protectParty(CommandSender sender, Party playerParty, String password) {
-        playerParty.setLocked(true);
-        playerParty.setPassword(password);
-        sender.sendMessage(LocaleLoader.getString("Party.Password.Set", password));
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyChangePasswordCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        Party playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+        switch (args.length) {
+            case 1:
+                unprotectParty(sender, playerParty);
+                return true;
+
+            case 2:
+                if (args[1].equalsIgnoreCase("clear") || args[1].equalsIgnoreCase("reset")) {
+                    unprotectParty(sender, playerParty);
+                    return true;
+                }
+
+                protectParty(sender, playerParty, args[1]);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "password", "[clear|reset]"));
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "password", "<" + LocaleLoader.getString("Commands.Usage.Password") + ">"));
+                return true;
+        }
+    }
+
+    private void unprotectParty(CommandSender sender, Party playerParty) {
+        playerParty.setLocked(true);
+        playerParty.setPassword(null);
+        sender.sendMessage(LocaleLoader.getString("Party.Password.Removed"));
+    }
+
+    private void protectParty(CommandSender sender, Party playerParty, String password) {
+        playerParty.setLocked(true);
+        playerParty.setPassword(password);
+        sender.sendMessage(LocaleLoader.getString("Party.Password.Set", password));
+    }
+}

+ 154 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyCommand.java

@@ -0,0 +1,154 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.commands.chat.PartyChatCommand;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyCommand implements CommandExecutor {
+    private McMMOPlayer mcMMOPlayer;
+    private Player player;
+
+    private CommandExecutor partyJoinCommand           = new PartyJoinCommand();
+    private CommandExecutor partyAcceptCommand         = new PartyAcceptCommand();
+    private CommandExecutor partyCreateCommand         = new PartyCreateCommand();
+    private CommandExecutor partyQuitCommand           = new PartyQuitCommand();
+    private CommandExecutor partyExpShareCommand       = new PartyExpShareCommand();
+    private CommandExecutor partyItemShareCommand      = new PartyItemShareCommand();
+    private CommandExecutor partyInviteCommand         = new PartyInviteCommand();
+    private CommandExecutor partyKickCommand           = new PartyKickCommand();
+    private CommandExecutor partyDisbandCommand        = new PartyDisbandCommand();
+    private CommandExecutor partyChangeOwnerCommand    = new PartyChangeOwnerCommand();
+    private CommandExecutor partyLockCommand           = new PartyLockCommand();
+    private CommandExecutor partyChangePasswordCommand = new PartyChangePasswordCommand();
+    private CommandExecutor partyRenameCommand         = new PartyRenameCommand();
+    private CommandExecutor partyInfoCommand           = new PartyInfoCommand();
+    private CommandExecutor partyHelpCommand           = new PartyHelpCommand();
+    private CommandExecutor partyTeleportCommand       = new PtpCommand();
+    private CommandExecutor partyChatCommand           = new PartyChatCommand();
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (CommandUtils.noConsoleUsage(sender)) {
+            return true;
+        }
+
+        if (!Permissions.party(sender)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        player = (Player) sender;
+        mcMMOPlayer = UserManager.getPlayer(player);
+
+        if (args.length < 1) {
+            if (!mcMMOPlayer.inParty()) {
+                sender.sendMessage(LocaleLoader.getString("Commands.Party.None"));
+                return printUsage();
+            }
+
+            return partyInfoCommand.onCommand(sender, command, label, args);
+        }
+
+        PartySubcommandType subcommand = PartySubcommandType.getSubcommand(args[0]);
+
+        if (subcommand == null) {
+            return printUsage();
+        }
+
+        // Can't use this for lock/unlock since they're handled by the same command
+        if (subcommand != PartySubcommandType.LOCK && subcommand != PartySubcommandType.UNLOCK && !Permissions.partySubcommand(sender, subcommand)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        switch (subcommand) {
+            case JOIN:
+                return partyJoinCommand.onCommand(sender, command, label, args);
+            case ACCEPT:
+                return partyAcceptCommand.onCommand(sender, command, label, args);
+            case CREATE:
+                return partyCreateCommand.onCommand(sender, command, label, args);
+            case HELP:
+                return partyHelpCommand.onCommand(sender, command, label, args);
+            default:
+                break;
+        }
+
+        // Party member commands
+        if (!mcMMOPlayer.inParty()) {
+            sender.sendMessage(LocaleLoader.getString("Commands.Party.None"));
+            return printUsage();
+        }
+
+        switch (subcommand) {
+            case INFO:
+                return partyInfoCommand.onCommand(sender, command, label, args);
+            case QUIT:
+                return partyQuitCommand.onCommand(sender, command, label, args);
+            case INVITE:
+                return partyInviteCommand.onCommand(sender, command, label, args);
+            case TELEPORT:
+                return partyTeleportCommand.onCommand(sender, command, label, extractArgs(args));
+            case CHAT:
+                return partyChatCommand.onCommand(sender, command, label, extractArgs(args));
+            default:
+                break;
+        }
+
+        // Party leader commands
+        if (!mcMMOPlayer.getParty().getLeader().equals(player.getName())) {
+            sender.sendMessage(LocaleLoader.getString("Party.NotOwner"));
+            return true;
+        }
+
+        switch (subcommand) {
+            case EXPSHARE:
+                return partyExpShareCommand.onCommand(sender, command, label, args);
+            case ITEMSHARE:
+                return partyItemShareCommand.onCommand(sender, command, label, args);
+            case KICK:
+                return partyKickCommand.onCommand(sender, command, label, args);
+            case DISBAND:
+                return partyDisbandCommand.onCommand(sender, command, label, args);
+            case OWNER:
+                return partyChangeOwnerCommand.onCommand(sender, command, label, args);
+            case LOCK:
+                // Fallthrough
+            case UNLOCK:
+                return partyLockCommand.onCommand(sender, command, label, args);
+            case PASSWORD:
+                return partyChangePasswordCommand.onCommand(sender, command, label, args);
+            case RENAME:
+                return partyRenameCommand.onCommand(sender, command, label, args);
+            default:
+                break;
+        }
+
+        return true;
+    }
+
+    private boolean printUsage() {
+        player.sendMessage(LocaleLoader.getString("Party.Help.0", "/party join"));
+        player.sendMessage(LocaleLoader.getString("Party.Help.1", "/party create"));
+        player.sendMessage(LocaleLoader.getString("Party.Help.2", "/party ?"));
+        return true;
+    }
+
+    private String[] extractArgs(String[] args) {
+        String[] newArgs = new String[args.length - 1];
+
+        for (int i = 1; i < args.length; i++) {
+            newArgs[i - 1] = args[1];
+        }
+
+        return newArgs;
+    }
+}

+ 56 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java

@@ -0,0 +1,56 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyCreateCommand implements CommandExecutor {
+    private McMMOPlayer mcMMOPlayer;
+    private Player player;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 2:
+                // Fallthrough
+            case 3:
+                Party newParty = PartyManager.getParty(args[1]);
+
+                // Check to see if the party exists, and if it does cancel creating a new party
+                if (newParty != null) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Party.AlreadyExists", args[1]));
+                    return true;
+                }
+
+                player = (Player) sender;
+                mcMMOPlayer = UserManager.getPlayer(player);
+
+                // Changing parties
+                if (!PartyManager.changeOrJoinParty(mcMMOPlayer, player, mcMMOPlayer.getParty(), args[1])) {
+                    return true;
+                }
+
+                PartyManager.createParty(player, mcMMOPlayer, args[1], getPassword(args));
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.3", "party", "create", "<" + LocaleLoader.getString("Commands.Usage.PartyName") + ">", "[" + LocaleLoader.getString("Commands.Usage.Password") + "]"));
+                return true;
+        }
+    }
+
+    private String getPassword(String[] args) {
+        if (args.length == 3) {
+            return args[2];
+        }
+
+        return null;
+    }
+}

+ 37 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java

@@ -0,0 +1,37 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+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.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyDisbandCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 1:
+                Party playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+                for (Player member : playerParty.getOnlineMembers()) {
+                    if (!PartyManager.handlePartyChangeEvent(member, playerParty.getName(), null, EventReason.KICKED_FROM_PARTY)) {
+                        return true;
+                    }
+
+                    member.sendMessage(LocaleLoader.getString("Party.Disband"));
+                }
+
+                PartyManager.disbandParty(playerParty);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "disband"));
+                return true;
+        }
+    }
+}

+ 55 - 55
src/main/java/com/gmail/nossr50/party/commands/PartyExpShareCommand.java → src/main/java/com/gmail/nossr50/commands/party/PartyExpShareCommand.java

@@ -1,55 +1,55 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.party.ShareHandler;
-import com.gmail.nossr50.party.ShareHandler.ShareMode;
-import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.Users;
-
-public class PartyExpShareCommand implements CommandExecutor {
-    private Party playerParty;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (!Config.getInstance().getExpShareEnabled()) {
-            sender.sendMessage(LocaleLoader.getString("Party.ExpShare.Disabled"));
-            return true;
-        }
-
-        switch (args.length) {
-        case 2:
-            playerParty = Users.getPlayer((Player) sender).getParty();
-
-            if (args[1].equalsIgnoreCase("none") || args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) {
-                handleChangingShareMode(ShareMode.NONE);
-            }
-            else if (args[1].equalsIgnoreCase("equal") || args[1].equalsIgnoreCase("even") || args[1].equalsIgnoreCase("on") || args[1].equalsIgnoreCase("true")) {
-                handleChangingShareMode(ShareMode.EQUAL);
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "expshare", "[NONE | EQUAL]"));
-            }
-
-            return true;
-
-        default:
-            sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "expshare", "<NONE | EQUAL>"));
-            return true;
-        }
-    }
-
-    private void handleChangingShareMode(ShareHandler.ShareMode mode) {
-        playerParty.setXpShareMode(mode);
-
-        for (Player member : playerParty.getOnlineMembers()) {
-            member.sendMessage(LocaleLoader.getString("Commands.Party.SetSharing", LocaleLoader.getString("Party.ShareType.Exp"), LocaleLoader.getString("Party.ShareMode." + StringUtils.getCapitalized(mode.toString()))));
-        }
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.ShareHandler;
+import com.gmail.nossr50.party.ShareHandler.ShareMode;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyExpShareCommand implements CommandExecutor {
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Config.getInstance().getExpShareEnabled()) {
+            sender.sendMessage(LocaleLoader.getString("Party.ExpShare.Disabled"));
+            return true;
+        }
+
+        switch (args.length) {
+            case 2:
+                playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+                if (args[1].equalsIgnoreCase("none") || args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) {
+                    handleChangingShareMode(ShareMode.NONE);
+                }
+                else if (args[1].equalsIgnoreCase("equal") || args[1].equalsIgnoreCase("even") || args[1].equalsIgnoreCase("on") || args[1].equalsIgnoreCase("true")) {
+                    handleChangingShareMode(ShareMode.EQUAL);
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "expshare", "[NONE | EQUAL]"));
+                }
+
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "expshare", "<NONE | EQUAL>"));
+                return true;
+        }
+    }
+
+    private void handleChangingShareMode(ShareHandler.ShareMode mode) {
+        playerParty.setXpShareMode(mode);
+
+        for (Player member : playerParty.getOnlineMembers()) {
+            member.sendMessage(LocaleLoader.getString("Commands.Party.SetSharing", LocaleLoader.getString("Party.ShareType.Exp"), LocaleLoader.getString("Party.ShareMode." + StringUtils.getCapitalized(mode.toString()))));
+        }
+    }
+}

+ 30 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyHelpCommand.java

@@ -0,0 +1,30 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public class PartyHelpCommand implements CommandExecutor {
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 1:
+                sender.sendMessage(LocaleLoader.getString("Party.Help.3", "/party join", "/party quit"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.1", "/party create"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.4", "/party <lock|unlock>"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.5", "/party password"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.6", "/party kick"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.7", "/party leader"));
+                sender.sendMessage(LocaleLoader.getString("Party.Help.8", "/party disband"));
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "help"));
+                return true;
+        }
+    }
+
+}

+ 102 - 102
src/main/java/com/gmail/nossr50/party/commands/PartyInfoCommand.java → src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java

@@ -1,102 +1,102 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.ChatColor;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.party.ShareHandler;
-import com.gmail.nossr50.util.Users;
-
-public class PartyInfoCommand implements CommandExecutor {
-    private Player player;
-    private Party playerParty;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        player = (Player) sender;
-        McMMOPlayer mcMMOPlayer = Users.getPlayer(player);
-        playerParty = mcMMOPlayer.getParty();
-
-        displayPartyHeader();
-        displayShareModeInfo();
-        displayMemberInfo();
-        return true;
-    }
-
-    private String createMembersList() {
-        StringBuilder memberList = new StringBuilder();
-
-        for (OfflinePlayer member : playerParty.getMembers()) {
-            if (playerParty.getLeader().equals(member.getName())) {
-                memberList.append(ChatColor.GOLD).append(member.getName()).append(" ");
-            }
-            else if (member.isOnline()) {
-                memberList.append(ChatColor.WHITE).append(member.getName()).append(" ");
-            }
-            else {
-                memberList.append(ChatColor.GRAY).append(member.getName()).append(" ");
-            }
-        }
-
-        return memberList.toString();
-    }
-
-    private void displayShareModeInfo() {
-        boolean xpShareEnabled = Config.getInstance().getExpShareEnabled();
-        boolean itemShareEnabled = Config.getInstance().getItemShareEnabled();
-        boolean itemSharingActive = playerParty.getItemShareMode() != ShareHandler.ShareMode.NONE;
-
-        if (!xpShareEnabled && !itemShareEnabled) {
-            return;
-        }
-
-        String expShareInfo = "";
-        String itemShareInfo = "";
-        String separator = "";
-
-        if (xpShareEnabled) {
-            expShareInfo = LocaleLoader.getString("Commands.Party.ExpShare", playerParty.getXpShareMode().toString());
-        }
-
-        if (itemShareEnabled) {
-            itemShareInfo = LocaleLoader.getString("Commands.Party.ItemShare", playerParty.getItemShareMode().toString());
-        }
-
-        if (xpShareEnabled && itemShareEnabled) {
-            separator = ChatColor.DARK_GRAY + " || ";
-        }
-
-        player.sendMessage(LocaleLoader.getString("Commands.Party.ShareMode") + expShareInfo + separator + itemShareInfo);
-        if (itemSharingActive) {
-            player.sendMessage(LocaleLoader.getString("Commands.Party.ItemShareCategories", playerParty.getItemShareCategories()));
-        }
-    }
-
-    private void displayPartyHeader() {
-        player.sendMessage(LocaleLoader.getString("Commands.Party.Header"));
-
-        if (playerParty.isLocked()) {
-            player.sendMessage(LocaleLoader.getString("Commands.Party.Status", playerParty.getName(), LocaleLoader.getString("Party.Status.Locked")));
-        }
-        else {
-            player.sendMessage(LocaleLoader.getString("Commands.Party.Status", playerParty.getName(), LocaleLoader.getString("Party.Status.Unlocked")));
-        }
-    }
-
-    private void displayMemberInfo() {
-        int membersNear = PartyManager.getNearMembers(player, playerParty, Config.getInstance().getPartyShareRange()).size();
-        int membersOnline = playerParty.getOnlineMembers().size() - 1;
-
-        player.sendMessage(LocaleLoader.getString("Commands.Party.Members.Header"));
-        player.sendMessage(LocaleLoader.getString("Commands.Party.MembersNear", membersNear, membersOnline));
-        player.sendMessage(LocaleLoader.getString("Commands.Party.Members", createMembersList()));
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.ChatColor;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.party.ShareHandler;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyInfoCommand implements CommandExecutor {
+    private Player player;
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        player = (Player) sender;
+        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+        playerParty = mcMMOPlayer.getParty();
+
+        displayPartyHeader();
+        displayShareModeInfo();
+        displayMemberInfo();
+        return true;
+    }
+
+    private String createMembersList() {
+        StringBuilder memberList = new StringBuilder();
+
+        for (OfflinePlayer member : playerParty.getMembers()) {
+            if (playerParty.getLeader().equals(member.getName())) {
+                memberList.append(ChatColor.GOLD).append(member.getName()).append(" ");
+            }
+            else if (member.isOnline()) {
+                memberList.append(ChatColor.WHITE).append(member.getName()).append(" ");
+            }
+            else {
+                memberList.append(ChatColor.GRAY).append(member.getName()).append(" ");
+            }
+        }
+
+        return memberList.toString();
+    }
+
+    private void displayShareModeInfo() {
+        boolean xpShareEnabled = Config.getInstance().getExpShareEnabled();
+        boolean itemShareEnabled = Config.getInstance().getItemShareEnabled();
+        boolean itemSharingActive = playerParty.getItemShareMode() != ShareHandler.ShareMode.NONE;
+
+        if (!xpShareEnabled && !itemShareEnabled) {
+            return;
+        }
+
+        String expShareInfo = "";
+        String itemShareInfo = "";
+        String separator = "";
+
+        if (xpShareEnabled) {
+            expShareInfo = LocaleLoader.getString("Commands.Party.ExpShare", playerParty.getXpShareMode().toString());
+        }
+
+        if (itemShareEnabled) {
+            itemShareInfo = LocaleLoader.getString("Commands.Party.ItemShare", playerParty.getItemShareMode().toString());
+        }
+
+        if (xpShareEnabled && itemShareEnabled) {
+            separator = ChatColor.DARK_GRAY + " || ";
+        }
+
+        player.sendMessage(LocaleLoader.getString("Commands.Party.ShareMode") + expShareInfo + separator + itemShareInfo);
+        if (itemSharingActive) {
+            player.sendMessage(LocaleLoader.getString("Commands.Party.ItemShareCategories", playerParty.getItemShareCategories()));
+        }
+    }
+
+    private void displayPartyHeader() {
+        player.sendMessage(LocaleLoader.getString("Commands.Party.Header"));
+
+        if (playerParty.isLocked()) {
+            player.sendMessage(LocaleLoader.getString("Commands.Party.Status", playerParty.getName(), LocaleLoader.getString("Party.Status.Locked")));
+        }
+        else {
+            player.sendMessage(LocaleLoader.getString("Commands.Party.Status", playerParty.getName(), LocaleLoader.getString("Party.Status.Unlocked")));
+        }
+    }
+
+    private void displayMemberInfo() {
+        int membersNear = PartyManager.getNearMembers(player, playerParty, Config.getInstance().getPartyShareRange()).size();
+        int membersOnline = playerParty.getOnlineMembers().size() - 1;
+
+        player.sendMessage(LocaleLoader.getString("Commands.Party.Members.Header"));
+        player.sendMessage(LocaleLoader.getString("Commands.Party.MembersNear", membersNear, membersOnline));
+        player.sendMessage(LocaleLoader.getString("Commands.Party.Members", createMembersList()));
+    }
+}

+ 72 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java

@@ -0,0 +1,72 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyInviteCommand implements CommandExecutor {
+    private McMMOPlayer mcMMOTarget;
+    private Player target;
+
+    private McMMOPlayer mcMMOPlayer;
+    private Player player;
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 2:
+                if (!mcMMO.p.getServer().getOfflinePlayer(args[1]).isOnline()) {
+                    sender.sendMessage(LocaleLoader.getString("Party.NotOnline", args[1]));
+                    return true;
+                }
+
+                mcMMOTarget = UserManager.getPlayer(args[1]);
+
+                if (mcMMOTarget == null) {
+                    sender.sendMessage(LocaleLoader.getString("Party.Player.Invalid"));
+                    return true;
+                }
+
+                target = mcMMOTarget.getPlayer();
+                mcMMOPlayer = UserManager.getPlayer((Player) sender);
+                player = mcMMOPlayer.getPlayer();
+
+                if (player.equals(target)) {
+                    sender.sendMessage(LocaleLoader.getString("Party.Invite.Self"));
+                    return true;
+                }
+
+                if (PartyManager.inSameParty(player, target)) {
+                    sender.sendMessage(LocaleLoader.getString("Party.Player.InSameParty", target.getName()));
+                    return true;
+                }
+
+                playerParty = mcMMOPlayer.getParty();
+
+                if (!PartyManager.canInvite(player, playerParty)) {
+                    player.sendMessage(LocaleLoader.getString("Party.Locked"));
+                    return true;
+                }
+
+                mcMMOTarget.setPartyInvite(playerParty);
+
+                sender.sendMessage(LocaleLoader.getString("Commands.Invite.Success"));
+                target.sendMessage(LocaleLoader.getString("Commands.Party.Invite.0", playerParty.getName(), player.getName()));
+                target.sendMessage(LocaleLoader.getString("Commands.Party.Invite.1"));
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "invite", "<" + LocaleLoader.getString("Commands.Usage.Player") + ">"));
+                return true;
+        }
+    }
+}

+ 101 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyItemShareCommand.java

@@ -0,0 +1,101 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.ShareHandler;
+import com.gmail.nossr50.party.ShareHandler.ShareMode;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyItemShareCommand implements CommandExecutor {
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (!Config.getInstance().getItemShareEnabled()) {
+            sender.sendMessage(LocaleLoader.getString("Party.ItemShare.Disabled"));
+            return true;
+        }
+
+        switch (args.length) {
+            case 2:
+                playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+                if (args[1].equalsIgnoreCase("none") || args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) {
+                    handleChangingShareMode(ShareMode.NONE);
+                }
+                else if (args[1].equalsIgnoreCase("equal") || args[1].equalsIgnoreCase("even")) {
+                    handleChangingShareMode(ShareMode.EQUAL);
+                }
+                else if (args[1].equalsIgnoreCase("random")) {
+                    handleChangingShareMode(ShareMode.RANDOM);
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "itemshare", "<NONE | EQUAL | RANDOM>"));
+                }
+
+                return true;
+
+            case 3:
+                playerParty = UserManager.getPlayer((Player) sender).getParty();
+                boolean toggle = false;
+
+                if (args[2].equalsIgnoreCase("true") || args[2].equalsIgnoreCase("on") || args[2].equalsIgnoreCase("enabled")) {
+                    toggle = true;
+                }
+                else if (args[2].equalsIgnoreCase("false") || args[2].equalsIgnoreCase("off") || args[2].equalsIgnoreCase("disabled")) {
+                    toggle = false;
+                }
+
+                if (args[1].equalsIgnoreCase("loot")) {
+                    playerParty.setSharingLootDrops(toggle);
+                }
+                else if (args[1].equalsIgnoreCase("mining")) {
+                    playerParty.setSharingMiningDrops(toggle);
+                }
+                else if (args[1].equalsIgnoreCase("herbalism")) {
+                    playerParty.setSharingHerbalismDrops(toggle);
+                }
+                else if (args[1].equalsIgnoreCase("woodcutting")) {
+                    playerParty.setSharingWoodcuttingDrops(toggle);
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "itemshare", "<loot | mining | herbalism | woodcutting> <true | false>"));
+                }
+
+                notifyToggleItemShareCategory(args, toggle);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "itemshare", "<NONE | EQUAL | RANDOM>"));
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "itemshare", "<loot | mining | herbalism | woodcutting> <true | false>"));
+                return true;
+        }
+    }
+
+    private void handleChangingShareMode(ShareHandler.ShareMode mode) {
+        playerParty.setItemShareMode(mode);
+
+        for (Player member : playerParty.getOnlineMembers()) {
+            member.sendMessage(LocaleLoader.getString("Commands.Party.SetSharing", LocaleLoader.getString("Party.ShareType.Item"), LocaleLoader.getString("Party.ShareMode." + StringUtils.getCapitalized(mode.toString()))));
+        }
+    }
+
+    private void notifyToggleItemShareCategory(String[] args, boolean toggle) {
+        String state = "disabled";
+
+        if (toggle) {
+            state = "enabled";
+        }
+
+        for (Player member : playerParty.getOnlineMembers()) {
+            member.sendMessage(LocaleLoader.getString("Commands.Party.ToggleShareCategory", StringUtils.getCapitalized(args[1]), state));
+        }
+    }
+}

+ 96 - 95
src/main/java/com/gmail/nossr50/party/commands/PartyJoinCommand.java → src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java

@@ -1,95 +1,96 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.util.Users;
-
-public class PartyJoinCommand implements CommandExecutor {
-    private McMMOPlayer mcMMOTarget;
-    private Player target;
-    private Party targetParty;
-
-    private McMMOPlayer mcMMOPlayer;
-    private Player player;
-    private Party playerParty;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        switch (args.length) {
-        case 2:
-        case 3:
-            // Verify target exists and is in a different party than the player
-            if (!canJoinParty(sender, args[1])) {
-                return true;
-            }
-
-            String password = getPassword(args);
-
-            // Make sure party passwords match
-            if (!PartyManager.checkPartyPassword(player, targetParty, password)) {
-                return true;
-            }
-
-            // Changing parties
-            if (!PartyManager.changeOrJoinParty(mcMMOPlayer, player, playerParty, targetParty.getName())) {
-                return true;
-            }
-
-            PartyManager.joinParty(player, mcMMOPlayer, targetParty, password);
-            return true;
-
-        default:
-            sender.sendMessage(LocaleLoader.getString("Commands.Usage.3", "party", "join", "<" + LocaleLoader.getString("Commands.Usage.Player") + ">", "[" + LocaleLoader.getString("Commands.Usage.Password") + "]"));
-            return true;
-        }
-    }
-
-    private String getPassword(String[] args) {
-        if (args.length == 3) {
-            return args[2];
-        }
-
-        return null;
-    }
-
-    private boolean canJoinParty(CommandSender sender, String targetName) {
-        if (!mcMMO.p.getServer().getOfflinePlayer(targetName).isOnline()) {
-            sender.sendMessage(LocaleLoader.getString("Party.NotOnline", targetName));
-            return false;
-        }
-
-        mcMMOTarget = Users.getPlayer(targetName);
-
-        if (mcMMOTarget == null) {
-            sender.sendMessage(LocaleLoader.getString("Party.Player.Invalid"));
-            return false;
-        }
-
-        target = mcMMOTarget.getPlayer();
-
-        if (!mcMMOTarget.inParty()) {
-            sender.sendMessage(LocaleLoader.getString("Party.PlayerNotInParty", targetName));
-            return false;
-        }
-
-        player = (Player) sender;
-        mcMMOPlayer = Users.getPlayer(player);
-        playerParty = mcMMOPlayer.getParty();
-        targetParty = mcMMOTarget.getParty();
-
-        if (player.equals(target) || (mcMMOPlayer.inParty() && playerParty.equals(targetParty))) {
-            sender.sendMessage(LocaleLoader.getString("Party.Join.Self"));
-            return false;
-        }
-
-        return true;
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyJoinCommand implements CommandExecutor {
+    private McMMOPlayer mcMMOTarget;
+    private Player target;
+    private Party targetParty;
+
+    private McMMOPlayer mcMMOPlayer;
+    private Player player;
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 2:
+                // Fallthrough
+            case 3:
+                // Verify target exists and is in a different party than the player
+                if (!canJoinParty(sender, args[1])) {
+                    return true;
+                }
+
+                String password = getPassword(args);
+
+                // Make sure party passwords match
+                if (!PartyManager.checkPartyPassword(player, targetParty, password)) {
+                    return true;
+                }
+
+                // Changing parties
+                if (!PartyManager.changeOrJoinParty(mcMMOPlayer, player, playerParty, targetParty.getName())) {
+                    return true;
+                }
+
+                PartyManager.joinParty(player, mcMMOPlayer, targetParty, password);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.3", "party", "join", "<" + LocaleLoader.getString("Commands.Usage.Player") + ">", "[" + LocaleLoader.getString("Commands.Usage.Password") + "]"));
+                return true;
+        }
+    }
+
+    private String getPassword(String[] args) {
+        if (args.length == 3) {
+            return args[2];
+        }
+
+        return null;
+    }
+
+    private boolean canJoinParty(CommandSender sender, String targetName) {
+        if (!mcMMO.p.getServer().getOfflinePlayer(targetName).isOnline()) {
+            sender.sendMessage(LocaleLoader.getString("Party.NotOnline", targetName));
+            return false;
+        }
+
+        mcMMOTarget = UserManager.getPlayer(targetName);
+
+        if (mcMMOTarget == null) {
+            sender.sendMessage(LocaleLoader.getString("Party.Player.Invalid"));
+            return false;
+        }
+
+        target = mcMMOTarget.getPlayer();
+
+        if (!mcMMOTarget.inParty()) {
+            sender.sendMessage(LocaleLoader.getString("Party.PlayerNotInParty", targetName));
+            return false;
+        }
+
+        player = (Player) sender;
+        mcMMOPlayer = UserManager.getPlayer(player);
+        playerParty = mcMMOPlayer.getParty();
+        targetParty = mcMMOTarget.getParty();
+
+        if (player.equals(target) || (mcMMOPlayer.inParty() && playerParty.equals(targetParty))) {
+            sender.sendMessage(LocaleLoader.getString("Party.Join.Self"));
+            return false;
+        }
+
+        return true;
+    }
+}

+ 49 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java

@@ -0,0 +1,49 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+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.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyKickCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 2:
+                Party playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+                OfflinePlayer target = mcMMO.p.getServer().getOfflinePlayer(args[1]);
+
+                if (!playerParty.getMembers().contains(target)) {
+                    sender.sendMessage(LocaleLoader.getString("Party.NotInYourParty", args[1]));
+                    return true;
+                }
+
+                if (target.isOnline()) {
+                    Player onlineTarget = target.getPlayer();
+                    String partyName = playerParty.getName();
+
+                    if (!PartyManager.handlePartyChangeEvent(onlineTarget, partyName, null, EventReason.KICKED_FROM_PARTY)) {
+                        return true;
+                    }
+
+                    onlineTarget.sendMessage(LocaleLoader.getString("Commands.Party.Kick", partyName));
+                }
+
+                PartyManager.removeFromParty(target, playerParty);
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "kick", "<" + LocaleLoader.getString("Commands.Usage.Player") + ">"));
+                return true;
+        }
+    }
+}

+ 97 - 97
src/main/java/com/gmail/nossr50/party/commands/PartyLockCommand.java → src/main/java/com/gmail/nossr50/commands/party/PartyLockCommand.java

@@ -1,97 +1,97 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.Party;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class PartyLockCommand implements CommandExecutor {
-    private Party playerParty;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        playerParty = Users.getPlayer((Player) sender).getParty();
-
-        switch (args.length) {
-        case 1:
-            if (args[0].equalsIgnoreCase("lock")) {
-                lockParty(sender, command);
-            }
-            else if (args[0].equalsIgnoreCase("unlock")) {
-                unlockParty(sender, command);
-            }
-
-            return true;
-
-        case 2:
-            if (!args[0].equalsIgnoreCase("lock")) {
-                sendUsageStrings(sender);
-                return true;
-            }
-
-            if (args[1].equalsIgnoreCase("on") || args[1].equalsIgnoreCase("true")) {
-                lockParty(sender, command);
-            }
-            else if (args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) {
-                unlockParty(sender, command);
-            }
-            else {
-                sendUsageStrings(sender);
-            }
-
-            return true;
-
-        default:
-            sendUsageStrings(sender);
-            return true;
-        }
-    }
-
-    /**
-     * Handle locking a party.
-     */
-    private void lockParty(CommandSender sender, Command command) {
-        if (!Permissions.partySubcommand(sender, PartySubcommandType.LOCK)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return;
-        }
-
-        if (playerParty.isLocked()) {
-            sender.sendMessage(LocaleLoader.getString("Party.IsLocked"));
-            return;
-        }
-
-        playerParty.setLocked(true);
-        sender.sendMessage(LocaleLoader.getString("Party.Locked"));
-    }
-
-    /**
-     * Handle unlocking a party.
-     *
-     * @return true if party is successfully unlocked, false otherwise.
-     */
-    private void unlockParty(CommandSender sender, Command command) {
-        if (!Permissions.partySubcommand(sender, PartySubcommandType.UNLOCK)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return;
-        }
-
-        if (!playerParty.isLocked()) {
-            sender.sendMessage(LocaleLoader.getString("Party.IsntLocked"));
-            return;
-        }
-
-        playerParty.setLocked(false);
-        sender.sendMessage(LocaleLoader.getString("Party.Unlocked"));
-    }
-
-    private void sendUsageStrings(CommandSender sender) {
-        sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "lock", "[on|off]"));
-        sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "unlock"));
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.party.Party;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyLockCommand implements CommandExecutor {
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        playerParty = UserManager.getPlayer((Player) sender).getParty();
+
+        switch (args.length) {
+            case 1:
+                if (args[0].equalsIgnoreCase("lock")) {
+                    lockParty(sender, command);
+                }
+                else if (args[0].equalsIgnoreCase("unlock")) {
+                    unlockParty(sender, command);
+                }
+
+                return true;
+
+            case 2:
+                if (!args[0].equalsIgnoreCase("lock")) {
+                    sendUsageStrings(sender);
+                    return true;
+                }
+
+                if (args[1].equalsIgnoreCase("on") || args[1].equalsIgnoreCase("true")) {
+                    lockParty(sender, command);
+                }
+                else if (args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) {
+                    unlockParty(sender, command);
+                }
+                else {
+                    sendUsageStrings(sender);
+                }
+
+                return true;
+
+            default:
+                sendUsageStrings(sender);
+                return true;
+        }
+    }
+
+    /**
+     * Handle locking a party.
+     */
+    private void lockParty(CommandSender sender, Command command) {
+        if (!Permissions.partySubcommand(sender, PartySubcommandType.LOCK)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return;
+        }
+
+        if (playerParty.isLocked()) {
+            sender.sendMessage(LocaleLoader.getString("Party.IsLocked"));
+            return;
+        }
+
+        playerParty.setLocked(true);
+        sender.sendMessage(LocaleLoader.getString("Party.Locked"));
+    }
+
+    /**
+     * Handle unlocking a party.
+     *
+     * @return true if party is successfully unlocked, false otherwise.
+     */
+    private void unlockParty(CommandSender sender, Command command) {
+        if (!Permissions.partySubcommand(sender, PartySubcommandType.UNLOCK)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return;
+        }
+
+        if (!playerParty.isLocked()) {
+            sender.sendMessage(LocaleLoader.getString("Party.IsntLocked"));
+            return;
+        }
+
+        playerParty.setLocked(false);
+        sender.sendMessage(LocaleLoader.getString("Party.Unlocked"));
+    }
+
+    private void sendUsageStrings(CommandSender sender) {
+        sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "lock", "[on|off]"));
+        sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "unlock"));
+    }
+}

+ 38 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java

@@ -0,0 +1,38 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+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.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyQuitCommand implements CommandExecutor {
+    private Player player;
+    private Party playerParty;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 1:
+                player = (Player) sender;
+                playerParty = UserManager.getPlayer(player).getParty();
+
+                if (!PartyManager.handlePartyChangeEvent(player, playerParty.getName(), null, EventReason.LEFT_PARTY)) {
+                    return true;
+                }
+
+                PartyManager.removeFromParty(player, playerParty);
+                sender.sendMessage(LocaleLoader.getString("Commands.Party.Leave"));
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.1", "party", "[quit|q|leave]"));
+                return true;
+        }
+    }
+}

+ 58 - 0
src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java

@@ -0,0 +1,58 @@
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+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.party.PartyManager;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PartyRenameCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        Party playerParty = UserManager.getPlayer((Player) sender).getParty();
+        String leaderName = playerParty.getLeader();
+
+        switch (args.length) {
+            case 2:
+                String newPartyName = args[1];
+
+                // This is to prevent party leaders from spamming other players with the rename message
+                if (playerParty.getName().equalsIgnoreCase(newPartyName)) {
+                    sender.sendMessage(LocaleLoader.getString("Party.Rename.Same"));
+                    return true;
+                }
+
+                Party newParty = PartyManager.getParty(newPartyName);
+
+                // Check to see if the party exists, and if it does cancel renaming the party
+                if (newParty != null) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Party.AlreadyExists", newPartyName));
+                    return true;
+                }
+
+                for (Player member : playerParty.getOnlineMembers()) {
+                    if (!PartyManager.handlePartyChangeEvent(member, playerParty.getName(), newPartyName, EventReason.CHANGED_PARTIES)) {
+                        return true;
+                    }
+
+                    if (!member.getName().equals(leaderName)) {
+                        member.sendMessage(LocaleLoader.getString("Party.InformedOnNameChange", leaderName, newPartyName));
+                    }
+                }
+
+                playerParty.setName(newPartyName);
+
+                sender.sendMessage(LocaleLoader.getString("Commands.Party.Rename", newPartyName));
+                return true;
+
+            default:
+                sender.sendMessage(LocaleLoader.getString("Commands.Usage.2", "party", "rename", "<" + LocaleLoader.getString("Commands.Usage.PartyName") + ">"));
+                return true;
+        }
+    }
+}

+ 48 - 48
src/main/java/com/gmail/nossr50/party/commands/PartySubcommandType.java → src/main/java/com/gmail/nossr50/commands/party/PartySubcommandType.java

@@ -1,48 +1,48 @@
-package com.gmail.nossr50.party.commands;
-
-public enum PartySubcommandType {
-    JOIN,
-    ACCEPT,
-    CREATE,
-    HELP,
-    INFO,
-    QUIT,
-    EXPSHARE,
-    ITEMSHARE,
-    INVITE,
-    KICK,
-    DISBAND,
-    OWNER,
-    LOCK,
-    UNLOCK,
-    PASSWORD,
-    RENAME,
-    TELEPORT,
-    CHAT;
-
-    public static PartySubcommandType getSubcommand(String commandName) {
-        for (PartySubcommandType command : values()) {
-            if (command.name().equalsIgnoreCase(commandName)) {
-                return command;
-            }
-        }
-
-        if (commandName.equalsIgnoreCase("?")) {
-            return HELP;
-        }
-        else if (commandName.equalsIgnoreCase("q") || commandName.equalsIgnoreCase("leave")) {
-            return QUIT;
-        }
-        else if (commandName.equalsIgnoreCase("leader")) {
-            return OWNER;
-        }
-        else if (commandName.equalsIgnoreCase("xpshare") || commandName.equalsIgnoreCase("shareexp") || commandName.equalsIgnoreCase("sharexp")) {
-            return EXPSHARE;
-        }
-        else if (commandName.equalsIgnoreCase("shareitem") || commandName.equalsIgnoreCase("shareitems")) {
-            return ITEMSHARE;
-        }
-
-        return null;
-    }
-}
+package com.gmail.nossr50.commands.party;
+
+public enum PartySubcommandType {
+    JOIN,
+    ACCEPT,
+    CREATE,
+    HELP,
+    INFO,
+    QUIT,
+    EXPSHARE,
+    ITEMSHARE,
+    INVITE,
+    KICK,
+    DISBAND,
+    OWNER,
+    LOCK,
+    UNLOCK,
+    PASSWORD,
+    RENAME,
+    TELEPORT,
+    CHAT;
+
+    public static PartySubcommandType getSubcommand(String commandName) {
+        for (PartySubcommandType command : values()) {
+            if (command.name().equalsIgnoreCase(commandName)) {
+                return command;
+            }
+        }
+
+        if (commandName.equalsIgnoreCase("?")) {
+            return HELP;
+        }
+        else if (commandName.equalsIgnoreCase("q") || commandName.equalsIgnoreCase("leave")) {
+            return QUIT;
+        }
+        else if (commandName.equalsIgnoreCase("leader")) {
+            return OWNER;
+        }
+        else if (commandName.equalsIgnoreCase("xpshare") || commandName.equalsIgnoreCase("shareexp") || commandName.equalsIgnoreCase("sharexp")) {
+            return EXPSHARE;
+        }
+        else if (commandName.equalsIgnoreCase("shareitem") || commandName.equalsIgnoreCase("shareitems")) {
+            return ITEMSHARE;
+        }
+
+        return null;
+    }
+}

+ 218 - 218
src/main/java/com/gmail/nossr50/party/commands/PtpCommand.java → src/main/java/com/gmail/nossr50/commands/party/PtpCommand.java

@@ -1,219 +1,219 @@
-package com.gmail.nossr50.party.commands;
-
-import org.bukkit.World;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.commands.CommandHelper;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.events.party.McMMOPartyTeleportEvent;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.party.PartyManager;
-import com.gmail.nossr50.util.Misc;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class PtpCommand implements CommandExecutor {
-    private Player player;
-    private McMMOPlayer mcMMOPlayer;
-    private PlayerProfile playerProfile;
-
-    private Player target;
-    private McMMOPlayer mcMMOTarget;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (CommandHelper.noConsoleUsage(sender)) {
-            return true;
-        }
-
-        switch (args.length) {
-        case 1:
-            player = (Player) sender;
-            mcMMOPlayer = Users.getPlayer(player);
-            playerProfile = mcMMOPlayer.getProfile();
-
-            if (args[0].equalsIgnoreCase("toggle")) {
-                if (!Permissions.partyTeleportToggle(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                return togglePartyTeleportation();
-            }
-
-            if (args[0].equalsIgnoreCase("acceptany") || args[0].equalsIgnoreCase("acceptall")) {
-                if (!Permissions.partyTeleportAcceptAll(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                return acceptAnyTeleportRequest();
-            }
-
-            int ptpCooldown = Config.getInstance().getPTPCommandCooldown();
-
-            if (playerProfile.getRecentlyHurt() + (ptpCooldown * Misc.TIME_CONVERSION_FACTOR) > System.currentTimeMillis()) {
-                player.sendMessage(LocaleLoader.getString("Party.Teleport.Hurt", ptpCooldown));
-                return true;
-            }
-
-            if (args[0].equalsIgnoreCase("accept")) {
-                if (!Permissions.partyTeleportAccept(sender)) {
-                    sender.sendMessage(command.getPermissionMessage());
-                    return true;
-                }
-
-                return acceptTeleportRequest();
-            }
-
-            return sendTeleportRequest(args[0]);
-
-        default:
-            return false;
-        }
-    }
-
-    private boolean sendTeleportRequest(String targetName) {
-        if (!canTeleport(targetName)) {
-            return true;
-        }
-
-        if (!mcMMOTarget.getPtpConfirmRequired()) {
-            return handlePartyTeleportEvent(player, target);
-        }
-
-        mcMMOTarget.setPtpRequest(player);
-        mcMMOTarget.actualizePtpTimeout();
-        player.sendMessage(LocaleLoader.getString("Commands.Invite.Success"));
-
-        int ptpRequestExpire = Config.getInstance().getPTPCommandTimeout();
-        target.sendMessage(LocaleLoader.getString("Commands.ptp.Request1", player.getName()));
-        target.sendMessage(LocaleLoader.getString("Commands.ptp.Request2", ptpRequestExpire));
-        return true;
-    }
-
-    private boolean acceptTeleportRequest() {
-        if (!mcMMOPlayer.hasPtpRequest()) {
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.NoRequests"));
-            return true;
-        }
-
-        int ptpRequestExpire = Config.getInstance().getPTPCommandTimeout();
-
-        if ((mcMMOPlayer.getPtpTimeout() + ptpRequestExpire) * Misc.TIME_CONVERSION_FACTOR < System.currentTimeMillis()) {
-            mcMMOPlayer.removePtpRequest();
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.RequestExpired"));
-            return true;
-        }
-
-        target = mcMMOPlayer.getPtpRequest();
-
-        if (!canTeleport(target.getName())) {
-            mcMMOPlayer.removePtpRequest();
-            return true;
-        }
-
-        if (Config.getInstance().getPTPCommandWorldPermissions()) {
-            World targetWorld = target.getWorld();
-            World playerWorld = player.getWorld();
-
-            if (!Permissions.partyTeleportAllWorlds(target)) {
-                if (!Permissions.partyTeleportWorld(target, targetWorld)) {
-                    target.sendMessage(LocaleLoader.getString("Commands.ptp.NoWorldPermissions", targetWorld.getName()));
-                    return true;
-                }
-                else if (targetWorld != playerWorld && !Permissions.partyTeleportWorld(target, playerWorld)) {
-                    target.sendMessage(LocaleLoader.getString("Commands.ptp.NoWorldPermissions", playerWorld.getName()));
-                    return true;
-                }
-            }
-        }
-
-        return handlePartyTeleportEvent(target, player);
-    }
-
-    private boolean acceptAnyTeleportRequest() {
-        if (mcMMOPlayer.getPtpConfirmRequired()) {
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.AcceptAny.Disabled"));
-        }
-        else {
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.AcceptAny.Enabled"));
-        }
-
-        mcMMOPlayer.togglePtpConfirmRequired();
-        return true;
-    }
-
-    private boolean togglePartyTeleportation() {
-        if (mcMMOPlayer.getPtpEnabled()) {
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.Disabled"));
-        }
-        else {
-            player.sendMessage(LocaleLoader.getString("Commands.ptp.Enabled"));
-        }
-
-        mcMMOPlayer.togglePtpUse();
-        return true;
-    }
-
-    private boolean canTeleport(String targetName) {
-        if (!mcMMO.p.getServer().getOfflinePlayer(targetName).isOnline()) {
-            player.sendMessage(LocaleLoader.getString("Party.NotOnline", targetName));
-            return false;
-        }
-
-        mcMMOTarget = Users.getPlayer(targetName);
-
-        if (mcMMOTarget == null) {
-            player.sendMessage(LocaleLoader.getString("Party.Player.Invalid"));
-            return false;
-        }
-
-        target = mcMMOTarget.getPlayer();
-
-        if (player.equals(target)) {
-            player.sendMessage(LocaleLoader.getString("Party.Teleport.Self"));
-            return false;
-        }
-
-        if (!PartyManager.inSameParty(player, target)) {
-            player.sendMessage(LocaleLoader.getString("Party.NotInYourParty", targetName));
-            return false;
-        }
-
-        if (!mcMMOTarget.getPtpEnabled()) {
-            player.sendMessage(LocaleLoader.getString("Party.Teleport.Disabled", target.getName()));
-            return false;
-        }
-
-        if (target.isDead()) {
-            player.sendMessage(LocaleLoader.getString("Party.Teleport.Dead"));
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean handlePartyTeleportEvent(Player player, Player target) {
-        McMMOPlayer mcMMOPlayer= Users.getPlayer(player);
-
-        McMMOPartyTeleportEvent event = new McMMOPartyTeleportEvent(player, target, mcMMOPlayer.getParty().getName());
-        mcMMO.p.getServer().getPluginManager().callEvent(event);
-
-        if (event.isCancelled()) {
-            return true;
-        }
-
-        player.teleport(target);
-        player.sendMessage(LocaleLoader.getString("Party.Teleport.Player", target.getName()));
-        target.sendMessage(LocaleLoader.getString("Party.Teleport.Target", player.getName()));
-        mcMMOPlayer.getProfile().setRecentlyHurt(System.currentTimeMillis());
-        return true;
-    }
+package com.gmail.nossr50.commands.party;
+
+import org.bukkit.World;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.events.party.McMMOPartyTeleportEvent;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.party.PartyManager;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class PtpCommand implements CommandExecutor {
+    private Player player;
+    private McMMOPlayer mcMMOPlayer;
+    private PlayerProfile playerProfile;
+
+    private Player target;
+    private McMMOPlayer mcMMOTarget;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (CommandUtils.noConsoleUsage(sender)) {
+            return true;
+        }
+
+        switch (args.length) {
+            case 1:
+                player = (Player) sender;
+                mcMMOPlayer = UserManager.getPlayer(player);
+                playerProfile = mcMMOPlayer.getProfile();
+
+                if (args[0].equalsIgnoreCase("toggle")) {
+                    if (!Permissions.partyTeleportToggle(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    return togglePartyTeleportation();
+                }
+
+                if (args[0].equalsIgnoreCase("acceptany") || args[0].equalsIgnoreCase("acceptall")) {
+                    if (!Permissions.partyTeleportAcceptAll(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    return acceptAnyTeleportRequest();
+                }
+
+                int ptpCooldown = Config.getInstance().getPTPCommandCooldown();
+
+                if (playerProfile.getRecentlyHurt() + (ptpCooldown * Misc.TIME_CONVERSION_FACTOR) > System.currentTimeMillis()) {
+                    player.sendMessage(LocaleLoader.getString("Party.Teleport.Hurt", ptpCooldown));
+                    return true;
+                }
+
+                if (args[0].equalsIgnoreCase("accept")) {
+                    if (!Permissions.partyTeleportAccept(sender)) {
+                        sender.sendMessage(command.getPermissionMessage());
+                        return true;
+                    }
+
+                    return acceptTeleportRequest();
+                }
+
+                return sendTeleportRequest(args[0]);
+
+            default:
+                return false;
+        }
+    }
+
+    private boolean sendTeleportRequest(String targetName) {
+        if (!canTeleport(targetName)) {
+            return true;
+        }
+
+        if (!mcMMOTarget.getPtpConfirmRequired()) {
+            return handlePartyTeleportEvent(player, target);
+        }
+
+        mcMMOTarget.setPtpRequest(player);
+        mcMMOTarget.actualizePtpTimeout();
+        player.sendMessage(LocaleLoader.getString("Commands.Invite.Success"));
+
+        int ptpRequestExpire = Config.getInstance().getPTPCommandTimeout();
+        target.sendMessage(LocaleLoader.getString("Commands.ptp.Request1", player.getName()));
+        target.sendMessage(LocaleLoader.getString("Commands.ptp.Request2", ptpRequestExpire));
+        return true;
+    }
+
+    private boolean acceptTeleportRequest() {
+        if (!mcMMOPlayer.hasPtpRequest()) {
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.NoRequests"));
+            return true;
+        }
+
+        int ptpRequestExpire = Config.getInstance().getPTPCommandTimeout();
+
+        if ((mcMMOPlayer.getPtpTimeout() + ptpRequestExpire) * Misc.TIME_CONVERSION_FACTOR < System.currentTimeMillis()) {
+            mcMMOPlayer.removePtpRequest();
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.RequestExpired"));
+            return true;
+        }
+
+        target = mcMMOPlayer.getPtpRequest();
+
+        if (!canTeleport(target.getName())) {
+            mcMMOPlayer.removePtpRequest();
+            return true;
+        }
+
+        if (Config.getInstance().getPTPCommandWorldPermissions()) {
+            World targetWorld = target.getWorld();
+            World playerWorld = player.getWorld();
+
+            if (!Permissions.partyTeleportAllWorlds(target)) {
+                if (!Permissions.partyTeleportWorld(target, targetWorld)) {
+                    target.sendMessage(LocaleLoader.getString("Commands.ptp.NoWorldPermissions", targetWorld.getName()));
+                    return true;
+                }
+                else if (targetWorld != playerWorld && !Permissions.partyTeleportWorld(target, playerWorld)) {
+                    target.sendMessage(LocaleLoader.getString("Commands.ptp.NoWorldPermissions", playerWorld.getName()));
+                    return true;
+                }
+            }
+        }
+
+        return handlePartyTeleportEvent(target, player);
+    }
+
+    private boolean acceptAnyTeleportRequest() {
+        if (mcMMOPlayer.getPtpConfirmRequired()) {
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.AcceptAny.Disabled"));
+        }
+        else {
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.AcceptAny.Enabled"));
+        }
+
+        mcMMOPlayer.togglePtpConfirmRequired();
+        return true;
+    }
+
+    private boolean togglePartyTeleportation() {
+        if (mcMMOPlayer.getPtpEnabled()) {
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.Disabled"));
+        }
+        else {
+            player.sendMessage(LocaleLoader.getString("Commands.ptp.Enabled"));
+        }
+
+        mcMMOPlayer.togglePtpUse();
+        return true;
+    }
+
+    private boolean canTeleport(String targetName) {
+        if (!mcMMO.p.getServer().getOfflinePlayer(targetName).isOnline()) {
+            player.sendMessage(LocaleLoader.getString("Party.NotOnline", targetName));
+            return false;
+        }
+
+        mcMMOTarget = UserManager.getPlayer(targetName);
+
+        if (mcMMOTarget == null) {
+            player.sendMessage(LocaleLoader.getString("Party.Player.Invalid"));
+            return false;
+        }
+
+        target = mcMMOTarget.getPlayer();
+
+        if (player.equals(target)) {
+            player.sendMessage(LocaleLoader.getString("Party.Teleport.Self"));
+            return false;
+        }
+
+        if (!PartyManager.inSameParty(player, target)) {
+            player.sendMessage(LocaleLoader.getString("Party.NotInYourParty", targetName));
+            return false;
+        }
+
+        if (!mcMMOTarget.getPtpEnabled()) {
+            player.sendMessage(LocaleLoader.getString("Party.Teleport.Disabled", target.getName()));
+            return false;
+        }
+
+        if (target.isDead()) {
+            player.sendMessage(LocaleLoader.getString("Party.Teleport.Dead"));
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean handlePartyTeleportEvent(Player player, Player target) {
+        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+
+        McMMOPartyTeleportEvent event = new McMMOPartyTeleportEvent(player, target, mcMMOPlayer.getParty().getName());
+        mcMMO.p.getServer().getPluginManager().callEvent(event);
+
+        if (event.isCancelled()) {
+            return true;
+        }
+
+        player.teleport(target);
+        player.sendMessage(LocaleLoader.getString("Party.Teleport.Player", target.getName()));
+        target.sendMessage(LocaleLoader.getString("Party.Teleport.Target", player.getName()));
+        mcMMOPlayer.getProfile().setRecentlyHurt(System.currentTimeMillis());
+        return true;
+    }
 }

+ 58 - 58
src/main/java/com/gmail/nossr50/commands/player/InspectCommand.java

@@ -5,14 +5,14 @@ import org.bukkit.command.CommandExecutor;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
-import com.gmail.nossr50.commands.CommandHelper;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillType;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
 
 public class InspectCommand implements CommandExecutor {
     @Override
@@ -20,68 +20,68 @@ public class InspectCommand implements CommandExecutor {
         PlayerProfile profile;
 
         switch (args.length) {
-        case 1:
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
+            case 1:
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
 
-            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false); //Temporary Profile
+                // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+                if (mcMMOPlayer == null) {
+                    profile = new PlayerProfile(args[0], false); // Temporary Profile
 
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                // TODO: Why do we care if this is a player?
-                if (sender instanceof Player && !Permissions.inspectOffline(sender)) {
-                    sender.sendMessage(LocaleLoader.getString("Inspect.Offline"));
-                    return true;
-                }
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
 
-                sender.sendMessage(LocaleLoader.getString("Inspect.OfflineStats", args[0]));
-
-                sender.sendMessage(LocaleLoader.getString("Stats.Header.Gathering"));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Excavation.Listener"), profile.getSkillLevel(SkillType.EXCAVATION), profile.getSkillXpLevel(SkillType.EXCAVATION), profile.getXpToLevel(SkillType.EXCAVATION)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Fishing.Listener"), profile.getSkillLevel(SkillType.FISHING), profile.getSkillXpLevel(SkillType.FISHING), profile.getXpToLevel(SkillType.FISHING)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Herbalism.Listener"), profile.getSkillLevel(SkillType.HERBALISM), profile.getSkillXpLevel(SkillType.HERBALISM), profile.getXpToLevel(SkillType.HERBALISM)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Mining.Listener"), profile.getSkillLevel(SkillType.MINING), profile.getSkillXpLevel(SkillType.MINING), profile.getXpToLevel(SkillType.MINING)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Woodcutting.Listener"), profile.getSkillLevel(SkillType.WOODCUTTING), profile.getSkillXpLevel(SkillType.WOODCUTTING), profile.getXpToLevel(SkillType.WOODCUTTING)));
-
-                sender.sendMessage(LocaleLoader.getString("Stats.Header.Combat"));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Axes.Listener"), profile.getSkillLevel(SkillType.AXES), profile.getSkillXpLevel(SkillType.AXES), profile.getXpToLevel(SkillType.AXES)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Archery.Listener"), profile.getSkillLevel(SkillType.ARCHERY), profile.getSkillXpLevel(SkillType.ARCHERY), profile.getXpToLevel(SkillType.ARCHERY)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Swords.Listener"), profile.getSkillLevel(SkillType.SWORDS), profile.getSkillXpLevel(SkillType.SWORDS), profile.getXpToLevel(SkillType.SWORDS)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Taming.Listener"), profile.getSkillLevel(SkillType.TAMING), profile.getSkillXpLevel(SkillType.TAMING), profile.getXpToLevel(SkillType.TAMING)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Unarmed.Listener"), profile.getSkillLevel(SkillType.UNARMED), profile.getSkillXpLevel(SkillType.UNARMED), profile.getXpToLevel(SkillType.UNARMED)));
-
-                sender.sendMessage(LocaleLoader.getString("Stats.Header.Misc"));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Acrobatics.Listener"), profile.getSkillLevel(SkillType.ACROBATICS), profile.getSkillXpLevel(SkillType.ACROBATICS), profile.getXpToLevel(SkillType.ACROBATICS)));
-                sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Repair.Listener"), profile.getSkillLevel(SkillType.REPAIR), profile.getSkillXpLevel(SkillType.REPAIR), profile.getXpToLevel(SkillType.REPAIR)));
-            }
-            else {
-                Player target = mcMMOPlayer.getPlayer();
-
-                if (sender instanceof Player) {
-                    Player inspector = (Player) sender;
-
-                    if (!Misc.isNear(inspector.getLocation(), target.getLocation(), 5.0) && !Permissions.inspectFar(inspector)) {
-                        sender.sendMessage(LocaleLoader.getString("Inspect.TooFar"));
+                    // TODO: Why do we care if this is a player?
+                    if (sender instanceof Player && !Permissions.inspectOffline(sender)) {
+                        sender.sendMessage(LocaleLoader.getString("Inspect.Offline"));
                         return true;
                     }
+
+                    sender.sendMessage(LocaleLoader.getString("Inspect.OfflineStats", args[0]));
+
+                    sender.sendMessage(LocaleLoader.getString("Stats.Header.Gathering"));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Excavation.Listener"), profile.getSkillLevel(SkillType.EXCAVATION), profile.getSkillXpLevel(SkillType.EXCAVATION), profile.getXpToLevel(SkillType.EXCAVATION)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Fishing.Listener"), profile.getSkillLevel(SkillType.FISHING), profile.getSkillXpLevel(SkillType.FISHING), profile.getXpToLevel(SkillType.FISHING)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Herbalism.Listener"), profile.getSkillLevel(SkillType.HERBALISM), profile.getSkillXpLevel(SkillType.HERBALISM), profile.getXpToLevel(SkillType.HERBALISM)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Mining.Listener"), profile.getSkillLevel(SkillType.MINING), profile.getSkillXpLevel(SkillType.MINING), profile.getXpToLevel(SkillType.MINING)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Woodcutting.Listener"), profile.getSkillLevel(SkillType.WOODCUTTING), profile.getSkillXpLevel(SkillType.WOODCUTTING), profile.getXpToLevel(SkillType.WOODCUTTING)));
+
+                    sender.sendMessage(LocaleLoader.getString("Stats.Header.Combat"));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Axes.Listener"), profile.getSkillLevel(SkillType.AXES), profile.getSkillXpLevel(SkillType.AXES), profile.getXpToLevel(SkillType.AXES)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Archery.Listener"), profile.getSkillLevel(SkillType.ARCHERY), profile.getSkillXpLevel(SkillType.ARCHERY), profile.getXpToLevel(SkillType.ARCHERY)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Swords.Listener"), profile.getSkillLevel(SkillType.SWORDS), profile.getSkillXpLevel(SkillType.SWORDS), profile.getXpToLevel(SkillType.SWORDS)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Taming.Listener"), profile.getSkillLevel(SkillType.TAMING), profile.getSkillXpLevel(SkillType.TAMING), profile.getXpToLevel(SkillType.TAMING)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Unarmed.Listener"), profile.getSkillLevel(SkillType.UNARMED), profile.getSkillXpLevel(SkillType.UNARMED), profile.getXpToLevel(SkillType.UNARMED)));
+
+                    sender.sendMessage(LocaleLoader.getString("Stats.Header.Misc"));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Acrobatics.Listener"), profile.getSkillLevel(SkillType.ACROBATICS), profile.getSkillXpLevel(SkillType.ACROBATICS), profile.getXpToLevel(SkillType.ACROBATICS)));
+                    sender.sendMessage(LocaleLoader.getString("Skills.Stats", LocaleLoader.getString("Repair.Listener"), profile.getSkillLevel(SkillType.REPAIR), profile.getSkillXpLevel(SkillType.REPAIR), profile.getXpToLevel(SkillType.REPAIR)));
                 }
-                profile = mcMMOPlayer.getProfile();
+                else {
+                    Player target = mcMMOPlayer.getPlayer();
+
+                    if (sender instanceof Player) {
+                        Player inspector = (Player) sender;
 
-                sender.sendMessage(LocaleLoader.getString("Inspect.Stats", target.getName()));
-                CommandHelper.printGatheringSkills(target, profile, sender);
-                CommandHelper.printCombatSkills(target, profile, sender);
-                CommandHelper.printMiscSkills(target, profile, sender);
-                sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel", mcMMOPlayer.getPowerLevel()));
-            }
+                        if (!Misc.isNear(inspector.getLocation(), target.getLocation(), 5.0) && !Permissions.inspectFar(inspector)) {
+                            sender.sendMessage(LocaleLoader.getString("Inspect.TooFar"));
+                            return true;
+                        }
+                    }
+                    profile = mcMMOPlayer.getProfile();
+
+                    sender.sendMessage(LocaleLoader.getString("Inspect.Stats", target.getName()));
+                    CommandUtils.printGatheringSkills(target, profile, sender);
+                    CommandUtils.printCombatSkills(target, profile, sender);
+                    CommandUtils.printMiscSkills(target, profile, sender);
+                    sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel", mcMMOPlayer.getPowerLevel()));
+                }
 
-            return true;
+                return true;
 
-        default:
-            return false;
+            default:
+                return false;
         }
     }
 }

+ 0 - 80
src/main/java/com/gmail/nossr50/commands/player/McabilityCommand.java

@@ -1,80 +0,0 @@
-package com.gmail.nossr50.commands.player;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
-
-public class McabilityCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        PlayerProfile profile;
-
-        switch (args.length) {
-        case 0:
-            if (!Permissions.mcability(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (profile.getAbilityUse()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Ability.Off"));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.Ability.On"));
-            }
-
-            profile.toggleAbilityUse();
-            return true;
-
-        case 1:
-            if (!Permissions.mcabilityOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            if (mcMMOPlayer == null) {
-                profile = new PlayerProfile(args[0], false);
-
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                    return true;
-                }
-
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-
-            Player player = mcMMOPlayer.getPlayer();
-            profile = mcMMOPlayer.getProfile();
-
-            if (!player.isOnline()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Offline"));
-                return true;
-            }
-
-            if (profile.getAbilityUse()) {
-                player.sendMessage(LocaleLoader.getString("Commands.Ability.Off"));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Commands.Ability.On"));
-            }
-
-            profile.toggleAbilityUse();
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 34
src/main/java/com/gmail/nossr50/commands/player/McnotifyCommand.java

@@ -1,34 +0,0 @@
-package com.gmail.nossr50.commands.player;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Users;
-
-public class McnotifyCommand implements CommandExecutor {
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        switch (args.length) {
-        case 0:
-            PlayerProfile profile = Users.getPlayer((Player) sender).getProfile();
-
-            if (profile.useChatNotifications()) {
-                sender.sendMessage(LocaleLoader.getString("Commands.Notifications.Off"));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.Notifications.On"));
-            }
-
-            profile.toggleChatNotifications();
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 60 - 60
src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java

@@ -8,83 +8,83 @@ import org.bukkit.entity.Player;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.Leaderboard;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
+import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.runnables.McRankAsync;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
+import com.gmail.nossr50.runnables.commands.McrankCommandAsyncTask;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.Users;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.SkillUtils;
 
 public class McrankCommand implements CommandExecutor {
     @Override
     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
         switch (args.length) {
-        case 0:
-            if (!Permissions.mcrank(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
-                return true;
-            }
-
-            if (!(sender instanceof Player)) {
-                return false;
-            }
+            case 0:
+                if (!Permissions.mcrank(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
+                    return true;
+                }
 
-            if (Config.getInstance().getUseMySQL()) {
-                sqlDisplay(sender, sender.getName());
-            }
-            else {
-                Leaderboard.updateLeaderboards(); // Make sure the information is up to date
-                flatfileDisplay(sender, sender.getName());
-            }
+                if (!(sender instanceof Player)) {
+                    return false;
+                }
 
-            return true;
+                if (Config.getInstance().getUseMySQL()) {
+                    sqlDisplay(sender, sender.getName());
+                }
+                else {
+                    LeaderboardManager.updateLeaderboards(); // Make sure the information is up to date
+                    flatfileDisplay(sender, sender.getName());
+                }
 
-        case 1:
-            if (!Permissions.mcrankOthers(sender)) {
-                sender.sendMessage(command.getPermissionMessage());
                 return true;
-            }
-
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(args[0]);
-
-            if (mcMMOPlayer == null) {
-                PlayerProfile profile = new PlayerProfile(args[0], false); //Temporary Profile
 
-                if (!profile.isLoaded()) {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+            case 1:
+                if (!Permissions.mcrankOthers(sender)) {
+                    sender.sendMessage(command.getPermissionMessage());
                     return true;
                 }
 
-                if (sender instanceof Player && !Permissions.mcrankOffline(sender)) {
-                    sender.sendMessage(LocaleLoader.getString("Inspect.Offline"));
-                    return true;
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(args[0]);
+
+                if (mcMMOPlayer == null) {
+                    PlayerProfile profile = new PlayerProfile(args[0], false); // Temporary Profile
+
+                    if (!profile.isLoaded()) {
+                        sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+                        return true;
+                    }
+
+                    if (sender instanceof Player && !Permissions.mcrankOffline(sender)) {
+                        sender.sendMessage(LocaleLoader.getString("Inspect.Offline"));
+                        return true;
+                    }
                 }
-            }
-            else {
-                Player target = mcMMOPlayer.getPlayer();
+                else {
+                    Player target = mcMMOPlayer.getPlayer();
 
-                if (sender instanceof Player && !Misc.isNear(((Player) sender).getLocation(), target.getLocation(), 5.0) && !Permissions.mcrankFar(sender)) {
-                    sender.sendMessage(LocaleLoader.getString("Inspect.TooFar"));
-                    return true;
+                    if (sender instanceof Player && !Misc.isNear(((Player) sender).getLocation(), target.getLocation(), 5.0) && !Permissions.mcrankFar(sender)) {
+                        sender.sendMessage(LocaleLoader.getString("Inspect.TooFar"));
+                        return true;
+                    }
                 }
-            }
 
-            if (Config.getInstance().getUseMySQL()) {
-                sqlDisplay(sender, args[0]);
-            }
-            else {
-                Leaderboard.updateLeaderboards(); // Make sure the information is up to date
-                flatfileDisplay(sender, args[0]);
-            }
+                if (Config.getInstance().getUseMySQL()) {
+                    sqlDisplay(sender, args[0]);
+                }
+                else {
+                    LeaderboardManager.updateLeaderboards(); // Make sure the information is up to date
+                    flatfileDisplay(sender, args[0]);
+                }
 
-            return true;
+                return true;
 
-        default:
-            return false;
+            default:
+                return false;
         }
     }
 
@@ -93,22 +93,22 @@ public class McrankCommand implements CommandExecutor {
         sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Player", playerName));
 
         for (SkillType skillType : SkillType.values()) {
-            int[] rankInts = Leaderboard.getPlayerRank(playerName, skillType);
+            int[] rankInts = LeaderboardManager.getPlayerRank(playerName, skillType);
 
             if (skillType.isChildSkill()) {
                 continue;
             }
 
             if (rankInts[1] == 0) {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillTools.getSkillName(skillType), LocaleLoader.getString("Commands.mcrank.Unranked"))); // Don't bother showing ranking for players without skills
+                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), LocaleLoader.getString("Commands.mcrank.Unranked"))); // Don't bother showing ranking for players without skills
             }
             else {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillTools.getSkillName(skillType), rankInts[0]));
+                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), rankInts[0]));
             }
         }
 
         // Show the powerlevel ranking
-        int[] rankInts = Leaderboard.getPlayerRank(playerName);
+        int[] rankInts = LeaderboardManager.getPlayerRank(playerName);
 
         if (rankInts[1] == 0) {
             sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", LocaleLoader.getString("Commands.mcrank.Unranked"))); // Don't bother showing ranking for players without skills
@@ -119,6 +119,6 @@ public class McrankCommand implements CommandExecutor {
     }
 
     private void sqlDisplay(CommandSender sender, String playerName) {
-        Bukkit.getScheduler().runTaskAsynchronously(mcMMO.p, new McRankAsync(playerName, sender));
+        Bukkit.getScheduler().runTaskAsynchronously(mcMMO.p, new McrankCommandAsyncTask(playerName, sender));
     }
 }

+ 50 - 50
src/main/java/com/gmail/nossr50/commands/player/McstatsCommand.java

@@ -1,50 +1,50 @@
-package com.gmail.nossr50.commands.player;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.commands.CommandHelper;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Users;
-
-public class McstatsCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (CommandHelper.noConsoleUsage(sender)) {
-            return true;
-        }
-
-        switch (args.length) {
-        case 0:
-            Player player = (Player) sender;
-            McMMOPlayer mcMMOPlayer = Users.getPlayer(player);
-            PlayerProfile profile = mcMMOPlayer.getProfile();
-
-            player.sendMessage(LocaleLoader.getString("Stats.Own.Stats"));
-            player.sendMessage(LocaleLoader.getString("mcMMO.NoSkillNote"));
-
-            CommandHelper.printGatheringSkills(player, profile);
-            CommandHelper.printCombatSkills(player, profile);
-            CommandHelper.printMiscSkills(player, profile);
-
-            int powerLevelCap = Config.getInstance().getPowerLevelCap();
-
-            if (powerLevelCap != Integer.MAX_VALUE) {
-                player.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Capped", mcMMOPlayer.getPowerLevel(), powerLevelCap));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Commands.PowerLevel", mcMMOPlayer.getPowerLevel()));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}
+package com.gmail.nossr50.commands.player;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class McstatsCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (CommandUtils.noConsoleUsage(sender)) {
+            return true;
+        }
+
+        switch (args.length) {
+            case 0:
+                Player player = (Player) sender;
+                McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+                PlayerProfile profile = mcMMOPlayer.getProfile();
+
+                player.sendMessage(LocaleLoader.getString("Stats.Own.Stats"));
+                player.sendMessage(LocaleLoader.getString("mcMMO.NoSkillNote"));
+
+                CommandUtils.printGatheringSkills(player, profile);
+                CommandUtils.printCombatSkills(player, profile);
+                CommandUtils.printMiscSkills(player, profile);
+
+                int powerLevelCap = Config.getInstance().getPowerLevelCap();
+
+                if (powerLevelCap != Integer.MAX_VALUE) {
+                    player.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Capped", mcMMOPlayer.getPowerLevel(), powerLevelCap));
+                }
+                else {
+                    player.sendMessage(LocaleLoader.getString("Commands.PowerLevel", mcMMOPlayer.getPowerLevel()));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 115 - 115
src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java

@@ -1,115 +1,115 @@
-package com.gmail.nossr50.commands.player;
-
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.Leaderboard;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.runnables.McTopAsync;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-
-public class MctopCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        boolean useMySQL = Config.getInstance().getUseMySQL();
-
-        switch (args.length) {
-        case 0:
-            display(1, "ALL", sender, useMySQL, command);
-            return true;
-
-        case 1:
-            if (StringUtils.isInt(args[0])) {
-                display(Integer.parseInt(args[0]), "ALL", sender, useMySQL, command);
-            }
-            else if (SkillTools.isSkill(args[0])) {
-                display(1, SkillType.getSkill(args[0]).toString(), sender, useMySQL, command);
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-            }
-
-            return true;
-
-        case 2:
-            if (!StringUtils.isInt(args[1])) {
-                return false;
-            }
-
-            if (SkillTools.isSkill(args[0])) {
-                display(Integer.parseInt(args[1]), SkillType.getSkill(args[0]).toString(), sender, useMySQL, command);
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    private void display(int page, String skill, CommandSender sender, boolean sql, Command command) {
-        if (sql) {
-            if (skill.equalsIgnoreCase("all")) {
-                sqlDisplay(page, "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing", sender, command);
-            }
-            else {
-                sqlDisplay(page, skill, sender, command);
-            }
-        }
-        else {
-            flatfileDisplay(page, skill, sender, command);
-        }
-    }
-
-    private void flatfileDisplay(int page, String skill, CommandSender sender, Command command) {
-        if (!skill.equalsIgnoreCase("all") && !Permissions.mctop(sender, SkillType.getSkill(skill))) {
-            sender.sendMessage(command.getPermissionMessage());
-            return;
-        }
-
-        Leaderboard.updateLeaderboards(); //Make sure we have the latest information
-
-        String[] info = Leaderboard.retrieveInfo(skill, page);
-
-        if (skill.equalsIgnoreCase("all")) {
-            sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Leaderboard"));
-        }
-        else {
-            sender.sendMessage(LocaleLoader.getString("Commands.Skill.Leaderboard", StringUtils.getCapitalized(skill)));
-        }
-
-        int n = (page * 10) - 9; // Position
-        for (String x : info) {
-            if (x != null) {
-                String digit = String.valueOf(n);
-
-                if (n < 10) {
-                    digit = "0" + digit;
-                }
-
-                String[] splitx = x.split(":");
-
-                // Format: 1. Playername - skill value
-                sender.sendMessage(digit + ". " + ChatColor.GREEN + splitx[1] + " - " + ChatColor.WHITE + splitx[0]);
-                n++;
-            }
-        }
-
-        sender.sendMessage(LocaleLoader.getString("Commands.mctop.Tip"));
-    }
-
-    private void sqlDisplay(int page, String query, CommandSender sender, Command command) {
-        Bukkit.getScheduler().runTaskAsynchronously(mcMMO.p, new McTopAsync(page, query, sender, command));
-    }
-}
+package com.gmail.nossr50.commands.player;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.runnables.commands.MctopCommandAsyncTask;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class MctopCommand implements CommandExecutor {
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        boolean useMySQL = Config.getInstance().getUseMySQL();
+
+        switch (args.length) {
+            case 0:
+                display(1, "ALL", sender, useMySQL, command);
+                return true;
+
+            case 1:
+                if (StringUtils.isInt(args[0])) {
+                    display(Integer.parseInt(args[0]), "ALL", sender, useMySQL, command);
+                }
+                else if (SkillUtils.isSkill(args[0])) {
+                    display(1, SkillType.getSkill(args[0]).toString(), sender, useMySQL, command);
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                }
+
+                return true;
+
+            case 2:
+                if (!StringUtils.isInt(args[1])) {
+                    return false;
+                }
+
+                if (SkillUtils.isSkill(args[0])) {
+                    display(Integer.parseInt(args[1]), SkillType.getSkill(args[0]).toString(), sender, useMySQL, command);
+                }
+                else {
+                    sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    private void display(int page, String skill, CommandSender sender, boolean sql, Command command) {
+        if (sql) {
+            if (skill.equalsIgnoreCase("all")) {
+                sqlDisplay(page, "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing", sender, command);
+            }
+            else {
+                sqlDisplay(page, skill, sender, command);
+            }
+        }
+        else {
+            flatfileDisplay(page, skill, sender, command);
+        }
+    }
+
+    private void flatfileDisplay(int page, String skill, CommandSender sender, Command command) {
+        if (!skill.equalsIgnoreCase("all") && !Permissions.mctop(sender, SkillType.getSkill(skill))) {
+            sender.sendMessage(command.getPermissionMessage());
+            return;
+        }
+
+        LeaderboardManager.updateLeaderboards(); // Make sure we have the latest information
+
+        String[] info = LeaderboardManager.retrieveInfo(skill, page);
+
+        if (skill.equalsIgnoreCase("all")) {
+            sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Leaderboard"));
+        }
+        else {
+            sender.sendMessage(LocaleLoader.getString("Commands.Skill.Leaderboard", StringUtils.getCapitalized(skill)));
+        }
+
+        int n = (page * 10) - 9; // Position
+        for (String x : info) {
+            if (x != null) {
+                String digit = String.valueOf(n);
+
+                if (n < 10) {
+                    digit = "0" + digit;
+                }
+
+                String[] splitx = x.split(":");
+
+                // Format: 1. Playername - skill value
+                sender.sendMessage(digit + ". " + ChatColor.GREEN + splitx[1] + " - " + ChatColor.WHITE + splitx[0]);
+                n++;
+            }
+        }
+
+        sender.sendMessage(LocaleLoader.getString("Commands.mctop.Tip"));
+    }
+
+    private void sqlDisplay(int page, String query, CommandSender sender, Command command) {
+        Bukkit.getScheduler().runTaskAsynchronously(mcMMO.p, new MctopCommandAsyncTask(page, query, sender, command));
+    }
+}

+ 105 - 105
src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsCommand.java → src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java

@@ -1,105 +1,105 @@
-package com.gmail.nossr50.skills.acrobatics;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class AcrobaticsCommand extends SkillCommand {
-    private String dodgeChance;
-    private String dodgeChanceLucky;
-    private String rollChance;
-    private String rollChanceLucky;
-    private String gracefulRollChance;
-    private String gracefulRollChanceLucky;
-
-    private boolean canDodge;
-    private boolean canRoll;
-    private boolean canGracefulRoll;
-
-    public AcrobaticsCommand() {
-        super(SkillType.ACROBATICS);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //DODGE
-        String[] dodgeStrings = calculateAbilityDisplayValues(Acrobatics.dodgeMaxBonusLevel, Acrobatics.dodgeMaxChance);
-        dodgeChance = dodgeStrings[0];
-        dodgeChanceLucky = dodgeStrings[1];
-
-        //ROLL
-        String[] rollStrings = calculateAbilityDisplayValues(Acrobatics.rollMaxBonusLevel, Acrobatics.rollMaxChance);
-        rollChance = rollStrings[0];
-        rollChanceLucky = rollStrings[1];
-
-        //GRACEFUL ROLL
-        String[] gracefulRollStrings = calculateAbilityDisplayValues(Acrobatics.gracefulRollMaxBonusLevel, Acrobatics.gracefulRollMaxChance);
-        gracefulRollChance = gracefulRollStrings[0];
-        gracefulRollChanceLucky = gracefulRollStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canDodge = Permissions.dodge(player);
-        canRoll = Permissions.roll(player);
-        canGracefulRoll = Permissions.gracefulRoll(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canDodge || canGracefulRoll || canRoll;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canRoll) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.0"), LocaleLoader.getString("Acrobatics.Effect.1")));
-        }
-
-        if (canGracefulRoll) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.2"), LocaleLoader.getString("Acrobatics.Effect.3")));
-        }
-
-        if (canDodge) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.4"), LocaleLoader.getString("Acrobatics.Effect.5")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canDodge || canGracefulRoll || canRoll;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canRoll) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Chance", new Object[] { rollChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { rollChanceLucky }));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Chance", new Object[] { rollChance }));
-            }
-        }
-
-        if (canGracefulRoll) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.GraceChance", new Object[] { gracefulRollChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { gracefulRollChanceLucky }));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.GraceChance", new Object[] { gracefulRollChance }));
-            }
-        }
-
-        if (canDodge) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.DodgeChance", new Object[] { dodgeChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { dodgeChanceLucky }));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Acrobatics.DodgeChance", new Object[] { dodgeChance }));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.acrobatics.Acrobatics;
+import com.gmail.nossr50.util.Permissions;
+
+public class AcrobaticsCommand extends SkillCommand {
+    private String dodgeChance;
+    private String dodgeChanceLucky;
+    private String rollChance;
+    private String rollChanceLucky;
+    private String gracefulRollChance;
+    private String gracefulRollChanceLucky;
+
+    private boolean canDodge;
+    private boolean canRoll;
+    private boolean canGracefulRoll;
+
+    public AcrobaticsCommand() {
+        super(SkillType.ACROBATICS);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // DODGE
+        String[] dodgeStrings = calculateAbilityDisplayValues(Acrobatics.dodgeMaxBonusLevel, Acrobatics.dodgeMaxChance);
+        dodgeChance = dodgeStrings[0];
+        dodgeChanceLucky = dodgeStrings[1];
+
+        // ROLL
+        String[] rollStrings = calculateAbilityDisplayValues(Acrobatics.rollMaxBonusLevel, Acrobatics.rollMaxChance);
+        rollChance = rollStrings[0];
+        rollChanceLucky = rollStrings[1];
+
+        // GRACEFUL ROLL
+        String[] gracefulRollStrings = calculateAbilityDisplayValues(Acrobatics.gracefulRollMaxBonusLevel, Acrobatics.gracefulRollMaxChance);
+        gracefulRollChance = gracefulRollStrings[0];
+        gracefulRollChanceLucky = gracefulRollStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canDodge = Permissions.dodge(player);
+        canRoll = Permissions.roll(player);
+        canGracefulRoll = Permissions.gracefulRoll(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canDodge || canGracefulRoll || canRoll;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canRoll) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.0"), LocaleLoader.getString("Acrobatics.Effect.1")));
+        }
+
+        if (canGracefulRoll) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.2"), LocaleLoader.getString("Acrobatics.Effect.3")));
+        }
+
+        if (canDodge) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.4"), LocaleLoader.getString("Acrobatics.Effect.5")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canDodge || canGracefulRoll || canRoll;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canRoll) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Chance", new Object[] { rollChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { rollChanceLucky }));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Chance", new Object[] { rollChance }));
+            }
+        }
+
+        if (canGracefulRoll) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.GraceChance", new Object[] { gracefulRollChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { gracefulRollChanceLucky }));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.GraceChance", new Object[] { gracefulRollChance }));
+            }
+        }
+
+        if (canDodge) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.DodgeChance", new Object[] { dodgeChance }) + LocaleLoader.getString("Perks.lucky.bonus", new Object[] { dodgeChanceLucky }));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Acrobatics.DodgeChance", new Object[] { dodgeChance }));
+            }
+        }
+    }
+}

+ 104 - 104
src/main/java/com/gmail/nossr50/skills/archery/ArcheryCommand.java → src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java

@@ -1,104 +1,104 @@
-package com.gmail.nossr50.skills.archery;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class ArcheryCommand extends SkillCommand {
-    private String skillShotBonus;
-    private String dazeChance;
-    private String dazeChanceLucky;
-    private String retrieveChance;
-    private String retrieveChanceLucky;
-
-    private boolean canSkillShot;
-    private boolean canDaze;
-    private boolean canRetrieve;
-
-    public ArcheryCommand() {
-        super(SkillType.ARCHERY);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //SKILL SHOT
-        double bonus = (skillValue / Archery.skillShotIncreaseLevel) * Archery.skillShotIncreasePercentage;
-
-        if (bonus > Archery.skillShotMaxBonusPercentage) {
-            skillShotBonus = percent.format(Archery.skillShotMaxBonusPercentage);
-        }
-        else {
-            skillShotBonus = percent.format(bonus);
-        }
-
-        //DAZE
-        String[] dazeStrings = calculateAbilityDisplayValues(Archery.dazeMaxBonusLevel, Archery.dazeMaxBonus);
-        dazeChance = dazeStrings[0];
-        dazeChanceLucky = dazeStrings[1];
-
-        //RETRIEVE
-        String[] retrieveStrings = calculateAbilityDisplayValues(Archery.retrieveMaxBonusLevel, Archery.retrieveMaxChance);
-        retrieveChance = retrieveStrings[0];
-        retrieveChanceLucky = retrieveStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canSkillShot = Permissions.bonusDamage(player, skill);
-        canDaze = Permissions.daze(player);
-        canRetrieve = Permissions.arrowRetrieval(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canSkillShot || canDaze || canRetrieve;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canSkillShot) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.0"), LocaleLoader.getString("Archery.Effect.1")));
-        }
-
-        if (canDaze) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.2"), LocaleLoader.getString("Archery.Effect.3", Archery.dazeModifier)));
-        }
-
-        if (canRetrieve) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.4"), LocaleLoader.getString("Archery.Effect.5")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canSkillShot || canDaze || canRetrieve;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canSkillShot) {
-            player.sendMessage(LocaleLoader.getString("Archery.Combat.SkillshotBonus", skillShotBonus));
-        }
-
-        if (canDaze) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Archery.Combat.DazeChance", dazeChance) + LocaleLoader.getString("Perks.lucky.bonus", dazeChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Archery.Combat.DazeChance", dazeChance));
-            }
-        }
-
-        if (canRetrieve) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Archery.Combat.RetrieveChance", retrieveChance) + LocaleLoader.getString("Perks.lucky.bonus", retrieveChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Archery.Combat.RetrieveChance", retrieveChance));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.archery.Archery;
+import com.gmail.nossr50.util.Permissions;
+
+public class ArcheryCommand extends SkillCommand {
+    private String skillShotBonus;
+    private String dazeChance;
+    private String dazeChanceLucky;
+    private String retrieveChance;
+    private String retrieveChanceLucky;
+
+    private boolean canSkillShot;
+    private boolean canDaze;
+    private boolean canRetrieve;
+
+    public ArcheryCommand() {
+        super(SkillType.ARCHERY);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // SKILL SHOT
+        double bonus = (skillValue / Archery.skillShotIncreaseLevel) * Archery.skillShotIncreasePercentage;
+
+        if (bonus > Archery.skillShotMaxBonusPercentage) {
+            skillShotBonus = percent.format(Archery.skillShotMaxBonusPercentage);
+        }
+        else {
+            skillShotBonus = percent.format(bonus);
+        }
+
+        // DAZE
+        String[] dazeStrings = calculateAbilityDisplayValues(Archery.dazeMaxBonusLevel, Archery.dazeMaxBonus);
+        dazeChance = dazeStrings[0];
+        dazeChanceLucky = dazeStrings[1];
+
+        // RETRIEVE
+        String[] retrieveStrings = calculateAbilityDisplayValues(Archery.retrieveMaxBonusLevel, Archery.retrieveMaxChance);
+        retrieveChance = retrieveStrings[0];
+        retrieveChanceLucky = retrieveStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canSkillShot = Permissions.bonusDamage(player, skill);
+        canDaze = Permissions.daze(player);
+        canRetrieve = Permissions.arrowRetrieval(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canSkillShot || canDaze || canRetrieve;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canSkillShot) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.0"), LocaleLoader.getString("Archery.Effect.1")));
+        }
+
+        if (canDaze) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.2"), LocaleLoader.getString("Archery.Effect.3", Archery.dazeModifier)));
+        }
+
+        if (canRetrieve) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Archery.Effect.4"), LocaleLoader.getString("Archery.Effect.5")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canSkillShot || canDaze || canRetrieve;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canSkillShot) {
+            player.sendMessage(LocaleLoader.getString("Archery.Combat.SkillshotBonus", skillShotBonus));
+        }
+
+        if (canDaze) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Archery.Combat.DazeChance", dazeChance) + LocaleLoader.getString("Perks.lucky.bonus", dazeChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Archery.Combat.DazeChance", dazeChance));
+            }
+        }
+
+        if (canRetrieve) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Archery.Combat.RetrieveChance", retrieveChance) + LocaleLoader.getString("Perks.lucky.bonus", retrieveChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Archery.Combat.RetrieveChance", retrieveChance));
+            }
+        }
+    }
+}

+ 128 - 128
src/main/java/com/gmail/nossr50/skills/axes/AxesCommand.java → src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java

@@ -1,128 +1,128 @@
-package com.gmail.nossr50.skills.axes;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class AxesCommand extends SkillCommand {
-    private String critChance;
-    private String critChanceLucky;
-    private String bonusDamage;
-    private String impactDamage;
-    private String greaterImpactDamage;
-    private String skullSplitterLength;
-    private String skullSplitterLengthEndurance;
-
-    private boolean canSkullSplitter;
-    private boolean canCritical;
-    private boolean canBonusDamage;
-    private boolean canImpact;
-    private boolean canGreaterImpact;
-
-    public AxesCommand() {
-        super(SkillType.AXES);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //IMPACT
-        impactDamage = String.valueOf(1 + (skillValue / Axes.impactIncreaseLevel));
-        greaterImpactDamage = String.valueOf(Axes.greaterImpactBonusDamage);
-
-        //SKULL SPLITTER
-        String[] skullSplitterStrings = calculateLengthDisplayValues();
-        skullSplitterLength = skullSplitterStrings[0];
-        skullSplitterLengthEndurance = skullSplitterStrings[1];
-
-        //CRITICAL STRIKES
-        String[] criticalStrikeStrings = calculateAbilityDisplayValues(Axes.criticalHitMaxBonusLevel, Axes.criticalHitMaxChance);
-        critChance = criticalStrikeStrings[0];
-        critChanceLucky = criticalStrikeStrings[1];
-
-        //AXE MASTERY
-        if (skillValue >= Axes.bonusDamageMaxBonusLevel) {
-            bonusDamage = String.valueOf(Axes.bonusDamageMaxBonus);
-        }
-        else {
-            bonusDamage = String.valueOf(skillValue / (Axes.bonusDamageMaxBonusLevel / Axes.bonusDamageMaxBonus));
-        }
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canSkullSplitter = Permissions.skullSplitter(player);
-        canCritical = Permissions.criticalStrikes(player);
-        canBonusDamage = Permissions.bonusDamage(player, skill);
-        canImpact = Permissions.armorImpact(player);
-        canGreaterImpact = Permissions.greaterImpact(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canSkullSplitter || canCritical || canBonusDamage || canImpact || canGreaterImpact;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canSkullSplitter) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.0"), LocaleLoader.getString("Axes.Effect.1")));
-        }
-
-        if (canCritical) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.2"), LocaleLoader.getString("Axes.Effect.3")));
-        }
-
-        if (canBonusDamage) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.4"), LocaleLoader.getString("Axes.Effect.5")));
-        }
-
-        if (canImpact) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.6"), LocaleLoader.getString("Axes.Effect.7")));
-        }
-
-        if (canGreaterImpact) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.8"), LocaleLoader.getString("Axes.Effect.9")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canSkullSplitter || canCritical || canBonusDamage || canImpact || canGreaterImpact;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canBonusDamage) {
-            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.0"), LocaleLoader.getString("Axes.Ability.Bonus.1", bonusDamage)));
-        }
-
-        if (canImpact) {
-            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.2"), LocaleLoader.getString("Axes.Ability.Bonus.3", impactDamage)));
-        }
-
-        if (canGreaterImpact) {
-            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.4"), LocaleLoader.getString("Axes.Ability.Bonus.5", greaterImpactDamage)));
-        }
-
-        if (canCritical) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Axes.Combat.CritChance", critChance) + LocaleLoader.getString("Perks.lucky.bonus", critChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Axes.Combat.CritChance", critChance));
-            }
-        }
-
-        if (canSkullSplitter) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Axes.Combat.SS.Length", skullSplitterLength) + LocaleLoader.getString("Perks.activationtime.bonus", skullSplitterLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Axes.Combat.SS.Length", skullSplitterLength));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.axes.Axes;
+import com.gmail.nossr50.util.Permissions;
+
+public class AxesCommand extends SkillCommand {
+    private String critChance;
+    private String critChanceLucky;
+    private String bonusDamage;
+    private String impactDamage;
+    private String greaterImpactDamage;
+    private String skullSplitterLength;
+    private String skullSplitterLengthEndurance;
+
+    private boolean canSkullSplitter;
+    private boolean canCritical;
+    private boolean canBonusDamage;
+    private boolean canImpact;
+    private boolean canGreaterImpact;
+
+    public AxesCommand() {
+        super(SkillType.AXES);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // IMPACT
+        impactDamage = String.valueOf(1 + (skillValue / Axes.impactIncreaseLevel));
+        greaterImpactDamage = String.valueOf(Axes.greaterImpactBonusDamage);
+
+        // SKULL SPLITTER
+        String[] skullSplitterStrings = calculateLengthDisplayValues();
+        skullSplitterLength = skullSplitterStrings[0];
+        skullSplitterLengthEndurance = skullSplitterStrings[1];
+
+        // CRITICAL STRIKES
+        String[] criticalStrikeStrings = calculateAbilityDisplayValues(Axes.criticalHitMaxBonusLevel, Axes.criticalHitMaxChance);
+        critChance = criticalStrikeStrings[0];
+        critChanceLucky = criticalStrikeStrings[1];
+
+        // AXE MASTERY
+        if (skillValue >= Axes.bonusDamageMaxBonusLevel) {
+            bonusDamage = String.valueOf(Axes.bonusDamageMaxBonus);
+        }
+        else {
+            bonusDamage = String.valueOf(skillValue / (Axes.bonusDamageMaxBonusLevel / Axes.bonusDamageMaxBonus));
+        }
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canSkullSplitter = Permissions.skullSplitter(player);
+        canCritical = Permissions.criticalStrikes(player);
+        canBonusDamage = Permissions.bonusDamage(player, skill);
+        canImpact = Permissions.armorImpact(player);
+        canGreaterImpact = Permissions.greaterImpact(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canSkullSplitter || canCritical || canBonusDamage || canImpact || canGreaterImpact;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canSkullSplitter) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.0"), LocaleLoader.getString("Axes.Effect.1")));
+        }
+
+        if (canCritical) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.2"), LocaleLoader.getString("Axes.Effect.3")));
+        }
+
+        if (canBonusDamage) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.4"), LocaleLoader.getString("Axes.Effect.5")));
+        }
+
+        if (canImpact) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.6"), LocaleLoader.getString("Axes.Effect.7")));
+        }
+
+        if (canGreaterImpact) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Axes.Effect.8"), LocaleLoader.getString("Axes.Effect.9")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canSkullSplitter || canCritical || canBonusDamage || canImpact || canGreaterImpact;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canBonusDamage) {
+            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.0"), LocaleLoader.getString("Axes.Ability.Bonus.1", bonusDamage)));
+        }
+
+        if (canImpact) {
+            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.2"), LocaleLoader.getString("Axes.Ability.Bonus.3", impactDamage)));
+        }
+
+        if (canGreaterImpact) {
+            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Axes.Ability.Bonus.4"), LocaleLoader.getString("Axes.Ability.Bonus.5", greaterImpactDamage)));
+        }
+
+        if (canCritical) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Axes.Combat.CritChance", critChance) + LocaleLoader.getString("Perks.lucky.bonus", critChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Axes.Combat.CritChance", critChance));
+            }
+        }
+
+        if (canSkullSplitter) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Axes.Combat.SS.Length", skullSplitterLength) + LocaleLoader.getString("Perks.activationtime.bonus", skullSplitterLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Axes.Combat.SS.Length", skullSplitterLength));
+            }
+        }
+    }
+}

+ 66 - 67
src/main/java/com/gmail/nossr50/skills/excavation/ExcavationCommand.java → src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java

@@ -1,67 +1,66 @@
-package com.gmail.nossr50.skills.excavation;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class ExcavationCommand extends SkillCommand {
-    private String gigaDrillBreakerLength;
-    private String gigaDrillBreakerLengthEndurance;
-
-    private boolean canGigaDrill;
-    private boolean canTreasureHunt;
-
-    public ExcavationCommand() {
-        super(SkillType.EXCAVATION);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //GIGA DRILL BREAKER
-        String gigaDrillStrings[] = calculateLengthDisplayValues();
-        gigaDrillBreakerLength = gigaDrillStrings[0];
-        gigaDrillBreakerLengthEndurance = gigaDrillStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canGigaDrill = Permissions.gigaDrillBreaker(player);
-        canTreasureHunt = Permissions.excavationTreasureHunter(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canGigaDrill || canTreasureHunt;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canGigaDrill) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Excavation.Effect.0"), LocaleLoader.getString("Excavation.Effect.1")));
-        }
-
-        if (canTreasureHunt) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Excavation.Effect.2"), LocaleLoader.getString("Excavation.Effect.3")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canGigaDrill;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canGigaDrill) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Excavation.Effect.Length", gigaDrillBreakerLength) + LocaleLoader.getString("Perks.activationtime.bonus", gigaDrillBreakerLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Excavation.Effect.Length", gigaDrillBreakerLength));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+
+public class ExcavationCommand extends SkillCommand {
+    private String gigaDrillBreakerLength;
+    private String gigaDrillBreakerLengthEndurance;
+
+    private boolean canGigaDrill;
+    private boolean canTreasureHunt;
+
+    public ExcavationCommand() {
+        super(SkillType.EXCAVATION);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // GIGA DRILL BREAKER
+        String gigaDrillStrings[] = calculateLengthDisplayValues();
+        gigaDrillBreakerLength = gigaDrillStrings[0];
+        gigaDrillBreakerLengthEndurance = gigaDrillStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canGigaDrill = Permissions.gigaDrillBreaker(player);
+        canTreasureHunt = Permissions.excavationTreasureHunter(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canGigaDrill || canTreasureHunt;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canGigaDrill) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Excavation.Effect.0"), LocaleLoader.getString("Excavation.Effect.1")));
+        }
+
+        if (canTreasureHunt) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Excavation.Effect.2"), LocaleLoader.getString("Excavation.Effect.3")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canGigaDrill;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canGigaDrill) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Excavation.Effect.Length", gigaDrillBreakerLength) + LocaleLoader.getString("Perks.activationtime.bonus", gigaDrillBreakerLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Excavation.Effect.Length", gigaDrillBreakerLength));
+            }
+        }
+    }
+}

+ 123 - 123
src/main/java/com/gmail/nossr50/skills/fishing/FishingCommand.java → src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java

@@ -1,125 +1,125 @@
-package com.gmail.nossr50.skills.fishing;
-
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.SkillManagerStore;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class FishingCommand extends SkillCommand {
-    private int lootTier;
-    private String magicChance;
-    private String magicChanceLucky;
-    private String chanceRaining = "";
-    private String shakeChance;
-    private String shakeChanceLucky;
-    private String fishermansDietRank;
-
-    private boolean canTreasureHunt;
-    private boolean canMagicHunt;
-    private boolean canShake;
-    private boolean canFishermansDiet;
-
-    public FishingCommand() {
-        super(SkillType.FISHING);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        lootTier = SkillManagerStore.getInstance().getFishingManager(player.getName()).getLootTier();
-
-        //TREASURE HUNTER
-        double enchantChance = lootTier * AdvancedConfig.getInstance().getFishingMagicMultiplier();
-
-        if (player.getWorld().hasStorm()) {
-            chanceRaining = LocaleLoader.getString("Fishing.Chance.Raining");
-            enchantChance = enchantChance * 1.1D;
-        }
-
-        String[] treasureHunterStrings = calculateAbilityDisplayValues(enchantChance);
-        magicChance = treasureHunterStrings[0];
-        magicChanceLucky = treasureHunterStrings[1];
-
-        //SHAKE
-        String[] shakeStrings = calculateAbilityDisplayValues(SkillManagerStore.getInstance().getFishingManager(player.getName()).getShakeProbability());
-        shakeChance = shakeStrings[0];
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.SkillManagerStore;
+import com.gmail.nossr50.skills.fishing.Fishing;
+import com.gmail.nossr50.util.Permissions;
+
+public class FishingCommand extends SkillCommand {
+    private int lootTier;
+    private String magicChance;
+    private String magicChanceLucky;
+    private String chanceRaining = "";
+    private String shakeChance;
+    private String shakeChanceLucky;
+    private String fishermansDietRank;
+
+    private boolean canTreasureHunt;
+    private boolean canMagicHunt;
+    private boolean canShake;
+    private boolean canFishermansDiet;
+
+    public FishingCommand() {
+        super(SkillType.FISHING);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        lootTier = SkillManagerStore.getInstance().getFishingManager(player.getName()).getLootTier();
+
+        // TREASURE HUNTER
+        double enchantChance = lootTier * AdvancedConfig.getInstance().getFishingMagicMultiplier();
+
+        if (player.getWorld().hasStorm()) {
+            chanceRaining = LocaleLoader.getString("Fishing.Chance.Raining");
+            enchantChance = enchantChance * 1.1D;
+        }
+
+        String[] treasureHunterStrings = calculateAbilityDisplayValues(enchantChance);
+        magicChance = treasureHunterStrings[0];
+        magicChanceLucky = treasureHunterStrings[1];
+
+        // SHAKE
+        String[] shakeStrings = calculateAbilityDisplayValues(SkillManagerStore.getInstance().getFishingManager(player.getName()).getShakeProbability());
+        shakeChance = shakeStrings[0];
         shakeChanceLucky = shakeStrings[1];
-
-        //FISHERMAN'S DIET
+
+        // FISHERMAN'S DIET
         fishermansDietRank = calculateRank(Fishing.fishermansDietMaxLevel, Fishing.fishermansDietRankLevel1);
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canTreasureHunt = Permissions.fishingTreasureHunter(player);
-        canMagicHunt = Permissions.magicHunter(player);
-        canShake = Permissions.shake(player);
-        canFishermansDiet = Permissions.fishermansDiet(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canTreasureHunt || canMagicHunt || canShake;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canTreasureHunt) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.0"), LocaleLoader.getString("Fishing.Effect.1")));
-        }
-
-        if (canMagicHunt) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.2"), LocaleLoader.getString("Fishing.Effect.3")));
-        }
-
-        if (canShake) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.4"), LocaleLoader.getString("Fishing.Effect.5")));
-        }
-
-        if (canFishermansDiet) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.6"), LocaleLoader.getString("Fishing.Effect.7")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canTreasureHunt || canMagicHunt || canShake;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canTreasureHunt) {
-            player.sendMessage(LocaleLoader.getString("Fishing.Ability.Rank", lootTier));
-        }
-
-        if (canMagicHunt) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Fishing.Enchant.Chance", magicChance) + chanceRaining +  LocaleLoader.getString("Perks.lucky.bonus", magicChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Fishing.Enchant.Chance", magicChance) + chanceRaining);
-            }
-        }
-
-        if (canShake) {
-            if (skillValue < AdvancedConfig.getInstance().getShakeUnlockLevel()) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Fishing.Ability.Locked.0", AdvancedConfig.getInstance().getShakeUnlockLevel())));
-            }
-            else {
-                if (isLucky) {
-                    player.sendMessage(LocaleLoader.getString("Fishing.Ability.Shake", shakeChance) + LocaleLoader.getString("Perks.lucky.bonus", shakeChanceLucky));
-                }
-                else {
-                    player.sendMessage(LocaleLoader.getString("Fishing.Ability.Shake", shakeChance));
-                }
-            }
-        }
-
-        if (canFishermansDiet) {
-            player.sendMessage(LocaleLoader.getString("Fishing.Ability.FD", fishermansDietRank));
-        }
-    }
-}
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canTreasureHunt = Permissions.fishingTreasureHunter(player);
+        canMagicHunt = Permissions.magicHunter(player);
+        canShake = Permissions.shake(player);
+        canFishermansDiet = Permissions.fishermansDiet(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canTreasureHunt || canMagicHunt || canShake;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canTreasureHunt) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.0"), LocaleLoader.getString("Fishing.Effect.1")));
+        }
+
+        if (canMagicHunt) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.2"), LocaleLoader.getString("Fishing.Effect.3")));
+        }
+
+        if (canShake) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.4"), LocaleLoader.getString("Fishing.Effect.5")));
+        }
+
+        if (canFishermansDiet) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Fishing.Effect.6"), LocaleLoader.getString("Fishing.Effect.7")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canTreasureHunt || canMagicHunt || canShake;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canTreasureHunt) {
+            player.sendMessage(LocaleLoader.getString("Fishing.Ability.Rank", lootTier));
+        }
+
+        if (canMagicHunt) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Fishing.Enchant.Chance", magicChance) + chanceRaining + LocaleLoader.getString("Perks.lucky.bonus", magicChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Fishing.Enchant.Chance", magicChance) + chanceRaining);
+            }
+        }
+
+        if (canShake) {
+            if (skillValue < AdvancedConfig.getInstance().getShakeUnlockLevel()) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Fishing.Ability.Locked.0", AdvancedConfig.getInstance().getShakeUnlockLevel())));
+            }
+            else {
+                if (isLucky) {
+                    player.sendMessage(LocaleLoader.getString("Fishing.Ability.Shake", shakeChance) + LocaleLoader.getString("Perks.lucky.bonus", shakeChanceLucky));
+                }
+                else {
+                    player.sendMessage(LocaleLoader.getString("Fishing.Ability.Shake", shakeChance));
+                }
+            }
+        }
+
+        if (canFishermansDiet) {
+            player.sendMessage(LocaleLoader.getString("Fishing.Ability.FD", fishermansDietRank));
+        }
+    }
+}

+ 180 - 180
src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismCommand.java → src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java

@@ -1,180 +1,180 @@
-package com.gmail.nossr50.skills.herbalism;
-
-import org.bukkit.Material;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class HerbalismCommand extends SkillCommand {
-    private String greenTerraLength;
-    private String greenTerraLengthEndurance;
-    private String greenThumbChance;
-    private String greenThumbChanceLucky;
-    private String greenThumbStage;
-    private String farmersDietRank;
-    private String doubleDropChance;
-    private String doubleDropChanceLucky;
-    private String hylianLuckChance;
-    private String hylianLuckChanceLucky;
-    private String shroomThumbChance;
-    private String shroomThumbChanceLucky;
-
-    private boolean hasHylianLuck;
-    private boolean canGreenTerra;
-    private boolean canGreenThumbWheat;
-    private boolean canGreenThumbBlocks;
-    private boolean canFarmersDiet;
-    private boolean canDoubleDrop;
-    private boolean canShroomThumb;
-    private boolean doubleDropsDisabled;
-
-    public HerbalismCommand() {
-        super(SkillType.HERBALISM);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //GREEN TERRA
-        String[] greenTerraStrings = calculateLengthDisplayValues();
-        greenTerraLength = greenTerraStrings[0];
-        greenTerraLengthEndurance = greenTerraStrings[1];
-
-        //FARMERS DIET
-        farmersDietRank = calculateRank(Herbalism.farmersDietMaxLevel, Herbalism.farmersDietRankLevel1);
-
-        //GREEN THUMB
-        greenThumbStage = calculateRank(Herbalism.greenThumbStageMaxLevel, Herbalism.greenThumbStageChangeLevel);
-
-        String[] greenThumbStrings = calculateAbilityDisplayValues(Herbalism.greenThumbMaxLevel, Herbalism.greenThumbMaxChance);
-        greenThumbChance = greenThumbStrings[0];
-        greenThumbChanceLucky = greenThumbStrings[1];
-
-        //DOUBLE DROPS
-        String[] doubleDropStrings = calculateAbilityDisplayValues(Herbalism.doubleDropsMaxLevel, Herbalism.doubleDropsMaxChance);
-        doubleDropChance = doubleDropStrings[0];
-        doubleDropChanceLucky = doubleDropStrings[1];
-
-        //HYLIAN LUCK
-        String[] hylianLuckStrings = calculateAbilityDisplayValues(Herbalism.hylianLuckMaxLevel, Herbalism.hylianLuckMaxChance);
-        hylianLuckChance = hylianLuckStrings[0];
-        hylianLuckChanceLucky = hylianLuckStrings[1];
-
-        //SHROOM THUMB
-        String[] shroomThumbStrings = calculateAbilityDisplayValues(Herbalism.shroomThumbMaxLevel, Herbalism.shroomThumbMaxChance);
-        shroomThumbChance = shroomThumbStrings[0];
-        shroomThumbChanceLucky = shroomThumbStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        hasHylianLuck = Permissions.hylianLuck(player);
-        canGreenTerra = Permissions.greenTerra(player);
-        canGreenThumbWheat = Permissions.greenThumbPlant(player, Material.CROPS); //TODO: This isn't really accurate - they could have perms for other crops but not wheat.
-        canGreenThumbBlocks = (Permissions.greenThumbBlock(player, Material.DIRT) || Permissions.greenThumbBlock(player, Material.COBBLESTONE) || Permissions.greenThumbBlock(player, Material.COBBLE_WALL) || Permissions.greenThumbBlock(player, Material.SMOOTH_BRICK));
-        canFarmersDiet = Permissions.farmersDiet(player);
-        canDoubleDrop = Permissions.doubleDrops(player, skill);
-        doubleDropsDisabled = skill.getDoubleDropsDisabled();
-        canShroomThumb = Permissions.shroomThumb(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canGreenTerra || (canDoubleDrop && !doubleDropsDisabled) || canFarmersDiet || canGreenThumbBlocks || canGreenThumbWheat || canShroomThumb;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canGreenTerra) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.0"), LocaleLoader.getString("Herbalism.Effect.1")));
-        }
-
-        if (canGreenThumbWheat) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.2"), LocaleLoader.getString("Herbalism.Effect.3")));
-        }
-
-        if (canGreenThumbBlocks) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.4"), LocaleLoader.getString("Herbalism.Effect.5")));
-        }
-
-        if (canFarmersDiet) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.6"), LocaleLoader.getString("Herbalism.Effect.7")));
-        }
-
-        if (hasHylianLuck) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.10"), LocaleLoader.getString("Herbalism.Effect.11")));
-        }
-
-        if (canShroomThumb) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.12"), LocaleLoader.getString("Herbalism.Effect.13")));
-        }
-
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.8"), LocaleLoader.getString("Herbalism.Effect.9")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canGreenTerra || (canDoubleDrop && !doubleDropsDisabled) || canFarmersDiet || canGreenThumbBlocks || canGreenThumbWheat || canShroomThumb;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canGreenTerra) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTe.Length", greenTerraLength) + LocaleLoader.getString("Perks.activationtime.bonus", greenTerraLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTe.Length", greenTerraLength));
-            }
-        }
-
-        if (canGreenThumbBlocks || canGreenThumbWheat) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Chance", greenThumbChance) + LocaleLoader.getString("Perks.lucky.bonus", greenThumbChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Chance", greenThumbChance));
-            }
-        }
-
-        if (canGreenThumbWheat) {
-            player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Stage", greenThumbStage));
-        }
-
-        if (canFarmersDiet) {
-            player.sendMessage(LocaleLoader.getString("Herbalism.Ability.FD", farmersDietRank));
-        }
-
-        if (hasHylianLuck) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.HylianLuck", hylianLuckChance) + LocaleLoader.getString("Perks.lucky.bonus", hylianLuckChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.HylianLuck", hylianLuckChance));
-            }
-        }
-
-        if (canShroomThumb) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.ShroomThumb.Chance", shroomThumbChance) + LocaleLoader.getString("Perks.lucky.bonus", shroomThumbChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.ShroomThumb.Chance", shroomThumbChance));
-            }
-        }
-
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.DoubleDropChance", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.DoubleDropChance", doubleDropChance));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import org.bukkit.Material;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.herbalism.Herbalism;
+import com.gmail.nossr50.util.Permissions;
+
+public class HerbalismCommand extends SkillCommand {
+    private String greenTerraLength;
+    private String greenTerraLengthEndurance;
+    private String greenThumbChance;
+    private String greenThumbChanceLucky;
+    private String greenThumbStage;
+    private String farmersDietRank;
+    private String doubleDropChance;
+    private String doubleDropChanceLucky;
+    private String hylianLuckChance;
+    private String hylianLuckChanceLucky;
+    private String shroomThumbChance;
+    private String shroomThumbChanceLucky;
+
+    private boolean hasHylianLuck;
+    private boolean canGreenTerra;
+    private boolean canGreenThumbWheat;
+    private boolean canGreenThumbBlocks;
+    private boolean canFarmersDiet;
+    private boolean canDoubleDrop;
+    private boolean canShroomThumb;
+    private boolean doubleDropsDisabled;
+
+    public HerbalismCommand() {
+        super(SkillType.HERBALISM);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // GREEN TERRA
+        String[] greenTerraStrings = calculateLengthDisplayValues();
+        greenTerraLength = greenTerraStrings[0];
+        greenTerraLengthEndurance = greenTerraStrings[1];
+
+        // FARMERS DIET
+        farmersDietRank = calculateRank(Herbalism.farmersDietMaxLevel, Herbalism.farmersDietRankLevel1);
+
+        // GREEN THUMB
+        greenThumbStage = calculateRank(Herbalism.greenThumbStageMaxLevel, Herbalism.greenThumbStageChangeLevel);
+
+        String[] greenThumbStrings = calculateAbilityDisplayValues(Herbalism.greenThumbMaxLevel, Herbalism.greenThumbMaxChance);
+        greenThumbChance = greenThumbStrings[0];
+        greenThumbChanceLucky = greenThumbStrings[1];
+
+        // DOUBLE DROPS
+        String[] doubleDropStrings = calculateAbilityDisplayValues(Herbalism.doubleDropsMaxLevel, Herbalism.doubleDropsMaxChance);
+        doubleDropChance = doubleDropStrings[0];
+        doubleDropChanceLucky = doubleDropStrings[1];
+
+        // HYLIAN LUCK
+        String[] hylianLuckStrings = calculateAbilityDisplayValues(Herbalism.hylianLuckMaxLevel, Herbalism.hylianLuckMaxChance);
+        hylianLuckChance = hylianLuckStrings[0];
+        hylianLuckChanceLucky = hylianLuckStrings[1];
+
+        // SHROOM THUMB
+        String[] shroomThumbStrings = calculateAbilityDisplayValues(Herbalism.shroomThumbMaxLevel, Herbalism.shroomThumbMaxChance);
+        shroomThumbChance = shroomThumbStrings[0];
+        shroomThumbChanceLucky = shroomThumbStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        hasHylianLuck = Permissions.hylianLuck(player);
+        canGreenTerra = Permissions.greenTerra(player);
+        canGreenThumbWheat = Permissions.greenThumbPlant(player, Material.CROPS); // TODO: This isn't really accurate - they could have perms for other crops but not wheat.
+        canGreenThumbBlocks = (Permissions.greenThumbBlock(player, Material.DIRT) || Permissions.greenThumbBlock(player, Material.COBBLESTONE) || Permissions.greenThumbBlock(player, Material.COBBLE_WALL) || Permissions.greenThumbBlock(player, Material.SMOOTH_BRICK));
+        canFarmersDiet = Permissions.farmersDiet(player);
+        canDoubleDrop = Permissions.doubleDrops(player, skill);
+        doubleDropsDisabled = skill.getDoubleDropsDisabled();
+        canShroomThumb = Permissions.shroomThumb(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canGreenTerra || (canDoubleDrop && !doubleDropsDisabled) || canFarmersDiet || canGreenThumbBlocks || canGreenThumbWheat || canShroomThumb;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canGreenTerra) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.0"), LocaleLoader.getString("Herbalism.Effect.1")));
+        }
+
+        if (canGreenThumbWheat) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.2"), LocaleLoader.getString("Herbalism.Effect.3")));
+        }
+
+        if (canGreenThumbBlocks) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.4"), LocaleLoader.getString("Herbalism.Effect.5")));
+        }
+
+        if (canFarmersDiet) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.6"), LocaleLoader.getString("Herbalism.Effect.7")));
+        }
+
+        if (hasHylianLuck) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.10"), LocaleLoader.getString("Herbalism.Effect.11")));
+        }
+
+        if (canShroomThumb) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.12"), LocaleLoader.getString("Herbalism.Effect.13")));
+        }
+
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Herbalism.Effect.8"), LocaleLoader.getString("Herbalism.Effect.9")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canGreenTerra || (canDoubleDrop && !doubleDropsDisabled) || canFarmersDiet || canGreenThumbBlocks || canGreenThumbWheat || canShroomThumb;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canGreenTerra) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTe.Length", greenTerraLength) + LocaleLoader.getString("Perks.activationtime.bonus", greenTerraLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTe.Length", greenTerraLength));
+            }
+        }
+
+        if (canGreenThumbBlocks || canGreenThumbWheat) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Chance", greenThumbChance) + LocaleLoader.getString("Perks.lucky.bonus", greenThumbChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Chance", greenThumbChance));
+            }
+        }
+
+        if (canGreenThumbWheat) {
+            player.sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Stage", greenThumbStage));
+        }
+
+        if (canFarmersDiet) {
+            player.sendMessage(LocaleLoader.getString("Herbalism.Ability.FD", farmersDietRank));
+        }
+
+        if (hasHylianLuck) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.HylianLuck", hylianLuckChance) + LocaleLoader.getString("Perks.lucky.bonus", hylianLuckChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.HylianLuck", hylianLuckChance));
+            }
+        }
+
+        if (canShroomThumb) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.ShroomThumb.Chance", shroomThumbChance) + LocaleLoader.getString("Perks.lucky.bonus", shroomThumbChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.ShroomThumb.Chance", shroomThumbChance));
+            }
+        }
+
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.DoubleDropChance", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Herbalism.Ability.DoubleDropChance", doubleDropChance));
+            }
+        }
+    }
+}

+ 149 - 148
src/main/java/com/gmail/nossr50/skills/mining/MiningCommand.java → src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java

@@ -1,148 +1,149 @@
-package com.gmail.nossr50.skills.mining;
-
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.SkillManagerStore;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class MiningCommand extends SkillCommand {
-    private String doubleDropChance;
-    private String doubleDropChanceLucky;
-    private String superBreakerLength;
-    private String superBreakerLengthEndurance;
-
-    private int blastMiningRank;
-    private int bonusTNTDrops;
-    private double blastRadiusIncrease;
-    private String oreBonus;
-    private String debrisReduction;
-    private String blastDamageDecrease;
-
-    private boolean canSuperBreaker;
-    private boolean canDoubleDrop;
-    private boolean canBlast;
-    private boolean canBiggerBombs;
-    private boolean canDemoExpert;
-    private boolean doubleDropsDisabled;
-
-    public MiningCommand() {
-        super(SkillType.MINING);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //SUPER BREAKER
-        String[] superBreakerStrings = calculateLengthDisplayValues();
-        superBreakerLength = superBreakerStrings[0];
-        superBreakerLengthEndurance = superBreakerStrings[1];
-
-        //DOUBLE DROPS
-        String[] doubleDropStrings = calculateAbilityDisplayValues(Mining.doubleDropsMaxLevel, Mining.doubleDropsMaxChance);
-        doubleDropChance = doubleDropStrings[0];
-        doubleDropChanceLucky = doubleDropStrings[1];
-
-        //BLAST MINING
-        MiningManager miningManager = SkillManagerStore.getInstance().getMiningManager(player.getName());
-        blastMiningRank = miningManager.getBlastMiningTier();
-        bonusTNTDrops = miningManager.getDropMultiplier();
-        oreBonus = percent.format(miningManager.getOreBonus() / 30.0D); // Base received in TNT is 30%
-        debrisReduction = percent.format(miningManager.getDebrisReduction() / 30.0D); // Base received in TNT is 30%
-        blastDamageDecrease = percent.format(miningManager.getBlastDamageModifier() / 100.0D);
-        blastRadiusIncrease = miningManager.getBlastRadiusModifier();
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canBiggerBombs = Permissions.biggerBombs(player);
-        canBlast = Permissions.remoteDetonation(player);
-        canDemoExpert = Permissions.demolitionsExpertise(player);
-        canDoubleDrop = Permissions.doubleDrops(player, skill);
-        canSuperBreaker = Permissions.superBreaker(player);
-        doubleDropsDisabled = skill.getDoubleDropsDisabled();
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canBiggerBombs || canBlast || canDemoExpert || (canDoubleDrop && !doubleDropsDisabled) || canSuperBreaker;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canSuperBreaker) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.0"), LocaleLoader.getString("Mining.Effect.1")));
-        }
-
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.2"), LocaleLoader.getString("Mining.Effect.3")));
-        }
-
-        if (canBlast) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.4"), LocaleLoader.getString("Mining.Effect.5")));
-        }
-
-        if (canBiggerBombs) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.6"), LocaleLoader.getString("Mining.Effect.7")));
-        }
-
-        if (canDemoExpert) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.8"), LocaleLoader.getString("Mining.Effect.9")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canBiggerBombs || canBlast || canDemoExpert || (canDoubleDrop && !doubleDropsDisabled) || canSuperBreaker;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Mining.Effect.DropChance", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Mining.Effect.DropChance", doubleDropChance));
-            }
-        }
-
-        if (canSuperBreaker) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Mining.Ability.Length", superBreakerLength) + LocaleLoader.getString("Perks.activationtime.bonus", superBreakerLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Mining.Ability.Length", superBreakerLength));
-            }
-        }
-
-        if (canBlast) {
-            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank1()) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.0", AdvancedConfig.getInstance().getBlastMiningRank1())));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Mining.Blast.Rank", blastMiningRank, LocaleLoader.getString("Mining.Blast.Effect", oreBonus, debrisReduction, bonusTNTDrops)));
-            }
-        }
-
-        if (canBiggerBombs) {
-            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank2()) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.1", AdvancedConfig.getInstance().getBlastMiningRank2())));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Mining.Blast.Radius.Increase", blastRadiusIncrease));
-            }
-        }
-
-        if (canDemoExpert) {
-            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank4()) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.2", AdvancedConfig.getInstance().getBlastMiningRank4())));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Mining.Effect.Decrease", blastDamageDecrease));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.SkillManagerStore;
+import com.gmail.nossr50.skills.mining.Mining;
+import com.gmail.nossr50.skills.mining.MiningManager;
+import com.gmail.nossr50.util.Permissions;
+
+public class MiningCommand extends SkillCommand {
+    private String doubleDropChance;
+    private String doubleDropChanceLucky;
+    private String superBreakerLength;
+    private String superBreakerLengthEndurance;
+
+    private int blastMiningRank;
+    private int bonusTNTDrops;
+    private double blastRadiusIncrease;
+    private String oreBonus;
+    private String debrisReduction;
+    private String blastDamageDecrease;
+
+    private boolean canSuperBreaker;
+    private boolean canDoubleDrop;
+    private boolean canBlast;
+    private boolean canBiggerBombs;
+    private boolean canDemoExpert;
+    private boolean doubleDropsDisabled;
+
+    public MiningCommand() {
+        super(SkillType.MINING);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // SUPER BREAKER
+        String[] superBreakerStrings = calculateLengthDisplayValues();
+        superBreakerLength = superBreakerStrings[0];
+        superBreakerLengthEndurance = superBreakerStrings[1];
+
+        // DOUBLE DROPS
+        String[] doubleDropStrings = calculateAbilityDisplayValues(Mining.doubleDropsMaxLevel, Mining.doubleDropsMaxChance);
+        doubleDropChance = doubleDropStrings[0];
+        doubleDropChanceLucky = doubleDropStrings[1];
+
+        // BLAST MINING
+        MiningManager miningManager = SkillManagerStore.getInstance().getMiningManager(player.getName());
+        blastMiningRank = miningManager.getBlastMiningTier();
+        bonusTNTDrops = miningManager.getDropMultiplier();
+        oreBonus = percent.format(miningManager.getOreBonus() / 30.0D); // Base received in TNT is 30%
+        debrisReduction = percent.format(miningManager.getDebrisReduction() / 30.0D); // Base received in TNT is 30%
+        blastDamageDecrease = percent.format(miningManager.getBlastDamageModifier() / 100.0D);
+        blastRadiusIncrease = miningManager.getBlastRadiusModifier();
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canBiggerBombs = Permissions.biggerBombs(player);
+        canBlast = Permissions.remoteDetonation(player);
+        canDemoExpert = Permissions.demolitionsExpertise(player);
+        canDoubleDrop = Permissions.doubleDrops(player, skill);
+        canSuperBreaker = Permissions.superBreaker(player);
+        doubleDropsDisabled = skill.getDoubleDropsDisabled();
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canBiggerBombs || canBlast || canDemoExpert || (canDoubleDrop && !doubleDropsDisabled) || canSuperBreaker;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canSuperBreaker) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.0"), LocaleLoader.getString("Mining.Effect.1")));
+        }
+
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.2"), LocaleLoader.getString("Mining.Effect.3")));
+        }
+
+        if (canBlast) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.4"), LocaleLoader.getString("Mining.Effect.5")));
+        }
+
+        if (canBiggerBombs) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.6"), LocaleLoader.getString("Mining.Effect.7")));
+        }
+
+        if (canDemoExpert) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Mining.Effect.8"), LocaleLoader.getString("Mining.Effect.9")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canBiggerBombs || canBlast || canDemoExpert || (canDoubleDrop && !doubleDropsDisabled) || canSuperBreaker;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Mining.Effect.DropChance", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Mining.Effect.DropChance", doubleDropChance));
+            }
+        }
+
+        if (canSuperBreaker) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Mining.Ability.Length", superBreakerLength) + LocaleLoader.getString("Perks.activationtime.bonus", superBreakerLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Mining.Ability.Length", superBreakerLength));
+            }
+        }
+
+        if (canBlast) {
+            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank1()) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.0", AdvancedConfig.getInstance().getBlastMiningRank1())));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Mining.Blast.Rank", blastMiningRank, LocaleLoader.getString("Mining.Blast.Effect", oreBonus, debrisReduction, bonusTNTDrops)));
+            }
+        }
+
+        if (canBiggerBombs) {
+            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank2()) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.1", AdvancedConfig.getInstance().getBlastMiningRank2())));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Mining.Blast.Radius.Increase", blastRadiusIncrease));
+            }
+        }
+
+        if (canDemoExpert) {
+            if (skillValue < AdvancedConfig.getInstance().getBlastMiningRank4()) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Mining.Ability.Locked.2", AdvancedConfig.getInstance().getBlastMiningRank4())));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Mining.Effect.Decrease", blastDamageDecrease));
+            }
+        }
+    }
+}

+ 166 - 164
src/main/java/com/gmail/nossr50/skills/repair/RepairCommand.java → src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java

@@ -1,164 +1,166 @@
-package com.gmail.nossr50.skills.repair;
-
-import org.bukkit.Material;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class RepairCommand extends SkillCommand {
-    private int arcaneForgingRank;
-    private String repairMasteryBonus;
-    private String superRepairChance;
-    private String superRepairChanceLucky;
-
-    private boolean canSuperRepair;
-    private boolean canMasterRepair;
-    private boolean canArcaneForge;
-    private boolean canSalvage;
-    private boolean canRepairStone;
-    private boolean canRepairIron;
-    private boolean canRepairGold;
-    private boolean canRepairDiamond;
-    private boolean canRepairString;
-    private boolean canRepairLeather;
-    private boolean canRepairWood;
-    private boolean arcaneBypass;
-
-    private int diamondLevel;
-    private int goldLevel;
-    private int ironLevel;
-    private int stoneLevel;
-
-    public RepairCommand() {
-        super(SkillType.REPAIR);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        // We're using pickaxes here, not the best but it works
-        Repairable diamondRepairable = mcMMO.repairManager.getRepairable(Material.DIAMOND_PICKAXE.getId());
-        Repairable goldRepairable = mcMMO.repairManager.getRepairable(Material.GOLD_PICKAXE.getId());
-        Repairable ironRepairable = mcMMO.repairManager.getRepairable(Material.IRON_PICKAXE.getId());
-        Repairable stoneRepairable = mcMMO.repairManager.getRepairable(Material.STONE_PICKAXE.getId());
-
-        //TODO: This isn't really accurate - if they don't have pickaxes loaded it doesn't always mean the repair level is 0
-        diamondLevel = (diamondRepairable == null) ? 0 : diamondRepairable.getMinimumLevel();
-        goldLevel = (goldRepairable == null) ? 0 : goldRepairable.getMinimumLevel();
-        ironLevel = (ironRepairable == null) ? 0 : ironRepairable.getMinimumLevel();
-        stoneLevel = (stoneRepairable == null) ? 0 : stoneRepairable.getMinimumLevel();
-
-        //REPAIR MASTERY
-        if (skillValue >= Repair.REPAIR_MASTERY_MAX_BONUS_LEVEL) {
-            repairMasteryBonus = percent.format(Repair.REPAIR_MASTERY_CHANCE_MAX / 100D);
-        }
-        else {
-            repairMasteryBonus = percent.format((( Repair.REPAIR_MASTERY_CHANCE_MAX / Repair.REPAIR_MASTERY_MAX_BONUS_LEVEL) * skillValue) / 100D);
-        }
-
-        //SUPER REPAIR
-        String[] superRepairStrings = calculateAbilityDisplayValues(Repair.SUPER_REPAIR_MAX_BONUS_LEVEL, Repair.SUPER_REPAIR_CHANCE_MAX);
-        superRepairChance = superRepairStrings[0];
-        superRepairChanceLucky = superRepairStrings[1];
-
-        //ARCANE FORGING
-        arcaneForgingRank = Repair.getArcaneForgingRank(profile);
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canSuperRepair = Permissions.superRepair(player);
-        canMasterRepair = Permissions.repairMastery(player);
-        canArcaneForge = Permissions.arcaneForging(player);
-        canSalvage = Permissions.salvage(player);
-        canRepairDiamond = Permissions.repairDiamond(player);
-        canRepairGold = Permissions.repairGold(player);
-        canRepairIron = Permissions.repairIron(player);
-        canRepairStone = Permissions.repairStone(player);
-        canRepairString = Permissions.repairString(player);
-        canRepairLeather = Permissions.repairLeather(player);
-        canRepairWood = Permissions.repairWood(player);
-        arcaneBypass = Permissions.arcaneBypass(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canArcaneForge || canSalvage || canRepairDiamond || canRepairGold || canRepairIron || canMasterRepair || canRepairStone || canSuperRepair || canRepairString || canRepairWood || canRepairLeather;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.0"), LocaleLoader.getString("Repair.Effect.1")));
-
-        if (canMasterRepair) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.2"), LocaleLoader.getString("Repair.Effect.3")));
-        }
-
-        if (canSuperRepair) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.4"), LocaleLoader.getString("Repair.Effect.5")));
-        }
-
-        /* Repair Level Requirements */
-
-        if (canRepairStone && stoneLevel > 0) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.14", stoneLevel), LocaleLoader.getString("Repair.Effect.15")));
-        }
-
-        if (canRepairIron && ironLevel > 0) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.12", ironLevel), LocaleLoader.getString("Repair.Effect.13")));
-        }
-
-        if (canRepairGold && goldLevel > 0) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.10", goldLevel), LocaleLoader.getString("Repair.Effect.11")));
-        }
-
-        if (canRepairDiamond && diamondLevel > 0) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.6", diamondLevel), LocaleLoader.getString("Repair.Effect.7")));
-        }
-
-        if (canSalvage && Salvage.salvageUnlockLevel > 0) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.16", Salvage.salvageUnlockLevel), LocaleLoader.getString("Repair.Effect.17")));
-        }
-
-        if (canArcaneForge) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.8"), LocaleLoader.getString("Repair.Effect.9")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canArcaneForge || canMasterRepair || canSuperRepair;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canMasterRepair) {
-            player.sendMessage(LocaleLoader.getString("Repair.Skills.Mastery", repairMasteryBonus));
-        }
-
-        if (canSuperRepair) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Repair.Skills.Super.Chance", superRepairChance) + LocaleLoader.getString("Perks.lucky.bonus", superRepairChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Repair.Skills.Super.Chance", superRepairChance));
-            }
-        }
-
-        if (canArcaneForge) {
-            player.sendMessage(LocaleLoader.getString("Repair.Arcane.Rank", arcaneForgingRank));
-
-            if (Repair.arcaneForgingEnchantLoss) {
-                player.sendMessage(LocaleLoader.getString("Repair.Arcane.Chance.Success", (arcaneBypass ? 100 : Repair.getEnchantChance(arcaneForgingRank))));
-            }
-
-            if (Repair.arcaneForgingDowngrades) {
-                player.sendMessage(LocaleLoader.getString("Repair.Arcane.Chance.Downgrade", (arcaneBypass ? 0 : Repair.getDowngradeChance(arcaneForgingRank))));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import org.bukkit.Material;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.repair.Repair;
+import com.gmail.nossr50.skills.repair.Repairable;
+import com.gmail.nossr50.skills.repair.Salvage;
+import com.gmail.nossr50.util.Permissions;
+
+public class RepairCommand extends SkillCommand {
+    private int arcaneForgingRank;
+    private String repairMasteryBonus;
+    private String superRepairChance;
+    private String superRepairChanceLucky;
+
+    private boolean canSuperRepair;
+    private boolean canMasterRepair;
+    private boolean canArcaneForge;
+    private boolean canSalvage;
+    private boolean canRepairStone;
+    private boolean canRepairIron;
+    private boolean canRepairGold;
+    private boolean canRepairDiamond;
+    private boolean canRepairString;
+    private boolean canRepairLeather;
+    private boolean canRepairWood;
+    private boolean arcaneBypass;
+
+    private int diamondLevel;
+    private int goldLevel;
+    private int ironLevel;
+    private int stoneLevel;
+
+    public RepairCommand() {
+        super(SkillType.REPAIR);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // We're using pickaxes here, not the best but it works
+        Repairable diamondRepairable = mcMMO.repairManager.getRepairable(Material.DIAMOND_PICKAXE.getId());
+        Repairable goldRepairable = mcMMO.repairManager.getRepairable(Material.GOLD_PICKAXE.getId());
+        Repairable ironRepairable = mcMMO.repairManager.getRepairable(Material.IRON_PICKAXE.getId());
+        Repairable stoneRepairable = mcMMO.repairManager.getRepairable(Material.STONE_PICKAXE.getId());
+
+        // TODO: This isn't really accurate - if they don't have pickaxes loaded it doesn't always mean the repair level is 0
+        diamondLevel = (diamondRepairable == null) ? 0 : diamondRepairable.getMinimumLevel();
+        goldLevel = (goldRepairable == null) ? 0 : goldRepairable.getMinimumLevel();
+        ironLevel = (ironRepairable == null) ? 0 : ironRepairable.getMinimumLevel();
+        stoneLevel = (stoneRepairable == null) ? 0 : stoneRepairable.getMinimumLevel();
+
+        // REPAIR MASTERY
+        if (skillValue >= Repair.repairMasteryMaxBonusLevel) {
+            repairMasteryBonus = percent.format(Repair.repairMasteryMaxBonus / 100D);
+        }
+        else {
+            repairMasteryBonus = percent.format(((Repair.repairMasteryMaxBonus / Repair.repairMasteryMaxBonusLevel) * skillValue) / 100D);
+        }
+
+        // SUPER REPAIR
+        String[] superRepairStrings = calculateAbilityDisplayValues(Repair.superRepairMaxBonusLevel, Repair.superRepairMaxChance);
+        superRepairChance = superRepairStrings[0];
+        superRepairChanceLucky = superRepairStrings[1];
+
+        // ARCANE FORGING
+        arcaneForgingRank = Repair.getArcaneForgingRank(profile);
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canSuperRepair = Permissions.superRepair(player);
+        canMasterRepair = Permissions.repairMastery(player);
+        canArcaneForge = Permissions.arcaneForging(player);
+        canSalvage = Permissions.salvage(player);
+        canRepairDiamond = Permissions.repairDiamond(player);
+        canRepairGold = Permissions.repairGold(player);
+        canRepairIron = Permissions.repairIron(player);
+        canRepairStone = Permissions.repairStone(player);
+        canRepairString = Permissions.repairString(player);
+        canRepairLeather = Permissions.repairLeather(player);
+        canRepairWood = Permissions.repairWood(player);
+        arcaneBypass = Permissions.arcaneBypass(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canArcaneForge || canSalvage || canRepairDiamond || canRepairGold || canRepairIron || canMasterRepair || canRepairStone || canSuperRepair || canRepairString || canRepairWood || canRepairLeather;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.0"), LocaleLoader.getString("Repair.Effect.1")));
+
+        if (canMasterRepair) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.2"), LocaleLoader.getString("Repair.Effect.3")));
+        }
+
+        if (canSuperRepair) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.4"), LocaleLoader.getString("Repair.Effect.5")));
+        }
+
+        /* Repair Level Requirements */
+
+        if (canRepairStone && stoneLevel > 0) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.14", stoneLevel), LocaleLoader.getString("Repair.Effect.15")));
+        }
+
+        if (canRepairIron && ironLevel > 0) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.12", ironLevel), LocaleLoader.getString("Repair.Effect.13")));
+        }
+
+        if (canRepairGold && goldLevel > 0) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.10", goldLevel), LocaleLoader.getString("Repair.Effect.11")));
+        }
+
+        if (canRepairDiamond && diamondLevel > 0) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.6", diamondLevel), LocaleLoader.getString("Repair.Effect.7")));
+        }
+
+        if (canSalvage && Salvage.salvageUnlockLevel > 0) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.16", Salvage.salvageUnlockLevel), LocaleLoader.getString("Repair.Effect.17")));
+        }
+
+        if (canArcaneForge) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.8"), LocaleLoader.getString("Repair.Effect.9")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canArcaneForge || canMasterRepair || canSuperRepair;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canMasterRepair) {
+            player.sendMessage(LocaleLoader.getString("Repair.Skills.Mastery", repairMasteryBonus));
+        }
+
+        if (canSuperRepair) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Repair.Skills.Super.Chance", superRepairChance) + LocaleLoader.getString("Perks.lucky.bonus", superRepairChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Repair.Skills.Super.Chance", superRepairChance));
+            }
+        }
+
+        if (canArcaneForge) {
+            player.sendMessage(LocaleLoader.getString("Repair.Arcane.Rank", arcaneForgingRank));
+
+            if (Repair.arcaneForgingEnchantLoss) {
+                player.sendMessage(LocaleLoader.getString("Repair.Arcane.Chance.Success", (arcaneBypass ? 100 : Repair.getEnchantChance(arcaneForgingRank))));
+            }
+
+            if (Repair.arcaneForgingDowngrades) {
+                player.sendMessage(LocaleLoader.getString("Repair.Arcane.Chance.Downgrade", (arcaneBypass ? 0 : Repair.getDowngradeChance(arcaneForgingRank))));
+            }
+        }
+    }
+}

+ 159 - 159
src/main/java/com/gmail/nossr50/skills/SkillCommand.java → src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java

@@ -1,159 +1,159 @@
-package com.gmail.nossr50.skills;
-
-import java.text.DecimalFormat;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.commands.CommandHelper;
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.PerksUtils;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.Users;
-
-public abstract class SkillCommand implements CommandExecutor {
-    protected SkillType skill;
-    private String skillString;
-
-    protected Player player;
-    protected PlayerProfile profile;
-    protected float skillValue;
-    protected boolean isLucky;
-    protected boolean hasEndurance;
-
-    protected DecimalFormat percent = new DecimalFormat("##0.00%");
-    protected DecimalFormat decimal = new DecimalFormat("##0.00");
-
-    public SkillCommand(SkillType skill) {
-        this.skill = skill;
-        this.skillString = StringUtils.getCapitalized(skill.toString());
-    }
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (CommandHelper.noConsoleUsage(sender)) {
-            return true;
-        }
-
-        player = (Player) sender;
-        profile = Users.getPlayer(player).getProfile();
-
-        if (profile == null) {
-            sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-            return true;
-        }
-
-        skillValue = profile.getSkillLevel(skill);
-        isLucky = Permissions.lucky(sender, skill);
-        hasEndurance = (Permissions.twelveSecondActivationBoost(sender) || Permissions.eightSecondActivationBoost(sender) || Permissions.fourSecondActivationBoost(sender));
-
-        dataCalculations();
-        permissionsCheck();
-
-        player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString(skillString + ".SkillName")));
-
-        if (!skill.isChildSkill()) {
-            player.sendMessage(LocaleLoader.getString("Commands.XPGain", LocaleLoader.getString("Commands.XPGain." + skillString)));
-            player.sendMessage(LocaleLoader.getString("Effects.Level", profile.getSkillLevel(skill), profile.getSkillXpLevel(skill), profile.getXpToLevel(skill)));
-        }
-
-        if (effectsHeaderPermissions()) {
-            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Effects.Effects")));
-        }
-
-        effectsDisplay();
-
-        if (statsHeaderPermissions()) {
-            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Commands.Stats.Self")));
-        }
-
-        statsDisplay();
-
-        return SkillGuide.grabGuidePageForSkill(skill, player, args);
-    }
-
-    protected String calculateRank(int maxLevel, int rankChangeLevel) {
-        if (skillValue >= maxLevel) {
-            return String.valueOf(maxLevel / rankChangeLevel);
-        }
-
-        return String.valueOf((int) (skillValue / rankChangeLevel));
-    }
-
-    protected String[] calculateAbilityDisplayValues(double chance) {
-        if (isLucky) {
-            double luckyChance = chance * 1.3333D;
-
-            if (luckyChance >= 100D) {
-                return new String[] { percent.format(chance / 100.0D), percent.format(1.0D) };
-            }
-
-            return new String[] { percent.format(chance / 100.0D), percent.format(luckyChance / 100.0D) };
-        }
-
-        return new String[] { percent.format(chance / 100.0D), null };
-    }
-
-    protected String[] calculateAbilityDisplayValues(int maxBonusLevel, double maxChance) {
-        double abilityChance;
-
-        if (skillValue >= maxBonusLevel) {
-            abilityChance = maxChance;
-        }
-        else {
-            abilityChance = (maxChance / maxBonusLevel) * skillValue;
-        }
-
-        if (isLucky) {
-            double luckyChance = abilityChance * 1.3333D;
-
-            if (luckyChance >= 100D) {
-                return new String[] { percent.format(abilityChance / 100.0D), percent.format(1.0D) };
-            }
-
-            return new String[] { percent.format(abilityChance / 100.0D), percent.format(luckyChance / 100.0D) };
-        }
-
-        return new String[] { percent.format(abilityChance / 100.0D), null };
-    }
-
-    protected String[] calculateLengthDisplayValues() {
-        int maxLength = skill.getAbility().getMaxTicks();
-        int length = 2 + (int) (skillValue / AdvancedConfig.getInstance().getAbilityLength());
-        int enduranceLength = PerksUtils.handleActivationPerks(player, length, maxLength);
-
-        if (maxLength != 0) {
-            if (length > maxLength) {
-                length = maxLength;
-            }
-        }
-
-        return new String[] { String.valueOf(length), String.valueOf(enduranceLength) };
-    }
-
-    protected void luckyEffectsDisplay() {
-        if (isLucky) {
-            String perkPrefix = LocaleLoader.getString("MOTD.PerksPrefix");
-            player.sendMessage(perkPrefix + LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Perks.lucky.name"), LocaleLoader.getString("Perks.lucky.desc", SkillTools.getSkillName(skill))));
-        }
-    }
-
-    protected abstract void dataCalculations();
-
-    protected abstract void permissionsCheck();
-
-    protected abstract boolean effectsHeaderPermissions();
-
-    protected abstract void effectsDisplay();
-
-    protected abstract boolean statsHeaderPermissions();
-
-    protected abstract void statsDisplay();
-}
+package com.gmail.nossr50.commands.skills;
+
+import java.text.DecimalFormat;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.PerksUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public abstract class SkillCommand implements CommandExecutor {
+    protected SkillType skill;
+    private String skillString;
+
+    protected Player player;
+    protected PlayerProfile profile;
+    protected float skillValue;
+    protected boolean isLucky;
+    protected boolean hasEndurance;
+
+    protected DecimalFormat percent = new DecimalFormat("##0.00%");
+    protected DecimalFormat decimal = new DecimalFormat("##0.00");
+
+    public SkillCommand(SkillType skill) {
+        this.skill = skill;
+        this.skillString = StringUtils.getCapitalized(skill.toString());
+    }
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (CommandUtils.noConsoleUsage(sender)) {
+            return true;
+        }
+
+        player = (Player) sender;
+        profile = UserManager.getPlayer(player).getProfile();
+
+        if (profile == null) {
+            sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
+            return true;
+        }
+
+        skillValue = profile.getSkillLevel(skill);
+        isLucky = Permissions.lucky(sender, skill);
+        hasEndurance = (Permissions.twelveSecondActivationBoost(sender) || Permissions.eightSecondActivationBoost(sender) || Permissions.fourSecondActivationBoost(sender));
+
+        dataCalculations();
+        permissionsCheck();
+
+        player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString(skillString + ".SkillName")));
+
+        if (!skill.isChildSkill()) {
+            player.sendMessage(LocaleLoader.getString("Commands.XPGain", LocaleLoader.getString("Commands.XPGain." + skillString)));
+            player.sendMessage(LocaleLoader.getString("Effects.Level", profile.getSkillLevel(skill), profile.getSkillXpLevel(skill), profile.getXpToLevel(skill)));
+        }
+
+        if (effectsHeaderPermissions()) {
+            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Effects.Effects")));
+        }
+
+        effectsDisplay();
+
+        if (statsHeaderPermissions()) {
+            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Commands.Stats.Self")));
+        }
+
+        statsDisplay();
+
+        return SkillGuideCommand.grabGuidePageForSkill(skill, player, args);
+    }
+
+    protected String calculateRank(int maxLevel, int rankChangeLevel) {
+        if (skillValue >= maxLevel) {
+            return String.valueOf(maxLevel / rankChangeLevel);
+        }
+
+        return String.valueOf((int) (skillValue / rankChangeLevel));
+    }
+
+    protected String[] calculateAbilityDisplayValues(double chance) {
+        if (isLucky) {
+            double luckyChance = chance * 1.3333D;
+
+            if (luckyChance >= 100D) {
+                return new String[] { percent.format(chance / 100.0D), percent.format(1.0D) };
+            }
+
+            return new String[] { percent.format(chance / 100.0D), percent.format(luckyChance / 100.0D) };
+        }
+
+        return new String[] { percent.format(chance / 100.0D), null };
+    }
+
+    protected String[] calculateAbilityDisplayValues(int maxBonusLevel, double maxChance) {
+        double abilityChance;
+
+        if (skillValue >= maxBonusLevel) {
+            abilityChance = maxChance;
+        }
+        else {
+            abilityChance = (maxChance / maxBonusLevel) * skillValue;
+        }
+
+        if (isLucky) {
+            double luckyChance = abilityChance * 1.3333D;
+
+            if (luckyChance >= 100D) {
+                return new String[] { percent.format(abilityChance / 100.0D), percent.format(1.0D) };
+            }
+
+            return new String[] { percent.format(abilityChance / 100.0D), percent.format(luckyChance / 100.0D) };
+        }
+
+        return new String[] { percent.format(abilityChance / 100.0D), null };
+    }
+
+    protected String[] calculateLengthDisplayValues() {
+        int maxLength = skill.getAbility().getMaxTicks();
+        int length = 2 + (int) (skillValue / AdvancedConfig.getInstance().getAbilityLength());
+        int enduranceLength = PerksUtils.handleActivationPerks(player, length, maxLength);
+
+        if (maxLength != 0) {
+            if (length > maxLength) {
+                length = maxLength;
+            }
+        }
+
+        return new String[] { String.valueOf(length), String.valueOf(enduranceLength) };
+    }
+
+    protected void luckyEffectsDisplay() {
+        if (isLucky) {
+            String perkPrefix = LocaleLoader.getString("MOTD.PerksPrefix");
+            player.sendMessage(perkPrefix + LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Perks.lucky.name"), LocaleLoader.getString("Perks.lucky.desc", SkillUtils.getSkillName(skill))));
+        }
+    }
+
+    protected abstract void dataCalculations();
+
+    protected abstract void permissionsCheck();
+
+    protected abstract boolean effectsHeaderPermissions();
+
+    protected abstract void effectsDisplay();
+
+    protected abstract boolean statsHeaderPermissions();
+
+    protected abstract void statsDisplay();
+}

+ 107 - 0
src/main/java/com/gmail/nossr50/commands/skills/SkillGuideCommand.java

@@ -0,0 +1,107 @@
+package com.gmail.nossr50.commands.skills;
+
+import java.util.ArrayList;
+
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public final class SkillGuideCommand {
+    private SkillGuideCommand() {}
+
+    public static int getTotalPageNumber(String address) {
+        String[] addressSplit = LocaleLoader.getString(address).split("\n");
+
+        if (addressSplit.length <= 8) {
+            return 1;
+        }
+
+        return (addressSplit.length / 8) + 1;
+    }
+
+    public static ArrayList<String> grabPageContents(String header, String address, int pagenum) {
+        int pageIndexStart = 0;
+
+        // Determine what string to start at
+        if (pagenum > 1) {
+            pageIndexStart = 8 * (pagenum - 1);
+        }
+
+        ArrayList<String> allStrings = new ArrayList<String>();
+        String split[] = LocaleLoader.getString(address).split("\n");
+
+        allStrings.add(LocaleLoader.getString("Guides.Header", header));
+
+        // Add targeted strings
+        while (allStrings.size() < 9) {
+            if (pageIndexStart + allStrings.size() > split.length) {
+                allStrings.add("");
+            }
+            else {
+                allStrings.add(split[pageIndexStart + allStrings.size() - 1]);
+            }
+        }
+
+        allStrings.add("Page " + pagenum + " of " + getTotalPageNumber(address));
+        return allStrings;
+    }
+
+    public static void clearChat(Player player) {
+        player.sendMessage("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); // Dear god why?
+    }
+
+    public static boolean grabGuidePageForSkill(SkillType skilltype, Player player, String[] args) {
+        String skillName = skilltype.toString();
+        String capitalized = StringUtils.getCapitalized(skillName);
+        String localized = SkillUtils.getSkillName(skilltype);
+        player.sendMessage(LocaleLoader.getString("Guides.Available", localized, localized.toLowerCase()));
+
+        String address = "Guides." + capitalized;
+
+        switch (args.length) {
+            case 0:
+                // We have to specify this, otherwise we get the usage string every time we call /skillname...
+                return true;
+
+            case 1:
+                if (!args[0].equals("?")) {
+                    return false;
+                }
+
+                SkillGuideCommand.clearChat(player);
+
+                for (String target : SkillGuideCommand.grabPageContents(localized, address, 1)) {
+                    player.sendMessage(target);
+                }
+
+                return true;
+
+            case 2:
+                int totalPages = SkillGuideCommand.getTotalPageNumber(address);
+
+                if (!StringUtils.isInt(args[1])) {
+                    player.sendMessage(LocaleLoader.getString("Guides.Page.Invalid"));
+                    return true;
+                }
+
+                if (Integer.parseInt(args[1]) > totalPages) {
+                    player.sendMessage(LocaleLoader.getString("Guides.Page.OutOfRange", totalPages));
+                    return true;
+                }
+
+                SkillGuideCommand.clearChat(player);
+
+                for (String target : SkillGuideCommand.grabPageContents(localized, address, Integer.parseInt(args[1]))) {
+                    player.sendMessage(target);
+                }
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+}

+ 122 - 121
src/main/java/com/gmail/nossr50/skills/smelting/SmeltingCommand.java → src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java

@@ -1,121 +1,122 @@
-package com.gmail.nossr50.skills.smelting;
-
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.SkillManagerStore;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class SmeltingCommand extends SkillCommand {
-    private String burnTimeModifier;
-    private String secondSmeltChance;
-    private String secondSmeltChanceLucky;
-    private String fluxMiningChance;
-    private String fluxMiningChanceLucky;
-    private int vanillaXPModifier;
-
-    private boolean canFuelEfficiency;
-    private boolean canSecondSmelt;
-    private boolean canFluxMine;
-    private boolean canVanillaXPBoost;
-
-    public SmeltingCommand() {
-        super(SkillType.SMELTING);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //FUEL EFFICIENCY
-        burnTimeModifier = decimal.format(1 + ((skillValue / Smelting.burnModifierMaxLevel) * Smelting.burnTimeMultiplier));
-
-        //SECOND SMELT
-        String[] secondSmeltStrings = calculateAbilityDisplayValues(Smelting.secondSmeltMaxLevel, Smelting.secondSmeltMaxChance);
-        secondSmeltChance = secondSmeltStrings[0];
-        secondSmeltChanceLucky = secondSmeltStrings[1];
-
-        //FLUX MINING
-        String[] fluxMiningStrings = calculateAbilityDisplayValues(Smelting.fluxMiningChance);
-        fluxMiningChance = fluxMiningStrings[0];
-        fluxMiningChanceLucky = fluxMiningStrings[1];
-
-        //VANILLA XP BOOST
-        vanillaXPModifier = SkillManagerStore.getInstance().getSmeltingManager(player.getName()).getVanillaXpMultiplier();
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canFuelEfficiency = Permissions.fuelEfficiency(player);
-        canSecondSmelt = Permissions.doubleDrops(player, skill);
-        canFluxMine = Permissions.fluxMining(player);
-        canVanillaXPBoost = Permissions.vanillaXpBoost(player, skill);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canFluxMine || canFuelEfficiency || canSecondSmelt || canVanillaXPBoost;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canFuelEfficiency) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.0"), LocaleLoader.getString("Smelting.Effect.1")));
-        }
-
-        if (canSecondSmelt) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.2"), LocaleLoader.getString("Smelting.Effect.3")));
-        }
-
-        if (canVanillaXPBoost) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.4"), LocaleLoader.getString("Smelting.Effect.5")));
-        }
-
-        if (canFluxMine) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.6"), LocaleLoader.getString("Smelting.Effect.7")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canFluxMine || canFuelEfficiency || canSecondSmelt || canVanillaXPBoost;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canFuelEfficiency) {
-            player.sendMessage(LocaleLoader.getString("Smelting.Ability.FuelEfficiency", burnTimeModifier));
-        }
-
-        if (canSecondSmelt) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Smelting.Ability.SecondSmelt", secondSmeltChance) + LocaleLoader.getString("Perks.lucky.bonus", secondSmeltChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Smelting.Ability.SecondSmelt", secondSmeltChance));
-            }
-        }
-
-        if (canVanillaXPBoost) {
-            if (skillValue < AdvancedConfig.getInstance().getSmeltingVanillaXPBoostRank1Level()) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Smelting.Ability.Locked.0", AdvancedConfig.getInstance().getSmeltingVanillaXPBoostRank1Level())));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Smelting.Ability.VanillaXPBoost", vanillaXPModifier));
-            }
-        }
-
-        if (canFluxMine) {
-            if (skillValue < Smelting.fluxMiningUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Smelting.Ability.Locked.1", Smelting.fluxMiningUnlockLevel)));
-            }
-            else if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Smelting.Ability.FluxMining", fluxMiningChance) + LocaleLoader.getString("Perks.lucky.bonus", fluxMiningChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Smelting.Ability.FluxMining", fluxMiningChance));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.SkillManagerStore;
+import com.gmail.nossr50.skills.smelting.Smelting;
+import com.gmail.nossr50.util.Permissions;
+
+public class SmeltingCommand extends SkillCommand {
+    private String burnTimeModifier;
+    private String secondSmeltChance;
+    private String secondSmeltChanceLucky;
+    private String fluxMiningChance;
+    private String fluxMiningChanceLucky;
+
+    private int vanillaXPModifier;
+
+    private boolean canFuelEfficiency;
+    private boolean canSecondSmelt;
+    private boolean canFluxMine;
+    private boolean canVanillaXPBoost;
+
+    public SmeltingCommand() {
+        super(SkillType.SMELTING);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // FUEL EFFICIENCY
+        burnTimeModifier = decimal.format(1 + ((skillValue / Smelting.burnModifierMaxLevel) * Smelting.burnTimeMultiplier));
+
+        // SECOND SMELT
+        String[] secondSmeltStrings = calculateAbilityDisplayValues(Smelting.secondSmeltMaxLevel, Smelting.secondSmeltMaxChance);
+        secondSmeltChance = secondSmeltStrings[0];
+        secondSmeltChanceLucky = secondSmeltStrings[1];
+
+        // FLUX MINING
+        String[] fluxMiningStrings = calculateAbilityDisplayValues(Smelting.fluxMiningChance);
+        fluxMiningChance = fluxMiningStrings[0];
+        fluxMiningChanceLucky = fluxMiningStrings[1];
+
+        // VANILLA XP BOOST
+        vanillaXPModifier = SkillManagerStore.getInstance().getSmeltingManager(player.getName()).getVanillaXpMultiplier();
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canFuelEfficiency = Permissions.fuelEfficiency(player);
+        canSecondSmelt = Permissions.doubleDrops(player, skill);
+        canFluxMine = Permissions.fluxMining(player);
+        canVanillaXPBoost = Permissions.vanillaXpBoost(player, skill);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canFluxMine || canFuelEfficiency || canSecondSmelt || canVanillaXPBoost;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canFuelEfficiency) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.0"), LocaleLoader.getString("Smelting.Effect.1")));
+        }
+
+        if (canSecondSmelt) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.2"), LocaleLoader.getString("Smelting.Effect.3")));
+        }
+
+        if (canVanillaXPBoost) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.4"), LocaleLoader.getString("Smelting.Effect.5")));
+        }
+
+        if (canFluxMine) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Smelting.Effect.6"), LocaleLoader.getString("Smelting.Effect.7")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canFluxMine || canFuelEfficiency || canSecondSmelt || canVanillaXPBoost;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canFuelEfficiency) {
+            player.sendMessage(LocaleLoader.getString("Smelting.Ability.FuelEfficiency", burnTimeModifier));
+        }
+
+        if (canSecondSmelt) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Smelting.Ability.SecondSmelt", secondSmeltChance) + LocaleLoader.getString("Perks.lucky.bonus", secondSmeltChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Smelting.Ability.SecondSmelt", secondSmeltChance));
+            }
+        }
+
+        if (canVanillaXPBoost) {
+            if (skillValue < AdvancedConfig.getInstance().getSmeltingVanillaXPBoostRank1Level()) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Smelting.Ability.Locked.0", AdvancedConfig.getInstance().getSmeltingVanillaXPBoostRank1Level())));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Smelting.Ability.VanillaXPBoost", vanillaXPModifier));
+            }
+        }
+
+        if (canFluxMine) {
+            if (skillValue < Smelting.fluxMiningUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Smelting.Ability.Locked.1", Smelting.fluxMiningUnlockLevel)));
+            }
+            else if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Smelting.Ability.FluxMining", fluxMiningChance) + LocaleLoader.getString("Perks.lucky.bonus", fluxMiningChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Smelting.Ability.FluxMining", fluxMiningChance));
+            }
+        }
+    }
+}

+ 117 - 117
src/main/java/com/gmail/nossr50/skills/swords/SwordsCommand.java → src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java

@@ -1,117 +1,117 @@
-package com.gmail.nossr50.skills.swords;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class SwordsCommand extends SkillCommand {
-    private String counterAttackChance;
-    private String counterAttackChanceLucky;
-    private String bleedLength;
-    private String bleedChance;
-    private String bleedChanceLucky;
-    private String serratedStrikesLength;
-    private String serratedStrikesLengthEndurance;
-
-    private boolean canCounter;
-    private boolean canSerratedStrike;
-    private boolean canBleed;
-
-    public SwordsCommand() {
-        super(SkillType.SWORDS);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //SERRATED STRIKES
-        String[] serratedStrikesStrings = calculateLengthDisplayValues();
-        serratedStrikesLength = serratedStrikesStrings[0];
-        serratedStrikesLengthEndurance = serratedStrikesStrings[1];
-
-        //BLEED
-        if (skillValue >= Swords.bleedMaxBonusLevel) {
-            bleedLength = String.valueOf(Swords.bleedMaxTicks);
-        }
-        else {
-            bleedLength = String.valueOf(Swords.bleedBaseTicks);
-        }
-
-        String[] bleedStrings = calculateAbilityDisplayValues(Swords.bleedMaxBonusLevel, Swords.bleedMaxChance);
-        bleedChance = bleedStrings[0];
-        bleedChanceLucky = bleedStrings[1];
-
-        //COUNTER ATTACK
-        String[] counterAttackStrings = calculateAbilityDisplayValues(Swords.counterAttackMaxBonusLevel, Swords.counterAttackMaxChance);
-        counterAttackChance = counterAttackStrings[0];
-        counterAttackChanceLucky = counterAttackStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canBleed = Permissions.bleed(player);
-        canCounter = Permissions.counterAttack(player);
-        canSerratedStrike = Permissions.serratedStrikes(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canBleed || canCounter || canSerratedStrike;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canCounter) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.0"), LocaleLoader.getString("Swords.Effect.1", percent.format(1.0D / Swords.counterAttackModifier))));
-        }
-
-        if (canSerratedStrike) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.2"), LocaleLoader.getString("Swords.Effect.3", percent.format(1.0D / Swords.serratedStrikesModifier))));
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.4"), LocaleLoader.getString("Swords.Effect.5", Swords.serratedStrikesBleedTicks)));
-        }
-
-        if (canBleed) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.6"), LocaleLoader.getString("Swords.Effect.7")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canBleed || canCounter || canSerratedStrike;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canCounter) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Swords.Combat.Counter.Chance", counterAttackChance) + LocaleLoader.getString("Perks.lucky.bonus", counterAttackChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Swords.Combat.Counter.Chance", counterAttackChance));
-            }
-        }
-
-        if (canBleed) {
-            player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Length", bleedLength));
-            player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Note"));
-
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Chance", bleedChance) + LocaleLoader.getString("Perks.lucky.bonus", bleedChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Chance", bleedChance));
-            }
-        }
-
-        if (canSerratedStrike) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Swords.SS.Length", serratedStrikesLength) + LocaleLoader.getString("Perks.activationtime.bonus", serratedStrikesLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Swords.SS.Length", serratedStrikesLength));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.swords.Swords;
+import com.gmail.nossr50.util.Permissions;
+
+public class SwordsCommand extends SkillCommand {
+    private String counterAttackChance;
+    private String counterAttackChanceLucky;
+    private String bleedLength;
+    private String bleedChance;
+    private String bleedChanceLucky;
+    private String serratedStrikesLength;
+    private String serratedStrikesLengthEndurance;
+
+    private boolean canCounter;
+    private boolean canSerratedStrike;
+    private boolean canBleed;
+
+    public SwordsCommand() {
+        super(SkillType.SWORDS);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // SERRATED STRIKES
+        String[] serratedStrikesStrings = calculateLengthDisplayValues();
+        serratedStrikesLength = serratedStrikesStrings[0];
+        serratedStrikesLengthEndurance = serratedStrikesStrings[1];
+
+        // BLEED
+        if (skillValue >= Swords.bleedMaxBonusLevel) {
+            bleedLength = String.valueOf(Swords.bleedMaxTicks);
+        }
+        else {
+            bleedLength = String.valueOf(Swords.bleedBaseTicks);
+        }
+
+        String[] bleedStrings = calculateAbilityDisplayValues(Swords.bleedMaxBonusLevel, Swords.bleedMaxChance);
+        bleedChance = bleedStrings[0];
+        bleedChanceLucky = bleedStrings[1];
+
+        // COUNTER ATTACK
+        String[] counterAttackStrings = calculateAbilityDisplayValues(Swords.counterAttackMaxBonusLevel, Swords.counterAttackMaxChance);
+        counterAttackChance = counterAttackStrings[0];
+        counterAttackChanceLucky = counterAttackStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canBleed = Permissions.bleed(player);
+        canCounter = Permissions.counterAttack(player);
+        canSerratedStrike = Permissions.serratedStrikes(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canBleed || canCounter || canSerratedStrike;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canCounter) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.0"), LocaleLoader.getString("Swords.Effect.1", percent.format(1.0D / Swords.counterAttackModifier))));
+        }
+
+        if (canSerratedStrike) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.2"), LocaleLoader.getString("Swords.Effect.3", percent.format(1.0D / Swords.serratedStrikesModifier))));
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.4"), LocaleLoader.getString("Swords.Effect.5", Swords.serratedStrikesBleedTicks)));
+        }
+
+        if (canBleed) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Swords.Effect.6"), LocaleLoader.getString("Swords.Effect.7")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canBleed || canCounter || canSerratedStrike;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canCounter) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Swords.Combat.Counter.Chance", counterAttackChance) + LocaleLoader.getString("Perks.lucky.bonus", counterAttackChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Swords.Combat.Counter.Chance", counterAttackChance));
+            }
+        }
+
+        if (canBleed) {
+            player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Length", bleedLength));
+            player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Note"));
+
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Chance", bleedChance) + LocaleLoader.getString("Perks.lucky.bonus", bleedChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Swords.Combat.Bleed.Chance", bleedChance));
+            }
+        }
+
+        if (canSerratedStrike) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Swords.SS.Length", serratedStrikesLength) + LocaleLoader.getString("Perks.activationtime.bonus", serratedStrikesLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Swords.SS.Length", serratedStrikesLength));
+            }
+        }
+    }
+}

+ 165 - 165
src/main/java/com/gmail/nossr50/skills/taming/TamingCommand.java → src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java

@@ -1,165 +1,165 @@
-package com.gmail.nossr50.skills.taming;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class TamingCommand extends SkillCommand {
-    private String goreChance;
-    private String goreChanceLucky;
-
-    private boolean canBeastLore;
-    private boolean canGore;
-    private boolean canSharpenedClaws;
-    private boolean canEnvironmentallyAware;
-    private boolean canThickFur;
-    private boolean canShockProof;
-    private boolean canCallWild;
-    private boolean canFastFood;
-    private boolean canHolyHound;
-
-    public TamingCommand() {
-        super(SkillType.TAMING);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        String[] goreStrings = calculateAbilityDisplayValues(Taming.goreMaxBonusLevel, Taming.goreMaxChance);
-        goreChance = goreStrings[0];
-        goreChanceLucky = goreStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canBeastLore = Permissions.beastLore(player);
-        canCallWild = Permissions.callOfTheWild(player);
-        canEnvironmentallyAware = Permissions.environmentallyAware(player);
-        canFastFood = Permissions.fastFoodService(player);
-        canGore = Permissions.gore(player);
-        canSharpenedClaws = Permissions.sharpenedClaws(player);
-        canShockProof = Permissions.shockProof(player);
-        canThickFur = Permissions.thickFur(player);
-        canHolyHound = Permissions.holyHound(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canBeastLore || canCallWild || canEnvironmentallyAware || canFastFood || canGore || canSharpenedClaws || canShockProof || canThickFur || canHolyHound;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canBeastLore) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.0"), LocaleLoader.getString("Taming.Effect.1")));
-        }
-
-        if (canGore) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.2"), LocaleLoader.getString("Taming.Effect.3")));
-        }
-
-        if (canSharpenedClaws) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.4"), LocaleLoader.getString("Taming.Effect.5")));
-        }
-
-        if (canEnvironmentallyAware) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.6"), LocaleLoader.getString("Taming.Effect.7")));
-        }
-
-        if (canThickFur) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.8"), LocaleLoader.getString("Taming.Effect.9")));
-        }
-
-        if (canShockProof) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.10"), LocaleLoader.getString("Taming.Effect.11")));
-        }
-
-        if (canFastFood) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.16"), LocaleLoader.getString("Taming.Effect.17")));
-        }
-
-        if (canHolyHound) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.18"), LocaleLoader.getString("Taming.Effect.19")));
-        }
-
-        if (canCallWild) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.12"), LocaleLoader.getString("Taming.Effect.13")));
-            player.sendMessage(LocaleLoader.getString("Taming.Effect.14", Config.getInstance().getTamingCOTWOcelotCost()));
-            player.sendMessage(LocaleLoader.getString("Taming.Effect.15", Config.getInstance().getTamingCOTWWolfCost()));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canEnvironmentallyAware || canFastFood || canGore || canSharpenedClaws || canShockProof || canThickFur || canHolyHound;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canFastFood) {
-            if (skillValue < Taming.fastFoodServiceUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.4", Taming.fastFoodServiceUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.8"), LocaleLoader.getString("Taming.Ability.Bonus.9", percent.format(Taming.fastFoodServiceActivationChance / 100D))));
-            }
-        }
-
-        if (canEnvironmentallyAware) {
-            if (skillValue < Taming.environmentallyAwareUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.0", Taming.environmentallyAwareUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.0"), LocaleLoader.getString("Taming.Ability.Bonus.1")));
-            }
-        }
-
-        if (canThickFur) {
-            if (skillValue < Taming.thickFurUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.1", Taming.thickFurUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.2"), LocaleLoader.getString("Taming.Ability.Bonus.3", Taming.thickFurModifier)));
-            }
-        }
-
-        if (canHolyHound) {
-            if (skillValue < Taming.holyHoundUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.5", Taming.holyHoundUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.10"), LocaleLoader.getString("Taming.Ability.Bonus.11")));
-            }
-        }
-
-        if (canShockProof) {
-            if (skillValue < Taming.shockProofUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.2", Taming.shockProofUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.4"), LocaleLoader.getString("Taming.Ability.Bonus.5", Taming.shockProofModifier)));
-            }
-        }
-
-        if (canSharpenedClaws) {
-            if (skillValue < Taming.sharpenedClawsUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.3", Taming.sharpenedClawsUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.6"), LocaleLoader.getString("Taming.Ability.Bonus.7", Taming.sharpenedClawsBonusDamage)));
-            }
-        }
-
-        if (canGore) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Taming.Combat.Chance.Gore", goreChance) + LocaleLoader.getString("Perks.lucky.bonus", goreChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Taming.Combat.Chance.Gore", goreChance));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.taming.Taming;
+import com.gmail.nossr50.util.Permissions;
+
+public class TamingCommand extends SkillCommand {
+    private String goreChance;
+    private String goreChanceLucky;
+
+    private boolean canBeastLore;
+    private boolean canGore;
+    private boolean canSharpenedClaws;
+    private boolean canEnvironmentallyAware;
+    private boolean canThickFur;
+    private boolean canShockProof;
+    private boolean canCallWild;
+    private boolean canFastFood;
+    private boolean canHolyHound;
+
+    public TamingCommand() {
+        super(SkillType.TAMING);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        String[] goreStrings = calculateAbilityDisplayValues(Taming.goreMaxBonusLevel, Taming.goreMaxChance);
+        goreChance = goreStrings[0];
+        goreChanceLucky = goreStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canBeastLore = Permissions.beastLore(player);
+        canCallWild = Permissions.callOfTheWild(player);
+        canEnvironmentallyAware = Permissions.environmentallyAware(player);
+        canFastFood = Permissions.fastFoodService(player);
+        canGore = Permissions.gore(player);
+        canSharpenedClaws = Permissions.sharpenedClaws(player);
+        canShockProof = Permissions.shockProof(player);
+        canThickFur = Permissions.thickFur(player);
+        canHolyHound = Permissions.holyHound(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canBeastLore || canCallWild || canEnvironmentallyAware || canFastFood || canGore || canSharpenedClaws || canShockProof || canThickFur || canHolyHound;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canBeastLore) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.0"), LocaleLoader.getString("Taming.Effect.1")));
+        }
+
+        if (canGore) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.2"), LocaleLoader.getString("Taming.Effect.3")));
+        }
+
+        if (canSharpenedClaws) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.4"), LocaleLoader.getString("Taming.Effect.5")));
+        }
+
+        if (canEnvironmentallyAware) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.6"), LocaleLoader.getString("Taming.Effect.7")));
+        }
+
+        if (canThickFur) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.8"), LocaleLoader.getString("Taming.Effect.9")));
+        }
+
+        if (canShockProof) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.10"), LocaleLoader.getString("Taming.Effect.11")));
+        }
+
+        if (canFastFood) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.16"), LocaleLoader.getString("Taming.Effect.17")));
+        }
+
+        if (canHolyHound) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.18"), LocaleLoader.getString("Taming.Effect.19")));
+        }
+
+        if (canCallWild) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Taming.Effect.12"), LocaleLoader.getString("Taming.Effect.13")));
+            player.sendMessage(LocaleLoader.getString("Taming.Effect.14", Config.getInstance().getTamingCOTWOcelotCost()));
+            player.sendMessage(LocaleLoader.getString("Taming.Effect.15", Config.getInstance().getTamingCOTWWolfCost()));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canEnvironmentallyAware || canFastFood || canGore || canSharpenedClaws || canShockProof || canThickFur || canHolyHound;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canFastFood) {
+            if (skillValue < Taming.fastFoodServiceUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.4", Taming.fastFoodServiceUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.8"), LocaleLoader.getString("Taming.Ability.Bonus.9", percent.format(Taming.fastFoodServiceActivationChance / 100D))));
+            }
+        }
+
+        if (canEnvironmentallyAware) {
+            if (skillValue < Taming.environmentallyAwareUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.0", Taming.environmentallyAwareUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.0"), LocaleLoader.getString("Taming.Ability.Bonus.1")));
+            }
+        }
+
+        if (canThickFur) {
+            if (skillValue < Taming.thickFurUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.1", Taming.thickFurUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.2"), LocaleLoader.getString("Taming.Ability.Bonus.3", Taming.thickFurModifier)));
+            }
+        }
+
+        if (canHolyHound) {
+            if (skillValue < Taming.holyHoundUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.5", Taming.holyHoundUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.10"), LocaleLoader.getString("Taming.Ability.Bonus.11")));
+            }
+        }
+
+        if (canShockProof) {
+            if (skillValue < Taming.shockProofUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.2", Taming.shockProofUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.4"), LocaleLoader.getString("Taming.Ability.Bonus.5", Taming.shockProofModifier)));
+            }
+        }
+
+        if (canSharpenedClaws) {
+            if (skillValue < Taming.sharpenedClawsUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Taming.Ability.Locked.3", Taming.sharpenedClawsUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Taming.Ability.Bonus.6"), LocaleLoader.getString("Taming.Ability.Bonus.7", Taming.sharpenedClawsBonusDamage)));
+            }
+        }
+
+        if (canGore) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Taming.Combat.Chance.Gore", goreChance) + LocaleLoader.getString("Perks.lucky.bonus", goreChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Taming.Combat.Chance.Gore", goreChance));
+            }
+        }
+    }
+}

+ 146 - 146
src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedCommand.java → src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java

@@ -1,146 +1,146 @@
-package com.gmail.nossr50.skills.unarmed;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class UnarmedCommand extends SkillCommand {
-    private String berserkLength;
-    private String berserkLengthEndurance;
-    private String deflectChance;
-    private String deflectChanceLucky;
-    private String disarmChance;
-    private String disarmChanceLucky;
-    private String ironGripChance;
-    private String ironGripChanceLucky;
-    private String ironArmBonus;
-
-    private boolean canBerserk;
-    private boolean canDisarm;
-    private boolean canBonusDamage;
-    private boolean canDeflect;
-    private boolean canIronGrip;
-
-    public UnarmedCommand() {
-        super(SkillType.UNARMED);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //BERSERK
-        String[] berserkStrings = calculateLengthDisplayValues();
-        berserkLength = berserkStrings[0];
-        berserkLengthEndurance = berserkStrings[1];
-
-        //DISARM
-        String[] disarmStrings = calculateAbilityDisplayValues(Unarmed.disarmMaxBonusLevel, Unarmed.disarmMaxChance);
-        disarmChance = disarmStrings[0];
-        disarmChanceLucky = disarmStrings[1];
-
-        //DEFLECT
-        String[] deflectStrings = calculateAbilityDisplayValues(Unarmed.deflectMaxBonusLevel, Unarmed.deflectMaxChance);
-        deflectChance = deflectStrings[0];
-        deflectChanceLucky = deflectStrings[1];
-
-        //IRON ARM
-        if (skillValue >= ((Unarmed.ironArmMaxBonusDamage - 3) * Unarmed.ironArmIncreaseLevel)) {
-            ironArmBonus = String.valueOf(Unarmed.ironArmMaxBonusDamage);
-        }
-        else {
-            ironArmBonus = String.valueOf(3 + (skillValue / Unarmed.ironArmIncreaseLevel));
-        }
-
-        //IRON GRIP
-        String[] ironGripStrings = calculateAbilityDisplayValues(Unarmed.ironGripMaxBonusLevel, Unarmed.ironGripMaxChance);
-        ironGripChance = ironGripStrings[0];
-        ironGripChanceLucky = ironGripStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canBerserk = Permissions.berserk(player);
-        canBonusDamage = Permissions.bonusDamage(player, skill);
-        canDeflect = Permissions.arrowDeflect(player);
-        canDisarm = Permissions.disarm(player);
-        canIronGrip = Permissions.ironGrip(player);
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return canBerserk || canBonusDamage || canDeflect || canDisarm || canIronGrip;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canBerserk) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.0"), LocaleLoader.getString("Unarmed.Effect.1")));
-        }
-
-        if (canDisarm) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.2"), LocaleLoader.getString("Unarmed.Effect.3")));
-        }
-
-        if (canBonusDamage) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.4"), LocaleLoader.getString("Unarmed.Effect.5")));
-        }
-
-        if (canDeflect) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.6"), LocaleLoader.getString("Unarmed.Effect.7")));
-        }
-
-        if (canIronGrip) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.8"), LocaleLoader.getString("Unarmed.Effect.9")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return canBerserk || canBonusDamage || canDeflect || canDisarm || canIronGrip;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canBonusDamage) {
-            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Unarmed.Ability.Bonus.0"), LocaleLoader.getString("Unarmed.Ability.Bonus.1", ironArmBonus)));
-        }
-
-        if (canDeflect) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.ArrowDeflect", deflectChance) + LocaleLoader.getString("Perks.lucky.bonus", deflectChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.ArrowDeflect", deflectChance));
-            }
-        }
-
-        if (canDisarm) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.Disarm", disarmChance) + LocaleLoader.getString("Perks.lucky.bonus", disarmChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.Disarm", disarmChance));
-            }
-        }
-
-        if (canIronGrip) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.IronGrip", ironGripChance) + LocaleLoader.getString("Perks.lucky.bonus", ironGripChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.IronGrip", ironGripChance));
-            }
-        }
-
-        if (canBerserk) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Berserk.Length", berserkLength) + LocaleLoader.getString("Perks.activationtime.bonus", berserkLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Berserk.Length", berserkLength));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.unarmed.Unarmed;
+import com.gmail.nossr50.util.Permissions;
+
+public class UnarmedCommand extends SkillCommand {
+    private String berserkLength;
+    private String berserkLengthEndurance;
+    private String deflectChance;
+    private String deflectChanceLucky;
+    private String disarmChance;
+    private String disarmChanceLucky;
+    private String ironGripChance;
+    private String ironGripChanceLucky;
+    private String ironArmBonus;
+
+    private boolean canBerserk;
+    private boolean canDisarm;
+    private boolean canBonusDamage;
+    private boolean canDeflect;
+    private boolean canIronGrip;
+
+    public UnarmedCommand() {
+        super(SkillType.UNARMED);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // BERSERK
+        String[] berserkStrings = calculateLengthDisplayValues();
+        berserkLength = berserkStrings[0];
+        berserkLengthEndurance = berserkStrings[1];
+
+        // DISARM
+        String[] disarmStrings = calculateAbilityDisplayValues(Unarmed.disarmMaxBonusLevel, Unarmed.disarmMaxChance);
+        disarmChance = disarmStrings[0];
+        disarmChanceLucky = disarmStrings[1];
+
+        // DEFLECT
+        String[] deflectStrings = calculateAbilityDisplayValues(Unarmed.deflectMaxBonusLevel, Unarmed.deflectMaxChance);
+        deflectChance = deflectStrings[0];
+        deflectChanceLucky = deflectStrings[1];
+
+        // IRON ARM
+        if (skillValue >= ((Unarmed.ironArmMaxBonusDamage - 3) * Unarmed.ironArmIncreaseLevel)) {
+            ironArmBonus = String.valueOf(Unarmed.ironArmMaxBonusDamage);
+        }
+        else {
+            ironArmBonus = String.valueOf(3 + (skillValue / Unarmed.ironArmIncreaseLevel));
+        }
+
+        // IRON GRIP
+        String[] ironGripStrings = calculateAbilityDisplayValues(Unarmed.ironGripMaxBonusLevel, Unarmed.ironGripMaxChance);
+        ironGripChance = ironGripStrings[0];
+        ironGripChanceLucky = ironGripStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canBerserk = Permissions.berserk(player);
+        canBonusDamage = Permissions.bonusDamage(player, skill);
+        canDeflect = Permissions.arrowDeflect(player);
+        canDisarm = Permissions.disarm(player);
+        canIronGrip = Permissions.ironGrip(player);
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return canBerserk || canBonusDamage || canDeflect || canDisarm || canIronGrip;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canBerserk) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.0"), LocaleLoader.getString("Unarmed.Effect.1")));
+        }
+
+        if (canDisarm) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.2"), LocaleLoader.getString("Unarmed.Effect.3")));
+        }
+
+        if (canBonusDamage) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.4"), LocaleLoader.getString("Unarmed.Effect.5")));
+        }
+
+        if (canDeflect) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.6"), LocaleLoader.getString("Unarmed.Effect.7")));
+        }
+
+        if (canIronGrip) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Unarmed.Effect.8"), LocaleLoader.getString("Unarmed.Effect.9")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return canBerserk || canBonusDamage || canDeflect || canDisarm || canIronGrip;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canBonusDamage) {
+            player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Unarmed.Ability.Bonus.0"), LocaleLoader.getString("Unarmed.Ability.Bonus.1", ironArmBonus)));
+        }
+
+        if (canDeflect) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.ArrowDeflect", deflectChance) + LocaleLoader.getString("Perks.lucky.bonus", deflectChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.ArrowDeflect", deflectChance));
+            }
+        }
+
+        if (canDisarm) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.Disarm", disarmChance) + LocaleLoader.getString("Perks.lucky.bonus", disarmChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.Disarm", disarmChance));
+            }
+        }
+
+        if (canIronGrip) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.IronGrip", ironGripChance) + LocaleLoader.getString("Perks.lucky.bonus", ironGripChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Chance.IronGrip", ironGripChance));
+            }
+        }
+
+        if (canBerserk) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Berserk.Length", berserkLength) + LocaleLoader.getString("Perks.activationtime.bonus", berserkLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Unarmed.Ability.Berserk.Length", berserkLength));
+            }
+        }
+    }
+}

+ 103 - 103
src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingCommand.java → src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java

@@ -1,103 +1,103 @@
-package com.gmail.nossr50.skills.woodcutting;
-
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.SkillCommand;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class WoodcuttingCommand extends SkillCommand {
-    private String treeFellerLength;
-    private String treeFellerLengthEndurance;
-    private String doubleDropChance;
-    private String doubleDropChanceLucky;
-
-    private boolean canTreeFell;
-    private boolean canLeafBlow;
-    private boolean canDoubleDrop;
-    private boolean doubleDropsDisabled;
-
-    public WoodcuttingCommand() {
-        super(SkillType.WOODCUTTING);
-    }
-
-    @Override
-    protected void dataCalculations() {
-        //TREE FELLER
-        String[] treeFellerStrings = calculateLengthDisplayValues();
-        treeFellerLength = treeFellerStrings[0];
-        treeFellerLengthEndurance = treeFellerStrings[1];
-
-        //DOUBLE DROPS
-        String[] doubleDropStrings = calculateAbilityDisplayValues(Woodcutting.doubleDropsMaxLevel, Woodcutting.doubleDropsMaxChance);
-        doubleDropChance = doubleDropStrings[0];
-        doubleDropChanceLucky = doubleDropStrings[1];
-    }
-
-    @Override
-    protected void permissionsCheck() {
-        canTreeFell = Permissions.treeFeller(player);
-        canDoubleDrop = Permissions.doubleDrops(player, skill);
-        canLeafBlow = Permissions.leafBlower(player);
-        doubleDropsDisabled = skill.getDoubleDropsDisabled();
-    }
-
-    @Override
-    protected boolean effectsHeaderPermissions() {
-        return (canDoubleDrop && !doubleDropsDisabled) || canLeafBlow || canTreeFell;
-    }
-
-    @Override
-    protected void effectsDisplay() {
-        luckyEffectsDisplay();
-
-        if (canTreeFell) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.0"), LocaleLoader.getString("Woodcutting.Effect.1")));
-        }
-
-        if (canLeafBlow) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.2"), LocaleLoader.getString("Woodcutting.Effect.3")));
-        }
-
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.4"), LocaleLoader.getString("Woodcutting.Effect.5")));
-        }
-    }
-
-    @Override
-    protected boolean statsHeaderPermissions() {
-        return (canDoubleDrop && !doubleDropsDisabled) || canLeafBlow || canTreeFell;
-    }
-
-    @Override
-    protected void statsDisplay() {
-        if (canLeafBlow) {
-            int leafBlowerUnlockLevel = AdvancedConfig.getInstance().getLeafBlowUnlockLevel();
-
-            if (skillValue < leafBlowerUnlockLevel) {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock",  LocaleLoader.getString("Woodcutting.Ability.Locked.0", leafBlowerUnlockLevel)));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Woodcutting.Ability.0"), LocaleLoader.getString("Woodcutting.Ability.1")));
-            }
-        }
-
-        if (canDoubleDrop && !doubleDropsDisabled) {
-            if (isLucky) {
-                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Chance.DDrop", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Chance.DDrop", doubleDropChance));
-            }
-        }
-
-        if (canTreeFell) {
-            if (hasEndurance) {
-                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Length", treeFellerLength) + LocaleLoader.getString("Perks.activationtime.bonus", treeFellerLengthEndurance));
-            }
-            else {
-                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Length", treeFellerLength));
-            }
-        }
-    }
-}
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.woodcutting.Woodcutting;
+import com.gmail.nossr50.util.Permissions;
+
+public class WoodcuttingCommand extends SkillCommand {
+    private String treeFellerLength;
+    private String treeFellerLengthEndurance;
+    private String doubleDropChance;
+    private String doubleDropChanceLucky;
+
+    private boolean canTreeFell;
+    private boolean canLeafBlow;
+    private boolean canDoubleDrop;
+    private boolean doubleDropsDisabled;
+
+    public WoodcuttingCommand() {
+        super(SkillType.WOODCUTTING);
+    }
+
+    @Override
+    protected void dataCalculations() {
+        // TREE FELLER
+        String[] treeFellerStrings = calculateLengthDisplayValues();
+        treeFellerLength = treeFellerStrings[0];
+        treeFellerLengthEndurance = treeFellerStrings[1];
+
+        // DOUBLE DROPS
+        String[] doubleDropStrings = calculateAbilityDisplayValues(Woodcutting.doubleDropsMaxLevel, Woodcutting.doubleDropsMaxChance);
+        doubleDropChance = doubleDropStrings[0];
+        doubleDropChanceLucky = doubleDropStrings[1];
+    }
+
+    @Override
+    protected void permissionsCheck() {
+        canTreeFell = Permissions.treeFeller(player);
+        canDoubleDrop = Permissions.doubleDrops(player, skill);
+        canLeafBlow = Permissions.leafBlower(player);
+        doubleDropsDisabled = skill.getDoubleDropsDisabled();
+    }
+
+    @Override
+    protected boolean effectsHeaderPermissions() {
+        return (canDoubleDrop && !doubleDropsDisabled) || canLeafBlow || canTreeFell;
+    }
+
+    @Override
+    protected void effectsDisplay() {
+        luckyEffectsDisplay();
+
+        if (canTreeFell) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.0"), LocaleLoader.getString("Woodcutting.Effect.1")));
+        }
+
+        if (canLeafBlow) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.2"), LocaleLoader.getString("Woodcutting.Effect.3")));
+        }
+
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            player.sendMessage(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Woodcutting.Effect.4"), LocaleLoader.getString("Woodcutting.Effect.5")));
+        }
+    }
+
+    @Override
+    protected boolean statsHeaderPermissions() {
+        return (canDoubleDrop && !doubleDropsDisabled) || canLeafBlow || canTreeFell;
+    }
+
+    @Override
+    protected void statsDisplay() {
+        if (canLeafBlow) {
+            int leafBlowerUnlockLevel = AdvancedConfig.getInstance().getLeafBlowUnlockLevel();
+
+            if (skillValue < leafBlowerUnlockLevel) {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Woodcutting.Ability.Locked.0", leafBlowerUnlockLevel)));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Woodcutting.Ability.0"), LocaleLoader.getString("Woodcutting.Ability.1")));
+            }
+        }
+
+        if (canDoubleDrop && !doubleDropsDisabled) {
+            if (isLucky) {
+                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Chance.DDrop", doubleDropChance) + LocaleLoader.getString("Perks.lucky.bonus", doubleDropChanceLucky));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Chance.DDrop", doubleDropChance));
+            }
+        }
+
+        if (canTreeFell) {
+            if (hasEndurance) {
+                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Length", treeFellerLength) + LocaleLoader.getString("Perks.activationtime.bonus", treeFellerLengthEndurance));
+            }
+            else {
+                player.sendMessage(LocaleLoader.getString("Woodcutting.Ability.Length", treeFellerLength));
+            }
+        }
+    }
+}

+ 29 - 29
src/main/java/com/gmail/nossr50/spout/commands/MchudCommand.java → src/main/java/com/gmail/nossr50/commands/spout/MchudCommand.java

@@ -1,29 +1,29 @@
-package com.gmail.nossr50.spout.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.spout.huds.HudType;
-
-public class MchudCommand extends SpoutCommand {
-    @Override
-    protected boolean noArguments(Command command, CommandSender sender, String[] args) {
-        return false;
-    }
-
-    @Override
-    protected boolean oneArgument(Command command, CommandSender sender, String[] args) {
-        for (HudType hudType : HudType.values()) {
-            if (hudType.toString().equalsIgnoreCase(args[0])) {
-                playerProfile.setHudType(hudType);
-                spoutHud.initializeXpBar();
-                spoutHud.updateXpBar();
-                return true;
-            }
-        }
-
-        sender.sendMessage(LocaleLoader.getString("Commands.mchud.Invalid"));
-        return true;
-    }
-}
+package com.gmail.nossr50.commands.spout;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.datatypes.spout.huds.HudType;
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public class MchudCommand extends SpoutCommand {
+    @Override
+    protected boolean noArguments(Command command, CommandSender sender, String[] args) {
+        return false;
+    }
+
+    @Override
+    protected boolean oneArgument(Command command, CommandSender sender, String[] args) {
+        for (HudType hudType : HudType.values()) {
+            if (hudType.toString().equalsIgnoreCase(args[0])) {
+                playerProfile.setHudType(hudType);
+                spoutHud.initializeXpBar();
+                spoutHud.updateXpBar();
+                return true;
+            }
+        }
+
+        sender.sendMessage(LocaleLoader.getString("Commands.mchud.Invalid"));
+        return true;
+    }
+}

+ 54 - 53
src/main/java/com/gmail/nossr50/spout/commands/SpoutCommand.java → src/main/java/com/gmail/nossr50/commands/spout/SpoutCommand.java

@@ -1,53 +1,54 @@
-package com.gmail.nossr50.spout.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.commands.CommandHelper;
-import com.gmail.nossr50.datatypes.PlayerProfile;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.spout.SpoutConfig;
-import com.gmail.nossr50.spout.huds.SpoutHud;
-import com.gmail.nossr50.util.Users;
-
-public abstract class SpoutCommand implements CommandExecutor {
-    protected PlayerProfile playerProfile;
-    protected SpoutHud spoutHud;
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (CommandHelper.noConsoleUsage(sender)) {
-            return true;
-        }
-
-        if (!mcMMO.spoutEnabled || !SpoutConfig.getInstance().getXPBarEnabled()) {
-            sender.sendMessage(LocaleLoader.getString("Commands.Disabled"));
-            return true;
-        }
-
-        playerProfile = Users.getPlayer((Player) sender).getProfile();
-        spoutHud = playerProfile.getSpoutHud();
-
-        if (spoutHud == null) {
-            sender.sendMessage(LocaleLoader.getString("Commands.Disabled"));
-            return true;
-        }
-
-        switch (args.length) {
-        case 0:
-            return noArguments(command, sender, args);
-
-        case 1:
-            return oneArgument(command, sender, args);
-
-        default:
-            return false;
-        }
-    }
-
-    protected abstract boolean noArguments(Command command, CommandSender sender, String[] args);
-    protected abstract boolean oneArgument(Command command, CommandSender sender, String[] args);
-}
+package com.gmail.nossr50.commands.spout;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.spout.SpoutConfig;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.spout.huds.McMMOHud;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.commands.CommandUtils;
+import com.gmail.nossr50.util.player.UserManager;
+
+public abstract class SpoutCommand implements CommandExecutor {
+    protected PlayerProfile playerProfile;
+    protected McMMOHud spoutHud;
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        if (CommandUtils.noConsoleUsage(sender)) {
+            return true;
+        }
+
+        if (!mcMMO.spoutEnabled || !SpoutConfig.getInstance().getXPBarEnabled()) {
+            sender.sendMessage(LocaleLoader.getString("Commands.Disabled"));
+            return true;
+        }
+
+        playerProfile = UserManager.getPlayer((Player) sender).getProfile();
+        spoutHud = playerProfile.getSpoutHud();
+
+        if (spoutHud == null) {
+            sender.sendMessage(LocaleLoader.getString("Commands.Disabled"));
+            return true;
+        }
+
+        switch (args.length) {
+            case 0:
+                return noArguments(command, sender, args);
+
+            case 1:
+                return oneArgument(command, sender, args);
+
+            default:
+                return false;
+        }
+    }
+
+    protected abstract boolean noArguments(Command command, CommandSender sender, String[] args);
+
+    protected abstract boolean oneArgument(Command command, CommandSender sender, String[] args);
+}

+ 64 - 64
src/main/java/com/gmail/nossr50/spout/commands/XplockCommand.java → src/main/java/com/gmail/nossr50/commands/spout/XplockCommand.java

@@ -1,64 +1,64 @@
-package com.gmail.nossr50.spout.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.utilities.SkillTools;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.util.Permissions;
-
-public class XplockCommand extends SpoutCommand {
-    @Override
-    protected boolean noArguments(Command command, CommandSender sender, String[] args) {
-        if (spoutHud.getXpBarLocked()) {
-            unlockXpBar(sender);
-            return true;
-        }
-
-        lockXpBar(sender, spoutHud.getLastGained());
-        return true;
-    }
-
-    @Override
-    protected boolean oneArgument(Command command, CommandSender sender, String[] args) {
-        if (args[0].equalsIgnoreCase("on")) {
-            lockXpBar(sender, spoutHud.getLastGained());
-            return true;
-        }
-
-        if (args[0].equalsIgnoreCase("off")) {
-            unlockXpBar(sender);
-            return true;
-        }
-
-        if (!SkillTools.isSkill(args[0])) {
-            sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
-            return true;
-        }
-
-        SkillType skill = SkillType.getSkill(args[0]);
-
-        if (!Permissions.xplock(sender, skill)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return true;
-        }
-
-        lockXpBar(sender, skill);
-        return true;
-    }
-
-    private void lockXpBar(CommandSender sender, SkillType skill) {
-        if (skill != null) {
-            spoutHud.setXpBarLocked(true);
-            spoutHud.setSkillLock(skill);
-            spoutHud.updateXpBar();
-            sender.sendMessage(LocaleLoader.getString("Commands.xplock.locked", SkillTools.getSkillName(skill)));
-        }
-    }
-
-    private void unlockXpBar(CommandSender sender) {
-        spoutHud.setXpBarLocked(false);
-        sender.sendMessage(LocaleLoader.getString("Commands.xplock.unlocked"));
-    }
-}
+package com.gmail.nossr50.commands.spout;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class XplockCommand extends SpoutCommand {
+    @Override
+    protected boolean noArguments(Command command, CommandSender sender, String[] args) {
+        if (spoutHud.getXpBarLocked()) {
+            unlockXpBar(sender);
+            return true;
+        }
+
+        lockXpBar(sender, spoutHud.getLastGained());
+        return true;
+    }
+
+    @Override
+    protected boolean oneArgument(Command command, CommandSender sender, String[] args) {
+        if (args[0].equalsIgnoreCase("on")) {
+            lockXpBar(sender, spoutHud.getLastGained());
+            return true;
+        }
+
+        if (args[0].equalsIgnoreCase("off")) {
+            unlockXpBar(sender);
+            return true;
+        }
+
+        if (!SkillUtils.isSkill(args[0])) {
+            sender.sendMessage(LocaleLoader.getString("Commands.Skill.Invalid"));
+            return true;
+        }
+
+        SkillType skill = SkillType.getSkill(args[0]);
+
+        if (!Permissions.xplock(sender, skill)) {
+            sender.sendMessage(command.getPermissionMessage());
+            return true;
+        }
+
+        lockXpBar(sender, skill);
+        return true;
+    }
+
+    private void lockXpBar(CommandSender sender, SkillType skill) {
+        if (skill != null) {
+            spoutHud.setXpBarLocked(true);
+            spoutHud.setSkillLock(skill);
+            spoutHud.updateXpBar();
+            sender.sendMessage(LocaleLoader.getString("Commands.xplock.locked", SkillUtils.getSkillName(skill)));
+        }
+    }
+
+    private void unlockXpBar(CommandSender sender) {
+        spoutHud.setXpBarLocked(false);
+        sender.sendMessage(LocaleLoader.getString("Commands.xplock.unlocked"));
+    }
+}

+ 3 - 3
src/main/java/com/gmail/nossr50/config/AutoUpdateConfigLoader.java

@@ -12,7 +12,7 @@ import java.util.Set;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.configuration.file.YamlConfiguration;
 
-import com.gmail.nossr50.util.metrics.MetricsManager;
+import com.gmail.nossr50.metrics.MetricsManager;
 
 public abstract class AutoUpdateConfigLoader extends ConfigLoader {
     public AutoUpdateConfigLoader(String relativePath, String fileName) {
@@ -64,7 +64,7 @@ public abstract class AutoUpdateConfigLoader extends ConfigLoader {
 
             // Rip out Bukkit's attempt to save comments at the top of the file
             while (output.indexOf('#') != -1) {
-                output = output.substring(output.indexOf('\n', output.indexOf('#'))+1);
+                output = output.substring(output.indexOf('\n', output.indexOf('#')) + 1);
             }
 
             // Read the internal config to get comments, then put them in the new one
@@ -81,7 +81,7 @@ public abstract class AutoUpdateConfigLoader extends ConfigLoader {
                     }
                     else if (line.contains(":")) {
                         line = line.substring(0, line.indexOf(":") + 1);
-                        if(!temp.isEmpty()) {
+                        if (!temp.isEmpty()) {
                             comments.put(line, temp);
                             temp = "";
                         }

+ 2 - 2
src/main/java/com/gmail/nossr50/config/Config.java

@@ -6,8 +6,8 @@ import org.bukkit.Material;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.entity.EntityType;
 
-import com.gmail.nossr50.skills.utilities.AbilityType;
-import com.gmail.nossr50.skills.utilities.SkillType;
+import com.gmail.nossr50.datatypes.skills.AbilityType;
+import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.util.StringUtils;
 
 public class Config extends AutoUpdateConfigLoader {

+ 80 - 79
src/main/java/com/gmail/nossr50/config/ConfigLoader.java

@@ -1,79 +1,80 @@
-package com.gmail.nossr50.config;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-import com.gmail.nossr50.mcMMO;
-
-public abstract class ConfigLoader {
-    protected static final mcMMO plugin = mcMMO.p;
-    protected String fileName;
-    protected File configFile;
-    protected FileConfiguration config;
-
-    public ConfigLoader(String relativePath, String fileName) {
-        this.fileName = fileName;
-        configFile = new File(plugin.getDataFolder(), relativePath + File.separator + fileName);
-        loadFile();
-    }
-
-    public ConfigLoader(String fileName) {
-        this.fileName = fileName;
-        configFile = new File(plugin.getDataFolder(), fileName);
-        loadFile();
-    }
-
-    protected void loadFile() {
-        if (!configFile.exists()) {
-            plugin.getLogger().info("Creating mcMMO " + fileName + " File...");
-            createFile();
-        }
-        else {
-            plugin.getLogger().info("Loading mcMMO " + fileName + " File...");
-        }
-
-        config = YamlConfiguration.loadConfiguration(configFile);
-    }
-
-    protected abstract void loadKeys();
-
-    protected void createFile() {
-        if (configFile.exists()) {
-            return;
-        }
-
-        configFile.getParentFile().mkdirs();
-
-        InputStream inputStream = plugin.getResource(fileName);
-
-        if (inputStream != null) {
-            try {
-                copyStreamToFile(inputStream, configFile);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-        else {
-            plugin.getLogger().severe("Missing resource file: '" + fileName + "' please notify the plugin authors");
-        }
-    }
-
-    private static void copyStreamToFile(InputStream inputStream, File file) throws Exception {
-        OutputStream outputStream = new FileOutputStream(file);
-
-        int read = 0;
-        byte[] bytes = new byte[1024];
-
-        while ((read = inputStream.read(bytes)) != -1) {
-            outputStream.write(bytes, 0, read);
-        }
-
-        inputStream.close();
-        outputStream.close();
-    }
-}
+package com.gmail.nossr50.config;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import com.gmail.nossr50.mcMMO;
+
+public abstract class ConfigLoader {
+    protected static final mcMMO plugin = mcMMO.p;
+    protected String fileName;
+    protected File configFile;
+    protected FileConfiguration config;
+
+    public ConfigLoader(String relativePath, String fileName) {
+        this.fileName = fileName;
+        configFile = new File(plugin.getDataFolder(), relativePath + File.separator + fileName);
+        loadFile();
+    }
+
+    public ConfigLoader(String fileName) {
+        this.fileName = fileName;
+        configFile = new File(plugin.getDataFolder(), fileName);
+        loadFile();
+    }
+
+    protected void loadFile() {
+        if (!configFile.exists()) {
+            plugin.getLogger().info("Creating mcMMO " + fileName + " File...");
+            createFile();
+        }
+        else {
+            plugin.getLogger().info("Loading mcMMO " + fileName + " File...");
+        }
+
+        config = YamlConfiguration.loadConfiguration(configFile);
+    }
+
+    protected abstract void loadKeys();
+
+    protected void createFile() {
+        if (configFile.exists()) {
+            return;
+        }
+
+        configFile.getParentFile().mkdirs();
+
+        InputStream inputStream = plugin.getResource(fileName);
+
+        if (inputStream != null) {
+            try {
+                copyStreamToFile(inputStream, configFile);
+            }
+            catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        else {
+            plugin.getLogger().severe("Missing resource file: '" + fileName + "' please notify the plugin authors");
+        }
+    }
+
+    private static void copyStreamToFile(InputStream inputStream, File file) throws Exception {
+        OutputStream outputStream = new FileOutputStream(file);
+
+        int read = 0;
+        byte[] bytes = new byte[1024];
+
+        while ((read = inputStream.read(bytes)) != -1) {
+            outputStream.write(bytes, 0, read);
+        }
+
+        inputStream.close();
+        outputStream.close();
+    }
+}

+ 101 - 94
src/main/java/com/gmail/nossr50/mods/config/CustomArmorConfig.java → src/main/java/com/gmail/nossr50/config/mods/CustomArmorConfig.java

@@ -1,94 +1,101 @@
-package com.gmail.nossr50.mods.config;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-
-import org.bukkit.configuration.ConfigurationSection;
-
-import com.gmail.nossr50.config.ConfigLoader;
-import com.gmail.nossr50.mods.datatypes.CustomItem;
-import com.gmail.nossr50.skills.repair.Repairable;
-import com.gmail.nossr50.skills.repair.RepairableFactory;
-
-public class CustomArmorConfig extends ConfigLoader{
-    private static CustomArmorConfig instance;
-    private List<Repairable> repairables;
-    public List<Integer> customBootIDs = new ArrayList<Integer>();
-    public List<Integer> customChestplateIDs = new ArrayList<Integer>();
-    public List<Integer> customHelmetIDs = new ArrayList<Integer>();
-    public List<Integer> customLeggingIDs = new ArrayList<Integer>();
-    public List<Integer> customIDs = new ArrayList<Integer>();
-    public List<CustomItem> customArmorList = new ArrayList<CustomItem>();
-    public HashMap<Integer, CustomItem> customArmor = new HashMap<Integer, CustomItem>();
-
-    public CustomArmorConfig() {
-        super("ModConfigs", "armor.yml");
-        loadKeys();
-    }
-
-    public static CustomArmorConfig getInstance() {
-        if (instance == null) {
-            instance = new CustomArmorConfig();
-        }
-
-        return instance;
-    }
-
-    @Override
-    protected void loadKeys() {
-        repairables = new ArrayList<Repairable>();
-
-        loadArmor("Boots", customBootIDs);
-        loadArmor("Chestplates", customChestplateIDs);
-        loadArmor("Helmets", customHelmetIDs);
-        loadArmor("Leggings", customLeggingIDs);
-    }
-
-    private void loadArmor(String armorType, List<Integer> idList) {
-        ConfigurationSection armorSection = config.getConfigurationSection(armorType);
-
-        if (armorSection == null)
-            return;
-
-        Set<String> armorConfigSet = armorSection.getKeys(false);
-
-        for (String armorName : armorConfigSet) {
-            int id = config.getInt(armorType + "." + armorName + ".ID", 0);
-            boolean repairable = config.getBoolean(armorType + "." + armorName + ".Repairable");
-            int repairID = config.getInt(armorType + "." + armorName + ".Repair_Material_ID", 0);
-            byte repairData = (byte) config.getInt(armorType + "." + armorName + ".Repair_Material_Data_Value", 0);
-            int repairQuantity = config.getInt(armorType + "." + armorName + ".Repair_Material_Quantity", 0);
-            short durability = (short) config.getInt(armorType + "." + armorName + ".Durability", 0);
-
-            if (id == 0) {
-                plugin.getLogger().warning("Missing ID. This item will be skipped.");
-                continue;
-            }
-
-            if (repairable && (repairID == 0 || repairQuantity == 0 || durability == 0)) {
-                plugin.getLogger().warning("Incomplete repair information. This item will be unrepairable.");
-                repairable = false;
-            }
-
-            CustomItem armor;
-
-            if (repairable) {
-                repairables.add(RepairableFactory.getRepairable(id, repairID, repairData, repairQuantity, durability));
-            }
-
-            armor = new CustomItem(id, durability);
-
-            idList.add(id);
-            customIDs.add(id);
-            customArmorList.add(armor);
-            customArmor.put(id, armor);
-        }
-    }
-
-    public List<Repairable> getLoadedRepairables() {
-        if (repairables == null) return new ArrayList<Repairable>();
-        return repairables;
-    }
-}
+package com.gmail.nossr50.config.mods;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import org.bukkit.configuration.ConfigurationSection;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.mods.CustomItem;
+import com.gmail.nossr50.skills.repair.Repairable;
+import com.gmail.nossr50.skills.repair.RepairableFactory;
+
+public class CustomArmorConfig extends ConfigLoader {
+    private static CustomArmorConfig instance;
+
+    private List<Repairable> repairables;
+
+    public List<Integer> customBootIDs       = new ArrayList<Integer>();
+    public List<Integer> customChestplateIDs = new ArrayList<Integer>();
+    public List<Integer> customHelmetIDs     = new ArrayList<Integer>();
+    public List<Integer> customLeggingIDs    = new ArrayList<Integer>();
+    public List<Integer> customIDs           = new ArrayList<Integer>();
+
+    public List<CustomItem> customArmorList = new ArrayList<CustomItem>();
+    public HashMap<Integer, CustomItem> customArmor = new HashMap<Integer, CustomItem>();
+
+    public CustomArmorConfig() {
+        super("ModConfigs", "armor.yml");
+        loadKeys();
+    }
+
+    public static CustomArmorConfig getInstance() {
+        if (instance == null) {
+            instance = new CustomArmorConfig();
+        }
+
+        return instance;
+    }
+
+    public List<Repairable> getLoadedRepairables() {
+        if (repairables == null) {
+            return new ArrayList<Repairable>();
+        }
+
+        return repairables;
+    }
+
+    @Override
+    protected void loadKeys() {
+        repairables = new ArrayList<Repairable>();
+
+        loadArmor("Boots", customBootIDs);
+        loadArmor("Chestplates", customChestplateIDs);
+        loadArmor("Helmets", customHelmetIDs);
+        loadArmor("Leggings", customLeggingIDs);
+    }
+
+    private void loadArmor(String armorType, List<Integer> idList) {
+        ConfigurationSection armorSection = config.getConfigurationSection(armorType);
+
+        if (armorSection == null) {
+            return;
+        }
+
+        Set<String> armorConfigSet = armorSection.getKeys(false);
+
+        for (String armorName : armorConfigSet) {
+            int id = config.getInt(armorType + "." + armorName + ".ID", 0);
+            boolean repairable = config.getBoolean(armorType + "." + armorName + ".Repairable");
+            int repairID = config.getInt(armorType + "." + armorName + ".Repair_Material_ID", 0);
+            byte repairData = (byte) config.getInt(armorType + "." + armorName + ".Repair_Material_Data_Value", 0);
+            int repairQuantity = config.getInt(armorType + "." + armorName + ".Repair_Material_Quantity", 0);
+            short durability = (short) config.getInt(armorType + "." + armorName + ".Durability", 0);
+
+            if (id == 0) {
+                plugin.getLogger().warning("Missing ID. This item will be skipped.");
+                continue;
+            }
+
+            if (repairable && (repairID == 0 || repairQuantity == 0 || durability == 0)) {
+                plugin.getLogger().warning("Incomplete repair information. This item will be unrepairable.");
+                repairable = false;
+            }
+
+            CustomItem armor;
+
+            if (repairable) {
+                repairables.add(RepairableFactory.getRepairable(id, repairID, repairData, repairQuantity, durability));
+            }
+
+            armor = new CustomItem(id, durability);
+
+            idList.add(id);
+            customIDs.add(id);
+            customArmorList.add(armor);
+            customArmor.put(id, armor);
+        }
+    }
+}

+ 120 - 117
src/main/java/com/gmail/nossr50/mods/config/CustomBlocksConfig.java → src/main/java/com/gmail/nossr50/config/mods/CustomBlockConfig.java

@@ -1,117 +1,120 @@
-package com.gmail.nossr50.mods.config;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.material.MaterialData;
-
-import com.gmail.nossr50.config.ConfigLoader;
-import com.gmail.nossr50.mods.datatypes.CustomBlock;
-
-public class CustomBlocksConfig extends ConfigLoader {
-    private static CustomBlocksConfig instance;
-    public List<ItemStack> customExcavationBlocks = new ArrayList<ItemStack>();
-    public List<ItemStack> customHerbalismBlocks = new ArrayList<ItemStack>();
-    public List<ItemStack> customMiningBlocks = new ArrayList<ItemStack>();
-    public List<ItemStack> customWoodcuttingBlocks = new ArrayList<ItemStack>();
-    public List<ItemStack> customOres = new ArrayList<ItemStack>();
-    public List<ItemStack> customLogs = new ArrayList<ItemStack>();
-    public List<ItemStack> customLeaves = new ArrayList<ItemStack>();
-    public List<ItemStack> customAbilityBlocks = new ArrayList<ItemStack>();
-    public List<ItemStack> customItems = new ArrayList<ItemStack>();
-    public List<CustomBlock> customBlocks = new ArrayList<CustomBlock>();
-
-    public CustomBlocksConfig() {
-        super("ModConfigs", "blocks.yml");
-        loadKeys();
-    }
-
-    public static CustomBlocksConfig getInstance() {
-        if (instance == null) {
-            instance = new CustomBlocksConfig();
-        }
-
-        return instance;
-    }
-
-    @Override
-    protected void loadKeys() {
-        loadBlocks("Excavation", customExcavationBlocks);
-        loadBlocks("Herbalism", customHerbalismBlocks);
-        loadBlocks("Mining", customMiningBlocks);
-        loadBlocks("Woodcutting", customWoodcuttingBlocks);
-        loadBlocks("Ability_Blocks", customAbilityBlocks);
-    }
-
-    private void loadBlocks(String skillType, List<ItemStack> blockList) {
-        ConfigurationSection skillSection = config.getConfigurationSection(skillType);
-
-        if (skillSection == null)
-            return;
-
-        Set<String> skillConfigSet = skillSection.getKeys(false);
-
-        for (String blockName : skillConfigSet) {
-            int id = config.getInt(skillType + "." + blockName + ".ID", 0);
-            byte data = (byte) config.getInt(skillType + "." + blockName + ".Data_Value", 0);
-            int xp = config.getInt(skillType + "." + blockName + ".XP_Gain", 0);
-            int tier = config.getInt(skillType + "." + blockName + ".Tier", 1);
-            boolean dropItem = config.getBoolean(skillType + "." + blockName + ".Drop_Item", false);
-            int dropID = config.getInt(skillType + "." + blockName + ".Drop_Item_ID", 0);
-            byte dropData = (byte) config.getInt(skillType + "." + blockName + ".Drop_Item_Data_Value", 0);
-            int minimumDropAmount = config.getInt(skillType + "." + blockName + ".Min_Drop_Item_Amount", 1);
-            int maxiumDropAmount = config.getInt(skillType + "." + blockName + ".Max_Drop_Item_Amount", 1);
-
-            CustomBlock block;
-            ItemStack itemDrop;
-            ItemStack blockItem;
-
-            if (id == 0) {
-                plugin.getLogger().warning("Missing ID. This block will be skipped.");
-                continue;
-            }
-
-            if (skillType.equals("Ability_Blocks")) {
-                blockItem = (new MaterialData(id, data)).toItemStack(1);
-
-                blockList.add(blockItem);
-                continue;
-            }
-
-            if (dropItem && dropID == 0) {
-                plugin.getLogger().warning("Incomplete item drop information. This block will drop itself.");
-                dropItem = false;
-            }
-
-            if (dropItem) {
-                itemDrop = (new MaterialData(dropID, dropData)).toItemStack(1);
-            }
-            else {
-                itemDrop = (new MaterialData(id, data)).toItemStack(1);
-            }
-
-            block = new CustomBlock(minimumDropAmount, maxiumDropAmount, itemDrop, tier, xp, data, id);
-            blockItem = (new MaterialData(id, data)).toItemStack(1);
-
-            if (skillType.equals("Mining") && config.getBoolean(skillType + "." + blockName + ".Is_Ore")) {
-                customOres.add(blockItem);
-            }
-            else if (skillType.equals("Woodcutting")) {
-                if (config.getBoolean(skillType + "." + blockName + ".Is_Log")) {
-                    customLogs.add(blockItem);
-                }
-                else {
-                    customLeaves.add(blockItem);
-                    block.setXpGain(0); //Leaves don't grant XP
-                }
-            }
-
-            blockList.add(blockItem);
-            customItems.add(blockItem);
-            customBlocks.add(block);
-        }
-    }
-}
+package com.gmail.nossr50.config.mods;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.material.MaterialData;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.mods.CustomBlock;
+
+public class CustomBlockConfig extends ConfigLoader {
+    private static CustomBlockConfig instance;
+
+    public List<ItemStack> customExcavationBlocks  = new ArrayList<ItemStack>();
+    public List<ItemStack> customHerbalismBlocks   = new ArrayList<ItemStack>();
+    public List<ItemStack> customMiningBlocks      = new ArrayList<ItemStack>();
+    public List<ItemStack> customWoodcuttingBlocks = new ArrayList<ItemStack>();
+    public List<ItemStack> customOres              = new ArrayList<ItemStack>();
+    public List<ItemStack> customLogs              = new ArrayList<ItemStack>();
+    public List<ItemStack> customLeaves            = new ArrayList<ItemStack>();
+    public List<ItemStack> customAbilityBlocks     = new ArrayList<ItemStack>();
+    public List<ItemStack> customItems             = new ArrayList<ItemStack>();
+
+    public List<CustomBlock> customBlocks = new ArrayList<CustomBlock>();
+
+    public CustomBlockConfig() {
+        super("ModConfigs", "blocks.yml");
+        loadKeys();
+    }
+
+    public static CustomBlockConfig getInstance() {
+        if (instance == null) {
+            instance = new CustomBlockConfig();
+        }
+
+        return instance;
+    }
+
+    @Override
+    protected void loadKeys() {
+        loadBlocks("Excavation", customExcavationBlocks);
+        loadBlocks("Herbalism", customHerbalismBlocks);
+        loadBlocks("Mining", customMiningBlocks);
+        loadBlocks("Woodcutting", customWoodcuttingBlocks);
+        loadBlocks("Ability_Blocks", customAbilityBlocks);
+    }
+
+    private void loadBlocks(String skillType, List<ItemStack> blockList) {
+        ConfigurationSection skillSection = config.getConfigurationSection(skillType);
+
+        if (skillSection == null) {
+            return;
+        }
+
+        Set<String> skillConfigSet = skillSection.getKeys(false);
+
+        for (String blockName : skillConfigSet) {
+            int id = config.getInt(skillType + "." + blockName + ".ID", 0);
+            byte data = (byte) config.getInt(skillType + "." + blockName + ".Data_Value", 0);
+            int xp = config.getInt(skillType + "." + blockName + ".XP_Gain", 0);
+            int tier = config.getInt(skillType + "." + blockName + ".Tier", 1);
+            boolean dropItem = config.getBoolean(skillType + "." + blockName + ".Drop_Item", false);
+            int dropID = config.getInt(skillType + "." + blockName + ".Drop_Item_ID", 0);
+            byte dropData = (byte) config.getInt(skillType + "." + blockName + ".Drop_Item_Data_Value", 0);
+            int minimumDropAmount = config.getInt(skillType + "." + blockName + ".Min_Drop_Item_Amount", 1);
+            int maxiumDropAmount = config.getInt(skillType + "." + blockName + ".Max_Drop_Item_Amount", 1);
+
+            CustomBlock block;
+            ItemStack itemDrop;
+            ItemStack blockItem;
+
+            if (id == 0) {
+                plugin.getLogger().warning("Missing ID. This block will be skipped.");
+                continue;
+            }
+
+            if (skillType.equals("Ability_Blocks")) {
+                blockItem = (new MaterialData(id, data)).toItemStack(1);
+
+                blockList.add(blockItem);
+                continue;
+            }
+
+            if (dropItem && dropID == 0) {
+                plugin.getLogger().warning("Incomplete item drop information. This block will drop itself.");
+                dropItem = false;
+            }
+
+            if (dropItem) {
+                itemDrop = (new MaterialData(dropID, dropData)).toItemStack(1);
+            }
+            else {
+                itemDrop = (new MaterialData(id, data)).toItemStack(1);
+            }
+
+            block = new CustomBlock(minimumDropAmount, maxiumDropAmount, itemDrop, tier, xp, data, id);
+            blockItem = (new MaterialData(id, data)).toItemStack(1);
+
+            if (skillType.equals("Mining") && config.getBoolean(skillType + "." + blockName + ".Is_Ore")) {
+                customOres.add(blockItem);
+            }
+            else if (skillType.equals("Woodcutting")) {
+                if (config.getBoolean(skillType + "." + blockName + ".Is_Log")) {
+                    customLogs.add(blockItem);
+                }
+                else {
+                    customLeaves.add(blockItem);
+                    block.setXpGain(0); // Leaves don't grant XP
+                }
+            }
+
+            blockList.add(blockItem);
+            customItems.add(blockItem);
+            customBlocks.add(block);
+        }
+    }
+}

+ 84 - 82
src/main/java/com/gmail/nossr50/mods/config/CustomEntityConfig.java → src/main/java/com/gmail/nossr50/config/mods/CustomEntityConfig.java

@@ -1,82 +1,84 @@
-package com.gmail.nossr50.mods.config;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.EntityType;
-import org.bukkit.inventory.ItemStack;
-
-import com.gmail.nossr50.config.ConfigLoader;
-import com.gmail.nossr50.mods.datatypes.CustomEntity;
-
-public class CustomEntityConfig extends ConfigLoader {
-    private static CustomEntityConfig instance;
-    public List<Integer> customEntityIds = new ArrayList<Integer>();
-    public List<Integer> customHostileEntityIds = new ArrayList<Integer>();
-    public List<Integer> customNeutralEntityIds = new ArrayList<Integer>();
-    public List<Integer> customPassiveEntityIds = new ArrayList<Integer>();
-    public List<EntityType> customEntityTypes = new ArrayList<EntityType>();
-    public List<CustomEntity> customEntities = new ArrayList<CustomEntity>();
-
-    public CustomEntityConfig() {
-        super("ModConfigs", "entities.yml");
-        loadKeys();
-    }
-
-    public static CustomEntityConfig getInstance() {
-        if (instance == null) {
-            instance = new CustomEntityConfig();
-        }
-
-        return instance;
-    }
-
-    @Override
-    protected void loadKeys() {
-        loadMobs("Hostile", customHostileEntityIds);
-        loadMobs("Neutral", customNeutralEntityIds);
-        loadMobs("Passive", customPassiveEntityIds);
-    }
-
-    private void loadMobs(String entityType, List<Integer> entityIdList) {
-        ConfigurationSection entitySection = config.getConfigurationSection(entityType);
-
-        if (entitySection == null) {
-            return;
-        }
-
-        Set<String> entityConfigSet = entitySection.getKeys(false);
-
-        for (String entityName : entityConfigSet) {
-            int id = config.getInt(entityType + "." + entityName + ".ID", 0);
-            EntityType type = EntityType.fromId(id);
-            double xpMultiplier = config.getDouble(entityType + "." + entityName + ".XP_Multiplier", 1.0D);
-            boolean canBeTamed = config.getBoolean(entityType + "." + entityName + ".Tameable", false);
-            int tamingXp = config.getInt(entityType + "." + entityName + "Taming_XP", 0);
-            boolean canBeSummoned = config.getBoolean(entityType + "." + entityName + "CanBeSummoned", false);
-            int callOfTheWildId = config.getInt(entityType + "." + entityName + "COTW_Material_ID", 0);
-            int callOfTheWildData = config.getInt(entityType + "." + entityName + "COTW_Material_Data", 0);
-            int callOfTheWildAmount = config.getInt(entityType + "." + entityName + "COTW_Material_Amount", 0);
-
-            CustomEntity entity;
-
-            if (id == 0) {
-                plugin.getLogger().warning("Missing ID. This block will be skipped.");
-                continue;
-            }
-
-            if (canBeSummoned && (callOfTheWildId == 0 || callOfTheWildAmount == 0)) {
-                plugin.getLogger().warning("Incomplete Call of the Wild information. This enitity will not be able to be summoned by Call of the Wild.");
-                canBeSummoned = false;
-            }
-
-            entity = new CustomEntity(id, type, xpMultiplier, canBeTamed, tamingXp, canBeSummoned, new ItemStack(callOfTheWildId, callOfTheWildData), callOfTheWildAmount);
-
-            entityIdList.add(id);
-            customEntityTypes.add(type);
-            customEntities.add(entity);
-        }
-    }
-}
+package com.gmail.nossr50.config.mods;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.mods.CustomEntity;
+
+public class CustomEntityConfig extends ConfigLoader {
+    private static CustomEntityConfig instance;
+
+    public List<Integer> customEntityIds        = new ArrayList<Integer>();
+    public List<Integer> customHostileEntityIds = new ArrayList<Integer>();
+    public List<Integer> customNeutralEntityIds = new ArrayList<Integer>();
+    public List<Integer> customPassiveEntityIds = new ArrayList<Integer>();
+
+    public List<EntityType> customEntityTypes = new ArrayList<EntityType>();
+    public List<CustomEntity> customEntities = new ArrayList<CustomEntity>();
+
+    public CustomEntityConfig() {
+        super("ModConfigs", "entities.yml");
+        loadKeys();
+    }
+
+    public static CustomEntityConfig getInstance() {
+        if (instance == null) {
+            instance = new CustomEntityConfig();
+        }
+
+        return instance;
+    }
+
+    @Override
+    protected void loadKeys() {
+        loadMobs("Hostile", customHostileEntityIds);
+        loadMobs("Neutral", customNeutralEntityIds);
+        loadMobs("Passive", customPassiveEntityIds);
+    }
+
+    private void loadMobs(String entityType, List<Integer> entityIdList) {
+        ConfigurationSection entitySection = config.getConfigurationSection(entityType);
+
+        if (entitySection == null) {
+            return;
+        }
+
+        Set<String> entityConfigSet = entitySection.getKeys(false);
+
+        for (String entityName : entityConfigSet) {
+            int id = config.getInt(entityType + "." + entityName + ".ID", 0);
+            EntityType type = EntityType.fromId(id);
+            double xpMultiplier = config.getDouble(entityType + "." + entityName + ".XP_Multiplier", 1.0D);
+            boolean canBeTamed = config.getBoolean(entityType + "." + entityName + ".Tameable", false);
+            int tamingXp = config.getInt(entityType + "." + entityName + "Taming_XP", 0);
+            boolean canBeSummoned = config.getBoolean(entityType + "." + entityName + "CanBeSummoned", false);
+            int callOfTheWildId = config.getInt(entityType + "." + entityName + "COTW_Material_ID", 0);
+            int callOfTheWildData = config.getInt(entityType + "." + entityName + "COTW_Material_Data", 0);
+            int callOfTheWildAmount = config.getInt(entityType + "." + entityName + "COTW_Material_Amount", 0);
+
+            CustomEntity entity;
+
+            if (id == 0) {
+                plugin.getLogger().warning("Missing ID. This block will be skipped.");
+                continue;
+            }
+
+            if (canBeSummoned && (callOfTheWildId == 0 || callOfTheWildAmount == 0)) {
+                plugin.getLogger().warning("Incomplete Call of the Wild information. This enitity will not be able to be summoned by Call of the Wild.");
+                canBeSummoned = false;
+            }
+
+            entity = new CustomEntity(id, type, xpMultiplier, canBeTamed, tamingXp, canBeSummoned, new ItemStack(callOfTheWildId, callOfTheWildData), callOfTheWildAmount);
+
+            entityIdList.add(id);
+            customEntityTypes.add(type);
+            customEntities.add(entity);
+        }
+    }
+}

+ 105 - 101
src/main/java/com/gmail/nossr50/mods/config/CustomToolsConfig.java → src/main/java/com/gmail/nossr50/config/mods/CustomToolConfig.java

@@ -1,101 +1,105 @@
-package com.gmail.nossr50.mods.config;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-
-import org.bukkit.configuration.ConfigurationSection;
-
-import com.gmail.nossr50.config.ConfigLoader;
-import com.gmail.nossr50.mods.datatypes.CustomTool;
-import com.gmail.nossr50.skills.repair.Repairable;
-import com.gmail.nossr50.skills.repair.RepairableFactory;
-
-public class CustomToolsConfig extends ConfigLoader {
-    private static CustomToolsConfig instance;
-    private List<Repairable> repairables;
-    public List<Integer> customAxeIDs = new ArrayList<Integer>();
-    public List<Integer> customBowIDs = new ArrayList<Integer>();
-    public List<Integer> customHoeIDs = new ArrayList<Integer>();
-    public List<Integer> customPickaxeIDs = new ArrayList<Integer>();
-    public List<Integer> customShovelIDs = new ArrayList<Integer>();
-    public List<Integer> customSwordIDs = new ArrayList<Integer>();
-    public List<Integer> customIDs = new ArrayList<Integer>();
-    public List<CustomTool> customToolList = new ArrayList<CustomTool>();
-    public HashMap<Integer, CustomTool> customTools = new HashMap<Integer, CustomTool>();
-
-    private CustomToolsConfig() {
-        super("ModConfigs", "tools.yml");
-        loadKeys();
-    }
-
-    public static CustomToolsConfig getInstance() {
-        if (instance == null) {
-            instance = new CustomToolsConfig();
-        }
-
-        return instance;
-    }
-
-    @Override
-    protected void loadKeys() {
-        repairables = new ArrayList<Repairable>();
-
-        loadTool("Axes", customAxeIDs);
-        loadTool("Bows", customBowIDs);
-        loadTool("Hoes", customHoeIDs);
-        loadTool("Pickaxes", customPickaxeIDs);
-        loadTool("Shovels", customShovelIDs);
-        loadTool("Swords", customSwordIDs);
-    }
-
-    private void loadTool(String toolType, List<Integer> idList) {
-        ConfigurationSection toolSection = config.getConfigurationSection(toolType);
-
-        if (toolSection == null)
-            return;
-
-        Set<String> toolConfigSet = toolSection.getKeys(false);
-
-        for (String toolName : toolConfigSet) {
-            int id = config.getInt(toolType + "." + toolName + ".ID", 0);
-            double multiplier = config.getDouble(toolType + "." + toolName + ".XP_Modifier", 1.0);
-            boolean abilityEnabled = config.getBoolean(toolType + "." + toolName + ".Ability_Enabled", true);
-            int tier = config.getInt(toolType + "." + toolName + ".Tier", 1);
-            boolean repairable = config.getBoolean(toolType + "." + toolName + ".Repairable");
-            int repairID = config.getInt(toolType + "." + toolName + ".Repair_Material_ID", 0);
-            byte repairData = (byte) config.getInt(toolType + "." + toolName + ".Repair_Material_Data_Value", 0);
-            int repairQuantity = config.getInt(toolType + "." + toolName + ".Repair_Material_Quantity", 0);
-            short durability = (short) config.getInt(toolType + "." + toolName + ".Durability", 0);
-
-            if (id == 0) {
-                plugin.getLogger().warning("Missing ID. This item will be skipped.");
-                continue;
-            }
-
-            if (repairable && (repairID == 0 || repairQuantity == 0 || durability == 0)) {
-                plugin.getLogger().warning("Incomplete repair information. This item will be unrepairable.");
-                repairable = false;
-            }
-
-            CustomTool tool;
-
-            if (repairable) {
-                repairables.add(RepairableFactory.getRepairable(id, repairID, repairData, repairQuantity, durability));
-            }
-
-            tool = new CustomTool(tier, abilityEnabled, multiplier, durability, id);
-
-            idList.add(id);
-            customIDs.add(id);
-            customToolList.add(tool);
-            customTools.put(id, tool);
-        }
-    }
-
-    public List<Repairable> getLoadedRepairables() {
-        if (repairables == null) return new ArrayList<Repairable>();
-        return repairables;
-    }
-}
+package com.gmail.nossr50.config.mods;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import org.bukkit.configuration.ConfigurationSection;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.mods.CustomTool;
+import com.gmail.nossr50.skills.repair.Repairable;
+import com.gmail.nossr50.skills.repair.RepairableFactory;
+
+public class CustomToolConfig extends ConfigLoader {
+    private static CustomToolConfig instance;
+    private List<Repairable> repairables;
+    public List<Integer> customAxeIDs = new ArrayList<Integer>();
+    public List<Integer> customBowIDs = new ArrayList<Integer>();
+    public List<Integer> customHoeIDs = new ArrayList<Integer>();
+    public List<Integer> customPickaxeIDs = new ArrayList<Integer>();
+    public List<Integer> customShovelIDs = new ArrayList<Integer>();
+    public List<Integer> customSwordIDs = new ArrayList<Integer>();
+    public List<Integer> customIDs = new ArrayList<Integer>();
+    public List<CustomTool> customToolList = new ArrayList<CustomTool>();
+    public HashMap<Integer, CustomTool> customTools = new HashMap<Integer, CustomTool>();
+
+    private CustomToolConfig() {
+        super("ModConfigs", "tools.yml");
+        loadKeys();
+    }
+
+    public static CustomToolConfig getInstance() {
+        if (instance == null) {
+            instance = new CustomToolConfig();
+        }
+
+        return instance;
+    }
+
+    public List<Repairable> getLoadedRepairables() {
+        if (repairables == null) {
+            return new ArrayList<Repairable>();
+        }
+
+        return repairables;
+    }
+
+    @Override
+    protected void loadKeys() {
+        repairables = new ArrayList<Repairable>();
+
+        loadTool("Axes", customAxeIDs);
+        loadTool("Bows", customBowIDs);
+        loadTool("Hoes", customHoeIDs);
+        loadTool("Pickaxes", customPickaxeIDs);
+        loadTool("Shovels", customShovelIDs);
+        loadTool("Swords", customSwordIDs);
+    }
+
+    private void loadTool(String toolType, List<Integer> idList) {
+        ConfigurationSection toolSection = config.getConfigurationSection(toolType);
+
+        if (toolSection == null) {
+            return;
+        }
+
+        Set<String> toolConfigSet = toolSection.getKeys(false);
+
+        for (String toolName : toolConfigSet) {
+            int id = config.getInt(toolType + "." + toolName + ".ID", 0);
+            double multiplier = config.getDouble(toolType + "." + toolName + ".XP_Modifier", 1.0);
+            boolean abilityEnabled = config.getBoolean(toolType + "." + toolName + ".Ability_Enabled", true);
+            int tier = config.getInt(toolType + "." + toolName + ".Tier", 1);
+            boolean repairable = config.getBoolean(toolType + "." + toolName + ".Repairable");
+            int repairID = config.getInt(toolType + "." + toolName + ".Repair_Material_ID", 0);
+            byte repairData = (byte) config.getInt(toolType + "." + toolName + ".Repair_Material_Data_Value", 0);
+            int repairQuantity = config.getInt(toolType + "." + toolName + ".Repair_Material_Quantity", 0);
+            short durability = (short) config.getInt(toolType + "." + toolName + ".Durability", 0);
+
+            if (id == 0) {
+                plugin.getLogger().warning("Missing ID. This item will be skipped.");
+                continue;
+            }
+
+            if (repairable && (repairID == 0 || repairQuantity == 0 || durability == 0)) {
+                plugin.getLogger().warning("Incomplete repair information. This item will be unrepairable.");
+                repairable = false;
+            }
+
+            CustomTool tool;
+
+            if (repairable) {
+                repairables.add(RepairableFactory.getRepairable(id, repairID, repairData, repairQuantity, durability));
+            }
+
+            tool = new CustomTool(tier, abilityEnabled, multiplier, durability, id);
+
+            idList.add(id);
+            customIDs.add(id);
+            customToolList.add(tool);
+            customTools.put(id, tool);
+        }
+    }
+}

+ 10 - 9
src/main/java/com/gmail/nossr50/config/ItemWeightsConfig.java → src/main/java/com/gmail/nossr50/config/party/ItemWeightConfig.java

@@ -1,27 +1,25 @@
-package com.gmail.nossr50.config;
+package com.gmail.nossr50.config.party;
 
 import org.bukkit.Material;
 
+import com.gmail.nossr50.config.ConfigLoader;
 import com.gmail.nossr50.util.StringUtils;
 
-public class ItemWeightsConfig extends ConfigLoader {
-    private static ItemWeightsConfig instance;
+public class ItemWeightConfig extends ConfigLoader {
+    private static ItemWeightConfig instance;
 
-    private ItemWeightsConfig() {
+    private ItemWeightConfig() {
         super("itemweights.yml");
     }
 
-    public static ItemWeightsConfig getInstance() {
+    public static ItemWeightConfig getInstance() {
         if (instance == null) {
-            instance = new ItemWeightsConfig();
+            instance = new ItemWeightConfig();
         }
 
         return instance;
     }
 
-    @Override
-    protected void loadKeys() {}
-
     public int getItemWeight(Material material) {
         String materialName = StringUtils.getPrettyItemString(material).replace(" ", "_");
         int itemWeight = config.getInt("Item_Weights.Default");
@@ -31,4 +29,7 @@ public class ItemWeightsConfig extends ConfigLoader {
         }
         return itemWeight;
     }
+
+    @Override
+    protected void loadKeys() {}
 }

+ 65 - 0
src/main/java/com/gmail/nossr50/config/spout/SpoutConfig.java

@@ -0,0 +1,65 @@
+package com.gmail.nossr50.config.spout;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.datatypes.spout.huds.HudType;
+
+public class SpoutConfig extends ConfigLoader {
+    private static SpoutConfig instance;
+    public HudType defaultHudType;
+
+    private SpoutConfig() {
+        super("spout.yml");
+        loadKeys();
+    }
+
+    public static SpoutConfig getInstance() {
+        if (instance == null) {
+            instance = new SpoutConfig();
+        }
+
+        return instance;
+    }
+
+    @Override
+    protected void loadKeys() {
+        // Setup default HUD
+        String temp = config.getString("Spout.HUD.Default", "STANDARD");
+
+        for (HudType hudType : HudType.values()) {
+            if (hudType.toString().equalsIgnoreCase(temp)) {
+                defaultHudType = hudType;
+                break;
+            }
+        }
+
+        if (defaultHudType == null) {
+            defaultHudType = HudType.STANDARD;
+        }
+    }
+
+    public boolean getShowPowerLevel() { return config.getBoolean("HUD.Show_Power_Level", true); }
+    public String getMenuKey() { return config.getString("Menu.Key", "KEY_M"); }
+
+    /* XP Bar */
+    public boolean getXPBarEnabled() { return config.getBoolean("XP.Bar.Enabled", true); }
+    public void setXPBarEnabled(boolean enabled) { config.set("XP.Bar.Enabled", enabled); }
+
+    public boolean getXPBarIconEnabled() { return config.getBoolean("XP.Icon.Enabled", true); }
+    public int getXPBarXPosition() { return config.getInt("XP.Bar.X_POS", 95); }
+    public int getXPBarYPosition() { return config.getInt("XP.Bar.Y_POS", 6); }
+    public int getXPIconXPosition() { return config.getInt("XP.Icon.X_POS", 78); }
+    public int getXPIconYPosition() { return config.getInt("XP.Icon.Y_POS", 2); }
+
+    /* HUD Colors */
+    public double getRetroHUDXPBorderRed() { return config.getDouble("HUD.Retro.Colors.Border.RED", 0.0); }
+    public double getRetroHUDXPBorderGreen() { return config.getDouble("HUD.Retro.Colors.Border.GREEN", 0.0); }
+    public double getRetroHUDXPBorderBlue() { return config.getDouble("HUD.Retro.Colors.Border.BLUE", 0.0); }
+    public double getRetroHUDXPBackgroundRed() { return config.getDouble("HUD.Retro.Colors.Background.RED", 0.75); }
+    public double getRetroHUDXPBackgroundGreen() { return config.getDouble("HUD.Retro.Colors.Background.GREEN", 0.75); }
+    public double getRetroHUDXPBackgroundBlue() { return config.getDouble("HUD.Retro.Colors.Background.BLUE", 0.75); }
+
+    public double getRetroHUDRed(SkillType skill) { return config.getDouble("HUD.Retro.Colors." + skill.toString().toLowerCase() +".RED", 0.3); }
+    public double getRetroHUDGreen(SkillType skill) { return config.getDouble("HUD.Retro.Colors." + skill.toString().toLowerCase() +".RED", 0.3); }
+    public double getRetroHUDBlue(SkillType skill) { return config.getDouble("HUD.Retro.Colors." + skill.toString().toLowerCase() +".RED", 0.3); }
+}

+ 287 - 285
src/main/java/com/gmail/nossr50/config/TreasuresConfig.java → src/main/java/com/gmail/nossr50/config/treasure/TreasureConfig.java

@@ -1,285 +1,287 @@
-package com.gmail.nossr50.config;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.bukkit.Material;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.material.MaterialData;
-
-import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
-import com.gmail.nossr50.datatypes.treasure.FishingTreasure;
-import com.gmail.nossr50.datatypes.treasure.HylianTreasure;
-import com.gmail.nossr50.datatypes.treasure.Treasure;
-
-public class TreasuresConfig extends ConfigLoader {
-    private static TreasuresConfig instance;
-    public List<ExcavationTreasure> excavationFromDirt = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromGrass = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromSand = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromGravel = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromClay = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromMycel = new ArrayList<ExcavationTreasure>();
-    public List<ExcavationTreasure> excavationFromSoulSand = new ArrayList<ExcavationTreasure>();
-
-    public List<HylianTreasure> hylianFromBushes = new ArrayList<HylianTreasure>();
-    public List<HylianTreasure> hylianFromFlowers = new ArrayList<HylianTreasure>();
-    public List<HylianTreasure> hylianFromPots = new ArrayList<HylianTreasure>();
-
-    public List<FishingTreasure> fishingRewards = new ArrayList<FishingTreasure>();
-
-    private TreasuresConfig() {
-        super("treasures.yml");
-        loadKeys();
-    }
-
-    public static TreasuresConfig getInstance() {
-        if (instance == null) {
-            instance = new TreasuresConfig();
-        }
-
-        return instance;
-    }
-
-    @Override
-    protected void loadKeys() {
-        Map<String, Treasure> treasures = new HashMap<String, Treasure>();
-        ConfigurationSection treasureSection = config.getConfigurationSection("Treasures");
-        Set<String> treasureConfigSet = treasureSection.getKeys(false);
-
-        for (String treasureName : treasureConfigSet) {
-
-            // Validate all the things!
-            List<String> reason = new ArrayList<String>();
-
-            /*
-             * ID, Amount, and Data
-             */
-
-            if (!config.contains("Treasures." + treasureName + ".ID")) {
-                reason.add("Missing ID");
-            }
-
-            if (!config.contains("Treasures." + treasureName + ".Amount")) {
-                reason.add("Missing Amount");
-            }
-
-            if (!config.contains("Treasures." + treasureName + ".Data")) {
-                reason.add("Missing Data");
-            }
-
-            int id = config.getInt("Treasures." + treasureName + ".ID");
-            int amount = config.getInt("Treasures." + treasureName + ".Amount");
-            int data = config.getInt("Treasures." + treasureName + ".Data");
-
-            if (Material.getMaterial(id) == null) {
-                reason.add("Invalid id: " + id);
-            }
-
-            if (amount < 1) {
-                reason.add("Invalid amount: " + amount);
-            }
-
-            if (data > 127 || data < -128) {
-                reason.add("Invalid data: " + data);
-            }
-
-            /*
-             * XP, Drop Chance, and Drop Level
-             */
-
-            if (!config.contains("Treasures." + treasureName + ".XP")) {
-                reason.add("Missing XP");
-            }
-
-            if (!config.contains("Treasures." + treasureName + ".Drop_Chance")) {
-                reason.add("Missing Drop_Chance");
-            }
-
-            if (!config.contains("Treasures." + treasureName + ".Drop_Level")) {
-                reason.add("Missing Drop_Level");
-            }
-
-            int xp = config.getInt("Treasures." + treasureName + ".XP");
-            Double dropChance = config.getDouble("Treasures." + treasureName + ".Drop_Chance");
-            int dropLevel = config.getInt("Treasures." + treasureName + ".Drop_Level");
-
-            if (xp < 0) {
-                reason.add("Invalid xp: " + xp);
-            }
-
-            if (dropChance < 0) {
-                reason.add("Invalid Drop_Chance: " + dropChance);
-            }
-
-            if (dropLevel < 0) {
-                reason.add("Invalid Drop_Level: " + dropLevel);
-            }
-
-            /*
-             * Drops From & Max Level
-             */
-
-            ItemStack item = (new MaterialData(id, (byte) data)).toItemStack(amount);
-
-            if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Fishing", false)) {
-                if (config.getConfigurationSection("Treasures." + treasureName + ".Drops_From").getKeys(false).size() != 1) {
-                    reason.add("Fishing drops cannot also be excavation drops");
-                }
-
-                if (!config.contains("Treasures." + treasureName + ".Max_Level")) {
-                    reason.add("Missing Max_Level");
-                }
-
-                int maxLevel = config.getInt("Treasures." + treasureName + ".Max_Level");
-
-                if (noErrorsInTreasure(reason)) {
-                    FishingTreasure fTreasure = new FishingTreasure(item, xp, dropChance, dropLevel, maxLevel);
-                    treasures.put(treasureName, fTreasure);
-                }
-            }
-            else {
-                ExcavationTreasure eTreasure = new ExcavationTreasure(item, xp, dropChance, dropLevel);
-                HylianTreasure hTreasure = new HylianTreasure(item, xp, dropChance, dropLevel);
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Dirt", false)) {
-                    eTreasure.setDropsFromDirt();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Grass", false)) {
-                    eTreasure.setDropsFromGrass();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Sand", false)) {
-                    eTreasure.setDropsFromSand();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Gravel", false)) {
-                    eTreasure.setDropsFromGravel();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Clay", false)) {
-                    eTreasure.setDropsFromClay();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Mycelium", false)) {
-                    eTreasure.setDropsFromMycel();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Soul_Sand", false)) {
-                    eTreasure.setDropsFromSoulSand();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Bushes", false)) {
-                    hTreasure.setDropsFromBushes();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Flowers", false)) {
-                    hTreasure.setDropsFromFlowers();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Pots", false)) {
-                    hTreasure.setDropsFromPots();
-                }
-
-                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Fishing", false)) {
-                    reason.add("Excavation drops cannot also be fishing drops");
-                }
-
-                if (noErrorsInTreasure(reason) && hTreasure.getDropsFrom() == (byte) 0x0) {
-                    treasures.put(treasureName, eTreasure);
-                }
-                else if (noErrorsInTreasure(reason) && eTreasure.getDropsFrom() == (byte) 0x0){
-                    treasures.put(treasureName, hTreasure);
-                }
-            }
-        }
-
-        List<String> excavationTreasures = config.getStringList("Excavation.Treasure");
-        List<String> fishingTreasures = config.getStringList("Fishing.Treasure");
-        List<String> hylianTreasures = config.getStringList("Hylian_Luck.Treasure");
-
-        for (Entry<String,Treasure> nextEntry : treasures.entrySet()) {
-            String treasureKey = nextEntry.getKey();
-            Treasure treasure = nextEntry.getValue();
-
-            if (treasure instanceof FishingTreasure) {
-                if (!fishingTreasures.contains(treasureKey)) {
-                    continue;
-                }
-
-                fishingRewards.add((FishingTreasure) treasure);
-            }
-            else if (treasure instanceof HylianTreasure) {
-                if (!hylianTreasures.contains(treasureKey)) {
-                    continue;
-                }
-
-                HylianTreasure hTreasure = (HylianTreasure) treasure;
-
-                if (hTreasure.getDropsFromBushes()) {
-                    hylianFromBushes.add(hTreasure);
-                }
-
-                if (hTreasure.getDropsFromFlowers()) {
-                    hylianFromFlowers.add(hTreasure);
-                }
-
-                if (hTreasure.getDropsFromPots()) {
-                    hylianFromPots.add(hTreasure);
-                }
-            }
-            else if (treasure instanceof ExcavationTreasure) {
-                if (!excavationTreasures.contains(treasureKey)) {
-                    continue;
-                }
-
-                ExcavationTreasure eTreasure = (ExcavationTreasure) treasure;
-
-                if (eTreasure.getDropsFromDirt()) {
-                    excavationFromDirt.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromGrass()) {
-                    excavationFromGrass.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromSand()) {
-                    excavationFromSand.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromGravel()) {
-                    excavationFromGravel.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromClay()) {
-                    excavationFromClay.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromMycel()) {
-                    excavationFromMycel.add(eTreasure);
-                }
-
-                if (eTreasure.getDropsFromSoulSand()) {
-                    excavationFromSoulSand.add(eTreasure);
-                }
-            }
-        }
-    }
-
-    private boolean noErrorsInTreasure(List<String> issues) {
-        if (issues.isEmpty()) {
-            return true;
-        }
-
-        for (String issue : issues) {
-            plugin.getLogger().warning(issue);
-        }
-        return false;
-    }
-}
+package com.gmail.nossr50.config.treasure;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.material.MaterialData;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
+import com.gmail.nossr50.datatypes.treasure.FishingTreasure;
+import com.gmail.nossr50.datatypes.treasure.HylianTreasure;
+import com.gmail.nossr50.datatypes.treasure.Treasure;
+
+public class TreasureConfig extends ConfigLoader {
+    private static TreasureConfig instance;
+
+    public List<ExcavationTreasure> excavationFromDirt     = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromGrass    = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromSand     = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromGravel   = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromClay     = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromMycel    = new ArrayList<ExcavationTreasure>();
+    public List<ExcavationTreasure> excavationFromSoulSand = new ArrayList<ExcavationTreasure>();
+
+    public List<HylianTreasure> hylianFromBushes  = new ArrayList<HylianTreasure>();
+    public List<HylianTreasure> hylianFromFlowers = new ArrayList<HylianTreasure>();
+    public List<HylianTreasure> hylianFromPots    = new ArrayList<HylianTreasure>();
+
+    public List<FishingTreasure> fishingRewards = new ArrayList<FishingTreasure>();
+
+    private TreasureConfig() {
+        super("treasures.yml");
+        loadKeys();
+    }
+
+    public static TreasureConfig getInstance() {
+        if (instance == null) {
+            instance = new TreasureConfig();
+        }
+
+        return instance;
+    }
+
+    @Override
+    protected void loadKeys() {
+        Map<String, Treasure> treasures = new HashMap<String, Treasure>();
+        ConfigurationSection treasureSection = config.getConfigurationSection("Treasures");
+        Set<String> treasureConfigSet = treasureSection.getKeys(false);
+
+        for (String treasureName : treasureConfigSet) {
+
+            // Validate all the things!
+            List<String> reason = new ArrayList<String>();
+
+            /*
+             * ID, Amount, and Data
+             */
+
+            if (!config.contains("Treasures." + treasureName + ".ID")) {
+                reason.add("Missing ID");
+            }
+
+            if (!config.contains("Treasures." + treasureName + ".Amount")) {
+                reason.add("Missing Amount");
+            }
+
+            if (!config.contains("Treasures." + treasureName + ".Data")) {
+                reason.add("Missing Data");
+            }
+
+            int id = config.getInt("Treasures." + treasureName + ".ID");
+            int amount = config.getInt("Treasures." + treasureName + ".Amount");
+            int data = config.getInt("Treasures." + treasureName + ".Data");
+
+            if (Material.getMaterial(id) == null) {
+                reason.add("Invalid id: " + id);
+            }
+
+            if (amount < 1) {
+                reason.add("Invalid amount: " + amount);
+            }
+
+            if (data > 127 || data < -128) {
+                reason.add("Invalid data: " + data);
+            }
+
+            /*
+             * XP, Drop Chance, and Drop Level
+             */
+
+            if (!config.contains("Treasures." + treasureName + ".XP")) {
+                reason.add("Missing XP");
+            }
+
+            if (!config.contains("Treasures." + treasureName + ".Drop_Chance")) {
+                reason.add("Missing Drop_Chance");
+            }
+
+            if (!config.contains("Treasures." + treasureName + ".Drop_Level")) {
+                reason.add("Missing Drop_Level");
+            }
+
+            int xp = config.getInt("Treasures." + treasureName + ".XP");
+            Double dropChance = config.getDouble("Treasures." + treasureName + ".Drop_Chance");
+            int dropLevel = config.getInt("Treasures." + treasureName + ".Drop_Level");
+
+            if (xp < 0) {
+                reason.add("Invalid xp: " + xp);
+            }
+
+            if (dropChance < 0) {
+                reason.add("Invalid Drop_Chance: " + dropChance);
+            }
+
+            if (dropLevel < 0) {
+                reason.add("Invalid Drop_Level: " + dropLevel);
+            }
+
+            /*
+             * Drops From & Max Level
+             */
+
+            ItemStack item = (new MaterialData(id, (byte) data)).toItemStack(amount);
+
+            if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Fishing", false)) {
+                if (config.getConfigurationSection("Treasures." + treasureName + ".Drops_From").getKeys(false).size() != 1) {
+                    reason.add("Fishing drops cannot also be excavation drops");
+                }
+
+                if (!config.contains("Treasures." + treasureName + ".Max_Level")) {
+                    reason.add("Missing Max_Level");
+                }
+
+                int maxLevel = config.getInt("Treasures." + treasureName + ".Max_Level");
+
+                if (noErrorsInTreasure(reason)) {
+                    FishingTreasure fTreasure = new FishingTreasure(item, xp, dropChance, dropLevel, maxLevel);
+                    treasures.put(treasureName, fTreasure);
+                }
+            }
+            else {
+                ExcavationTreasure eTreasure = new ExcavationTreasure(item, xp, dropChance, dropLevel);
+                HylianTreasure hTreasure = new HylianTreasure(item, xp, dropChance, dropLevel);
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Dirt", false)) {
+                    eTreasure.setDropsFromDirt();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Grass", false)) {
+                    eTreasure.setDropsFromGrass();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Sand", false)) {
+                    eTreasure.setDropsFromSand();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Gravel", false)) {
+                    eTreasure.setDropsFromGravel();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Clay", false)) {
+                    eTreasure.setDropsFromClay();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Mycelium", false)) {
+                    eTreasure.setDropsFromMycel();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Soul_Sand", false)) {
+                    eTreasure.setDropsFromSoulSand();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Bushes", false)) {
+                    hTreasure.setDropsFromBushes();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Flowers", false)) {
+                    hTreasure.setDropsFromFlowers();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Pots", false)) {
+                    hTreasure.setDropsFromPots();
+                }
+
+                if (config.getBoolean("Treasures." + treasureName + ".Drops_From.Fishing", false)) {
+                    reason.add("Excavation drops cannot also be fishing drops");
+                }
+
+                if (noErrorsInTreasure(reason) && hTreasure.getDropsFrom() == (byte) 0x0) {
+                    treasures.put(treasureName, eTreasure);
+                }
+                else if (noErrorsInTreasure(reason) && eTreasure.getDropsFrom() == (byte) 0x0) {
+                    treasures.put(treasureName, hTreasure);
+                }
+            }
+        }
+
+        List<String> excavationTreasures = config.getStringList("Excavation.Treasure");
+        List<String> fishingTreasures = config.getStringList("Fishing.Treasure");
+        List<String> hylianTreasures = config.getStringList("Hylian_Luck.Treasure");
+
+        for (Entry<String, Treasure> nextEntry : treasures.entrySet()) {
+            String treasureKey = nextEntry.getKey();
+            Treasure treasure = nextEntry.getValue();
+
+            if (treasure instanceof FishingTreasure) {
+                if (!fishingTreasures.contains(treasureKey)) {
+                    continue;
+                }
+
+                fishingRewards.add((FishingTreasure) treasure);
+            }
+            else if (treasure instanceof HylianTreasure) {
+                if (!hylianTreasures.contains(treasureKey)) {
+                    continue;
+                }
+
+                HylianTreasure hTreasure = (HylianTreasure) treasure;
+
+                if (hTreasure.getDropsFromBushes()) {
+                    hylianFromBushes.add(hTreasure);
+                }
+
+                if (hTreasure.getDropsFromFlowers()) {
+                    hylianFromFlowers.add(hTreasure);
+                }
+
+                if (hTreasure.getDropsFromPots()) {
+                    hylianFromPots.add(hTreasure);
+                }
+            }
+            else if (treasure instanceof ExcavationTreasure) {
+                if (!excavationTreasures.contains(treasureKey)) {
+                    continue;
+                }
+
+                ExcavationTreasure eTreasure = (ExcavationTreasure) treasure;
+
+                if (eTreasure.getDropsFromDirt()) {
+                    excavationFromDirt.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromGrass()) {
+                    excavationFromGrass.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromSand()) {
+                    excavationFromSand.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromGravel()) {
+                    excavationFromGravel.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromClay()) {
+                    excavationFromClay.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromMycel()) {
+                    excavationFromMycel.add(eTreasure);
+                }
+
+                if (eTreasure.getDropsFromSoulSand()) {
+                    excavationFromSoulSand.add(eTreasure);
+                }
+            }
+        }
+    }
+
+    private boolean noErrorsInTreasure(List<String> issues) {
+        if (issues.isEmpty()) {
+            return true;
+        }
+
+        for (String issue : issues) {
+            plugin.getLogger().warning(issue);
+        }
+        return false;
+    }
+}

+ 231 - 174
src/main/java/com/gmail/nossr50/database/Database.java → src/main/java/com/gmail/nossr50/database/DatabaseManager.java

@@ -10,19 +10,19 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
-import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.runnables.SQLReconnect;
-import com.gmail.nossr50.datatypes.McMMOPlayer;
-import com.gmail.nossr50.skills.utilities.SkillType;
-import com.gmail.nossr50.spout.SpoutTools;
-import com.gmail.nossr50.spout.huds.SpoutHud;
-import com.gmail.nossr50.util.Users;
-
-public final class Database {
+import com.gmail.nossr50.datatypes.database.DatabaseUpdateType;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.datatypes.spout.huds.McMMOHud;
+import com.gmail.nossr50.runnables.database.SQLReconnectTask;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.spout.SpoutUtils;
+
+public final class DatabaseManager {
     private static String connectionString;
 
     private static String tablePrefix = Config.getInstance().getMySQLTablePrefix();
@@ -32,7 +32,7 @@ public final class Database {
     private static final double SCALING_FACTOR = 40;
 
     // Minimum wait in nanoseconds (default 500ms)
-    private static final long MIN_WAIT = 500L*1000000L;
+    private static final long MIN_WAIT = 500L * 1000000L;
 
     // Maximum time to wait between reconnects (default 5 minutes)
     private static final long MAX_WAIT = 5L * 60L * 1000L * 1000000L;
@@ -43,10 +43,12 @@ public final class Database {
     // When next to try connecting to Database in nanoseconds
     private static long nextReconnectTimestamp = 0L;
 
-    // How many connection attemtps have failed
+    // How many connection attempts have failed
     private static int reconnectAttempt = 0;
 
-    private Database() {}
+    private static final long ONE_MONTH = 2630000000L;
+
+    private DatabaseManager() {}
 
     /**
      * Attempt to connect to the mySQL database.
@@ -68,13 +70,17 @@ public final class Database {
             connection = DriverManager.getConnection(connectionString, connectionProperties);
 
             mcMMO.p.getLogger().info("Connection to MySQL was a success!");
-        } catch (SQLException ex) {
+        }
+        catch (SQLException ex) {
             connection = null;
+
             if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
                 mcMMO.p.getLogger().info("Connection to MySQL failed!");
             }
-        } catch (ClassNotFoundException ex) {
+        }
+        catch (ClassNotFoundException ex) {
             connection = null;
+
             if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
                 mcMMO.p.getLogger().info("MySQL database driver not found!");
             }
@@ -147,140 +153,44 @@ public final class Database {
                 + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
                 + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
 
-        checkDatabaseStructure(DatabaseUpdate.FISHING);
-        checkDatabaseStructure(DatabaseUpdate.BLAST_MINING);
-        checkDatabaseStructure(DatabaseUpdate.CASCADE_DELETE);
-        checkDatabaseStructure(DatabaseUpdate.INDEX);
+        checkDatabaseStructure(DatabaseUpdateType.FISHING);
+        checkDatabaseStructure(DatabaseUpdateType.BLAST_MINING);
+        checkDatabaseStructure(DatabaseUpdateType.CASCADE_DELETE);
+        checkDatabaseStructure(DatabaseUpdateType.INDEX);
     }
 
     /**
-     * Check database structure for missing values.
+     * Attempt to write the SQL query.
      *
-     * @param update Type of data to check updates for
+     * @param sql Query to write.
+     * @return true if the query was successfully written, false otherwise.
      */
-    private static void checkDatabaseStructure(DatabaseUpdate update) {
-        String sql = null;
-        ResultSet resultSet = null;
-        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
-
-        switch (update) {
-        case BLAST_MINING:
-            sql = "SELECT * FROM  `" + tablePrefix + "cooldowns` ORDER BY  `" + tablePrefix + "cooldowns`.`blast_mining` ASC LIMIT 0 , 30";
-            break;
-
-        case CASCADE_DELETE:
-            write("ALTER TABLE `" + tablePrefix + "huds` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-            write("ALTER TABLE `" + tablePrefix + "experience` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-            write("ALTER TABLE `" + tablePrefix + "cooldowns` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-            write("ALTER TABLE `" + tablePrefix + "skills` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-            break;
-
-        case FISHING:
-            sql = "SELECT * FROM  `" + tablePrefix + "experience` ORDER BY  `" + tablePrefix + "experience`.`fishing` ASC LIMIT 0 , 30";
-            break;
-
-        case INDEX:
-            if (read("SHOW INDEX FROM " + tablePrefix + "skills").size() != 13 && checkConnected()) {
-                mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
-                write("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_taming` (`taming`) USING BTREE, "
-                        + "ADD INDEX `idx_mining` (`mining`) USING BTREE, "
-                        + "ADD INDEX `idx_woodcutting` (`woodcutting`) USING BTREE, "
-                        + "ADD INDEX `idx_repair` (`repair`) USING BTREE, "
-                        + "ADD INDEX `idx_unarmed` (`unarmed`) USING BTREE, "
-                        + "ADD INDEX `idx_herbalism` (`herbalism`) USING BTREE, "
-                        + "ADD INDEX `idx_excavation` (`excavation`) USING BTREE, "
-                        + "ADD INDEX `idx_archery` (`archery`) USING BTREE, "
-                        + "ADD INDEX `idx_swords` (`swords`) USING BTREE, "
-                        + "ADD INDEX `idx_axes` (`axes`) USING BTREE, "
-                        + "ADD INDEX `idx_acrobatics` (`acrobatics`) USING BTREE, "
-                        + "ADD INDEX `idx_fishing` (`fishing`) USING BTREE;");
-            }
-            break;
-
-        default:
-            break;
+    public static boolean write(String sql) {
+        if (!checkConnected()) {
+            return false;
         }
 
         PreparedStatement statement = null;
         try {
-            if (!checkConnected()) return;
             statement = connection.prepareStatement(sql);
-            resultSet = statement.executeQuery();
-
-            while (resultSet.next()) {
-                ArrayList<String> column = new ArrayList<String>();
-
-                for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
-                    column.add(resultSet.getString(i));
-                }
-
-                rows.put(resultSet.getRow(), column);
-            }
+            statement.executeUpdate();
+            return true;
         }
         catch (SQLException ex) {
-            switch (update) {
-            case BLAST_MINING:
-                mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
-                write("ALTER TABLE `"+tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0' ;");
-                break;
-
-            case FISHING:
-                mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
-                write("ALTER TABLE `"+tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
-                write("ALTER TABLE `"+tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
-                break;
-
-            default:
-                break;
-            }
-        } finally {
-            if (resultSet != null) {
-                try     {
-                    resultSet.close();
-                } catch (SQLException e) {
-                    // Ignore the error, we're leaving
-                }
-            }
+            printErrors(ex);
+            return false;
+        }
+        finally {
             if (statement != null) {
                 try {
                     statement.close();
-                } catch (SQLException e) {
-                    // Ignore the error, we're leaving
                 }
-            }
-        }
-    }
-
-    /**
-     * Attempt to write the SQL query.
-     *
-     * @param sql Query to write.
-     * @return true if the query was successfully written, false otherwise.
-     */
-    public static boolean write(String sql) {
-        if (checkConnected()) {
-            PreparedStatement statement = null;
-            try {
-                statement = connection.prepareStatement(sql);
-                statement.executeUpdate();
-                return true;
-            }
-            catch (SQLException ex) {
-                printErrors(ex);
-                return false;
-            } finally {
-                if (statement != null) {
-                    try {
-                        statement.close();
-                    } catch (SQLException e) {
-                        printErrors(e);
-                        return false;
-                    }
+                catch (SQLException e) {
+                    printErrors(e);
+                    return false;
                 }
             }
         }
-
-        return false;
     }
 
     /**
@@ -291,22 +201,23 @@ public final class Database {
      */
     public static int update(String sql) {
         int ret = 0;
+
         if (checkConnected()) {
             PreparedStatement statement = null;
             try {
                 statement = connection.prepareStatement(sql);
                 ret = statement.executeUpdate();
-                return ret;
-            } catch (SQLException ex) {
+            }
+            catch (SQLException ex) {
                 printErrors(ex);
-                return 0;
-            } finally {
+            }
+            finally {
                 if (statement != null) {
                     try {
                         statement.close();
-                    } catch (SQLException e) {
+                    }
+                    catch (SQLException e) {
                         printErrors(e);
-                        return 0;
                     }
                 }
             }
@@ -322,12 +233,14 @@ public final class Database {
      * @return the value in the first row / first field
      */
     public static int getInt(String sql) {
-        ResultSet resultSet;
+        ResultSet resultSet = null;
         int result = 0;
 
         if (checkConnected()) {
+            PreparedStatement statement = null;
+
             try {
-                PreparedStatement statement = connection.prepareStatement(sql);
+                statement = connection.prepareStatement(sql);
                 resultSet = statement.executeQuery();
 
                 if (resultSet.next()) {
@@ -336,12 +249,20 @@ public final class Database {
                 else {
                     result = 0;
                 }
-
-                statement.close();
             }
             catch (SQLException ex) {
                 printErrors(ex);
             }
+            finally {
+                if (statement != null) {
+                    try {
+                        statement.close();
+                    }
+                    catch (SQLException e) {
+                        printErrors(e);
+                    }
+                }
+            }
         }
 
         return result;
@@ -375,7 +296,8 @@ public final class Database {
         if (exists) {
             try {
                 isClosed = connection.isClosed();
-            } catch (SQLException e) {
+            }
+            catch (SQLException e) {
                 isClosed = true;
                 e.printStackTrace();
                 printErrors(e);
@@ -384,9 +306,9 @@ public final class Database {
             if (!isClosed) {
                 try {
                     isValid = connection.isValid(VALID_TIMEOUT);
-                } catch (SQLException e) {
-                    // Don't print stack trace because it's valid to lose idle connections
-                    // to the server and have to restart them.
+                }
+                catch (SQLException e) {
+                    // Don't print stack trace because it's valid to lose idle connections to the server and have to restart them.
                     isValid = false;
                 }
             }
@@ -404,7 +326,8 @@ public final class Database {
         if (exists && !isClosed) {
             try {
                 connection.close();
-            } catch (SQLException ex) {
+            }
+            catch (SQLException ex) {
                 // This is a housekeeping exercise, ignore errors
             }
         }
@@ -417,22 +340,21 @@ public final class Database {
             if (connection != null && !connection.isClosed()) {
                 // Schedule a database save if we really had an outage
                 if (reconnectAttempt > 1) {
-                    mcMMO.p.getServer().getScheduler().scheduleSyncDelayedTask(mcMMO.p, new SQLReconnect(), 5);
+                    mcMMO.p.getServer().getScheduler().scheduleSyncDelayedTask(mcMMO.p, new SQLReconnectTask(), 5);
                 }
                 nextReconnectTimestamp = 0;
                 reconnectAttempt = 0;
                 return true;
             }
-        } catch (SQLException e) {
+        }
+        catch (SQLException e) {
             // Failed to check isClosed, so presume connection is bad and attempt later
             e.printStackTrace();
             printErrors(e);
         }
 
         reconnectAttempt++;
-
-        nextReconnectTimestamp = (long)(System.nanoTime() + Math.min(MAX_WAIT, (reconnectAttempt*SCALING_FACTOR*MIN_WAIT)));
-
+        nextReconnectTimestamp = (long)(System.nanoTime() + Math.min(MAX_WAIT, (reconnectAttempt * SCALING_FACTOR * MIN_WAIT)));
         return false;
     }
 
@@ -447,8 +369,10 @@ public final class Database {
         HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
 
         if (checkConnected()) {
+            PreparedStatement statement = null;
+
             try {
-                PreparedStatement statement = connection.prepareStatement(sql);
+                statement = connection.prepareStatement(sql);
                 resultSet = statement.executeQuery();
 
                 while (resultSet.next()) {
@@ -460,12 +384,20 @@ public final class Database {
 
                     rows.put(resultSet.getRow(), column);
                 }
-
-                statement.close();
             }
             catch (SQLException ex) {
                 printErrors(ex);
             }
+            finally {
+                if (statement != null) {
+                    try {
+                        statement.close();
+                    }
+                    catch (SQLException e) {
+                        printErrors(e);
+                    }
+                }
+            }
         }
 
         return rows;
@@ -477,12 +409,15 @@ public final class Database {
 
         if (checkConnected()) {
             try {
-                for (SkillType skillType: SkillType.values()) {
+                for (SkillType skillType : SkillType.values()) {
                     if (skillType.isChildSkill()) {
                         continue;
                     }
 
-                    String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillType.name().toLowerCase() + " > 0 AND " + skillType.name().toLowerCase() + " > (SELECT " + skillType.name().toLowerCase() + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "')";
+                    String skillName = skillType.name().toLowerCase();
+                    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 = '" + playerName + "')";
 
                     PreparedStatement statement = connection.prepareStatement(sql);
                     resultSet = statement.executeQuery();
@@ -491,7 +426,9 @@ public final class Database {
 
                     int rank = resultSet.getInt("rank");
 
-                    sql = "SELECT user, " + skillType.name().toLowerCase() + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillType.name().toLowerCase() + " > 0 AND " + skillType.name().toLowerCase() + " = (SELECT " + skillType.name().toLowerCase() + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "') ORDER BY user";
+                    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";
 
                     statement = connection.prepareStatement(sql);
                     resultSet = statement.executeQuery();
@@ -506,7 +443,11 @@ public final class Database {
                     statement.close();
                 }
 
-                String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > (SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "')";
+                String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                             "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
+                             "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > " +
+                             "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                             "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "')";
 
                 PreparedStatement statement = connection.prepareStatement(sql);
                 resultSet = statement.executeQuery();
@@ -515,7 +456,12 @@ public final class Database {
 
                 int rank = resultSet.getInt("rank");
 
-                sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing = (SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "') ORDER BY user";
+                sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                      "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
+                      "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing = " +
+                      "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = '" + playerName + "') ORDER BY user";
 
                 statement = connection.prepareStatement(sql);
                 resultSet = statement.executeQuery();
@@ -539,14 +485,20 @@ public final class Database {
 
     public static void purgePowerlessSQL() {
         mcMMO.p.getLogger().info("Purging powerless users...");
-        HashMap<Integer, ArrayList<String>> usernames = read("SELECT u.user FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u WHERE s.user_id = u.id AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0");
-        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM (SELECT u.id FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u WHERE s.user_id = u.id AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0) AS p)");
+        HashMap<Integer, ArrayList<String>> usernames;
+
+        usernames = read("SELECT u.user FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u " + "WHERE s.user_id = u.id AND " +
+                "(s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0");
+
+        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM " +
+                "(SELECT u.id FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u " + "WHERE s.user_id = u.id " +
+                "AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0) AS p)");
 
         int purgedUsers = 0;
         for (int i = 1; i <= usernames.size(); i++) {
             String playerName = usernames.get(i).get(0);
 
-            if (playerName == null || Bukkit.getOfflinePlayer(playerName).isOnline()) {
+            if (playerName == null || mcMMO.p.getServer().getOfflinePlayer(playerName).isOnline()) {
                 continue;
             }
 
@@ -560,7 +512,7 @@ public final class Database {
     public static void purgeOldSQL() {
         mcMMO.p.getLogger().info("Purging old users...");
         long currentTime = System.currentTimeMillis();
-        long purgeTime = 2630000000L * Config.getInstance().getOldUsersCutoff();
+        long purgeTime = ONE_MONTH * Config.getInstance().getOldUsersCutoff();
         HashMap<Integer, ArrayList<String>> usernames = read("SELECT user FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")");
         write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM (SELECT id FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")) AS p)");
 
@@ -579,32 +531,137 @@ public final class Database {
         mcMMO.p.getLogger().info("Purged " + purgedUsers + " users from the database.");
     }
 
-    private static void printErrors(SQLException ex) {
-        mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage());
-        mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState());
-        mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode());
-    }
-
     public static void profileCleanup(String playerName) {
-        McMMOPlayer mcmmoPlayer = Users.getPlayer(playerName);
+        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(playerName);
 
-        if (mcmmoPlayer != null) {
-            Player player = mcmmoPlayer.getPlayer();
-            SpoutHud spoutHud = mcmmoPlayer.getProfile().getSpoutHud();
+        if (mcMMOPlayer != null) {
+            Player player = mcMMOPlayer.getPlayer();
+            McMMOHud spoutHud = mcMMOPlayer.getProfile().getSpoutHud();
 
             if (spoutHud != null) {
                 spoutHud.removeWidgets();
             }
 
-            Users.remove(playerName);
+            UserManager.remove(playerName);
 
             if (player.isOnline()) {
-                Users.addUser(player);
+                UserManager.addUser(player);
 
                 if (mcMMO.spoutEnabled) {
-                    SpoutTools.reloadSpoutPlayer(player);
+                    SpoutUtils.reloadSpoutPlayer(player);
+                }
+            }
+        }
+    }
+
+    /**
+     * Check database structure for missing values.
+     *
+     * @param update Type of data to check updates for
+     */
+    private static void checkDatabaseStructure(DatabaseUpdateType update) {
+        String sql = null;
+        ResultSet resultSet = null;
+        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
+
+        switch (update) {
+            case BLAST_MINING:
+                sql = "SELECT * FROM  `" + tablePrefix + "cooldowns` ORDER BY  `" + tablePrefix + "cooldowns`.`blast_mining` ASC LIMIT 0 , 30";
+                break;
+
+            case CASCADE_DELETE:
+                write("ALTER TABLE `" + tablePrefix + "huds` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "experience` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "cooldowns` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "skills` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                break;
+
+            case FISHING:
+                sql = "SELECT * FROM  `" + tablePrefix + "experience` ORDER BY  `" + tablePrefix + "experience`.`fishing` ASC LIMIT 0 , 30";
+                break;
+
+            case INDEX:
+                if (read("SHOW INDEX FROM " + tablePrefix + "skills").size() != 13 && checkConnected()) {
+                    mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
+                    write("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_taming` (`taming`) USING BTREE, "
+                            + "ADD INDEX `idx_mining` (`mining`) USING BTREE, "
+                            + "ADD INDEX `idx_woodcutting` (`woodcutting`) USING BTREE, "
+                            + "ADD INDEX `idx_repair` (`repair`) USING BTREE, "
+                            + "ADD INDEX `idx_unarmed` (`unarmed`) USING BTREE, "
+                            + "ADD INDEX `idx_herbalism` (`herbalism`) USING BTREE, "
+                            + "ADD INDEX `idx_excavation` (`excavation`) USING BTREE, "
+                            + "ADD INDEX `idx_archery` (`archery`) USING BTREE, "
+                            + "ADD INDEX `idx_swords` (`swords`) USING BTREE, "
+                            + "ADD INDEX `idx_axes` (`axes`) USING BTREE, "
+                            + "ADD INDEX `idx_acrobatics` (`acrobatics`) USING BTREE, "
+                            + "ADD INDEX `idx_fishing` (`fishing`) USING BTREE;");
                 }
+                break;
+
+            default:
+                break;
+        }
+
+        PreparedStatement statement = null;
+        try {
+            if (!checkConnected()) {
+                return;
+            }
+
+            statement = connection.prepareStatement(sql);
+            resultSet = statement.executeQuery();
+
+            while (resultSet.next()) {
+                ArrayList<String> column = new ArrayList<String>();
+
+                for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
+                    column.add(resultSet.getString(i));
+                }
+
+                rows.put(resultSet.getRow(), column);
             }
         }
+        catch (SQLException ex) {
+            switch (update) {
+                case BLAST_MINING:
+                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
+                    write("ALTER TABLE `"+tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0' ;");
+                    break;
+
+                case FISHING:
+                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
+                    write("ALTER TABLE `"+tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
+                    write("ALTER TABLE `"+tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore the error, we're leaving
+                }
+            }
+
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore the error, we're leaving
+                }
+            }
+        }
+    }
+
+    private static void printErrors(SQLException ex) {
+        mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage());
+        mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState());
+        mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode());
     }
 }

+ 0 - 8
src/main/java/com/gmail/nossr50/database/DatabaseUpdate.java

@@ -1,8 +0,0 @@
-package com.gmail.nossr50.database;
-
-public enum DatabaseUpdate {
-    FISHING,
-    BLAST_MINING,
-    CASCADE_DELETE,
-    INDEX;
-}

+ 47 - 45
src/main/java/com/gmail/nossr50/database/Leaderboard.java → src/main/java/com/gmail/nossr50/database/LeaderboardManager.java

@@ -10,48 +10,49 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 
-import org.bukkit.Bukkit;
-
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.skills.utilities.SkillType;
+import com.gmail.nossr50.datatypes.database.PlayerStat;
+import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.util.StringUtils;
 
-public final class Leaderboard {
+public final class LeaderboardManager {
     private static HashMap<SkillType, List<PlayerStat>> playerStatHash = new HashMap<SkillType, List<PlayerStat>>();
     private static List<PlayerStat> powerLevels = new ArrayList<PlayerStat>();
     private static long lastUpdate = 0;
 
-    private Leaderboard() {}
+    private static final long UPDATE_WAIT_TIME = 600000L; // 10 minutes
+    private static final long ONE_MONTH = 2630000000L;
+
+    private LeaderboardManager() {}
 
     /**
      * Update the leader boards.
      */
     public static void updateLeaderboards() {
-        if(System.currentTimeMillis() < lastUpdate + 600000) {
-            return; //Only update FFS leaderboards every 10 minutes.. this puts a lot of strain on the server (depending on the size of the database) and should not be done frequently
+        // Only update FFS leaderboards every 10 minutes.. this puts a lot of strain on the server (depending on the size of the database) and should not be done frequently
+        if (System.currentTimeMillis() < lastUpdate + UPDATE_WAIT_TIME) {
+            return;
         }
 
-        lastUpdate = System.currentTimeMillis(); //Log when the last update was run
-
-        //Initialize lists
-        List<PlayerStat> mining, woodcutting, herbalism, excavation, acrobatics, repair, swords, axes, archery, unarmed, taming, fishing;
-
-        mining = new ArrayList<PlayerStat>();
-        woodcutting = new ArrayList<PlayerStat>();
-        herbalism = new ArrayList<PlayerStat>();
-        excavation = new ArrayList<PlayerStat>();
-        acrobatics = new ArrayList<PlayerStat>();
-        repair = new ArrayList<PlayerStat>();
-        swords = new ArrayList<PlayerStat>();
-        axes = new ArrayList<PlayerStat>();
-        archery = new ArrayList<PlayerStat>();
-        unarmed = new ArrayList<PlayerStat>();
-        taming = new ArrayList<PlayerStat>();
-        fishing = new ArrayList<PlayerStat>();
-        powerLevels = new ArrayList<PlayerStat>();
-
-        //Read from the FlatFile database and fill our arrays with information
+        lastUpdate = System.currentTimeMillis(); // Log when the last update was run
+
+        // Initialize lists
+        List<PlayerStat> mining      = new ArrayList<PlayerStat>();
+        List<PlayerStat> woodcutting = new ArrayList<PlayerStat>();
+        List<PlayerStat> herbalism   = new ArrayList<PlayerStat>();
+        List<PlayerStat> excavation  = new ArrayList<PlayerStat>();
+        List<PlayerStat> acrobatics  = new ArrayList<PlayerStat>();
+        List<PlayerStat> repair      = new ArrayList<PlayerStat>();
+        List<PlayerStat> swords      = new ArrayList<PlayerStat>();
+        List<PlayerStat> axes        = new ArrayList<PlayerStat>();
+        List<PlayerStat> archery     = new ArrayList<PlayerStat>();
+        List<PlayerStat> unarmed     = new ArrayList<PlayerStat>();
+        List<PlayerStat> taming      = new ArrayList<PlayerStat>();
+        List<PlayerStat> fishing     = new ArrayList<PlayerStat>();
+        List<PlayerStat> powerLevels = new ArrayList<PlayerStat>();
+
+        // Read from the FlatFile database and fill our arrays with information
         try {
             FileReader file = new FileReader(mcMMO.getUsersFilePath());
             BufferedReader in = new BufferedReader(file);
@@ -64,7 +65,7 @@ public final class Leaderboard {
                 String p = character[0];
                 int powerLevel = 0;
 
-                //Prevent the same player from being added multiple times (I'd like to note that this shouldn't happen...)
+                // Prevent the same player from being added multiple times (I'd like to note that this shouldn't happen...)
                 if (players.contains(p)) {
                     continue;
                 }
@@ -136,10 +137,11 @@ public final class Leaderboard {
             in.close();
         }
         catch (Exception e) {
-            mcMMO.p.getLogger().severe(("Exception while reading " + mcMMO.getUsersFilePath() + " (Are you sure you formatted it correctly?)" + e.toString()));
+            mcMMO.p.getLogger().severe("Exception while reading " + mcMMO.getUsersFilePath() + " (Are you sure you formatted it correctly?)" + e.toString());
         }
 
         SkillComparator c = new SkillComparator();
+
         Collections.sort(mining, c);
         Collections.sort(woodcutting, c);
         Collections.sort(repair, c);
@@ -227,6 +229,7 @@ public final class Leaderboard {
                 currentPos++;
                 continue;
             }
+
             return new int[] {0, 0};
         }
 
@@ -246,19 +249,13 @@ public final class Leaderboard {
                 currentPos++;
                 continue;
             }
+
             return new int[] {0, 0};
         }
 
         return new int[] {0, 0};
     }
 
-    private static class SkillComparator implements Comparator<PlayerStat> {
-        @Override
-        public int compare(PlayerStat o1, PlayerStat o2) {
-            return (o2.statVal - o1.statVal);
-        }
-    }
-
     public static boolean removeFlatFileUser(String playerName) {
         boolean worked = false;
 
@@ -274,18 +271,18 @@ public final class Leaderboard {
 
             while ((line = in.readLine()) != null) {
 
-                /* Write out the same file but when we get to the player we want to remove, we skip his line. */
+                // Write out the same file but when we get to the player we want to remove, we skip his line.
                 if (!line.split(":")[0].equalsIgnoreCase(playerName)) {
                     writer.append(line).append("\r\n");
                 }
                 else {
                     mcMMO.p.getLogger().info("User found, removing...");
                     worked = true;
-                    continue; //Skip the player
+                    continue; // Skip the player
                 }
             }
 
-            out = new FileWriter(usersFilePath); //Write out the new file
+            out = new FileWriter(usersFilePath); // Write out the new file
             out.write(writer.toString());
         }
         catch (Exception e) {
@@ -314,13 +311,12 @@ public final class Leaderboard {
         return worked;
     }
 
-    
     public static void purgePowerlessFlatfile() {
         mcMMO.p.getLogger().info("Purging powerless users...");
 
         int purgedUsers = 0;
         for (PlayerStat stat : powerLevels) {
-            if (stat.statVal == 0 && removeFlatFileUser(stat.name) && !Bukkit.getOfflinePlayer(stat.name).isOnline()) {
+            if (stat.statVal == 0 && removeFlatFileUser(stat.name) && !mcMMO.p.getServer().getOfflinePlayer(stat.name).isOnline()) {
                 purgedUsers++;
             }
         }
@@ -337,7 +333,7 @@ public final class Leaderboard {
     private static int removeOldFlatfileUsers() {
         int removedPlayers = 0;
         long currentTime = System.currentTimeMillis();
-        long purgeTime = 2630000000L * Config.getInstance().getOldUsersCutoff();
+        long purgeTime = ONE_MONTH * Config.getInstance().getOldUsersCutoff();
 
         BufferedReader in = null;
         FileWriter out = null;
@@ -351,7 +347,7 @@ public final class Leaderboard {
 
             while ((line = in.readLine()) != null) {
 
-                /* Write out the same file but when we get to the player we want to remove, we skip his line. */
+                // Write out the same file but when we get to the player we want to remove, we skip his line.
                 String[] splitLine = line.split(":");
 
                 if (splitLine.length > 37) {
@@ -361,7 +357,7 @@ public final class Leaderboard {
                     else {
                         mcMMO.p.getLogger().info("User found, removing...");
                         removedPlayers++;
-                        continue; //Skip the player
+                        continue; // Skip the player
                     }
                 }
                 else {
@@ -369,7 +365,7 @@ public final class Leaderboard {
                 }
             }
 
-            out = new FileWriter(usersFilePath); //Write out the new file
+            out = new FileWriter(usersFilePath); // Write out the new file
             out.write(writer.toString());
         }
         catch (Exception e) {
@@ -398,4 +394,10 @@ public final class Leaderboard {
         return removedPlayers;
     }
 
+    private static class SkillComparator implements Comparator<PlayerStat> {
+        @Override
+        public int compare(PlayerStat o1, PlayerStat o2) {
+            return (o2.statVal - o1.statVal);
+        }
+    }
 }

+ 0 - 45
src/main/java/com/gmail/nossr50/database/commands/McpurgeCommand.java

@@ -1,45 +0,0 @@
-package com.gmail.nossr50.database.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.Database;
-import com.gmail.nossr50.database.Leaderboard;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-
-public class McpurgeCommand implements CommandExecutor{
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (!Permissions.mcpurge(sender)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return true;
-        }
-
-        switch (args.length) {
-        case 0:
-            if (Config.getInstance().getUseMySQL()) {
-                Database.purgePowerlessSQL();
-
-                if (Config.getInstance().getOldUsersCutoff() != -1) {
-                    Database.purgeOldSQL();
-                }
-            }
-            else {
-                Leaderboard.purgePowerlessFlatfile();
-
-                if (Config.getInstance().getOldUsersCutoff() != -1) {
-                    Leaderboard.purgeOldFlatfile();
-                }
-            }
-
-            sender.sendMessage(LocaleLoader.getString("Commands.mcpurge.Success"));
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 51
src/main/java/com/gmail/nossr50/database/commands/McremoveCommand.java

@@ -1,51 +0,0 @@
-package com.gmail.nossr50.database.commands;
-
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.Database;
-import com.gmail.nossr50.database.Leaderboard;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Permissions;
-
-public class McremoveCommand implements CommandExecutor {
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        if (!Permissions.mcremove(sender)) {
-            sender.sendMessage(command.getPermissionMessage());
-            return true;
-        }
-
-        switch (args.length) {
-        case 1:
-            /* MySQL */
-            if (Config.getInstance().getUseMySQL()) {
-                String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-
-                if (Database.update("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.user = '" + args[0] + "'") != 0) {
-                    Database.profileCleanup(args[0]);
-                    sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
-                }
-                else {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                }
-            }
-            else {
-                if (Leaderboard.removeFlatFileUser(args[0])) {
-                    Database.profileCleanup(args[0]);
-                    sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
-                }
-                else {
-                    sender.sendMessage(LocaleLoader.getString("Commands.DoesNotExist"));
-                }
-            }
-
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}

+ 0 - 21
src/main/java/com/gmail/nossr50/database/runnables/SQLReconnect.java

@@ -1,21 +0,0 @@
-package com.gmail.nossr50.database.runnables;
-
-import org.bukkit.entity.Player;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.database.Database;
-import com.gmail.nossr50.util.Users;
-
-public class SQLReconnect implements Runnable {
-    @Override
-    public void run() {
-        if (Database.checkConnected()) {
-            Users.saveAll(); //Save all profiles
-            Users.clearAll(); //Clear the profiles
-
-            for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                Users.addUser(player); //Add in new profiles, forcing them to 'load' again from MySQL
-            }
-        }
-    }
-}

+ 8 - 0
src/main/java/com/gmail/nossr50/datatypes/database/DatabaseUpdateType.java

@@ -0,0 +1,8 @@
+package com.gmail.nossr50.datatypes.database;
+
+public enum DatabaseUpdateType {
+    FISHING,
+    BLAST_MINING,
+    CASCADE_DELETE,
+    INDEX;
+}

+ 11 - 11
src/main/java/com/gmail/nossr50/database/PlayerStat.java → src/main/java/com/gmail/nossr50/datatypes/database/PlayerStat.java

@@ -1,11 +1,11 @@
-package com.gmail.nossr50.database;
-
-public class PlayerStat {
-    public String name;
-    public int statVal = 0;
-
-    public PlayerStat(String name, int value) {
-        this.name = name;
-        this.statVal = value;
-    }
-}
+package com.gmail.nossr50.datatypes.database;
+
+public class PlayerStat {
+    public String name;
+    public int statVal = 0;
+
+    public PlayerStat(String name, int value) {
+        this.name = name;
+        this.statVal = value;
+    }
+}

Some files were not shown because too many files changed in this diff