diff --git a/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java b/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java index 866563f..807a14c 100644 --- a/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java +++ b/src/dev/w1zzrd/invtweaks/InvTweaksPlugin.java @@ -1,5 +1,6 @@ package dev.w1zzrd.invtweaks; +import dev.w1zzrd.invtweaks.command.MagnetCommandExecutor; import dev.w1zzrd.invtweaks.command.SortCommandExecutor; import dev.w1zzrd.invtweaks.listener.SortListener; import dev.w1zzrd.invtweaks.listener.StackReplaceListener; @@ -21,8 +22,16 @@ public final class InvTweaksPlugin extends JavaPlugin { */ public static final String LOG_PLUGIN_NAME = "[InventoryTweaks]"; + // TODO: Magic values: make a config + private static final double MAGNET_DISTANCE = 8.0; + private static final long MAGNET_INTERVAL = 5; + private static final int MAGNET_SUBDIVIDE = 2; + private final Logger logger = Bukkit.getLogger(); + // Command executor references in case I need them or something idk + private SortCommandExecutor sortCommandExecutor; + private MagnetCommandExecutor magnetCommandExecutor; @Override public void onEnable() { @@ -36,8 +45,8 @@ public final class InvTweaksPlugin extends JavaPlugin { public void onDisable() { logger.fine(LOG_PLUGIN_NAME + " Plugin disabled"); - // Un-register all listeners - HandlerList.unregisterAll(this); + disableEvents(); + disableCommands(); } @@ -45,7 +54,12 @@ public final class InvTweaksPlugin extends JavaPlugin { * Initialize commands registered by this plugin */ private void initCommands() { - Objects.requireNonNull(getCommand("sort")).setExecutor(new SortCommandExecutor()); + sortCommandExecutor = new SortCommandExecutor(); + magnetCommandExecutor = new MagnetCommandExecutor(this, MAGNET_DISTANCE, MAGNET_INTERVAL, MAGNET_SUBDIVIDE); + + // TODO: Bind command by annotation + Objects.requireNonNull(getCommand("sort")).setExecutor(sortCommandExecutor); + Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor); } /** @@ -54,7 +68,24 @@ public final class InvTweaksPlugin extends JavaPlugin { private void initEvents() { final PluginManager pluginManager = getServer().getPluginManager(); + // TODO: Register listeners by annotation pluginManager.registerEvents(new StackReplaceListener(), this); pluginManager.registerEvents(new SortListener(), this); } + + /** + * Do whatever is necessary to disable commands and their execution + */ + private void disableCommands() { + sortCommandExecutor = null; + magnetCommandExecutor = null; + } + + /** + * Do whatever is necessary to disable event listeners + */ + private void disableEvents() { + // Un-register all listeners + HandlerList.unregisterAll(this); + } } diff --git a/src/dev/w1zzrd/invtweaks/command/MagnetCommandExecutor.java b/src/dev/w1zzrd/invtweaks/command/MagnetCommandExecutor.java new file mode 100644 index 0000000..e482af3 --- /dev/null +++ b/src/dev/w1zzrd/invtweaks/command/MagnetCommandExecutor.java @@ -0,0 +1,229 @@ +package dev.w1zzrd.invtweaks.command; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; + +public class MagnetCommandExecutor implements CommandExecutor { + + /** + * List of players with magnet mode active + */ + private final List activeMagnets = new ArrayList<>(); + private final List activeMagnetsView = Collections.unmodifiableList(activeMagnets); + + private final Plugin plugin; + private final long interval; + private final int subdivide; + + private final double sqRadius; + + + private int divIndex = 0; + + private BukkitTask refreshTask = null; + + /** + * Initialize the magnet executor and manger + * @param plugin Owner plugin for this executor + * @param sqRadius Radius of the cube to search for items in (half side length) + * @param interval Interval (in ticks) between magnetism checks for active magnets + * @param subdivide What fraction of the list of active magnets should be iterated over during a magnetism check. + * Set to 1 to check the whole list each iteration + */ + public MagnetCommandExecutor(final Plugin plugin, final double sqRadius, final long interval, final int subdivide) { + this.plugin = plugin; + this.sqRadius = sqRadius; + this.interval = interval; + this.subdivide = subdivide; + } + + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) + return false; + + toggleMagnet((Player) sender); + + return true; + } + + /** + * Toggle magnet state for the given player + * @param player Player to toggle state for + * @return True if, after this method call, the UUID is part of the list of active magnets, else false. + */ + public boolean toggleMagnet(final Player player) { + return toggleMagnet(player.getUniqueId()); + } + + /** + * Toggle magnet state for the given UUID + * @param uuid UUID to toggle state for + * @return True if, after this method call, the UUID is part of the list of active magnets, else false. + */ + public boolean toggleMagnet(final UUID uuid) { + try { + final int index = Collections.binarySearch(activeMagnets, uuid); + + // See JavaDoc: Collections.binarySearch + if (index < 0) { + activeMagnets.add(-(index + 1), uuid); + return true; + } else { + activeMagnets.remove(index); + return false; + } + } finally { + updateMagnetismTask(); + } + } + + + /** + * Get an list of the currently active magnets + * @return Unmodifiable list view of the active magnets + */ + public List getActiveMagnets() { + return activeMagnetsView; + } + + /** + * Remove a player from the list of active magnets + * @param player Player to remove from active magnets + * @return True if a call to this method changed the list of active magnets + */ + public boolean removeMagnet(final Player player) { + return removeMagnet(player.getUniqueId()); + } + + /** + * Remove a UUID from the list of active magnets + * @param uuid UUID to remove from active magnets + * @return True if a call to this method changed the list of active magnets + */ + public boolean removeMagnet(final UUID uuid) { + try { + final int index = Collections.binarySearch(activeMagnets, uuid); + if (index < 0) + return false; + + activeMagnets.remove(index); + return true; + } finally { + updateMagnetismTask(); + } + } + + /** + * Add a player to the list of active magnets + * @param player Player to mark as a magnet + * @return True if a call to this method changed the list of active magnets + */ + public boolean addMagnet(final Player player) { + return addMagnet(player.getUniqueId()); + } + + /** + * Add a UUID to the list of active magnets + * @param uuid UUID to mark as a magnet + * @return True if a call to this method changed the list of active magnets + */ + public boolean addMagnet(final UUID uuid) { + try { + final int index = Collections.binarySearch(activeMagnets, uuid); + + if (index < 0) { + activeMagnets.add(-(index + 1), uuid); + return true; + } + + return false; + } finally { + updateMagnetismTask(); + } + } + + /** + * Check if a given player is a magnet + * @param player Player to check magnet state for + * @return True if the given player is marked as a magnet + */ + public boolean isMagnet(final Player player) { + return isMagnet(player.getUniqueId()); + } + + /** + * Check if a given UUID is registered as a magnet + * @param uuid UUID of a player to check + * @return True if the given UUID is marked as a magnet + */ + public boolean isMagnet(final UUID uuid) { + return Collections.binarySearch(activeMagnets, uuid) >= 0; + } + + /** + * Check if a magnet refresh task needs to be registered for magnet players

+ * + * A refresh is created if there are active magnet players, there it no active refresh task and the plugin is + * enabled. The refresh task is cancelled and state reset if there are no active magnet players, or the plugin is + * disabled. + */ + private void updateMagnetismTask() { + if (refreshTask == null && activeMagnets.size() > 0 && plugin.isEnabled()) + refreshTask = Bukkit.getScheduler().runTaskTimer(plugin, this::taskApplyMagnetism, 0, interval); + else if (refreshTask != null && (activeMagnets.size() == 0 || !plugin.isEnabled())) { + Bukkit.getScheduler().cancelTask(refreshTask.getTaskId()); + refreshTask = null; + activeMagnets.clear(); + divIndex = 0; + } + } + + /** + * Task for teleporting items to magnet players + * + * @see MagnetCommandExecutor#updateMagnetismTask() + */ + private void taskApplyMagnetism() { + final int size = activeMagnets.size(); + + final List toRemove = new ArrayList<>(); + + // Iterate over a subdivision of the active magnets + for (int index = divIndex; index < size; index += subdivide) { + final UUID uuid = activeMagnets.get(index); + final Player player = Bukkit.getPlayer(uuid); + + // If the given magnet has logged out, remove the player after the loop + if (player == null) toRemove.add(uuid); + else { + final Location playerLocation = player.getLocation(); + + // Get all items near the player that can don't have a pickup delay and teleport them to the player + player.getWorld() + .getNearbyEntities(playerLocation, sqRadius, sqRadius, sqRadius) + .stream() + .filter(it -> it instanceof Item) + .filter(it -> ((Item) it).getPickupDelay() <= 0) + .forEach(it -> it.teleport(playerLocation)); + } + } + + // Remove logged-out players + toRemove.forEach(this::removeMagnet); + + // Update subdivision to check next iteration + divIndex = (divIndex + 1) % subdivide; + } +} diff --git a/src/plugin.yml b/src/plugin.yml index 84cc531..424204b 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -7,4 +7,8 @@ commands: sort: description: Sort chest you are looking at usage: / - permission: invtweaks.sort \ No newline at end of file + permission: invtweaks.sort + magnet: + description: Toggle item magnet mode for player + usage: / + permission: invtweaks.magnet \ No newline at end of file