commit 61cc2b433764003a2be8029cddf7f9e228eedd19 Author: MagmaGuy Date: Sat Nov 3 20:49:10 2018 +0000 Version 0.0.1 - Initial commit of the TreeAssist recode - Added the animation - Added solid block detection - Added prototype drop mechanic - Added the config file (not fully hooked up to the filters yet) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f95163d --- /dev/null +++ b/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + epictimber + EpicTimber + 1.0-SNAPSHOT + + + UTF-8 + + + + ${project.artifactId} + clean resources:resources package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + + org.spigotmc + spigot-api + 1.13-R0.1-SNAPSHOT + provided + + + + org.bukkit + bukkit + 1.13-R0.1-SNAPSHOT + provided + + + + \ No newline at end of file diff --git a/src/main/java/com/songoda/epictimber/DefaultConfig.java b/src/main/java/com/songoda/epictimber/DefaultConfig.java new file mode 100644 index 0000000..270e7e6 --- /dev/null +++ b/src/main/java/com/songoda/epictimber/DefaultConfig.java @@ -0,0 +1,40 @@ +package com.songoda.epictimber; + +import org.bukkit.configuration.Configuration; + +public class DefaultConfig { + + /* + This value is just cached so it can easily and safely be accessed during runtime + */ + public static Configuration configuration; + + /* + Storing these values in final strings makes it so you can change the keys or refactor their names later on without + ever having to alter any code directly. + Also they are easier to refer to using an IDE. + */ + public static final String AXES_ONLY = "Only topple down trees cut down using axes"; + public static final String ACCURATE_AXE_DURABILITY = "Lower durability proportionately to the amount of blocks toppled down"; + public static final String CREATIVE_DISALLOWED = "Players in creative mode can't topple down trees"; + public static final String PERMISSIONS_ONLY = "Only allow players with the permission node to topple down trees"; + + + public static void initialize() { + + Configuration newConfiguration = EpicTimber.plugin.getConfig(); + + newConfiguration.addDefault(AXES_ONLY, true); + newConfiguration.addDefault(ACCURATE_AXE_DURABILITY, true); + newConfiguration.addDefault(CREATIVE_DISALLOWED, true); + newConfiguration.addDefault(PERMISSIONS_ONLY, true); + + newConfiguration.options().copyDefaults(true); + + EpicTimber.plugin.saveDefaultConfig(); + + configuration = newConfiguration; + + } + +} diff --git a/src/main/java/com/songoda/epictimber/EpicTimber.java b/src/main/java/com/songoda/epictimber/EpicTimber.java new file mode 100644 index 0000000..d602fd2 --- /dev/null +++ b/src/main/java/com/songoda/epictimber/EpicTimber.java @@ -0,0 +1,38 @@ +package com.songoda.epictimber; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +/* +Note: In this plugin, I have called the act of a tree falling over with pseudo-physics "toppling over". This is reflected +in the documentation, config files and variable names. +PS: MagmaGuy was here + */ + +public class EpicTimber extends JavaPlugin { + + public static Plugin plugin; + + @Override + public void onEnable() { + + plugin = this; + /* + Register the main event that handles toppling down trees + */ + Bukkit.getServer().getPluginManager().registerEvents(new TreeFallHandler(), this); + + /* + Initialize and cache config + */ + DefaultConfig.initialize(); + + } + + @Override + public void onDisable() { + + } + +} diff --git a/src/main/java/com/songoda/epictimber/TreeFallHandler.java b/src/main/java/com/songoda/epictimber/TreeFallHandler.java new file mode 100644 index 0000000..bc7fbd3 --- /dev/null +++ b/src/main/java/com/songoda/epictimber/TreeFallHandler.java @@ -0,0 +1,230 @@ +package com.songoda.epictimber; + +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TreeFallHandler implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST) + public void onTreeBreak(BlockBreakEvent event) { + + if (event.isCancelled()) return; + parseTree(event.getBlock(), event.getPlayer()); + + + } + + /* + Incorporate all checks that would disqualify this event from happening + */ + private static boolean eventIsValid(BlockBreakEvent event) { + /* + General catchers + */ + if (event.isCancelled()) return false; + if (!validMaterials.contains(event.getBlock().getType())) return false; + /* + Config-based catchers + */ + if (DefaultConfig.configuration.getBoolean(DefaultConfig.CREATIVE_DISALLOWED) && + event.getPlayer().getGameMode().equals(GameMode.CREATIVE)) + return false; + return !DefaultConfig.configuration.getBoolean(DefaultConfig.AXES_ONLY) || + (event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.DIAMOND_AXE) || + event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.GOLDEN_AXE) || + event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.IRON_AXE) || + event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.STONE_AXE) || + event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WOODEN_AXE)); + } + + /* + Used to check if a tree is a tree + */ + private static List validMaterials = new ArrayList<>(Arrays.asList( + Material.ACACIA_LOG, + Material.BIRCH_LOG, + Material.DARK_OAK_LOG, + Material.JUNGLE_LOG, + Material.OAK_LOG, + Material.SPRUCE_LOG + )); + + /* + Used to limit the blocks that constitute a tree + */ + private static List validSurroundingMaterials = new ArrayList<>(Arrays.asList( + Material.ACACIA_LEAVES, + Material.BIRCH_LEAVES, + Material.DARK_OAK_LEAVES, + Material.JUNGLE_LEAVES, + Material.OAK_LEAVES, + Material.SPRUCE_LEAVES, + Material.COCOA_BEANS + )); + + private static void parseTree(Block block, Player player) { + /* + Check if material is valid + */ + if (!validMaterials.contains(block.getType())) return; + + ArrayList 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 + */ + int offset = 1; + int maxHeight = 31; //make configurable + + for (int i = 0; i < maxHeight; i++) { + + if ((i + 1) % 3 == 0) + offset++; + + /* + This expands the inverted search pyramid vertically + */ + int xOffset = -offset; + for (int j = xOffset - 1; j < offset; j++) { + + int zOffset = -offset; + + for (int k = zOffset - 1; k < offset; k++) { + + Block thisBlock = block.getLocation().clone().add(new Vector(xOffset, i, zOffset)).getBlock(); + + /* + 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()) && + !thisBlock.getType().equals(Material.AIR) && + !thisBlock.getType().equals(Material.VINE) && + !thisBlock.getType().equals(Material.ROSE_BUSH) && + !thisBlock.getType().equals(Material.ORANGE_TULIP) && + !thisBlock.getType().equals(Material.PINK_TULIP) && + !thisBlock.getType().equals(Material.RED_TULIP) && + !thisBlock.getType().equals(Material.WHITE_TULIP) && + !thisBlock.getType().equals(Material.TALL_GRASS) && + !thisBlock.getType().equals(Material.FERN) && + !thisBlock.getType().equals(Material.LARGE_FERN) && + !thisBlock.getType().equals(Material.DEAD_BUSH) && + !thisBlock.getType().equals(Material.BROWN_MUSHROOM) && + !thisBlock.getType().equals(Material.RED_MUSHROOM)) + return; + + if (validSurroundingMaterials.contains(thisBlock.getType())) + containsSecondaryBlock = true; + + if (!thisBlock.getType().equals(Material.AIR)) + blocks.add(thisBlock); + + zOffset++; + + } + + xOffset++; + + } + + if (block.getLocation().clone().add(new Vector(0, i, 0)).getBlock().getType().equals(Material.AIR)) + if (i > 1) + break; + else + return; + + } + + /* + If there are no leaves, don't see it as a tree + */ + if (!containsSecondaryBlock) return; + + toppleAnimation(block, blocks, player); + + } + + private static void toppleAnimation(Block originalBlock, ArrayList blocks, Player player) { + + Vector velocityVector = originalBlock.getLocation().clone().subtract(player.getLocation().clone()).toVector().normalize().setY(0); + + for (Block block : blocks) { + + if (block.getType().equals(Material.AIR)) continue; + + FallingBlock fallingBlock = block.getWorld().spawnFallingBlock(block.getLocation(), block.getBlockData()); + + /* + Set tipping over effect + The horizontal velocity going away from the player increases as the Y moves away from the player + */ + block.setType(Material.AIR); + double multiplier = (block.getLocation().getY() - player.getLocation().getY()) * 0.1; + fallingBlock.setVelocity(velocityVector.clone().multiply(multiplier)); + fallingBlock.setDropItem(true); + fallingBlock.setGravity(false); + new BukkitRunnable() { + @Override + public void run() { + fallingBlock.setGravity(true); + } + }.runTaskLater(EpicTimber.plugin, 5); + new BukkitRunnable() { + @Override + public void run() { + fallingBlock.setVelocity(fallingBlock.getVelocity().subtract(new Vector(0, 0.05, 0))); + if (hasNearbySolidBlock(fallingBlock)) { + cancel(); + fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial(), 1)); + fallingBlock.remove(); + fallingBlock.getLocation().getWorld().spawnParticle(Particle.SMOKE_LARGE, fallingBlock.getLocation(), 3, 0.2, 0.2, 0.2, 0.05); + } + } + }.runTaskTimer(EpicTimber.plugin, 0, 1); + + } + + } + + + /* + Since the block accelerates as it falls, increase the search radius the longer it has been falling for + */ + private static boolean hasNearbySolidBlock(FallingBlock fallingBlock) { + + if (!fallingBlock.getLocation().subtract(new Vector(0, 1, 0)).getBlock().getType().equals(Material.AIR)) + return true; + + /* + Lower comparative intensity by predicting the blocks through which the falling block will pass by by checking + its velocity + */ + Location predictedLocation = fallingBlock.getLocation().clone().add(fallingBlock.getVelocity()); + + return !predictedLocation.getBlock().getType().equals(Material.AIR); + + } + + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..1521d21 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,10 @@ +name: EpicTimber +version: 0.0.1 +author: Songoda +main: com.songoda.epictimber.EpicTimber +api-version: 1.13 +commands: + epictimber: + description: Does something, probably + usage: /epictimber + aliases: [ep] \ No newline at end of file