Add force-remove command for Multiverse inventory groups
This commit is contained in:
11
README.md
11
README.md
@@ -28,12 +28,12 @@ silently replace that inventory.
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
The finished plugin is `target/AntiInventories-1.0.0.jar`.
|
||||
The finished plugin is `target/AntiInventories-1.1.0.jar`.
|
||||
|
||||
## Install
|
||||
|
||||
1. Back up the Multiverse-Inventories data directory.
|
||||
2. Put `AntiInventories-1.0.0.jar` in the server's `plugins` directory.
|
||||
2. Put `AntiInventories-1.1.0.jar` in the server's `plugins` directory.
|
||||
3. Make sure Multiverse-Core and Multiverse-Inventories are installed.
|
||||
4. Start the server.
|
||||
5. Edit `plugins/AntiInventories/config.yml`, or use the commands below.
|
||||
@@ -44,12 +44,19 @@ The finished plugin is `target/AntiInventories-1.0.0.jar`.
|
||||
| --- | --- |
|
||||
| `/antiinv add <world>` | Add a bypass world |
|
||||
| `/antiinv remove <world>` | Remove a bypass world |
|
||||
| `/antiinv forceremove <world>` | Remove the world from every explicit Multiverse-Inventories group and AntiInventories, then reload both plugins |
|
||||
| `/antiinv list` | Show bypass worlds and direction |
|
||||
| `/antiinv reload` | Reload `config.yml` |
|
||||
|
||||
The aliases `/antiinventories`, `/antiinv`, and `/ai` are available. Commands
|
||||
require `antiinventories.admin`, which defaults to server operators.
|
||||
|
||||
`/antiinv list` also shows a clickable **Force remove** control beside each
|
||||
bypass world. It places the force-removal command in chat for confirmation.
|
||||
If a world belongs to a Multiverse-Inventories group through a wildcard or
|
||||
regular expression, the command stops atomically and reports that pattern
|
||||
instead of changing it and potentially removing other worlds.
|
||||
|
||||
## Direction setting
|
||||
|
||||
`bypass-direction` controls which crossings are ignored:
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>dev.antiinventories</groupId>
|
||||
<artifactId>anti-inventories</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
<name>AntiInventories</name>
|
||||
<description>Bypass Multiverse-Inventories for selected worlds.</description>
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
@@ -15,14 +19,19 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
private static final List<String> SUBCOMMANDS = List.of("add", "remove", "list", "reload");
|
||||
private static final List<String> SUBCOMMANDS = List.of("add", "remove", "forceremove", "list", "reload");
|
||||
|
||||
private final AntiInventoriesPlugin plugin;
|
||||
private final BypassSettings settings;
|
||||
private final MultiverseInventoriesHook multiverseHook;
|
||||
|
||||
AntiInventoriesCommand(AntiInventoriesPlugin plugin, BypassSettings settings) {
|
||||
AntiInventoriesCommand(
|
||||
AntiInventoriesPlugin plugin,
|
||||
BypassSettings settings,
|
||||
MultiverseInventoriesHook multiverseHook) {
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
this.multiverseHook = multiverseHook;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,6 +48,7 @@ final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
return switch (args[0].toLowerCase(Locale.ROOT)) {
|
||||
case "add" -> addWorld(sender, label, args);
|
||||
case "remove" -> removeWorld(sender, label, args);
|
||||
case "forceremove" -> forceRemoveWorld(sender, label, args);
|
||||
case "list" -> listWorlds(sender);
|
||||
case "reload" -> reload(sender);
|
||||
default -> {
|
||||
@@ -84,6 +94,59 @@ final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean forceRemoveWorld(CommandSender sender, String label, String[] args) {
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(Component.text("Usage: /" + label + " forceremove <world>", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
String worldName = canonicalWorldName(args[1]);
|
||||
MultiverseInventoriesHook.ForceRemovalResult result;
|
||||
try {
|
||||
result = multiverseHook.forceRemoveWorld(worldName);
|
||||
} catch (ReflectiveOperationException | LinkageError exception) {
|
||||
plugin.getLogger().log(
|
||||
java.util.logging.Level.SEVERE,
|
||||
"Could not force-remove " + worldName + " from Multiverse-Inventories.",
|
||||
exception);
|
||||
sender.sendMessage(Component.text(
|
||||
"Force removal failed. Check the server console; no AntiInventories setting was changed.",
|
||||
NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!result.patternGroups().isEmpty()) {
|
||||
sender.sendMessage(Component.text(
|
||||
"Force removal stopped: " + worldName + " is matched by a wildcard or regex in group(s): "
|
||||
+ String.join(", ", result.patternGroups()) + ".",
|
||||
NamedTextColor.RED));
|
||||
sender.sendMessage(Component.text(
|
||||
"No AntiInventories or explicit Multiverse-Inventories setting was changed.",
|
||||
NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean removedFromAntiInventories = settings.remove(worldName);
|
||||
plugin.saveBypassWorlds();
|
||||
boolean multiverseReloaded = multiverseHook.reloadMultiverseInventories();
|
||||
plugin.reloadPluginConfig();
|
||||
|
||||
sender.sendMessage(Component.text("Force removal completed for " + worldName + ".", NamedTextColor.GREEN));
|
||||
sender.sendMessage(Component.text(
|
||||
"Multiverse groups removed: "
|
||||
+ (result.removedGroups().isEmpty() ? "(none)" : String.join(", ", result.removedGroups())),
|
||||
NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text(
|
||||
"Removed from AntiInventories: " + (removedFromAntiInventories ? "yes" : "not previously listed"),
|
||||
NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text(
|
||||
"Reloaded Multiverse-Inventories: " + (multiverseReloaded ? "yes" : "command was not available"),
|
||||
multiverseReloaded ? NamedTextColor.GREEN : NamedTextColor.RED));
|
||||
sender.sendMessage(Component.text("Reloaded AntiInventories: yes", NamedTextColor.GREEN));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean listWorlds(CommandSender sender) {
|
||||
List<String> worlds = settings.worlds();
|
||||
String formattedWorlds = worlds.isEmpty() ? "(none)" : String.join(", ", worlds);
|
||||
@@ -93,6 +156,15 @@ final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
sender.sendMessage(
|
||||
Component.text("Direction: ", NamedTextColor.GOLD)
|
||||
.append(Component.text(settings.direction().name(), NamedTextColor.WHITE)));
|
||||
for (String world : worlds) {
|
||||
sender.sendMessage(
|
||||
Component.text("• " + world + " ", NamedTextColor.WHITE)
|
||||
.append(Component.text("[Force remove]", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.suggestCommand("/antiinv forceremove " + world))
|
||||
.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"Remove from Multiverse-Inventories and AntiInventories, then reload both.",
|
||||
NamedTextColor.YELLOW)))));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -106,6 +178,7 @@ final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
sender.sendMessage(Component.text("AntiInventories commands:", NamedTextColor.GOLD));
|
||||
sender.sendMessage(Component.text("/" + label + " add <world>", NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text("/" + label + " remove <world>", NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text("/" + label + " forceremove <world>", NamedTextColor.RED));
|
||||
sender.sendMessage(Component.text("/" + label + " list", NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text("/" + label + " reload", NamedTextColor.YELLOW));
|
||||
}
|
||||
@@ -130,6 +203,11 @@ final class AntiInventoriesCommand implements CommandExecutor, TabCompleter {
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("remove")) {
|
||||
return filter(settings.worlds(), args[1]);
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("forceremove")) {
|
||||
Set<String> candidates = new LinkedHashSet<>(settings.worlds());
|
||||
Bukkit.getWorlds().stream().map(World::getName).forEach(candidates::add);
|
||||
return filter(List.copyOf(candidates), args[1]);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public final class AntiInventoriesPlugin extends JavaPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
AntiInventoriesCommand commandHandler = new AntiInventoriesCommand(this, settings);
|
||||
AntiInventoriesCommand commandHandler = new AntiInventoriesCommand(this, settings, hook);
|
||||
PluginCommand command = Objects.requireNonNull(
|
||||
getCommand("antiinventories"),
|
||||
"antiinventories command is missing from plugin.yml");
|
||||
|
||||
@@ -2,6 +2,11 @@ package dev.antiinventories;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
@@ -20,11 +25,16 @@ final class MultiverseInventoriesHook {
|
||||
"org.mvplugins.multiverse.inventories.event.ReadOnlyShareHandlingEvent";
|
||||
private static final String WRITE_ONLY_EVENT =
|
||||
"org.mvplugins.multiverse.inventories.event.WriteOnlyShareHandlingEvent";
|
||||
private static final String INVENTORIES_API =
|
||||
"org.mvplugins.multiverse.inventories.MultiverseInventoriesApi";
|
||||
private static final String WORLD_GROUP =
|
||||
"org.mvplugins.multiverse.inventories.profile.group.WorldGroup";
|
||||
|
||||
private final AntiInventoriesPlugin plugin;
|
||||
private final BypassSettings settings;
|
||||
private final Listener listener = new Listener() {
|
||||
};
|
||||
private Plugin inventories;
|
||||
|
||||
MultiverseInventoriesHook(AntiInventoriesPlugin plugin, BypassSettings settings) {
|
||||
this.plugin = plugin;
|
||||
@@ -33,7 +43,7 @@ final class MultiverseInventoriesHook {
|
||||
|
||||
boolean register() {
|
||||
PluginManager pluginManager = plugin.getServer().getPluginManager();
|
||||
Plugin inventories = pluginManager.getPlugin("Multiverse-Inventories");
|
||||
inventories = pluginManager.getPlugin("Multiverse-Inventories");
|
||||
if (inventories == null || !inventories.isEnabled()) {
|
||||
plugin.getLogger().severe("Multiverse-Inventories is not installed or enabled.");
|
||||
return false;
|
||||
@@ -54,6 +64,58 @@ final class MultiverseInventoriesHook {
|
||||
}
|
||||
}
|
||||
|
||||
ForceRemovalResult forceRemoveWorld(String worldName) throws ReflectiveOperationException {
|
||||
ClassLoader classLoader = inventories.getClass().getClassLoader();
|
||||
Class<?> apiClass = Class.forName(INVENTORIES_API, true, classLoader);
|
||||
Object api = apiClass.getMethod("get").invoke(null);
|
||||
Method getWorldGroupManager = apiClass.getMethod("getWorldGroupManager");
|
||||
Object groupManager = getWorldGroupManager.invoke(api);
|
||||
Class<?> groupManagerType = getWorldGroupManager.getReturnType();
|
||||
Method getGroupsForWorld = groupManagerType.getMethod("getGroupsForWorld", String.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> groups = new ArrayList<>((List<Object>) getGroupsForWorld.invoke(groupManager, worldName));
|
||||
Class<?> worldGroupType = Class.forName(WORLD_GROUP, true, classLoader);
|
||||
Method getName = worldGroupType.getMethod("getName");
|
||||
Method getConfigWorlds = worldGroupType.getMethod("getConfigWorlds");
|
||||
Method removeWorlds = worldGroupType.getMethod("removeWorlds", Collection.class);
|
||||
|
||||
List<String> removedGroups = new ArrayList<>();
|
||||
List<String> patternGroups = new ArrayList<>();
|
||||
List<Object> explicitGroups = new ArrayList<>();
|
||||
String normalizedWorld = worldName.toLowerCase(Locale.ROOT);
|
||||
|
||||
for (Object group : groups) {
|
||||
String groupName = (String) getName.invoke(group);
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> configuredWorlds = (Set<String>) getConfigWorlds.invoke(group);
|
||||
if (!configuredWorlds.contains(normalizedWorld)) {
|
||||
patternGroups.add(groupName);
|
||||
continue;
|
||||
}
|
||||
explicitGroups.add(group);
|
||||
}
|
||||
|
||||
if (!patternGroups.isEmpty()) {
|
||||
return new ForceRemovalResult(List.of(), List.copyOf(patternGroups));
|
||||
}
|
||||
|
||||
for (Object group : explicitGroups) {
|
||||
boolean removed = (boolean) removeWorlds.invoke(group, Set.of(worldName));
|
||||
if (removed) {
|
||||
removedGroups.add((String) getName.invoke(group));
|
||||
}
|
||||
}
|
||||
|
||||
return new ForceRemovalResult(List.copyOf(removedGroups), List.copyOf(patternGroups));
|
||||
}
|
||||
|
||||
boolean reloadMultiverseInventories() {
|
||||
return plugin.getServer().dispatchCommand(
|
||||
plugin.getServer().getConsoleSender(),
|
||||
"mvinv reload");
|
||||
}
|
||||
|
||||
private void registerWorldChangeHook(PluginManager manager, ClassLoader classLoader)
|
||||
throws ReflectiveOperationException {
|
||||
Class<? extends Event> eventClass = loadEventClass(WORLD_CHANGE_EVENT, classLoader);
|
||||
@@ -136,4 +198,7 @@ final class MultiverseInventoriesHook {
|
||||
private interface PlayerRule {
|
||||
boolean shouldBypass(Player player);
|
||||
}
|
||||
|
||||
record ForceRemovalResult(List<String> removedGroups, List<String> patternGroups) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: AntiInventories
|
||||
version: '1.0.0'
|
||||
version: '1.1.0'
|
||||
main: dev.antiinventories.AntiInventoriesPlugin
|
||||
description: Keeps the current inventory when players cross selected world borders.
|
||||
api-version: '1.21.11'
|
||||
@@ -10,7 +10,7 @@ depend:
|
||||
commands:
|
||||
antiinventories:
|
||||
description: Manage worlds that bypass Multiverse-Inventories.
|
||||
usage: /antiinventories <add|remove|list|reload> [world]
|
||||
usage: /antiinventories <add|remove|forceremove|list|reload> [world]
|
||||
aliases:
|
||||
- antiinv
|
||||
- ai
|
||||
@@ -20,4 +20,3 @@ permissions:
|
||||
antiinventories.admin:
|
||||
description: Allows management of AntiInventories.
|
||||
default: op
|
||||
|
||||
|
||||
@@ -50,6 +50,15 @@ class BypassSettingsTest {
|
||||
assertTrue(settings.shouldBypass("AutoMine", "Wild_Forest1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removesConfiguredWorldCaseInsensitively() {
|
||||
BypassSettings settings = settings(BypassDirection.EITHER, "AutoMine", "Wild_Forest1");
|
||||
|
||||
assertTrue(settings.remove("automine"));
|
||||
assertFalse(settings.contains("AutoMine"));
|
||||
assertTrue(settings.contains("Wild_Forest1"));
|
||||
}
|
||||
|
||||
private static BypassSettings settings(BypassDirection direction, String... worlds) {
|
||||
BypassSettings settings = new BypassSettings();
|
||||
settings.load(List.of(worlds), direction.name(), true, true);
|
||||
|
||||
Reference in New Issue
Block a user