Intial commit
This commit is contained in:
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