Intial commit
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
target/
|
||||
.idea/
|
||||
*.iml
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# AntiInventories
|
||||
|
||||
AntiInventories is a small Paper 1.21.11 add-on for Multiverse-Inventories 5.
|
||||
It lets selected worlds keep the inventory a player already has instead of
|
||||
loading a different Multiverse-Inventories profile.
|
||||
|
||||
For example, with `AutoMine` configured:
|
||||
|
||||
```text
|
||||
Wild_Forest1 -> AutoMine
|
||||
```
|
||||
|
||||
The player arrives in `AutoMine` with the exact inventory they had in
|
||||
`Wild_Forest1`. Login, logout, and optional game-mode profile handling are also
|
||||
bypassed while the player is in a configured world, so reconnecting does not
|
||||
silently replace that inventory.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Paper 1.21.11
|
||||
- Java 21 or newer
|
||||
- Multiverse-Core 5
|
||||
- Multiverse-Inventories 5.3.1 or newer
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
The finished plugin is `target/AntiInventories-1.0.0.jar`.
|
||||
|
||||
## Install
|
||||
|
||||
1. Back up the Multiverse-Inventories data directory.
|
||||
2. Put `AntiInventories-1.0.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.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Purpose |
|
||||
| --- | --- |
|
||||
| `/antiinv add <world>` | Add a bypass world |
|
||||
| `/antiinv remove <world>` | Remove a bypass world |
|
||||
| `/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.
|
||||
|
||||
## Direction setting
|
||||
|
||||
`bypass-direction` controls which crossings are ignored:
|
||||
|
||||
- `ENTERING`: keep the current inventory only when entering a bypass world.
|
||||
- `LEAVING`: keep it only when leaving a bypass world.
|
||||
- `EITHER`: keep it when either side is a bypass world.
|
||||
|
||||
`EITHER` is the default. Be aware that it intentionally lets a bypass world act
|
||||
as a bridge between otherwise separated inventory groups. Use `ENTERING` if
|
||||
players should carry items into `AutoMine` but should load the destination
|
||||
profile when they leave it.
|
||||
|
||||
The plugin cancels Multiverse-Inventories' official share-handling event. It
|
||||
does not duplicate, serialize, clear, or restore item stacks itself. Because
|
||||
the whole Multiverse-Inventories transaction is skipped, other profile values
|
||||
such as experience, health, and hunger also remain unchanged on a bypassed
|
||||
crossing.
|
||||
60
pom.xml
Normal file
60
pom.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>dev.antiinventories</groupId>
|
||||
<artifactId>anti-inventories</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>AntiInventories</name>
|
||||
<description>Bypass Multiverse-Inventories for selected worlds.</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
|
||||
<junit.version>5.14.1</junit.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${paper.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>AntiInventories-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.4</version>
|
||||
<configuration>
|
||||
<useModulePath>false</useModulePath>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
146
src/main/java/dev/antiinventories/AntiInventoriesCommand.java
Normal file
146
src/main/java/dev/antiinventories/AntiInventoriesCommand.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
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 final AntiInventoriesPlugin plugin;
|
||||
private final BypassSettings settings;
|
||||
|
||||
AntiInventoriesCommand(AntiInventoriesPlugin plugin, BypassSettings settings) {
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
sendUsage(sender, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
return switch (args[0].toLowerCase(Locale.ROOT)) {
|
||||
case "add" -> addWorld(sender, label, args);
|
||||
case "remove" -> removeWorld(sender, label, args);
|
||||
case "list" -> listWorlds(sender);
|
||||
case "reload" -> reload(sender);
|
||||
default -> {
|
||||
sendUsage(sender, label);
|
||||
yield true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean addWorld(CommandSender sender, String label, String[] args) {
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(Component.text("Usage: /" + label + " add <world>", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
String worldName = canonicalWorldName(args[1]);
|
||||
if (!settings.add(worldName)) {
|
||||
sender.sendMessage(Component.text(worldName + " is already a bypass world.", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.saveBypassWorlds();
|
||||
sender.sendMessage(Component.text("Added " + worldName + " to the bypass worlds.", NamedTextColor.GREEN));
|
||||
if (Bukkit.getWorld(worldName) == null) {
|
||||
sender.sendMessage(Component.text("Warning: that world is not currently loaded.", NamedTextColor.YELLOW));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean removeWorld(CommandSender sender, String label, String[] args) {
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(Component.text("Usage: /" + label + " remove <world>", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!settings.remove(args[1])) {
|
||||
sender.sendMessage(Component.text(args[1] + " is not a bypass world.", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.saveBypassWorlds();
|
||||
sender.sendMessage(Component.text("Removed " + args[1] + " from the bypass worlds.", NamedTextColor.GREEN));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean listWorlds(CommandSender sender) {
|
||||
List<String> worlds = settings.worlds();
|
||||
String formattedWorlds = worlds.isEmpty() ? "(none)" : String.join(", ", worlds);
|
||||
sender.sendMessage(
|
||||
Component.text("Bypass worlds: ", NamedTextColor.GOLD)
|
||||
.append(Component.text(formattedWorlds, NamedTextColor.WHITE)));
|
||||
sender.sendMessage(
|
||||
Component.text("Direction: ", NamedTextColor.GOLD)
|
||||
.append(Component.text(settings.direction().name(), NamedTextColor.WHITE)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean reload(CommandSender sender) {
|
||||
plugin.reloadPluginConfig();
|
||||
sender.sendMessage(Component.text("AntiInventories configuration reloaded.", NamedTextColor.GREEN));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendUsage(CommandSender sender, String label) {
|
||||
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 + " list", NamedTextColor.YELLOW));
|
||||
sender.sendMessage(Component.text("/" + label + " reload", NamedTextColor.YELLOW));
|
||||
}
|
||||
|
||||
private static String canonicalWorldName(String input) {
|
||||
World world = Bukkit.getWorld(input);
|
||||
return world == null ? input : world.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String alias,
|
||||
@NotNull String[] args) {
|
||||
if (args.length == 1) {
|
||||
return filter(SUBCOMMANDS, args[0]);
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("add")) {
|
||||
return filter(Bukkit.getWorlds().stream().map(World::getName).toList(), args[1]);
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("remove")) {
|
||||
return filter(settings.worlds(), args[1]);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static List<String> filter(List<String> candidates, String prefix) {
|
||||
String normalizedPrefix = prefix.toLowerCase(Locale.ROOT);
|
||||
List<String> matches = new ArrayList<>();
|
||||
for (String candidate : candidates) {
|
||||
if (candidate.toLowerCase(Locale.ROOT).startsWith(normalizedPrefix)) {
|
||||
matches.add(candidate);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
53
src/main/java/dev/antiinventories/AntiInventoriesPlugin.java
Normal file
53
src/main/java/dev/antiinventories/AntiInventoriesPlugin.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class AntiInventoriesPlugin extends JavaPlugin {
|
||||
private final BypassSettings settings = new BypassSettings();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
reloadPluginConfig();
|
||||
|
||||
MultiverseInventoriesHook hook = new MultiverseInventoriesHook(this, settings);
|
||||
if (!hook.register()) {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
AntiInventoriesCommand commandHandler = new AntiInventoriesCommand(this, settings);
|
||||
PluginCommand command = Objects.requireNonNull(
|
||||
getCommand("antiinventories"),
|
||||
"antiinventories command is missing from plugin.yml");
|
||||
command.setExecutor(commandHandler);
|
||||
command.setTabCompleter(commandHandler);
|
||||
|
||||
getLogger().info(
|
||||
"Enabled with " + settings.worlds().size()
|
||||
+ " bypass world(s), direction " + settings.direction() + ".");
|
||||
}
|
||||
|
||||
void reloadPluginConfig() {
|
||||
reloadConfig();
|
||||
String configuredDirection = getConfig().getString("bypass-direction", "EITHER");
|
||||
BypassDirection parsedDirection = BypassDirection.parse(configuredDirection);
|
||||
if (!parsedDirection.name().equalsIgnoreCase(configuredDirection.trim())) {
|
||||
getLogger().warning(
|
||||
"Unknown bypass-direction '" + configuredDirection + "'; using EITHER.");
|
||||
}
|
||||
|
||||
settings.load(
|
||||
getConfig().getStringList("bypass-worlds"),
|
||||
configuredDirection,
|
||||
getConfig().getBoolean("bypass-gamemode-handling", true),
|
||||
getConfig().getBoolean("bypass-session-handling", true));
|
||||
}
|
||||
|
||||
void saveBypassWorlds() {
|
||||
getConfig().set("bypass-worlds", settings.worlds());
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
30
src/main/java/dev/antiinventories/BypassDirection.java
Normal file
30
src/main/java/dev/antiinventories/BypassDirection.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
enum BypassDirection {
|
||||
ENTERING,
|
||||
LEAVING,
|
||||
EITHER;
|
||||
|
||||
static BypassDirection parse(String value) {
|
||||
if (value == null) {
|
||||
return EITHER;
|
||||
}
|
||||
|
||||
try {
|
||||
return valueOf(value.trim().toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return EITHER;
|
||||
}
|
||||
}
|
||||
|
||||
boolean matches(boolean sourceIsBypassed, boolean destinationIsBypassed) {
|
||||
return switch (this) {
|
||||
case ENTERING -> destinationIsBypassed;
|
||||
case LEAVING -> sourceIsBypassed;
|
||||
case EITHER -> sourceIsBypassed || destinationIsBypassed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
70
src/main/java/dev/antiinventories/BypassSettings.java
Normal file
70
src/main/java/dev/antiinventories/BypassSettings.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
final class BypassSettings {
|
||||
private final Map<String, String> worlds = new LinkedHashMap<>();
|
||||
private BypassDirection direction = BypassDirection.EITHER;
|
||||
private boolean bypassGameModeHandling = true;
|
||||
private boolean bypassSessionHandling = true;
|
||||
|
||||
void load(
|
||||
Collection<String> configuredWorlds,
|
||||
String configuredDirection,
|
||||
boolean bypassGameMode,
|
||||
boolean bypassSession) {
|
||||
worlds.clear();
|
||||
configuredWorlds.forEach(this::add);
|
||||
direction = BypassDirection.parse(configuredDirection);
|
||||
bypassGameModeHandling = bypassGameMode;
|
||||
bypassSessionHandling = bypassSession;
|
||||
}
|
||||
|
||||
boolean add(String worldName) {
|
||||
String cleanName = clean(worldName);
|
||||
if (cleanName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return worlds.putIfAbsent(normalize(cleanName), cleanName) == null;
|
||||
}
|
||||
|
||||
boolean remove(String worldName) {
|
||||
return worlds.remove(normalize(clean(worldName))) != null;
|
||||
}
|
||||
|
||||
boolean contains(String worldName) {
|
||||
return worlds.containsKey(normalize(clean(worldName)));
|
||||
}
|
||||
|
||||
boolean shouldBypass(String fromWorld, String toWorld) {
|
||||
return direction.matches(contains(fromWorld), contains(toWorld));
|
||||
}
|
||||
|
||||
boolean shouldBypassGameMode(String currentWorld) {
|
||||
return bypassGameModeHandling && contains(currentWorld);
|
||||
}
|
||||
|
||||
boolean shouldBypassSession(String currentWorld) {
|
||||
return bypassSessionHandling && contains(currentWorld);
|
||||
}
|
||||
|
||||
List<String> worlds() {
|
||||
return List.copyOf(worlds.values());
|
||||
}
|
||||
|
||||
BypassDirection direction() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
private static String clean(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
return value.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
139
src/main/java/dev/antiinventories/MultiverseInventoriesHook.java
Normal file
139
src/main/java/dev/antiinventories/MultiverseInventoriesHook.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
final class MultiverseInventoriesHook {
|
||||
private static final String WORLD_CHANGE_EVENT =
|
||||
"org.mvplugins.multiverse.inventories.event.WorldChangeShareHandlingEvent";
|
||||
private static final String GAME_MODE_CHANGE_EVENT =
|
||||
"org.mvplugins.multiverse.inventories.event.GameModeChangeShareHandlingEvent";
|
||||
private static final String READ_ONLY_EVENT =
|
||||
"org.mvplugins.multiverse.inventories.event.ReadOnlyShareHandlingEvent";
|
||||
private static final String WRITE_ONLY_EVENT =
|
||||
"org.mvplugins.multiverse.inventories.event.WriteOnlyShareHandlingEvent";
|
||||
|
||||
private final AntiInventoriesPlugin plugin;
|
||||
private final BypassSettings settings;
|
||||
private final Listener listener = new Listener() {
|
||||
};
|
||||
|
||||
MultiverseInventoriesHook(AntiInventoriesPlugin plugin, BypassSettings settings) {
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
boolean register() {
|
||||
PluginManager pluginManager = plugin.getServer().getPluginManager();
|
||||
Plugin inventories = pluginManager.getPlugin("Multiverse-Inventories");
|
||||
if (inventories == null || !inventories.isEnabled()) {
|
||||
plugin.getLogger().severe("Multiverse-Inventories is not installed or enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
registerWorldChangeHook(pluginManager, inventories.getClass().getClassLoader());
|
||||
registerGameModeChangeHook(pluginManager, inventories.getClass().getClassLoader());
|
||||
registerSessionHook(pluginManager, inventories.getClass().getClassLoader(), READ_ONLY_EVENT);
|
||||
registerSessionHook(pluginManager, inventories.getClass().getClassLoader(), WRITE_ONLY_EVENT);
|
||||
return true;
|
||||
} catch (ReflectiveOperationException | LinkageError exception) {
|
||||
plugin.getLogger().log(
|
||||
Level.SEVERE,
|
||||
"This Multiverse-Inventories version does not expose the required share-handling events.",
|
||||
exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerWorldChangeHook(PluginManager manager, ClassLoader classLoader)
|
||||
throws ReflectiveOperationException {
|
||||
Class<? extends Event> eventClass = loadEventClass(WORLD_CHANGE_EVENT, classLoader);
|
||||
Method getFromWorld = eventClass.getMethod("getFromWorld");
|
||||
Method getToWorld = eventClass.getMethod("getToWorld");
|
||||
|
||||
manager.registerEvent(
|
||||
eventClass,
|
||||
listener,
|
||||
EventPriority.HIGHEST,
|
||||
(ignored, event) -> {
|
||||
try {
|
||||
String fromWorld = (String) getFromWorld.invoke(event);
|
||||
String toWorld = (String) getToWorld.invoke(event);
|
||||
if (settings.shouldBypass(fromWorld, toWorld)) {
|
||||
((Cancellable) event).setCancelled(true);
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException exception) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Could not process a Multiverse world change.", exception);
|
||||
}
|
||||
},
|
||||
plugin,
|
||||
false);
|
||||
}
|
||||
|
||||
private void registerGameModeChangeHook(PluginManager manager, ClassLoader classLoader)
|
||||
throws ReflectiveOperationException {
|
||||
Class<? extends Event> eventClass = loadEventClass(GAME_MODE_CHANGE_EVENT, classLoader);
|
||||
registerCurrentWorldHook(
|
||||
manager,
|
||||
eventClass,
|
||||
event -> settings.shouldBypassGameMode(event.getWorld().getName()),
|
||||
"game-mode");
|
||||
}
|
||||
|
||||
private void registerSessionHook(PluginManager manager, ClassLoader classLoader, String eventClassName)
|
||||
throws ReflectiveOperationException {
|
||||
Class<? extends Event> eventClass = loadEventClass(eventClassName, classLoader);
|
||||
registerCurrentWorldHook(
|
||||
manager,
|
||||
eventClass,
|
||||
player -> settings.shouldBypassSession(player.getWorld().getName()),
|
||||
"session");
|
||||
}
|
||||
|
||||
private void registerCurrentWorldHook(
|
||||
PluginManager manager,
|
||||
Class<? extends Event> eventClass,
|
||||
PlayerRule rule,
|
||||
String handlingName)
|
||||
throws NoSuchMethodException {
|
||||
Method getPlayer = eventClass.getMethod("getPlayer");
|
||||
manager.registerEvent(
|
||||
eventClass,
|
||||
listener,
|
||||
EventPriority.HIGHEST,
|
||||
(ignored, event) -> {
|
||||
try {
|
||||
Player player = (Player) getPlayer.invoke(event);
|
||||
if (rule.shouldBypass(player)) {
|
||||
((Cancellable) event).setCancelled(true);
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException exception) {
|
||||
plugin.getLogger().log(
|
||||
Level.SEVERE,
|
||||
"Could not process Multiverse " + handlingName + " handling.",
|
||||
exception);
|
||||
}
|
||||
},
|
||||
plugin,
|
||||
false);
|
||||
}
|
||||
|
||||
private static Class<? extends Event> loadEventClass(String className, ClassLoader classLoader)
|
||||
throws ClassNotFoundException {
|
||||
return Class.forName(className, true, classLoader).asSubclass(Event.class);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PlayerRule {
|
||||
boolean shouldBypass(Player player);
|
||||
}
|
||||
}
|
||||
18
src/main/resources/config.yml
Normal file
18
src/main/resources/config.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Worlds listed here bypass Multiverse-Inventories share handling.
|
||||
# World names are matched case-insensitively.
|
||||
bypass-worlds:
|
||||
- AutoMine
|
||||
|
||||
# When a bypass world should make a world change keep the current inventory:
|
||||
# ENTERING - only when the destination is listed
|
||||
# LEAVING - only when the source is listed
|
||||
# EITHER - when the source or destination is listed
|
||||
bypass-direction: EITHER
|
||||
|
||||
# If Multiverse-Inventories separates data by game mode, also prevent a
|
||||
# game-mode inventory swap while a player is inside a bypass world.
|
||||
bypass-gamemode-handling: true
|
||||
|
||||
# Prevent Multiverse-Inventories from loading or saving one of its profiles
|
||||
# when a player joins or leaves the server inside a bypass world.
|
||||
bypass-session-handling: true
|
||||
23
src/main/resources/plugin.yml
Normal file
23
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
name: AntiInventories
|
||||
version: '1.0.0'
|
||||
main: dev.antiinventories.AntiInventoriesPlugin
|
||||
description: Keeps the current inventory when players cross selected world borders.
|
||||
api-version: '1.21.11'
|
||||
author: AntiInventories
|
||||
depend:
|
||||
- Multiverse-Inventories
|
||||
|
||||
commands:
|
||||
antiinventories:
|
||||
description: Manage worlds that bypass Multiverse-Inventories.
|
||||
usage: /antiinventories <add|remove|list|reload> [world]
|
||||
aliases:
|
||||
- antiinv
|
||||
- ai
|
||||
permission: antiinventories.admin
|
||||
|
||||
permissions:
|
||||
antiinventories.admin:
|
||||
description: Allows management of AntiInventories.
|
||||
default: op
|
||||
|
||||
58
src/test/java/dev/antiinventories/BypassSettingsTest.java
Normal file
58
src/test/java/dev/antiinventories/BypassSettingsTest.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package dev.antiinventories;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class BypassSettingsTest {
|
||||
@Test
|
||||
void matchesWorldNamesCaseInsensitively() {
|
||||
BypassSettings settings = settings(BypassDirection.EITHER, "AutoMine");
|
||||
|
||||
assertTrue(settings.contains("automine"));
|
||||
assertTrue(settings.contains("AUTOMINE"));
|
||||
assertFalse(settings.contains("Wild_Forest1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void eitherDirectionBypassesEnteringAndLeaving() {
|
||||
BypassSettings settings = settings(BypassDirection.EITHER, "AutoMine");
|
||||
|
||||
assertTrue(settings.shouldBypass("Wild_Forest1", "AutoMine"));
|
||||
assertTrue(settings.shouldBypass("AutoMine", "Wild_Forest1"));
|
||||
assertFalse(settings.shouldBypass("Wild_Forest1", "Lobby"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enteringDirectionOnlyChecksDestination() {
|
||||
BypassSettings settings = settings(BypassDirection.ENTERING, "AutoMine");
|
||||
|
||||
assertTrue(settings.shouldBypass("Wild_Forest1", "AutoMine"));
|
||||
assertFalse(settings.shouldBypass("AutoMine", "Wild_Forest1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void leavingDirectionOnlyChecksSource() {
|
||||
BypassSettings settings = settings(BypassDirection.LEAVING, "AutoMine");
|
||||
|
||||
assertFalse(settings.shouldBypass("Wild_Forest1", "AutoMine"));
|
||||
assertTrue(settings.shouldBypass("AutoMine", "Wild_Forest1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidDirectionFallsBackToEither() {
|
||||
BypassSettings settings = new BypassSettings();
|
||||
settings.load(List.of("AutoMine"), "invalid", true, true);
|
||||
|
||||
assertTrue(settings.shouldBypass("Wild_Forest1", "AutoMine"));
|
||||
assertTrue(settings.shouldBypass("AutoMine", "Wild_Forest1"));
|
||||
}
|
||||
|
||||
private static BypassSettings settings(BypassDirection direction, String... worlds) {
|
||||
BypassSettings settings = new BypassSettings();
|
||||
settings.load(List.of(worlds), direction.name(), true, true);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user