diff --git a/res/plugin.yml b/res/plugin.yml index 2d4e936..8f22425 100644 --- a/res/plugin.yml +++ b/res/plugin.yml @@ -18,6 +18,10 @@ commands: description: Search for a given item in all nearby inventories usage: / {item type} permission: invtweaks.search + find: + description: Show all chests that contain a given item + usage: / {item type} + permissino: invtweaks.find capitator: description: Toggle tree capitation for an axe in your main hand usage: / diff --git a/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java b/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java index 3e6363e..d67039f 100644 --- a/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java +++ b/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java @@ -41,6 +41,7 @@ public final class InvTweaksPlugin extends JavaPlugin { private MagnetCommandExecutor magnetCommandExecutor; private SearchCommandExecutor searchCommandExecutor; private NamedChestCommand namedChestCommandExecutor; + private FindCommandExecutor findCommandExecutor; private CapitatorCommand capitatorCommand; private PersistentData data; private NamedChestManager chestManager; @@ -143,6 +144,7 @@ public final class InvTweaksPlugin extends JavaPlugin { magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData()); searchCommandExecutor = new SearchCommandExecutor(this, "search"); namedChestCommandExecutor = new NamedChestCommand(chestManager); + findCommandExecutor = new FindCommandExecutor(this); if (activateCapitator) capitatorCommand = new CapitatorCommand(capitatorEnchantment.getEnchantment()); @@ -152,6 +154,7 @@ public final class InvTweaksPlugin extends JavaPlugin { Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor); Objects.requireNonNull(getCommand("search")).setExecutor(searchCommandExecutor); Objects.requireNonNull(getCommand("chestname")).setExecutor(namedChestCommandExecutor); + Objects.requireNonNull(getCommand("find")).setExecutor(findCommandExecutor); if (activateCapitator) Objects.requireNonNull(getCommand("capitator")).setExecutor(capitatorCommand); @@ -164,6 +167,7 @@ public final class InvTweaksPlugin extends JavaPlugin { magnetCommandExecutor.onDisable(); capitatorCommand = null; + findCommandExecutor = null; namedChestCommandExecutor = null; searchCommandExecutor = null; magnetCommandExecutor = null; diff --git a/src/dev/w1zzrd/invtweaks/command/FindCommandExecutor.java b/src/dev/w1zzrd/invtweaks/command/FindCommandExecutor.java new file mode 100644 index 0000000..60f4867 --- /dev/null +++ b/src/dev/w1zzrd/invtweaks/command/FindCommandExecutor.java @@ -0,0 +1,98 @@ +package dev.w1zzrd.invtweaks.command; + +import dev.w1zzrd.spigot.wizcompat.command.CommandUtils; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Container; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching; +import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue; +import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*; + +public final class FindCommandExecutor implements CommandExecutor { + private static final int SEARCH_RADIUS = 3; + private static final long DESPAWN_WAIT = 20 * 10; + + private final Plugin plugin; + + public FindCommandExecutor(final Plugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + Material targetMaterial; + + if (assertTrue(sender instanceof Player, "Command can only be run by players", sender) || + assertTrue(args.length == 1, "Exactly one argument is expected", sender) || + assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format("Unknown item/block: %s", args[0]), sender)) + return true; + + final Player player = (Player) sender; + + final List matches = searchChunks( + player.getLocation().getChunk(), + SEARCH_RADIUS, + Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX + ); + + final List entities = matches.stream() + .filter(it -> Arrays.stream(((Container)it).getSnapshotInventory().getContents()).filter(Objects::nonNull).map(ItemStack::getType).anyMatch(targetMaterial::equals)) + .map(state -> spawnMarker(player, state.getLocation().add(0.5, 0.2, 0.5))) + .collect(Collectors.toList()); + + if (assertTrue(entities.size() > 0, "No containers neardby contain that item/block", sender)) + return true; + + Bukkit.getScheduler().runTaskLater(plugin, () -> { + entities.forEach(it -> sendEntityDespawnPacket(player, it)); + }, DESPAWN_WAIT); + + return true; + } + + private static int spawnMarker(final Player target, final Location location) { + final Object entity = createFakeSlime(target); + setSlimeSize(entity, 1); + setEntityCollision(entity, false); + setEntityInvisible(entity, true); + setEntityInvulnerable(entity, true); + setEntityGlowing(entity, true); + setEntityLocation(entity, location.getX(), location.getY(), location.getZ(), 0f, 0f); + + sendEntitySpawnPacket(target, entity); + sendEntityMetadataPacket(target, entity); + + return getEntityID(entity); + } + + private static List searchChunks(final Chunk middle, final int radius, final Material... targets) { + final int xMin = middle.getX() - radius; + final int xMax = middle.getX() + radius; + final int zMin = middle.getZ() - radius; + final int zMax = middle.getZ() + radius; + + final World sourceWorld = middle.getWorld(); + + final List targetMaterials = Arrays.asList(targets); + final ArrayList collect = new ArrayList<>(); + for (int x = xMin; x <= xMax; ++x) + for (int z = zMin; z <= zMax; ++z) + Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add); + + return collect; + } +} diff --git a/src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java b/src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java index 6ae5a61..fbf9391 100644 --- a/src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java +++ b/src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java @@ -3,10 +3,7 @@ package dev.w1zzrd.invtweaks.command; import dev.w1zzrd.invtweaks.InvTweaksPlugin; import dev.w1zzrd.invtweaks.serialization.SearchConfig; import dev.w1zzrd.spigot.wizcompat.command.ConfigurableCommandExecutor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.block.*; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -17,10 +14,7 @@ import org.bukkit.plugin.Plugin; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.logging.Logger; import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching; @@ -54,17 +48,14 @@ public class SearchCommandExecutor extends ConfigurableCommandExecutor matches = searchBlocks( - player.getLocation(), - player.getWorld(), - config.getSearchRadiusX(), config.getSearchRadiusY(), config.getSearchRadiusZ(), - Material.CHEST, Material.SHULKER_BOX + final List matches = searchChunks( + player.getLocation().getChunk(), + config.getSearchRadiusX(), + Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX ); // Ensure we found inventory-holding blocks @@ -152,7 +143,24 @@ public class SearchCommandExecutor extends ConfigurableCommandExecutor searchBlocks( + private static List searchChunks(final Chunk middle, final int radius, final Material... targets) { + final int xMin = middle.getX() - radius; + final int xMax = middle.getX() + radius; + final int zMin = middle.getZ() - radius; + final int zMax = middle.getZ() + radius; + + final World sourceWorld = middle.getWorld(); + + final List targetMaterials = Arrays.asList(targets); + final ArrayList collect = new ArrayList<>(); + for (int x = xMin; x <= xMax; ++x) + for (int z = zMin; z <= zMax; ++z) + Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add); + + return collect; + } + + private static List searchBlocks( final Location centre, final World world, final int rx, diff --git a/src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java b/src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java index e2f5e0c..726c6d2 100644 --- a/src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java +++ b/src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java @@ -112,9 +112,16 @@ public class TabCompletionListener implements Listener { public static Material getMaterialMatching(final String arg) { final List mats = getAllMaterialsMatching(arg).collect(Collectors.toList()); + if (mats.size() == 0) + return null; + + if (mats.size() == 1) + return mats.get(0); + return mats.stream() - .filter(it -> multiNS ? arg.equals(it.getKey().toString()) : arg.equals(it.getKey().getKey())) - .findFirst().orElse(mats.size() == 1 ? mats.get(0) : null); + .filter(it -> multiNS || arg.contains(":") ? arg.equals(it.getKey().toString()) : arg.equals(it.getKey().getKey())) + .findFirst() + .orElse(null); } }