Version 0.0.6

- Added custom loot
- Enhanced the tree search algorithm so it can do branches (fixes the format of a number of trees) among other tweaks
- Fixed a number of loot issues
- Fixed the tree leaf fall replant % being too high
- Added a no animation version of the tree drop
- Added a LeafToSapling converter util
- Fixed durability issue where the plugin was taking too much durability from axes
- Added dirt/grass/coarse dirt ledge compatibility, allows for more flexibility in the tree identification process
This commit is contained in:
MagmaGuy 2018-11-07 04:41:40 +00:00
parent 8aa7c177c1
commit 54ab6c9690
12 changed files with 346 additions and 77 deletions

View File

@ -2,6 +2,7 @@ package com.songoda.ultimatetimber;
import com.songoda.ultimatetimber.commands.CommandHandler;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import com.songoda.ultimatetimber.treefall.CustomLoot;
import com.songoda.ultimatetimber.treefall.TreeFallAnimation;
import com.songoda.ultimatetimber.treefall.TreeFallEvent;
import com.songoda.ultimatetimber.utils.Methods;
@ -45,10 +46,15 @@ public class UltimateTimber extends JavaPlugin {
Bukkit.getServer().getPluginManager().registerEvents(new TreeFallAnimation(), this);
/*
Initialize and cache config
Initialize config
*/
DefaultConfig.initialize();
/*
Initialize custom loot
*/
CustomLoot.initializeCustomItems();
/*
Cache valid worlds for later use
*/

View File

@ -5,6 +5,9 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.configuration.Configuration;
import java.util.Arrays;
import java.util.Collections;
public class DefaultConfig {
/*
@ -26,6 +29,9 @@ public class DefaultConfig {
public static final String REPLANT_SAPLING = "Replant sapling when tree is cut down";
public static final String REPLANT_FROM_LEAVES = "Fallen leaves have a chance to plant saplings";
public static final String CUSTOM_AUDIO = "Use custom sounds for trees falling";
public static final String SHOW_ANIMATION = "Show tree fall animation";
public static final String CUSTOM_LOOT_LIST = "Custom loot";
private static final String CUSTOM_LOOT_ITEM = "Material:GOLDEN_APPLE,Chance:1";
public static void initialize() {
UltimateTimber plugin = UltimateTimber.getInstance();
@ -40,6 +46,7 @@ public class DefaultConfig {
configuration.addDefault(REPLANT_SAPLING, true);
configuration.addDefault(REPLANT_FROM_LEAVES, true);
configuration.addDefault(CUSTOM_AUDIO, true);
configuration.addDefault(SHOW_ANIMATION, true);
/*
Add all worlds that exist in the world at startup
@ -47,6 +54,8 @@ public class DefaultConfig {
for (World world : Bukkit.getServer().getWorlds())
configuration.addDefault(VALID_WORLDS + world.getName(), true);
configuration.addDefault(CUSTOM_LOOT_LIST, Collections.singletonList(CUSTOM_LOOT_ITEM));
configuration.options().copyDefaults(true);
plugin.saveConfig();

View File

@ -1,5 +1,6 @@
package com.songoda.ultimatetimber.treefall;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
@ -8,10 +9,11 @@ import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.LinkedHashSet;
public class AxeDurability {
public static void adjustAxeDamage(ArrayList<Block> blocks, Player player) {
public static void adjustAxeDamage(LinkedHashSet<Block> blocks, Player player) {
if (!(player.getInventory().getItemInMainHand().getType().equals(Material.DIAMOND_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.GOLDEN_AXE) ||
@ -22,7 +24,21 @@ public class AxeDurability {
ItemStack itemStack = player.getInventory().getItemInMainHand();
ItemMeta itemMeta = itemStack.getItemMeta();
Damageable damageableMeta = (Damageable) itemMeta;
damageableMeta.setDamage(damageableMeta.getDamage() + blocks.size());
for (Block block : blocks)
if (block.getType().equals(Material.ACACIA_LOG) ||
block.getType().equals(Material.BIRCH_LOG) ||
block.getType().equals(Material.DARK_OAK_LOG) ||
block.getType().equals(Material.JUNGLE_LOG) ||
block.getType().equals(Material.OAK_LOG) ||
block.getType().equals(Material.SPRUCE_LOG) ||
block.getType().equals(Material.STRIPPED_ACACIA_LOG) ||
block.getType().equals(Material.STRIPPED_BIRCH_LOG) ||
block.getType().equals(Material.STRIPPED_DARK_OAK_LOG) ||
block.getType().equals(Material.STRIPPED_JUNGLE_LOG) ||
block.getType().equals(Material.STRIPPED_OAK_LOG) ||
block.getType().equals(Material.STRIPPED_SPRUCE_LOG))
damageableMeta.setDamage(damageableMeta.getDamage() + 1);
itemStack.setItemMeta((ItemMeta) damageableMeta);
}

View File

@ -0,0 +1,60 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class CustomLoot {
private static HashMap<ItemStack, Double> itemMap = new HashMap<>();
public static void doCustomItemDrop(Location location) {
for (ItemStack itemStack : itemMap.keySet())
if ((ThreadLocalRandom.current().nextDouble()) < itemMap.get(itemStack) / 100)
location.getWorld().dropItem(location, itemStack);
}
public static void initializeCustomItems() {
FileConfiguration fileConfiguration = UltimateTimber.getInstance().getConfig();
List<String> arrayList = (List<String>) fileConfiguration.getList(DefaultConfig.CUSTOM_LOOT_LIST);
for (String string : arrayList) {
Material material = null;
double chance = 0;
String materialString = string.split(",")[0].replace("Material:", "");
try {
material = Material.valueOf(materialString);
} catch (Exception ex) {
Bukkit.getLogger().warning("[UltimateTimber] Warning: " + materialString + " is not a valid material name.");
}
String chanceString = string.split(",")[1].replace("Chance:", "");
try {
chance = Double.parseDouble(chanceString);
} catch (Exception ex) {
Bukkit.getLogger().warning("[UltimateTimber] Warning: " + chanceString + " is not a valid chance.");
}
if (material == null || chance == 0) continue;
ItemStack itemStack = new ItemStack(material);
itemMap.put(itemStack, chance);
}
}
}

View File

@ -0,0 +1,67 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.utils.LeafToSaplingConverter;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.inventory.ItemStack;
import java.util.LinkedHashSet;
import java.util.concurrent.ThreadLocalRandom;
public class NoAnimationTreeDestroyer {
/*
Only ever triggers when people have tree falling animations off in the config
*/
public static void destroyTree(LinkedHashSet<Block> blocks, boolean hasBonusLoot, boolean hasSilkTouch) {
for (Block block : blocks) {
Material material = LeafToSaplingConverter.convertLeaves(block.getType());
if (material.equals(Material.AIR)) continue;
if (material.equals(Material.VINE)) continue;
if (hasSilkTouch) {
if (hasBonusLoot)
block.getWorld().dropItem(block.getLocation(), new ItemStack(block.getType(), 1));
block.getWorld().dropItem(block.getLocation(), new ItemStack(block.getType(), 1));
CustomLoot.doCustomItemDrop(block.getLocation());
block.setType(Material.AIR);
continue;
}
if (material.equals(Material.ACACIA_SAPLING) ||
material.equals(Material.BIRCH_SAPLING) ||
material.equals(Material.DARK_OAK_SAPLING) ||
material.equals(Material.JUNGLE_SAPLING) ||
material.equals(Material.OAK_SAPLING) ||
material.equals(Material.SPRUCE_SAPLING)) {
if (ThreadLocalRandom.current().nextDouble() < 0.05) {
if (hasBonusLoot) {
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
}
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
continue;
} else {
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
continue;
}
}
if (hasBonusLoot)
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
}
}
}

View File

@ -1,11 +1,13 @@
package com.songoda.ultimatetimber.treefall;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
public class TreeChecker {
@ -32,7 +34,7 @@ public class TreeChecker {
/*
Used to limit the blocks that constitute a tree
*/
private static List<Material> validSurroundingMaterials = new ArrayList<>(Arrays.asList(
public static List<Material> validTreeMaterials = new ArrayList<>(Arrays.asList(
Material.ACACIA_LEAVES,
Material.BIRCH_LEAVES,
Material.DARK_OAK_LEAVES,
@ -44,16 +46,13 @@ public class TreeChecker {
Material.RED_MUSHROOM_BLOCK
));
public static ArrayList<Block> parseTree(Block block) {
public static LinkedHashSet<Block> parseTree(Block block, boolean isSourceBlock) {
/*
Check if material is valid
*/
if (!validMaterials.contains(block.getType())) return null;
ArrayList<Block> blocks = new ArrayList<>();
boolean containsSecondaryBlock = false;
/*
Check if block is surrounded by air (bottom blocks are)
Every third block expand one block laterally until the main block line reaches air or the max height is reached
@ -61,6 +60,12 @@ public class TreeChecker {
int offset = 1;
int maxHeight = 31;
/*
This is used to crawl the location up the tree
*/
Location blockLocation = block.getLocation().clone();
Material originalMaterial = block.getType();
for (int i = 0; i < maxHeight; i++) {
if ((i + 1) % 2 == 0 && offset < 6)
@ -70,21 +75,31 @@ public class TreeChecker {
This expands the inverted search pyramid vertically
*/
int xOffset = -offset;
for (int j = xOffset - 1; j < offset; j++) {
for (int j = xOffset; j < offset + 1; j++) {
int zOffset = -offset;
for (int k = zOffset - 1; k < offset; k++) {
for (int k = zOffset; k < offset + 1; k++) {
Block thisBlock = block.getLocation().clone().add(new Vector(xOffset, i, zOffset)).getBlock();
Block thisBlock = blockLocation.clone().add(new Vector(xOffset, 0, zOffset)).getBlock();
if (allBlocks.contains(thisBlock)) continue;
/*
This exclusion list should include everything you may find near trees as to not invalidate trees
in natural forests and such
Construction blocks aren't included because that would bust buildings.
*/
if (!thisBlock.getType().equals(block.getType()) &&
!validSurroundingMaterials.contains(thisBlock.getType()) &&
if ((thisBlock.getType().equals(Material.DIRT) ||
thisBlock.getType().equals(Material.COARSE_DIRT) ||
thisBlock.getType().equals(Material.GRASS_BLOCK)) &&
(i > 1) && isSourceBlock) {
return null;
}
if (!thisBlock.getType().equals(originalMaterial) &&
!validTreeMaterials.contains(thisBlock.getType()) &&
!thisBlock.getType().equals(Material.AIR) &&
!thisBlock.getType().equals(Material.VINE) &&
!thisBlock.getType().equals(Material.ROSE_BUSH) &&
@ -116,20 +131,21 @@ public class TreeChecker {
!thisBlock.getType().equals(Material.DARK_OAK_SAPLING) &&
!thisBlock.getType().equals(Material.VOID_AIR) &&
!thisBlock.getType().equals(Material.CAVE_AIR) &&
(!thisBlock.getType().equals(Material.DIRT) &&
!thisBlock.getType().equals(Material.COARSE_DIRT) &&
!thisBlock.getType().equals(Material.GRASS_BLOCK) &&
(i == 0 || i == 1)))
return null;
if (validSurroundingMaterials.contains(thisBlock.getType()))
containsSecondaryBlock = true;
if (!thisBlock.getType().equals(Material.AIR) &&
!thisBlock.getType().equals(Material.DIRT) &&
!thisBlock.getType().equals(Material.COARSE_DIRT) &&
!thisBlock.getType().equals(Material.GRASS_BLOCK))
blocks.add(thisBlock);
!thisBlock.getType().equals(Material.GRASS_BLOCK)) {
return null;
}
if (!thisBlock.getType().equals(Material.AIR) &&
!thisBlock.getType().equals(Material.CAVE_AIR) &&
!thisBlock.getType().equals(Material.VOID_AIR) &&
!thisBlock.getType().equals(Material.DIRT) &&
!thisBlock.getType().equals(Material.COARSE_DIRT) &&
!thisBlock.getType().equals(Material.GRASS_BLOCK)) {
allBlocks.add(thisBlock);
}
zOffset++;
@ -139,20 +155,63 @@ public class TreeChecker {
}
if (block.getLocation().clone().add(new Vector(0, i, 0)).getBlock().getType().equals(Material.AIR))
if (i > 1)
break;
else
/*
Increment the height of the block location
*/
blockLocation.add(new Vector(0, 1, 0));
/*
Detect if it's the end of the tree
If a block above it continues the same material type as the original block, continue with that block as the
new source block
If it doesn't break the loop and return the blocks list
*/
if (blockLocation.getBlock().getType().equals(Material.AIR) || validTreeMaterials.contains(blockLocation.getBlock().getType())) {
if (isSourceBlock && blockLocation.clone().subtract(block.getLocation().clone()).getY() < 2)
return null;
ArrayList<Block> newBlocks = scanNearbyBranching(originalMaterial, blockLocation);
if (newBlocks != null)
for (Block newBlock : newBlocks) {
LinkedHashSet<Block> newBlockList = parseTree(newBlock, false);
if (newBlockList == null) {
return null;
} else {
allBlocks.addAll(newBlocks);
}
}
else if (blockLocation.getBlock().getType().equals(Material.AIR))
break;
}
}
/*
If there are no leaves, don't see it as a tree
*/
if (!containsSecondaryBlock) return null;
return allBlocks;
return blocks;
}
private static LinkedHashSet<Block> allBlocks = new LinkedHashSet<>();
/*
Some trees veer to a side as they reach the top, this scans for blocks adjacent to the air block above the trunk
*/
private static ArrayList<Block> scanNearbyBranching(Material originalMaterial, Location location) {
ArrayList<Block> newBlocks = new ArrayList<>();
for (int i = -1; i < 2; i++)
for (int j = -1; j < 2; j++) {
Block nearbyBlock = location.clone().add(new Vector(i, 0, j)).getBlock();
if (!nearbyBlock.getType().equals(originalMaterial)) continue;
if (allBlocks.contains(nearbyBlock)) continue;
if (nearbyBlock.getLocation().equals(location)) continue;
newBlocks.add(nearbyBlock);
}
if (newBlocks.isEmpty()) return null;
return newBlocks;
}

View File

@ -16,6 +16,7 @@ import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.LinkedHashSet;
public class TreeFallAnimation implements Listener {
@ -100,7 +101,7 @@ public class TreeFallAnimation implements Listener {
Initially, the tree will start slowly toppling over.
After a short while, it goes over the tipping point and the fall accelerates.
*/
public void startAnimation(Block originalBlock, ArrayList<Block> blocks, Player player) {
public void startAnimation(Block originalBlock, LinkedHashSet<Block> blocks, Player player) {
/*
This vector makes sure that the entire tree falls in the same direction from the same reference point
*/

View File

@ -4,12 +4,13 @@ import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.block.Block;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import java.util.ArrayList;
import java.util.LinkedHashSet;
public class TreeFallEvent implements Listener {
@ -21,17 +22,41 @@ public class TreeFallEvent implements Listener {
public void onTreeBreak(BlockBreakEvent event) {
if (!EventFilter.eventIsValid(event)) return;
ArrayList<Block> blocks = TreeChecker.parseTree(event.getBlock());
if (blocks == null) return;
LinkedHashSet<Block> blocks = TreeChecker.parseTree(event.getBlock(), true);
/*
Check if the list of blocks carried over from the tree parser contains everything you'd expect from a tree
*/
if (blocks == null)
return;
boolean containsLeaves = false;
for (Block block : blocks)
if (TreeChecker.validTreeMaterials.contains(block.getType())) {
containsLeaves = true;
break;
}
if (!containsLeaves)
return;
FileConfiguration fileConfiguration = UltimateTimber.getInstance().getConfig();
if (fileConfiguration.getBoolean(DefaultConfig.ACCURATE_AXE_DURABILITY))
AxeDurability.adjustAxeDamage(blocks, event.getPlayer());
if (fileConfiguration.getBoolean(DefaultConfig.CUSTOM_AUDIO))
TreeSounds.tipOverNoise(event.getBlock().getLocation());
TreeFallAnimation treeFallAnimation = new TreeFallAnimation();
treeFallAnimation.startAnimation(event.getBlock(), blocks, event.getPlayer());
if (fileConfiguration.getBoolean(DefaultConfig.SHOW_ANIMATION)) {
TreeFallAnimation treeFallAnimation = new TreeFallAnimation();
treeFallAnimation.startAnimation(event.getBlock(), blocks, event.getPlayer());
} else {
NoAnimationTreeDestroyer.destroyTree(blocks, event.getPlayer().hasPermission("ultimatetimber.bonusloot"),
event.getPlayer().getInventory().getItemInMainHand().containsEnchantment(Enchantment.SILK_TOUCH));
}
}

View File

@ -1,5 +1,6 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.utils.LeafToSaplingConverter;
import org.bukkit.Material;
import org.bukkit.entity.FallingBlock;
import org.bukkit.inventory.ItemStack;
@ -10,40 +11,16 @@ public class TreeLoot {
public static void convertFallingBlock(FallingBlock fallingBlock, boolean hasBonusLoot, boolean hasSilkTouch) {
Material material;
switch (fallingBlock.getBlockData().getMaterial()) {
case ACACIA_LEAVES:
material = Material.ACACIA_SAPLING;
break;
case BIRCH_LEAVES:
material = Material.BIRCH_SAPLING;
break;
case DARK_OAK_LEAVES:
material = Material.DARK_OAK_SAPLING;
break;
case JUNGLE_LEAVES:
material = Material.JUNGLE_SAPLING;
break;
case OAK_LEAVES:
material = Material.OAK_SAPLING;
break;
case SPRUCE_LEAVES:
material = Material.SPRUCE_SAPLING;
break;
default:
material = fallingBlock.getBlockData().getMaterial();
}
Material material = LeafToSaplingConverter.convertLeaves(fallingBlock.getBlockData().getMaterial());
if (material.equals(Material.VINE))
return;
if (hasSilkTouch) {
if (hasBonusLoot)
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial(), 1));
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial(), 1));
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
}
@ -53,14 +30,20 @@ public class TreeLoot {
material.equals(Material.JUNGLE_SAPLING) ||
material.equals(Material.OAK_SAPLING) ||
material.equals(Material.SPRUCE_SAPLING)) {
if (ThreadLocalRandom.current().nextDouble() < 0.05) {
if (hasBonusLoot)
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
return;
} else return;
}
if (ThreadLocalRandom.current().nextDouble() < 0.05) {
if (hasBonusLoot) {
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
}
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
} else {
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
}
}
if (hasBonusLoot)
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
@ -68,4 +51,6 @@ public class TreeLoot {
}
}

View File

@ -94,7 +94,7 @@ public class TreeReplant {
if (material == null) return;
if (ThreadLocalRandom.current().nextDouble() > 0.05) return;
if (ThreadLocalRandom.current().nextDouble() > 0.01) return;
Block block = fallingBlock.getLocation().clone().subtract(new Vector(0, 1, 0)).getBlock();

View File

@ -0,0 +1,41 @@
package com.songoda.ultimatetimber.utils;
import org.bukkit.Material;
public class LeafToSaplingConverter {
/*
Defaults to returning the same material type that is fed into it
*/
public static Material convertLeaves(Material material){
switch (material) {
case ACACIA_LEAVES:
material = org.bukkit.Material.ACACIA_SAPLING;
break;
case BIRCH_LEAVES:
material = org.bukkit.Material.BIRCH_SAPLING;
break;
case DARK_OAK_LEAVES:
material = org.bukkit.Material.DARK_OAK_SAPLING;
break;
case JUNGLE_LEAVES:
material = org.bukkit.Material.JUNGLE_SAPLING;
break;
case OAK_LEAVES:
material = org.bukkit.Material.OAK_SAPLING;
break;
case SPRUCE_LEAVES:
material = org.bukkit.Material.SPRUCE_SAPLING;
break;
default:
material = material;
}
return material;
}
}

View File

@ -1,5 +1,5 @@
name: UltimateTimber
version: 0.0.4
version: 0.0.5
author: Songoda
main: com.songoda.ultimatetimber.UltimateTimber
api-version: 1.13