Implement tree capitator

This commit is contained in:
Gabriel Tofvesson 2021-06-20 17:13:06 +02:00
parent 4a602b42f6
commit 0d37534eed
6 changed files with 400 additions and 27 deletions

View File

@ -15,4 +15,8 @@ commands:
search:
description: Search for a given item in all nearby inventories
usage: /<command> {item type}
permission: invtweaks.search
permission: invtweaks.search
capitator:
description: Toggle tree capitation for an axe in your main hand
usage: /<command>
permission: invtweaks.capitator

View File

@ -1,22 +1,25 @@
package dev.w1zzrd.invtweaks;
import dev.w1zzrd.invtweaks.command.CapitatorCommand;
import dev.w1zzrd.invtweaks.command.MagnetCommandExecutor;
import dev.w1zzrd.invtweaks.command.SearchCommandExecutor;
import dev.w1zzrd.invtweaks.command.SortCommandExecutor;
import dev.w1zzrd.invtweaks.listener.MagnetismListener;
import dev.w1zzrd.invtweaks.listener.TabCompletionListener;
import dev.w1zzrd.invtweaks.enchantment.CapitatorEnchantment;
import dev.w1zzrd.invtweaks.listener.*;
import dev.w1zzrd.invtweaks.serialization.MagnetConfig;
import dev.w1zzrd.invtweaks.listener.SortListener;
import dev.w1zzrd.invtweaks.listener.StackReplaceListener;
import dev.w1zzrd.invtweaks.serialization.MagnetData;
import dev.w1zzrd.invtweaks.serialization.SearchConfig;
import dev.w1zzrd.invtweaks.serialization.UUIDList;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;
@ -31,18 +34,24 @@ public final class InvTweaksPlugin extends JavaPlugin {
public static final String LOG_PLUGIN_NAME = "[InventoryTweaks]";
private static final String PERSISTENT_DATA_NAME = "data";
private static final String ENCHANTMENT_CAPITATOR_NAME = "Capitator";
private final Logger logger = Bukkit.getLogger();
// Command executor references in case I need them or something idk
private SortCommandExecutor sortCommandExecutor;
private MagnetCommandExecutor magnetCommandExecutor;
private SearchCommandExecutor searchCommandExecutor;
private CapitatorCommand capitatorCommand;
private DataStore data;
private NamespacedKey capitatorEnchantmentKey;
private Enchantment capitatorEnchantment;
@Override
public void onEnable() {
logger.fine(LOG_PLUGIN_NAME + " Plugin enabled");
initEnchantments();
enablePersistentData();
initCommands();
initEvents();
@ -55,6 +64,7 @@ public final class InvTweaksPlugin extends JavaPlugin {
disableEvents();
disableCommands();
disablePersistentData();
disableEnchantments();
}
@Override
@ -78,18 +88,43 @@ public final class InvTweaksPlugin extends JavaPlugin {
return data;
}
/**
* Initialize commands registered by this plugin
*/
private void initCommands() {
sortCommandExecutor = new SortCommandExecutor();
magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData());
searchCommandExecutor = new SearchCommandExecutor(this, "search");
private void initEnchantments() {
capitatorEnchantmentKey = new NamespacedKey(this, ENCHANTMENT_CAPITATOR_NAME);
capitatorEnchantment = new CapitatorEnchantment(ENCHANTMENT_CAPITATOR_NAME, capitatorEnchantmentKey);
// TODO: Bind command by annotation
Objects.requireNonNull(getCommand("sort")).setExecutor(sortCommandExecutor);
Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor);
Objects.requireNonNull(getCommand("search")).setExecutor(searchCommandExecutor);
try {
final Field acceptingField = Enchantment.class.getDeclaredField("acceptingNew");
acceptingField.setAccessible(true);
acceptingField.set(null, true);
Enchantment.registerEnchantment(capitatorEnchantment);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
private void disableEnchantments() {
try {
final Field byKeyField = Enchantment.class.getDeclaredField("byKey");
final Field byNameField = Enchantment.class.getDeclaredField("byName");
byKeyField.setAccessible(true);
byNameField.setAccessible(true);
final Object byKey = byKeyField.get(null);
final Object byName = byNameField.get(null);
if (byKey instanceof final Map<?, ?> byKeyMap && byName instanceof final Map<?, ?> byNameMap) {
byKeyMap.remove(capitatorEnchantmentKey);
byNameMap.remove(ENCHANTMENT_CAPITATOR_NAME);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
capitatorEnchantment = null;
capitatorEnchantmentKey = null;
}
/**
@ -102,17 +137,7 @@ public final class InvTweaksPlugin extends JavaPlugin {
pluginManager.registerEvents(new SortListener(), this);
pluginManager.registerEvents(new MagnetismListener(magnetCommandExecutor), this);
pluginManager.registerEvents(new TabCompletionListener(), this);
}
/**
* Do whatever is necessary to disable commands and their execution
*/
private void disableCommands() {
magnetCommandExecutor.onDisable();
sortCommandExecutor = null;
magnetCommandExecutor = null;
searchCommandExecutor = null;
pluginManager.registerEvents(new TreeCapitatorListener(capitatorEnchantment), this);
}
/**
@ -123,6 +148,34 @@ public final class InvTweaksPlugin extends JavaPlugin {
HandlerList.unregisterAll(this);
}
/**
* Initialize commands registered by this plugin
*/
private void initCommands() {
sortCommandExecutor = new SortCommandExecutor();
magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData());
searchCommandExecutor = new SearchCommandExecutor(this, "search");
capitatorCommand = new CapitatorCommand(capitatorEnchantment);
// TODO: Bind command by annotation
Objects.requireNonNull(getCommand("sort")).setExecutor(sortCommandExecutor);
Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor);
Objects.requireNonNull(getCommand("search")).setExecutor(searchCommandExecutor);
Objects.requireNonNull(getCommand("capitator")).setExecutor(capitatorCommand);
}
/**
* Do whatever is necessary to disable commands and their execution
*/
private void disableCommands() {
magnetCommandExecutor.onDisable();
capitatorCommand = null;
searchCommandExecutor = null;
magnetCommandExecutor = null;
sortCommandExecutor = null;
}
/**
* Register type serializers/deserializers for configurations and YAML files
*

View File

@ -0,0 +1,37 @@
package dev.w1zzrd.invtweaks.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class CapitatorCommand implements CommandExecutor {
private final Enchantment capitatorEnchantment;
public CapitatorCommand(final Enchantment capitatorEnchantment) {
this.capitatorEnchantment = capitatorEnchantment;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof final Player caller) {
final ItemStack stack = caller.getInventory().getItemInMainHand();
if (stack.getType().name().endsWith("_AXE")) {
if (stack.containsEnchantment(capitatorEnchantment)) {
stack.removeEnchantment(capitatorEnchantment);
sender.spigot().sendMessage(CommandUtils.successMessage("Item is now a regular axe"));
} else {
stack.addEnchantment(capitatorEnchantment, 1);
sender.spigot().sendMessage(CommandUtils.successMessage("Item is now a capitator axe"));
}
} else
sender.spigot().sendMessage(CommandUtils.errorMessage("Only axes can be tree capitators!"));
} else
sender.spigot().sendMessage(CommandUtils.errorMessage("Only players can create tree capitators!"));
return true;
}
}

View File

@ -1,6 +1,7 @@
package dev.w1zzrd.invtweaks.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.command.CommandSender;
@ -16,4 +17,16 @@ public final class CommandUtils {
return !condition;
}
public static BaseComponent errorMessage(final String message) {
final BaseComponent component = new TextComponent(message);
component.setColor(ChatColor.DARK_RED);
return component;
}
public static BaseComponent successMessage(final String message) {
final BaseComponent component = new TextComponent(message);
component.setColor(ChatColor.GREEN);
return component;
}
}

View File

@ -0,0 +1,55 @@
package dev.w1zzrd.invtweaks.enchantment;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.inventory.ItemStack;
public final class CapitatorEnchantment extends Enchantment {
private final String name;
public CapitatorEnchantment(String name, NamespacedKey key) {
super(key);
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getMaxLevel() {
return 1;
}
@Override
public int getStartLevel() {
return 1;
}
@Override
public EnchantmentTarget getItemTarget() {
return EnchantmentTarget.TOOL;
}
@Override
public boolean isTreasure() {
return false;
}
@Override
public boolean isCursed() {
return false;
}
@Override
public boolean conflictsWith(Enchantment other) {
return false;
}
@Override
public boolean canEnchantItem(ItemStack item) {
return item.getType().name().endsWith("_AXE");
}
}

View File

@ -0,0 +1,211 @@
package dev.w1zzrd.invtweaks.listener;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.*;
import static org.bukkit.Material.*;
public class TreeCapitatorListener implements Listener {
private static final int MAX_SEARCH_BLOCKS = 69420;
private static final List<Material> targetMaterials = Arrays.asList(
ACACIA_LOG,
OAK_LOG,
BIRCH_LOG,
JUNGLE_LOG,
DARK_OAK_LOG,
SPRUCE_LOG,
ACACIA_LEAVES,
OAK_LEAVES,
BIRCH_LEAVES,
JUNGLE_LEAVES,
DARK_OAK_LEAVES,
SPRUCE_LEAVES
);
private static final List<Material> leaves = Arrays.asList(
ACACIA_LEAVES,
OAK_LEAVES,
BIRCH_LEAVES,
JUNGLE_LEAVES,
DARK_OAK_LEAVES,
SPRUCE_LEAVES
);
private final Enchantment capitatorEnchantment;
public TreeCapitatorListener(final Enchantment capitatorEnchantment) {
this.capitatorEnchantment = capitatorEnchantment;
}
@EventHandler
public void onBlockBreak(final BlockBreakEvent event) {
final ItemStack handTool = event.getPlayer().getInventory().getItemInMainHand();
if (event.isCancelled() || !handTool.containsEnchantment(capitatorEnchantment))
return;
if (handTool.getItemMeta() instanceof final Damageable tool) {
if (!leaves.contains(event.getBlock().getType()) && targetMaterials.contains(event.getBlock().getType())) {
int logBreakCount = 0;
for (final Block found : findAdjacent(event.getBlock(), getMaxUses(handTool, tool)))
if (event.getPlayer().getGameMode() == GameMode.CREATIVE)
found.setType(AIR);
else {
if (!leaves.contains(found.getType()))
++logBreakCount;
found.breakNaturally(handTool);
}
if (event.getPlayer().getGameMode() != GameMode.CREATIVE && !applyDamage(handTool, logBreakCount)) {
event.getPlayer().getInventory().setItemInMainHand(new ItemStack(AIR));
event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f);
}
event.setCancelled(true);
}
}
}
private static int getMaxUses(final ItemStack stack, final Damageable tool) {
return ((stack.getEnchantmentLevel(Enchantment.DURABILITY) + 1) * (stack.getType().getMaxDurability()) - tool.getDamage());
}
private static boolean applyDamage(final ItemStack stack, final int brokenBlocks) {
final ItemMeta meta = stack.getItemMeta();
if (meta instanceof final Damageable toolMeta) {
final int unbreakingDivider = stack.getEnchantmentLevel(Enchantment.DURABILITY) + 1;
final int round = brokenBlocks % unbreakingDivider;
final int dmg = (brokenBlocks / unbreakingDivider) + (round != 0 ? 1 : 0);
if (dmg > (stack.getType().getMaxDurability() - toolMeta.getDamage()))
return false;
toolMeta.setDamage(toolMeta.getDamage() + dmg);
stack.setItemMeta(meta);
}
return true;
}
private static List<Block> findAdjacent(final Block start, final int softMax) {
List<Block> frontier = new ArrayList<>();
final List<Block> matches = new ArrayList<>();
frontier.add(start);
matches.add(start);
int total = 1;
int softMaxCount = 1;
// Keep finding blocks until we have no new matches
while (frontier.size() > 0 && total < MAX_SEARCH_BLOCKS && softMaxCount < softMax) {
final long result = addAdjacents(frontier, matches, total, softMaxCount, softMax);
total = (int) (result >>> 32);
softMaxCount = (int) (result & 0xFFFFFFFFL);
}
return matches;
}
private static long addAdjacents(final List<Block> frontier, final List<Block> collect, int total, int softMax, final int softMaxCap) {
final List<Block> newFrontier = new ArrayList<>();
OUTER:
for (final Block check : frontier)
for (int x = -1; x <= 1; ++x)
for (int y = -1; y <= 1; ++y)
for (int z = -1; z <= 1; ++z)
if ((x | y | z) != 0) {
final Block offset = offset(collect, check, x, y, z);
if (offset != null && !binaryContains(newFrontier, offset)) {
binaryInsert(collect, offset);
binaryInsert(newFrontier, offset);
if (!leaves.contains(offset.getType())) {
++softMax;
if (softMax >= softMaxCap)
break OUTER;
}
}
// Short-circuit for max search
++total;
if (total == MAX_SEARCH_BLOCKS)
break OUTER;
}
frontier.clear();
frontier.addAll(newFrontier);
return (((long)total) << 32) | (((long) softMax) & 0xFFFFFFFFL);
}
private static Block offset(final List<Block> checked, final Block source, int x, int y, int z) {
final Block offset = source.getWorld().getBlockAt(source.getLocation().add(x, y, z));
if (targetMaterials.contains(offset.getType()) && (!leaves.contains(source.getType()) || !leaves.contains(offset.getType())) && !binaryContains(checked, offset))
return offset;
return null;
}
private static boolean binaryContains(final List<Block> collection, final Block find) {
return binarySearchBlock(collection, find) >= 0;
}
private static void binaryInsert(final List<Block> collection, final Block insert) {
final int index = binarySearchBlock(collection, insert);
if (index >= 0)
return;
collection.add(-(index + 1), insert);
}
private static void binaryRemove(final List<Block> collection, final Block remove) {
final int index = binarySearchBlock(collection, remove);
if (index < 0)
return;
collection.remove(index);
}
private static int binarySearchBlock(final List<Block> collection, final Block find) {
return Collections.binarySearch(collection, find, TreeCapitatorListener::blockCompare);
}
private static int blockCompare(final Block b1, final Block b2) {
return coordinateCompare(b1.getX(), b1.getY(), b1.getZ(), b2.getX(), b2.getY(), b2.getZ());
}
private static int coordinateCompare(final int x1, final int y1, final int z1, final int x2, final int y2, final int z2) {
final int x = Integer.compare(x1, x2);
if (x != 0)
return x;
final int y = Integer.compare(y1, y2);
if (y != 0)
return y;
return Integer.compare(z1, z2);
}
}