192 lines
7.7 KiB
Java
192 lines
7.7 KiB
Java
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.*;
|
|
import org.bukkit.block.*;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.inventory.InventoryHolder;
|
|
import org.bukkit.plugin.Plugin;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.*;
|
|
import java.util.logging.Logger;
|
|
|
|
import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching;
|
|
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
|
|
|
/**
|
|
* Handler for executions of /search command
|
|
*/
|
|
public class SearchCommandExecutor extends ConfigurableCommandExecutor<SearchConfig> {
|
|
|
|
private static final Logger logger = Bukkit.getLogger();
|
|
|
|
// TODO: Move to config (magic values)
|
|
private static final String
|
|
ERR_NOT_PLAYER = "Command must be run by an in-game player",
|
|
ERR_NO_ARG = "Command expects an item or block name as an argument",
|
|
ERR_UNKNOWN = "Unknown item/block name \"%s\"",
|
|
ERR_NO_INVENTORIES = "No inventories could be found";
|
|
|
|
|
|
public SearchCommandExecutor(final Plugin plugin, final String path) {
|
|
super(plugin, path);
|
|
}
|
|
|
|
|
|
@Override
|
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
final Material targetMaterial;
|
|
if (assertTrue(sender instanceof Player, ERR_NOT_PLAYER, sender) ||
|
|
assertTrue(args.length == 1, ERR_NO_ARG, sender) ||
|
|
assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format(ERR_UNKNOWN, args[0]), sender)
|
|
) return true;
|
|
|
|
final Player player = (Player) sender;
|
|
|
|
final SearchConfig config = getConfig();
|
|
|
|
final List<BlockState> matches = searchChunks(
|
|
player.getLocation().getChunk(),
|
|
config.getSearchRadiusX(),
|
|
Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX
|
|
);
|
|
|
|
// Ensure we found inventory-holding blocks
|
|
if (assertTrue(matches.size() != 0, ERR_NO_INVENTORIES, sender))
|
|
return true;
|
|
|
|
final InventoryHolder result;
|
|
|
|
FIND_RESULT:
|
|
{
|
|
for (final BlockState check : matches) {
|
|
final InventoryHolder holder;
|
|
if (check instanceof Chest)
|
|
holder = Objects.requireNonNull(((Chest) check).getBlockInventory().getHolder()).getInventory().getHolder();
|
|
else if (check instanceof ShulkerBox)
|
|
holder = ((ShulkerBox) check).getInventory().getHolder();
|
|
else {
|
|
logger.info(InvTweaksPlugin.LOG_PLUGIN_NAME + " Found non-matching block");
|
|
continue;
|
|
}
|
|
|
|
assert holder != null;
|
|
|
|
if (holder instanceof DoubleChest) {
|
|
final InventoryHolder left = Objects.requireNonNull(((DoubleChest) holder).getLeftSide());
|
|
final InventoryHolder right = Objects.requireNonNull(((DoubleChest) holder).getRightSide());
|
|
|
|
if (left.getInventory().contains(targetMaterial) || right.getInventory().contains(targetMaterial)) {
|
|
result = holder;
|
|
break FIND_RESULT;
|
|
}
|
|
} else if (holder.getInventory().contains(targetMaterial)) {
|
|
result = holder;
|
|
break FIND_RESULT;
|
|
}
|
|
}
|
|
assertTrue(false, "Could not find inventory with target item/block", sender);
|
|
return true;
|
|
}
|
|
|
|
if (result instanceof DoubleChest) {
|
|
final DoubleChest dChest = (DoubleChest) result;
|
|
|
|
// Black magic to make chest lid animation behave for double chests
|
|
try {
|
|
final Field tileField = dChest.getInventory().getClass().getDeclaredField("tile");
|
|
tileField.setAccessible(true);
|
|
|
|
final Object tile = tileField.get(dChest.getInventory());
|
|
|
|
final Field tecField = tile.getClass().getDeclaredField("tileentitychest");
|
|
tecField.setAccessible(true);
|
|
|
|
final Field entityField = player.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("entity");
|
|
entityField.setAccessible(true);
|
|
|
|
final Object entity = entityField.get(player);
|
|
|
|
final Method openContainerMethod = entity.getClass().getDeclaredMethod("openContainer", tile.getClass().getInterfaces()[0]);
|
|
openContainerMethod.setAccessible(true);
|
|
|
|
openContainerMethod.invoke(entity, tile);
|
|
|
|
final Field activeContainerField = entity.getClass().getSuperclass().getDeclaredField("activeContainer");
|
|
activeContainerField.setAccessible(true);
|
|
|
|
final Object activeContainer = activeContainerField.get(entity);
|
|
|
|
final Field checkReachableField = activeContainer.getClass().getSuperclass().getDeclaredField("checkReachable");
|
|
checkReachableField.setAccessible(true);
|
|
|
|
// Disable reach checks for container while it is open
|
|
checkReachableField.set(activeContainer, false);
|
|
|
|
return true;
|
|
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
|
logger.fine(InvTweaksPlugin.LOG_PLUGIN_NAME + " Could not use internal openContainer method; chest lids may stay open");
|
|
}
|
|
}
|
|
|
|
// Default behaviour for non-double-chest inventories
|
|
// (plus fallback for double-chests on "unsupported" versions)
|
|
player.openInventory(result.getInventory());
|
|
|
|
return true;
|
|
}
|
|
|
|
private static List<BlockState> 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<Material> targetMaterials = Arrays.asList(targets);
|
|
final ArrayList<BlockState> 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<BlockState> searchBlocks(
|
|
final Location centre,
|
|
final World world,
|
|
final int rx,
|
|
final int ry,
|
|
final int rz,
|
|
final Material... targets
|
|
) {
|
|
if (targets.length == 0)
|
|
return Collections.emptyList();
|
|
|
|
final int x = centre.getBlockX(), y = centre.getBlockY(), z = centre.getBlockZ();
|
|
final ArrayList<BlockState> matches = new ArrayList<>();
|
|
for (int dx = -rx; dx < rx; ++dx)
|
|
for (int dy = -ry; dy < ry; ++dy)
|
|
CHECK_Z:
|
|
for (int dz = -rz; dz < rz; ++dz) {
|
|
final Block check = world.getBlockAt(x + dx, y + dy, z + dz);
|
|
final Material checkMaterial = check.getType();
|
|
for (Material target : targets)
|
|
if (target == checkMaterial) {
|
|
matches.add(check.getState());
|
|
continue CHECK_Z;
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
}
|