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());
+ }
+ }
+ }
}