WoodcuttingManager.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package com.gmail.nossr50.skills.woodcutting;
  2. import com.gmail.nossr50.config.Config;
  3. import com.gmail.nossr50.config.experience.ExperienceConfig;
  4. import com.gmail.nossr50.datatypes.experience.XPGainReason;
  5. import com.gmail.nossr50.datatypes.interactions.NotificationType;
  6. import com.gmail.nossr50.datatypes.player.McMMOPlayer;
  7. import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
  8. import com.gmail.nossr50.datatypes.skills.SubSkillType;
  9. import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
  10. import com.gmail.nossr50.mcMMO;
  11. import com.gmail.nossr50.skills.SkillManager;
  12. import com.gmail.nossr50.util.*;
  13. import com.gmail.nossr50.util.player.NotificationManager;
  14. import com.gmail.nossr50.util.random.RandomChanceUtil;
  15. import com.gmail.nossr50.util.skills.CombatUtils;
  16. import com.gmail.nossr50.util.skills.RankUtils;
  17. import com.gmail.nossr50.util.skills.SkillActivationType;
  18. import com.gmail.nossr50.util.skills.SkillUtils;
  19. import org.bukkit.Bukkit;
  20. import org.bukkit.Material;
  21. import org.bukkit.block.Block;
  22. import org.bukkit.block.BlockFace;
  23. import org.bukkit.block.BlockState;
  24. import org.bukkit.entity.Player;
  25. import org.bukkit.event.player.PlayerItemDamageEvent;
  26. import org.bukkit.inventory.ItemStack;
  27. import java.util.ArrayList;
  28. import java.util.HashSet;
  29. import java.util.List;
  30. import java.util.Set;
  31. public class WoodcuttingManager extends SkillManager {
  32. private boolean treeFellerReachedThreshold = false;
  33. private static int treeFellerThreshold; //TODO: Shared setting, will be removed in 2.2
  34. /**
  35. * The x/y differences to the blocks in a flat cylinder around the center
  36. * block, which is excluded.
  37. */
  38. private static final int[][] directions = {
  39. new int[] {-2, -1}, new int[] {-2, 0}, new int[] {-2, 1},
  40. new int[] {-1, -2}, new int[] {-1, -1}, new int[] {-1, 0}, new int[] {-1, 1}, new int[] {-1, 2},
  41. new int[] { 0, -2}, new int[] { 0, -1}, new int[] { 0, 1}, new int[] { 0, 2},
  42. new int[] { 1, -2}, new int[] { 1, -1}, new int[] { 1, 0}, new int[] { 1, 1}, new int[] { 1, 2},
  43. new int[] { 2, -1}, new int[] { 2, 0}, new int[] { 2, 1},
  44. };
  45. public WoodcuttingManager(McMMOPlayer mcMMOPlayer) {
  46. super(mcMMOPlayer, PrimarySkillType.WOODCUTTING);
  47. treeFellerThreshold = Config.getInstance().getTreeFellerThreshold();
  48. }
  49. public boolean canUseLeafBlower(ItemStack heldItem) {
  50. return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER)
  51. && RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER)
  52. && ItemUtils.isAxe(heldItem);
  53. }
  54. public boolean canUseTreeFeller(ItemStack heldItem) {
  55. return mcMMOPlayer.getAbilityMode(SuperAbilityType.TREE_FELLER)
  56. && ItemUtils.isAxe(heldItem);
  57. }
  58. private boolean canGetDoubleDrops() {
  59. return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
  60. && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
  61. && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer());
  62. }
  63. /**
  64. * Begins Woodcutting
  65. *
  66. * @param blockState Block being broken
  67. */
  68. public void woodcuttingBlockCheck(BlockState blockState) {
  69. int xp = getExperienceFromLog(blockState);
  70. switch (blockState.getType()) {
  71. case BROWN_MUSHROOM_BLOCK:
  72. case RED_MUSHROOM_BLOCK:
  73. break;
  74. default:
  75. if (canGetDoubleDrops()) {
  76. checkForDoubleDrop(blockState);
  77. }
  78. }
  79. applyXpGain(xp, XPGainReason.PVE);
  80. }
  81. /**
  82. * Begins Tree Feller
  83. *
  84. * @param blockState Block being broken
  85. */
  86. public void processTreeFeller(BlockState blockState) {
  87. Player player = getPlayer();
  88. Set<BlockState> treeFellerBlocks = new HashSet<BlockState>();
  89. treeFellerReachedThreshold = false;
  90. processTree(blockState, treeFellerBlocks);
  91. // If the player is trying to break too many blocks
  92. if (treeFellerReachedThreshold) {
  93. treeFellerReachedThreshold = false;
  94. NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Threshold");
  95. return;
  96. }
  97. // If the tool can't sustain the durability loss
  98. if (!handleDurabilityLoss(treeFellerBlocks, player.getInventory().getItemInMainHand(), player)) {
  99. NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Splinter");
  100. double health = player.getHealth();
  101. if (health > 1) {
  102. CombatUtils.dealDamage(player, Misc.getRandom().nextInt((int) (health - 1)));
  103. }
  104. return;
  105. }
  106. dropTreeFellerLootFromBlocks(treeFellerBlocks);
  107. treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time.
  108. }
  109. /**
  110. * Processes Tree Feller in a recursive manner
  111. *
  112. * @param blockState Block being checked
  113. * @param treeFellerBlocks List of blocks to be removed
  114. */
  115. /*
  116. * Algorithm: An int[][] of X/Z directions is created on static class
  117. * initialization, representing a cylinder with radius of about 2 - the
  118. * (0,0) center and all (+-2, +-2) corners are omitted.
  119. *
  120. * processTreeFellerTargetBlock() returns a boolean, which is used for the sole purpose of
  121. * switching between these two behaviors:
  122. *
  123. * (Call blockState "this log" for the below explanation.)
  124. *
  125. * [A] There is another log above this log (TRUNK)
  126. * Only the flat cylinder in the directions array is searched.
  127. * [B] There is not another log above this log (BRANCH AND TOP)
  128. * The cylinder in the directions array is extended up and down by 1
  129. * block in the Y-axis, and the block below this log is checked as
  130. * well. Due to the fact that the directions array will catch all
  131. * blocks on a red mushroom, the special method for it is eliminated.
  132. *
  133. * This algorithm has been shown to achieve a performance of 2-5
  134. * milliseconds on regular trees and 10-15 milliseconds on jungle trees
  135. * once the JIT has optimized the function (use the ability about 4 times
  136. * before taking measurements).
  137. */
  138. private void processTree(BlockState blockState, Set<BlockState> treeFellerBlocks) {
  139. List<BlockState> futureCenterBlocks = new ArrayList<BlockState>();
  140. // Check the block up and take different behavior (smaller search) if it's a log
  141. if (processTreeFellerTargetBlock(blockState.getBlock().getRelative(BlockFace.UP).getState(), futureCenterBlocks, treeFellerBlocks)) {
  142. for (int[] dir : directions) {
  143. processTreeFellerTargetBlock(blockState.getBlock().getRelative(dir[0], 0, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
  144. if (treeFellerReachedThreshold) {
  145. return;
  146. }
  147. }
  148. }
  149. else {
  150. // Cover DOWN
  151. processTreeFellerTargetBlock(blockState.getBlock().getRelative(BlockFace.DOWN).getState(), futureCenterBlocks, treeFellerBlocks);
  152. // Search in a cube
  153. for (int y = -1; y <= 1; y++) {
  154. for (int[] dir : directions) {
  155. processTreeFellerTargetBlock(blockState.getBlock().getRelative(dir[0], y, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
  156. if (treeFellerReachedThreshold) {
  157. return;
  158. }
  159. }
  160. }
  161. }
  162. // Recursive call for each log found
  163. for (BlockState futureCenterBlock : futureCenterBlocks) {
  164. if (treeFellerReachedThreshold) {
  165. return;
  166. }
  167. processTree(futureCenterBlock, treeFellerBlocks);
  168. }
  169. }
  170. /**
  171. * Handles the durability loss
  172. *
  173. * @param treeFellerBlocks List of blocks to be removed
  174. * @param inHand tool being used
  175. * @param player the player holding the item
  176. * @return True if the tool can sustain the durability loss
  177. */
  178. private static boolean handleDurabilityLoss(Set<BlockState> treeFellerBlocks, ItemStack inHand, Player player) {
  179. //Treat the NBT tag for unbreakable and the durability enchant differently
  180. if(inHand.getItemMeta() != null && inHand.getItemMeta().isUnbreakable()) {
  181. return true;
  182. }
  183. short durabilityLoss = 0;
  184. Material type = inHand.getType();
  185. for (BlockState blockState : treeFellerBlocks) {
  186. if (BlockUtils.isLog(blockState)) {
  187. durabilityLoss += Config.getInstance().getAbilityToolDamage();
  188. }
  189. }
  190. // Call PlayerItemDamageEvent first to make sure it's not cancelled
  191. final PlayerItemDamageEvent event = new PlayerItemDamageEvent(player, inHand, durabilityLoss);
  192. Bukkit.getPluginManager().callEvent(event);
  193. if (event.isCancelled()) {
  194. return true;
  195. }
  196. SkillUtils.handleDurabilityChange(inHand, durabilityLoss);
  197. return (inHand.getDurability() < (mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability()));
  198. }
  199. /**
  200. * Handle a block addition to the list of blocks to be removed and to the
  201. * list of blocks used for future recursive calls of
  202. * 'processTree()'
  203. *
  204. * @param blockState Block to be added
  205. * @param futureCenterBlocks List of blocks that will be used to call
  206. * 'processTree()'
  207. * @param treeFellerBlocks List of blocks to be removed
  208. * @return true if and only if the given blockState was a Log not already
  209. * in treeFellerBlocks.
  210. */
  211. private boolean processTreeFellerTargetBlock(BlockState blockState, List<BlockState> futureCenterBlocks, Set<BlockState> treeFellerBlocks) {
  212. if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) {
  213. return false;
  214. }
  215. // Without this check Tree Feller propagates through leaves until the threshold is hit
  216. if (treeFellerBlocks.size() > treeFellerThreshold) {
  217. treeFellerReachedThreshold = true;
  218. }
  219. if (BlockUtils.isLog(blockState)) {
  220. treeFellerBlocks.add(blockState);
  221. futureCenterBlocks.add(blockState);
  222. return true;
  223. }
  224. else if (BlockUtils.isLeaves(blockState)) {
  225. treeFellerBlocks.add(blockState);
  226. return false;
  227. }
  228. return false;
  229. }
  230. /**
  231. * Handles the dropping of blocks
  232. *
  233. * @param treeFellerBlocks List of blocks to be dropped
  234. */
  235. private void dropTreeFellerLootFromBlocks(Set<BlockState> treeFellerBlocks) {
  236. Player player = getPlayer();
  237. int xp = 0;
  238. int processedLogCount = 0;
  239. for (BlockState blockState : treeFellerBlocks) {
  240. int beforeXP = xp;
  241. Block block = blockState.getBlock();
  242. if (!EventUtils.simulateBlockBreak(block, player, true)) {
  243. break; // TODO: Shouldn't we use continue instead?
  244. }
  245. Material material = blockState.getType();
  246. //TODO: Update this to drop the correct items/blocks via NMS
  247. if (material == Material.BROWN_MUSHROOM_BLOCK || material == Material.RED_MUSHROOM_BLOCK) {
  248. xp += processTreeFellerXPGains(blockState, processedLogCount);
  249. Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
  250. } else if (mcMMO.getModManager().isCustomLeaf(blockState)) {
  251. Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
  252. } else {
  253. if (BlockUtils.isLog(blockState)) {
  254. if (canGetDoubleDrops()) {
  255. checkForDoubleDrop(blockState);
  256. }
  257. xp += processTreeFellerXPGains(blockState, processedLogCount);
  258. Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
  259. }
  260. if (BlockUtils.isLeaves(blockState)) {
  261. Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
  262. }
  263. }
  264. blockState.setType(Material.AIR);
  265. blockState.update(true);
  266. //Update only when XP changes
  267. processedLogCount = updateProcessedLogCount(xp, processedLogCount, beforeXP);
  268. }
  269. applyXpGain(xp, XPGainReason.PVE);
  270. }
  271. private int updateProcessedLogCount(int xp, int processedLogCount, int beforeXP) {
  272. if(beforeXP != xp)
  273. processedLogCount+=1;
  274. return processedLogCount;
  275. }
  276. /**
  277. * Retrieves the experience reward from logging via Tree Feller
  278. * Experience is reduced per log processed so far
  279. * Experience is only reduced if the config option to reduce Tree Feller XP is set
  280. * Experience per log will not fall below 1 unless the experience for that log is set to 0 in the config
  281. *
  282. * @param blockState Log being broken
  283. * @param woodCount how many logs have given out XP for this tree feller so far
  284. * @return Amount of experience
  285. */
  286. private static int processTreeFellerXPGains(BlockState blockState, int woodCount) {
  287. int rawXP = ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType());
  288. if(rawXP <= 0)
  289. return 0;
  290. if(ExperienceConfig.getInstance().isTreeFellerXPReduced()) {
  291. int reducedXP = rawXP - (woodCount * 5);
  292. rawXP = Math.max(1, reducedXP);
  293. return rawXP;
  294. } else {
  295. return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType());
  296. }
  297. }
  298. /**
  299. * Retrieves the experience reward from a log
  300. *
  301. * @param blockState Log being broken
  302. * @return Amount of experience
  303. */
  304. protected static int getExperienceFromLog(BlockState blockState) {
  305. if (mcMMO.getModManager().isCustomLog(blockState)) {
  306. return mcMMO.getModManager().getBlock(blockState).getXpGain();
  307. }
  308. return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType());
  309. }
  310. /**
  311. * Checks for double drops
  312. *
  313. * @param blockState Block being broken
  314. */
  315. protected static void checkForDoubleDrop(BlockState blockState) {
  316. if (mcMMO.getModManager().isCustomLog(blockState) && mcMMO.getModManager().getBlock(blockState).isDoubleDropEnabled()) {
  317. Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
  318. }
  319. else {
  320. if (Config.getInstance().getWoodcuttingDoubleDropsEnabled(blockState.getBlockData())) {
  321. Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
  322. }
  323. }
  324. }
  325. }