From 0af3c82612142b261df686ee13cbcb0fb8194aee Mon Sep 17 00:00:00 2001 From: nossr50 Date: Tue, 2 Jul 2019 14:46:55 -0700 Subject: [PATCH] Fixed a bug that could prevent a Tree from being removed by Tree Feller if 2 or more players were using Tree Feller simultaneously --- Changelog.txt | 3 + pom.xml | 2 +- .../skills/woodcutting/Woodcutting.java | 247 ++---------------- .../woodcutting/WoodcuttingManager.java | 230 +++++++++++++++- 4 files changed, 248 insertions(+), 234 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 0da17bf62..58a362229 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,6 @@ +Version 2.1.94 + Fixed a bug where 2 people using Tree Feller could result in the tree being rejected for being too big + Version 2.1.93 Fixed a bug where players would be told they could not breed summoned animals when the animals weren't summoned (bug didn't actually do anything besides send you a message) diff --git a/pom.xml b/pom.xml index d9949935f..6c0c5e07d 100755 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.gmail.nossr50.mcMMO mcMMO - 2.1.93 + 2.1.94-SNAPSHOT mcMMO https://github.com/mcMMO-Dev/mcMMO diff --git a/src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java b/src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java index 9b08beab7..6f98b08cc 100644 --- a/src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java +++ b/src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java @@ -1,220 +1,27 @@ -package com.gmail.nossr50.skills.woodcutting; - -import com.gmail.nossr50.config.Config; -import com.gmail.nossr50.config.experience.ExperienceConfig; -import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.BlockUtils; -import com.gmail.nossr50.util.Misc; -import com.gmail.nossr50.util.skills.SkillUtils; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.inventory.ItemStack; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public final class Woodcutting { - public static int treeFellerThreshold = Config.getInstance().getTreeFellerThreshold(); - protected static boolean treeFellerReachedThreshold = false; - - private Woodcutting() {} - - /** - * Retrieves the experience reward from a log - * - * @param blockState Log being broken - * @return Amount of experience - */ - protected static int getExperienceFromLog(BlockState blockState) { - if (mcMMO.getModManager().isCustomLog(blockState)) { - return mcMMO.getModManager().getBlock(blockState).getXpGain(); - } - - return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); - } - - /** - * Retrieves the experience reward from logging via Tree Feller - * Experience is reduced per log processed so far - * Experience is only reduced if the config option to reduce Tree Feller XP is set - * Experience per log will not fall below 1 unless the experience for that log is set to 0 in the config - * - * @param blockState Log being broken - * @param woodCount how many logs have given out XP for this tree feller so far - * @return Amount of experience - */ - protected static int processTreeFellerXPGains(BlockState blockState, int woodCount) { - int rawXP = ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); - - if(rawXP <= 0) - return 0; - - if(ExperienceConfig.getInstance().isTreeFellerXPReduced()) { - int reducedXP = 1 + (woodCount * 5); - rawXP = Math.max(1, rawXP - reducedXP); - return rawXP; - } else { - return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); - } - } - - - - /** - * Checks for double drops - * - * @param blockState Block being broken - */ - protected static void checkForDoubleDrop(BlockState blockState) { - if (mcMMO.getModManager().isCustomLog(blockState) && mcMMO.getModManager().getBlock(blockState).isDoubleDropEnabled()) { - Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops()); - } - else { - if (Config.getInstance().getWoodcuttingDoubleDropsEnabled(blockState.getBlockData())) { - Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops()); - } - } - } - - /** - * The x/y differences to the blocks in a flat cylinder around the center - * block, which is excluded. - */ - private static final int[][] directions = { - new int[] {-2, -1}, new int[] {-2, 0}, new int[] {-2, 1}, - new int[] {-1, -2}, new int[] {-1, -1}, new int[] {-1, 0}, new int[] {-1, 1}, new int[] {-1, 2}, - new int[] { 0, -2}, new int[] { 0, -1}, new int[] { 0, 1}, new int[] { 0, 2}, - new int[] { 1, -2}, new int[] { 1, -1}, new int[] { 1, 0}, new int[] { 1, 1}, new int[] { 1, 2}, - new int[] { 2, -1}, new int[] { 2, 0}, new int[] { 2, 1}, - }; - - /** - * Processes Tree Feller in a recursive manner - * - * @param blockState Block being checked - * @param treeFellerBlocks List of blocks to be removed - */ - /* - * Algorithm: An int[][] of X/Z directions is created on static class - * initialization, representing a cylinder with radius of about 2 - the - * (0,0) center and all (+-2, +-2) corners are omitted. - * - * handleBlock() returns a boolean, which is used for the sole purpose of - * switching between these two behaviors: - * - * (Call blockState "this log" for the below explanation.) - * - * [A] There is another log above this log (TRUNK) - * Only the flat cylinder in the directions array is searched. - * [B] There is not another log above this log (BRANCH AND TOP) - * The cylinder in the directions array is extended up and down by 1 - * block in the Y-axis, and the block below this log is checked as - * well. Due to the fact that the directions array will catch all - * blocks on a red mushroom, the special method for it is eliminated. - * - * This algorithm has been shown to achieve a performance of 2-5 - * milliseconds on regular trees and 10-15 milliseconds on jungle trees - * once the JIT has optimized the function (use the ability about 4 times - * before taking measurements). - */ - protected static void processTree(BlockState blockState, Set treeFellerBlocks) { - List futureCenterBlocks = new ArrayList(); - - // Check the block up and take different behavior (smaller search) if it's a log - if (handleBlock(blockState.getBlock().getRelative(BlockFace.UP).getState(), futureCenterBlocks, treeFellerBlocks)) { - for (int[] dir : directions) { - handleBlock(blockState.getBlock().getRelative(dir[0], 0, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks); - - if (treeFellerReachedThreshold) { - return; - } - } - } - else { - // Cover DOWN - handleBlock(blockState.getBlock().getRelative(BlockFace.DOWN).getState(), futureCenterBlocks, treeFellerBlocks); - // Search in a cube - for (int y = -1; y <= 1; y++) { - for (int[] dir : directions) { - handleBlock(blockState.getBlock().getRelative(dir[0], y, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks); - - if (treeFellerReachedThreshold) { - return; - } - } - } - } - - // Recursive call for each log found - for (BlockState futureCenterBlock : futureCenterBlocks) { - if (treeFellerReachedThreshold) { - return; - } - - processTree(futureCenterBlock, treeFellerBlocks); - } - } - - /** - * Handles the durability loss - * - * @param treeFellerBlocks List of blocks to be removed - * @param inHand tool being used - * @return True if the tool can sustain the durability loss - */ - protected static boolean handleDurabilityLoss(Set treeFellerBlocks, ItemStack inHand) { - //Treat the NBT tag for unbreakable and the durability enchant differently - if(inHand.getItemMeta() != null && inHand.getItemMeta().isUnbreakable()) { - return true; - } - - short durabilityLoss = 0; - Material type = inHand.getType(); - - for (BlockState blockState : treeFellerBlocks) { - if (BlockUtils.isLog(blockState)) { - durabilityLoss += Config.getInstance().getAbilityToolDamage(); - } - } - - SkillUtils.handleDurabilityChange(inHand, durabilityLoss); - return (inHand.getDurability() < (mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability())); - } - - /** - * Handle a block addition to the list of blocks to be removed and to the - * list of blocks used for future recursive calls of - * 'processTree()' - * - * @param blockState Block to be added - * @param futureCenterBlocks List of blocks that will be used to call - * 'processTree()' - * @param treeFellerBlocks List of blocks to be removed - * @return true if and only if the given blockState was a Log not already - * in treeFellerBlocks. - */ - private static boolean handleBlock(BlockState blockState, List futureCenterBlocks, Set treeFellerBlocks) { - if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) { - return false; - } - - // Without this check Tree Feller propagates through leaves until the threshold is hit - if (treeFellerBlocks.size() > treeFellerThreshold) { - treeFellerReachedThreshold = true; - } - - if (BlockUtils.isLog(blockState)) { - treeFellerBlocks.add(blockState); - futureCenterBlocks.add(blockState); - return true; - } - else if (BlockUtils.isLeaves(blockState)) { - treeFellerBlocks.add(blockState); - return false; - } - return false; - } -} +//package com.gmail.nossr50.skills.woodcutting; +// +//import com.gmail.nossr50.config.Config; +//import com.gmail.nossr50.config.experience.ExperienceConfig; +//import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +//import com.gmail.nossr50.mcMMO; +//import com.gmail.nossr50.util.BlockUtils; +//import com.gmail.nossr50.util.Misc; +//import com.gmail.nossr50.util.skills.SkillUtils; +//import org.bukkit.Material; +//import org.bukkit.block.BlockFace; +//import org.bukkit.block.BlockState; +//import org.bukkit.inventory.ItemStack; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Set; +// +//public final class Woodcutting { +// public static int treeFellerThreshold = Config.getInstance().getTreeFellerThreshold(); +// +// private Woodcutting() {} +// +// +// +// +//} diff --git a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java index 81b4a0b86..f2cc6256b 100644 --- a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java @@ -1,5 +1,7 @@ package com.gmail.nossr50.skills.woodcutting; +import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.datatypes.experience.XPGainReason; import com.gmail.nossr50.datatypes.interactions.NotificationType; import com.gmail.nossr50.datatypes.player.McMMOPlayer; @@ -14,19 +16,39 @@ import com.gmail.nossr50.util.random.RandomChanceUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillActivationType; +import com.gmail.nossr50.util.skills.SkillUtils; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; public class WoodcuttingManager extends SkillManager { + private boolean treeFellerReachedThreshold = false; + private static int treeFellerThreshold; //TODO: Shared setting, will be removed in 2.2 + + /** + * The x/y differences to the blocks in a flat cylinder around the center + * block, which is excluded. + */ + private static final int[][] directions = { + new int[] {-2, -1}, new int[] {-2, 0}, new int[] {-2, 1}, + new int[] {-1, -2}, new int[] {-1, -1}, new int[] {-1, 0}, new int[] {-1, 1}, new int[] {-1, 2}, + new int[] { 0, -2}, new int[] { 0, -1}, new int[] { 0, 1}, new int[] { 0, 2}, + new int[] { 1, -2}, new int[] { 1, -1}, new int[] { 1, 0}, new int[] { 1, 1}, new int[] { 1, 2}, + new int[] { 2, -1}, new int[] { 2, 0}, new int[] { 2, 1}, + }; + public WoodcuttingManager(McMMOPlayer mcMMOPlayer) { super(mcMMOPlayer, PrimarySkillType.WOODCUTTING); + treeFellerThreshold = Config.getInstance().getTreeFellerThreshold(); } public boolean canUseLeafBlower(ItemStack heldItem) { @@ -52,7 +74,7 @@ public class WoodcuttingManager extends SkillManager { * @param blockState Block being broken */ public void woodcuttingBlockCheck(BlockState blockState) { - int xp = Woodcutting.getExperienceFromLog(blockState); + int xp = getExperienceFromLog(blockState); switch (blockState.getType()) { case BROWN_MUSHROOM_BLOCK: @@ -61,7 +83,7 @@ public class WoodcuttingManager extends SkillManager { default: if (canGetDoubleDrops()) { - Woodcutting.checkForDoubleDrop(blockState); + checkForDoubleDrop(blockState); } } @@ -77,20 +99,20 @@ public class WoodcuttingManager extends SkillManager { Player player = getPlayer(); Set treeFellerBlocks = new HashSet(); - Woodcutting.treeFellerReachedThreshold = false; + treeFellerReachedThreshold = false; - Woodcutting.processTree(blockState, treeFellerBlocks); + processTree(blockState, treeFellerBlocks); // If the player is trying to break too many blocks - if (Woodcutting.treeFellerReachedThreshold) { - Woodcutting.treeFellerReachedThreshold = false; + if (treeFellerReachedThreshold) { + treeFellerReachedThreshold = false; NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Threshold"); return; } // If the tool can't sustain the durability loss - if (!Woodcutting.handleDurabilityLoss(treeFellerBlocks, player.getInventory().getItemInMainHand())) { + if (!handleDurabilityLoss(treeFellerBlocks, player.getInventory().getItemInMainHand())) { NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Splinter"); double health = player.getHealth(); @@ -102,8 +124,135 @@ public class WoodcuttingManager extends SkillManager { return; } - dropBlocks(treeFellerBlocks); - Woodcutting.treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time. + dropTreeFellerLootFromBlocks(treeFellerBlocks); + treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time. + } + + /** + * Processes Tree Feller in a recursive manner + * + * @param blockState Block being checked + * @param treeFellerBlocks List of blocks to be removed + */ + /* + * Algorithm: An int[][] of X/Z directions is created on static class + * initialization, representing a cylinder with radius of about 2 - the + * (0,0) center and all (+-2, +-2) corners are omitted. + * + * processTreeFellerTargetBlock() returns a boolean, which is used for the sole purpose of + * switching between these two behaviors: + * + * (Call blockState "this log" for the below explanation.) + * + * [A] There is another log above this log (TRUNK) + * Only the flat cylinder in the directions array is searched. + * [B] There is not another log above this log (BRANCH AND TOP) + * The cylinder in the directions array is extended up and down by 1 + * block in the Y-axis, and the block below this log is checked as + * well. Due to the fact that the directions array will catch all + * blocks on a red mushroom, the special method for it is eliminated. + * + * This algorithm has been shown to achieve a performance of 2-5 + * milliseconds on regular trees and 10-15 milliseconds on jungle trees + * once the JIT has optimized the function (use the ability about 4 times + * before taking measurements). + */ + private void processTree(BlockState blockState, Set treeFellerBlocks) { + List futureCenterBlocks = new ArrayList(); + + // Check the block up and take different behavior (smaller search) if it's a log + if (processTreeFellerTargetBlock(blockState.getBlock().getRelative(BlockFace.UP).getState(), futureCenterBlocks, treeFellerBlocks)) { + for (int[] dir : directions) { + processTreeFellerTargetBlock(blockState.getBlock().getRelative(dir[0], 0, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks); + + if (treeFellerReachedThreshold) { + return; + } + } + } + else { + // Cover DOWN + processTreeFellerTargetBlock(blockState.getBlock().getRelative(BlockFace.DOWN).getState(), futureCenterBlocks, treeFellerBlocks); + // Search in a cube + for (int y = -1; y <= 1; y++) { + for (int[] dir : directions) { + processTreeFellerTargetBlock(blockState.getBlock().getRelative(dir[0], y, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks); + + if (treeFellerReachedThreshold) { + return; + } + } + } + } + + // Recursive call for each log found + for (BlockState futureCenterBlock : futureCenterBlocks) { + if (treeFellerReachedThreshold) { + return; + } + + processTree(futureCenterBlock, treeFellerBlocks); + } + } + + /** + * Handles the durability loss + * + * @param treeFellerBlocks List of blocks to be removed + * @param inHand tool being used + * @return True if the tool can sustain the durability loss + */ + private static boolean handleDurabilityLoss(Set treeFellerBlocks, ItemStack inHand) { + //Treat the NBT tag for unbreakable and the durability enchant differently + if(inHand.getItemMeta() != null && inHand.getItemMeta().isUnbreakable()) { + return true; + } + + short durabilityLoss = 0; + Material type = inHand.getType(); + + for (BlockState blockState : treeFellerBlocks) { + if (BlockUtils.isLog(blockState)) { + durabilityLoss += Config.getInstance().getAbilityToolDamage(); + } + } + + SkillUtils.handleDurabilityChange(inHand, durabilityLoss); + return (inHand.getDurability() < (mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability())); + } + + /** + * Handle a block addition to the list of blocks to be removed and to the + * list of blocks used for future recursive calls of + * 'processTree()' + * + * @param blockState Block to be added + * @param futureCenterBlocks List of blocks that will be used to call + * 'processTree()' + * @param treeFellerBlocks List of blocks to be removed + * @return true if and only if the given blockState was a Log not already + * in treeFellerBlocks. + */ + private boolean processTreeFellerTargetBlock(BlockState blockState, List futureCenterBlocks, Set treeFellerBlocks) { + if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) { + return false; + } + + // Without this check Tree Feller propagates through leaves until the threshold is hit + if (treeFellerBlocks.size() > treeFellerThreshold) { + treeFellerReachedThreshold = true; + } + + if (BlockUtils.isLog(blockState)) { + treeFellerBlocks.add(blockState); + futureCenterBlocks.add(blockState); + return true; + } + else if (BlockUtils.isLeaves(blockState)) { + treeFellerBlocks.add(blockState); + return false; + } + return false; } /** @@ -111,7 +260,7 @@ public class WoodcuttingManager extends SkillManager { * * @param treeFellerBlocks List of blocks to be dropped */ - private void dropBlocks(Set treeFellerBlocks) { + private void dropTreeFellerLootFromBlocks(Set treeFellerBlocks) { Player player = getPlayer(); int xp = 0; int processedLogCount = 0; @@ -127,16 +276,16 @@ public class WoodcuttingManager extends SkillManager { //TODO: Update this to drop the correct items/blocks via NMS if (material == Material.BROWN_MUSHROOM_BLOCK || material == Material.RED_MUSHROOM_BLOCK) { - xp += Woodcutting.processTreeFellerXPGains(blockState, processedLogCount); + xp += processTreeFellerXPGains(blockState, processedLogCount); Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops()); } else if (mcMMO.getModManager().isCustomLeaf(blockState)) { Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops()); } else { if (BlockUtils.isLog(blockState)) { if (canGetDoubleDrops()) { - Woodcutting.checkForDoubleDrop(blockState); + checkForDoubleDrop(blockState); } - xp += Woodcutting.processTreeFellerXPGains(blockState, processedLogCount); + xp += processTreeFellerXPGains(blockState, processedLogCount); Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops()); } if (BlockUtils.isLeaves(blockState)) { @@ -151,4 +300,59 @@ public class WoodcuttingManager extends SkillManager { applyXpGain(xp, XPGainReason.PVE); } + + /** + * Retrieves the experience reward from logging via Tree Feller + * Experience is reduced per log processed so far + * Experience is only reduced if the config option to reduce Tree Feller XP is set + * Experience per log will not fall below 1 unless the experience for that log is set to 0 in the config + * + * @param blockState Log being broken + * @param woodCount how many logs have given out XP for this tree feller so far + * @return Amount of experience + */ + private static int processTreeFellerXPGains(BlockState blockState, int woodCount) { + int rawXP = ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); + + if(rawXP <= 0) + return 0; + + if(ExperienceConfig.getInstance().isTreeFellerXPReduced()) { + int reducedXP = 1 + (woodCount * 5); + rawXP = Math.max(1, rawXP - reducedXP); + return rawXP; + } else { + return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); + } + } + + /** + * Retrieves the experience reward from a log + * + * @param blockState Log being broken + * @return Amount of experience + */ + protected static int getExperienceFromLog(BlockState blockState) { + if (mcMMO.getModManager().isCustomLog(blockState)) { + return mcMMO.getModManager().getBlock(blockState).getXpGain(); + } + + return ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); + } + + /** + * Checks for double drops + * + * @param blockState Block being broken + */ + protected static void checkForDoubleDrop(BlockState blockState) { + if (mcMMO.getModManager().isCustomLog(blockState) && mcMMO.getModManager().getBlock(blockState).isDoubleDropEnabled()) { + Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops()); + } + else { + if (Config.getInstance().getWoodcuttingDoubleDropsEnabled(blockState.getBlockData())) { + Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops()); + } + } + } }