Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
278c51d056 | ||
|
|
caed081f82 | ||
|
|
7276da7665 | ||
|
|
90b8729129 | ||
|
|
c24f198ed0 | ||
|
|
0c37c9e934 | ||
|
|
454bcd36e5 | ||
|
|
af7d5a8cb0 | ||
|
|
a44361f5e4 | ||
|
|
8d508358f7 | ||
|
|
7c6d7d5883 | ||
|
|
c78dbcd395 | ||
|
|
b2c1aafb62 | ||
|
|
d08e6fae38 | ||
|
|
d6ccbd9eee | ||
|
|
d880ea95ed | ||
|
|
38aa2acf8e | ||
|
|
5845fdd541 | ||
|
|
01cfcc5684 | ||
|
|
62a4e7e27d | ||
|
|
ac174dcf66 | ||
|
|
4095829eca | ||
|
|
13dc1bd0d0 | ||
|
|
3a6c52337f | ||
|
|
61e3ffb7e5 | ||
|
|
41f9989eef | ||
|
|
896524b57a | ||
|
|
42ca91d05b | ||
|
|
5db5b948cc | ||
|
|
2e5679491c | ||
|
|
980d6784df | ||
|
|
a2f6e201d0 | ||
|
|
f2e193eda3 | ||
|
|
4084cd4af1 | ||
|
|
6fe87fbc7b | ||
|
|
6611ec4457 | ||
|
|
e4bff69372 | ||
|
|
5dba08dd1b | ||
|
|
2e0ce517a8 | ||
|
|
7fcfa64538 | ||
|
|
cce041877f | ||
|
|
6ba51fa573 | ||
|
|
bbfc1a05e8 | ||
|
|
80fcf8fe19 | ||
|
|
b99de26deb | ||
|
|
fb240a19fa | ||
|
|
180680f297 | ||
|
|
91f9842b24 | ||
|
|
bf8244e587 | ||
|
|
94bcca914f | ||
|
|
23b429940c | ||
|
|
3df46bc55c | ||
|
|
2f3306aa97 | ||
|
|
82c84c2c7c | ||
|
|
2642d940ba | ||
|
|
404ab738c0 | ||
|
|
bc1a2ff8a1 | ||
|
|
ba93195671 | ||
|
|
9ddfa59712 | ||
|
|
ef2b42d9a0 | ||
|
|
245fac98e5 |
No files matched your search
@@ -4,6 +4,7 @@ import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Minecart;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
|
||||
/**
|
||||
@@ -37,8 +38,35 @@ public interface BlockSafety {
|
||||
*/
|
||||
boolean playerCanSpawnHereSafely(Location l);
|
||||
|
||||
/**
|
||||
* Finds a portal-block next to the specified {@link Location}.
|
||||
*
|
||||
* @param location The {@link Location}
|
||||
* @return The next portal-block's {@link Location}.
|
||||
*/
|
||||
Location findPortalBlockNextTo(Location location);
|
||||
|
||||
/**
|
||||
* Gets the next safe location around the given location.
|
||||
*
|
||||
* @param location A {@link Location}.
|
||||
* @return A safe {@link Location}.
|
||||
*/
|
||||
@Nullable Location getSafeLocation(Location location);
|
||||
|
||||
/**
|
||||
* Gets the next safe location around the given location.
|
||||
*
|
||||
* @param location A {@link Location}.
|
||||
* @param tolerance The tolerance of how far up and down to search.
|
||||
* @param radius The radius around given location to search.
|
||||
* @return A safe {@link Location}.
|
||||
*/
|
||||
@Nullable Location getSafeLocation(Location location, int tolerance, int radius);
|
||||
|
||||
/**
|
||||
* Gets a safe bed spawn location OR null if the bed is invalid.
|
||||
*
|
||||
* @param l The location of the bead head (block with the pillow on it).
|
||||
* @return Safe location around the bed or null if no location was found.
|
||||
*/
|
||||
@@ -46,6 +74,7 @@ public interface BlockSafety {
|
||||
|
||||
/**
|
||||
* Gets the location of the top block at the specified {@link Location}.
|
||||
*
|
||||
* @param l Any {@link Location}.
|
||||
* @return The {@link Location} of the top-block.
|
||||
*/
|
||||
@@ -53,6 +82,7 @@ public interface BlockSafety {
|
||||
|
||||
/**
|
||||
* Gets the location of the top block at the specified {@link Location}.
|
||||
*
|
||||
* @param l Any {@link Location}.
|
||||
* @return The {@link Location} of the top-block.
|
||||
*/
|
||||
@@ -60,6 +90,7 @@ public interface BlockSafety {
|
||||
|
||||
/**
|
||||
* Checks if an entity would be on track at the specified {@link Location}.
|
||||
*
|
||||
* @param l The {@link Location}.
|
||||
* @return True if an entity would be on tracks at the specified {@link Location}.
|
||||
*/
|
||||
@@ -67,6 +98,7 @@ public interface BlockSafety {
|
||||
|
||||
/**
|
||||
* Checks if the specified {@link Minecart} can spawn safely.
|
||||
*
|
||||
* @param cart The {@link Minecart}.
|
||||
* @return True if the minecart can spawn safely.
|
||||
*/
|
||||
@@ -74,6 +106,7 @@ public interface BlockSafety {
|
||||
|
||||
/**
|
||||
* Checks if the specified {@link Vehicle} can spawn safely.
|
||||
*
|
||||
* @param vehicle The {@link Vehicle}.
|
||||
* @return True if the vehicle can spawn safely.
|
||||
*/
|
||||
|
||||
@@ -45,14 +45,4 @@ public interface Destination<T extends DestinationInstance> {
|
||||
* @return True if the SafeTeleporter will be used, false if not.
|
||||
*/
|
||||
boolean checkTeleportSafety();
|
||||
|
||||
/**
|
||||
* Returns the teleporter to use for this destination.
|
||||
*
|
||||
* <p>By default, Multiverse will automatically use SafeTeleporter. If you want to use a different teleporter, you can
|
||||
* override this method.</p>
|
||||
*
|
||||
* @return The custom teleporter to use for this destination. Return null to use the default teleporter.
|
||||
*/
|
||||
@Nullable Teleporter getTeleporter();
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.mvplugins.multiverse.core.api;
|
||||
import io.vavr.control.Try;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.placeholders.MultiverseCorePlaceholders;
|
||||
|
||||
@Contract
|
||||
@@ -27,30 +27,11 @@ public interface MVConfig {
|
||||
Try<Void> save();
|
||||
|
||||
/**
|
||||
* Gets the nodes for the config.
|
||||
* Gets the handler for managing config with string names and values.
|
||||
*
|
||||
* @return The nodes for the config.
|
||||
* @return The config handle for string properties.
|
||||
*/
|
||||
NodeGroup getNodes();
|
||||
|
||||
/**
|
||||
* Gets a property from the config.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @return A {@link Try} with the value of the property, otherwise a {@link Try.Failure} if there is no property by
|
||||
* that name.
|
||||
*/
|
||||
Try<Object> getProperty(String name);
|
||||
|
||||
/**
|
||||
* Sets a property in the config.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The value of the property.
|
||||
* @return An empty {@link Try} if the property was set successfully, otherwise a {@link Try.Failure} with the
|
||||
* exception explaining why the property could not be set.
|
||||
*/
|
||||
Try<Void> setProperty(String name, Object value);
|
||||
StringPropertyHandle getStringPropertyHandle();
|
||||
|
||||
/**
|
||||
* Sets world access permissions should be enforced.
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.api;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
|
||||
|
||||
/**
|
||||
* Used to safely teleport people.
|
||||
*/
|
||||
@Contract
|
||||
public interface SafeTTeleporter extends Teleporter {
|
||||
|
||||
/**
|
||||
* Gets the next safe location around the given location.
|
||||
* @param l A {@link Location}.
|
||||
* @return A safe {@link Location}.
|
||||
*/
|
||||
Location getSafeLocation(Location l);
|
||||
|
||||
/**
|
||||
* Gets the next safe location around the given location.
|
||||
* @param l A {@link Location}.
|
||||
* @param tolerance The tolerance.
|
||||
* @param radius The radius.
|
||||
* @return A safe {@link Location}.
|
||||
*/
|
||||
Location getSafeLocation(Location l, int tolerance, int radius);
|
||||
|
||||
/**
|
||||
* Safely teleport the entity to the MVDestination. This will perform checks to see if the place is safe, and if
|
||||
* it's not, will adjust the final destination accordingly.
|
||||
*
|
||||
* @param teleporter Person who performed the teleport command.
|
||||
* @param teleportee Entity to teleport
|
||||
* @param destination Destination to teleport them to
|
||||
* @return true for success, false for failure
|
||||
*/
|
||||
@Deprecated
|
||||
TeleportResult safelyTeleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination);
|
||||
|
||||
/**
|
||||
* Safely teleport the entity to the MVDestination. This will perform checks to see if the place is safe, and if
|
||||
* it's not, will adjust the final destination accordingly.
|
||||
*
|
||||
* @param teleporter Person who performed the teleport command.
|
||||
* @param teleportee Entity to teleport
|
||||
* @param destination Destination to teleport them to
|
||||
* @return true for success, false for failure
|
||||
*/
|
||||
CompletableFuture<TeleportResult> safelyTeleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination);
|
||||
|
||||
/**
|
||||
* Safely teleport the entity to the Location. This may perform checks to
|
||||
* see if the place is safe, and if
|
||||
* it's not, will adjust the final destination accordingly.
|
||||
*
|
||||
* @param teleporter Person who issued the teleport command.
|
||||
* @param teleportee Entity to teleport.
|
||||
* @param location Location to teleport them to.
|
||||
* @param safely Should the destination be checked for safety before teleport?
|
||||
* @return true for success, false for failure.
|
||||
*/
|
||||
TeleportResult safelyTeleport(CommandSender teleporter, Entity teleportee, Location location,
|
||||
boolean safely);
|
||||
|
||||
/**
|
||||
* Returns a safe location for the entity to spawn at.
|
||||
*
|
||||
* @param entity The entity to spawn
|
||||
* @param destination The MVDestination to take the entity to.
|
||||
* @return A new location to spawn the entity at.
|
||||
*/
|
||||
Location getSafeLocation(Entity entity, DestinationInstance destination);
|
||||
|
||||
/**
|
||||
* Finds a portal-block next to the specified {@link Location}.
|
||||
* @param l The {@link Location}
|
||||
* @return The next portal-block's {@link Location}.
|
||||
*/
|
||||
Location findPortalBlockNextTo(Location l);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.api;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
|
||||
|
||||
@Contract
|
||||
public interface Teleporter {
|
||||
/**
|
||||
* Teleport the entity to the Multiverse Destination.
|
||||
*
|
||||
* @param teleporter Person who performed the teleport command.
|
||||
* @param teleportee Entity to teleport
|
||||
* @param destination Destination to teleport them to
|
||||
* @return true for success, false for failure
|
||||
*/
|
||||
@Deprecated
|
||||
TeleportResult teleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination);
|
||||
|
||||
/**
|
||||
* Teleport the entity to the Multiverse Destination.
|
||||
*
|
||||
* @param teleporter Person who performed the teleport command.
|
||||
* @param teleportee Entity to teleport
|
||||
* @param destination Destination to teleport them to
|
||||
* @return true for success, false for failure
|
||||
*/
|
||||
CompletableFuture<TeleportResult> teleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination);
|
||||
}
|
||||
@@ -49,6 +49,6 @@ class CheckCommand extends MultiverseCommand {
|
||||
"{player}", player.getName(),
|
||||
"{destination}", destination.toString());
|
||||
// TODO: More detailed output on permissions required.
|
||||
this.destinationsProvider.checkTeleportPermissions(issuer, player, destination);
|
||||
// this.destinationsProvider.checkTeleportPermissions(issuer, player, destination);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.context.MVConfigValue;
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
|
||||
@@ -33,35 +32,35 @@ class ConfigCommand extends MultiverseCommand {
|
||||
|
||||
@Subcommand("config")
|
||||
@CommandPermission("multiverse.core.config")
|
||||
@CommandCompletion("@mvconfigs")
|
||||
@Syntax("<name> [new-value]")
|
||||
@Description("") // TODO: Description
|
||||
@CommandCompletion("@mvconfigs @mvconfigvalues")
|
||||
@Syntax("<name> [value]")
|
||||
@Description("Show or set a config value.")
|
||||
void onConfigCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<name>")
|
||||
@Description("") // TODO: Description
|
||||
@Description("The name of the config to set or show.")
|
||||
String name,
|
||||
|
||||
@Optional
|
||||
@Syntax("[new-value]")
|
||||
@Description("") // TODO: Description
|
||||
MVConfigValue value) {
|
||||
@Syntax("[value]")
|
||||
@Description("The value to set the config to. If not specified, the current value will be shown.")
|
||||
String value) {
|
||||
if (value == null) {
|
||||
showConfigValue(issuer, name);
|
||||
return;
|
||||
}
|
||||
updateConfigValue(issuer, name, value.getValue());
|
||||
updateConfigValue(issuer, name, value);
|
||||
}
|
||||
|
||||
private void showConfigValue(MVCommandIssuer issuer, String name) {
|
||||
config.getProperty(name)
|
||||
config.getStringPropertyHandle().getProperty(name)
|
||||
.onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value))
|
||||
.onFailure(e -> issuer.sendMessage(e.getMessage()));
|
||||
}
|
||||
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, Object value) {
|
||||
config.setProperty(name, value)
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, String value) {
|
||||
config.getStringPropertyHandle().setPropertyString(name, value)
|
||||
.onSuccess(ignore -> {
|
||||
config.save();
|
||||
issuer.sendMessage("Successfully set " + name + " to " + value);
|
||||
|
||||
@@ -71,7 +71,7 @@ class CreateCommand extends MultiverseCommand {
|
||||
|
||||
@Subcommand("create")
|
||||
@CommandPermission("multiverse.core.create")
|
||||
@CommandCompletion("@empty @flags:groupName=mvcreatecommand")
|
||||
@CommandCompletion("@empty @environments @flags:groupName=mvcreatecommand")
|
||||
@Syntax("<name> <environment> --seed [seed] --generator [generator[:id]] --world-type [worldtype] --adjust-spawn --no-structures")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Conditions;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
@@ -17,48 +20,82 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class DeleteCommand extends MultiverseCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
|
||||
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
|
||||
.addAlias("-r")
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
DeleteCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
|
||||
DeleteCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
|
||||
super(commandManager);
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
}
|
||||
|
||||
@Subcommand("delete")
|
||||
@CommandPermission("multiverse.core.delete")
|
||||
@CommandCompletion("@mvworlds:scope=both")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=mvdeletecommand")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.delete.description}")
|
||||
void onDeleteCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Single
|
||||
@Conditions("worldname:scope=both")
|
||||
@Conditions("worldname:scope=loaded")
|
||||
@Syntax("<world>")
|
||||
@Description("The world you want to delete.")
|
||||
String worldName) {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flags) {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand(
|
||||
issuer.getIssuer(),
|
||||
() -> {
|
||||
issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", worldName);
|
||||
worldManager.deleteWorld(worldName)
|
||||
.onSuccess(deletedWorldName -> {
|
||||
Logging.fine("World delete success: " + deletedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, "{world}", deletedWorldName);
|
||||
}).onFailure(failure -> {
|
||||
Logging.fine("World delete failure: " + failure);
|
||||
issuer.sendError(failure.getFailureMessage());
|
||||
});
|
||||
}, this.commandManager.formatMessage(
|
||||
issuer, MessageType.INFO, MVCorei18n.DELETE_PROMPT, "{world}", worldName)));
|
||||
runDeleteCommand(issuer, world, parsedFlags);
|
||||
}, this.commandManager.formatMessage(issuer, MessageType.INFO, MVCorei18n.DELETE_PROMPT,
|
||||
"{world}", world.getName())));
|
||||
}
|
||||
|
||||
private void runDeleteCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", world.getName());
|
||||
|
||||
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
: Async.completedFuture(Collections.emptyList());
|
||||
|
||||
future.thenRun(() -> doWorldDeleting(issuer, world));
|
||||
}
|
||||
|
||||
private void doWorldDeleting(MVCommandIssuer issuer, LoadedMultiverseWorld world) {
|
||||
worldManager.deleteWorld(world)
|
||||
.onSuccess(deletedWorldName -> {
|
||||
Logging.fine("World delete success: " + deletedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, "{world}", deletedWorldName);
|
||||
}).onFailure(failure -> {
|
||||
Logging.fine("World delete failure: " + failure);
|
||||
issuer.sendError(failure.getFailureMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,96 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.context.GameRuleValue;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
@Subcommand("gamerule|rule|gamerules|rules")
|
||||
class GameruleCommand extends MultiverseCommand {
|
||||
|
||||
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
|
||||
.builder("--page", Integer.class)
|
||||
.addAlias("-p")
|
||||
.context(value -> {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidCommandArgument("Invalid page number: " + value);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
|
||||
.builder("--filter", ContentFilter.class)
|
||||
.addAlias("-f")
|
||||
.context(value -> {
|
||||
try {
|
||||
return RegexContentFilter.fromString(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidCommandArgument("Invalid filter: " + value);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
GameruleCommand(@NotNull MVCommandManager commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Subcommand("gamerule")
|
||||
@Subcommand("set")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules true|false|@range:1-10 @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> <Gamerule value> [World or *]")
|
||||
@Description("{@@mv-core.gamerule.description}")
|
||||
void onGameruleCommand(
|
||||
BukkitCommandIssuer issuer,
|
||||
@Description("{@@mv-core.gamerule.set.description}")
|
||||
void onGameruleSetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Description("{@@mv-core.gamerule.gamerule.description}")
|
||||
@Description("{@@mv-core.gamerule.set.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Syntax("<Value>")
|
||||
@Description("{@@mv-core.gamerule.value.description}")
|
||||
@Description("{@@mv-core.gamerule.set.value.description}")
|
||||
GameRuleValue gameRuleValue,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.world.description}")
|
||||
@Description("{@@mv-core.gamerule.set.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
Object value = gameRuleValue.getValue();
|
||||
boolean success = true;
|
||||
@@ -55,7 +98,7 @@ class GameruleCommand extends MultiverseCommand {
|
||||
// Set gamerules and add false to list if it fails
|
||||
World bukkitWorld = world.getBukkitWorld().getOrNull();
|
||||
if (bukkitWorld == null || !bukkitWorld.setGameRule(gamerule, value)) {
|
||||
issuer.sendError(MVCorei18n.GAMERULE_FAILED,
|
||||
issuer.sendError(MVCorei18n.GAMERULE_SET_FAILED,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{value}", value.toString(),
|
||||
"{world}", world.getName(),
|
||||
@@ -66,16 +109,115 @@ class GameruleCommand extends MultiverseCommand {
|
||||
// Tell user if it was successful
|
||||
if (success) {
|
||||
if (worlds.length == 1) {
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_SUCCESS_SINGLE,
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_SET_SUCCESS_SINGLE,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{value}", value.toString(),
|
||||
"{world}", worlds[0].getName());
|
||||
} else if (worlds.length > 1) {
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_SUCCESS_MULTIPLE,
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_SET_SUCCESS_MULTIPLE,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{value}", value.toString(),
|
||||
"{count}", String.valueOf(worlds.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subcommand("reset")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> [World or *]")
|
||||
@Description("{@@mv-core.gamerule.reset.description}")
|
||||
void onGameruleSetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Description("{@@mv-core.gamerule.reset.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.reset.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
AtomicBoolean success = new AtomicBoolean(true);
|
||||
Arrays.stream(worlds).forEach(world -> world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setGameRule(gamerule, bukkitWorld.getGameRuleDefault(gamerule));
|
||||
}).onEmpty(() -> {
|
||||
success.set(false);
|
||||
issuer.sendError(MVCorei18n.GAMERULE_RESET_FAILED,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{world}", world.getName());
|
||||
}));
|
||||
|
||||
// Tell user if it was successful
|
||||
if (success.get()) {
|
||||
if (worlds.length == 1) {
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_RESET_SUCCESS_SINGLE,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{world}", worlds[0].getName());
|
||||
} else if (worlds.length > 1) {
|
||||
issuer.sendInfo(MVCorei18n.GAMERULE_RESET_SUCCESS_MULTIPLE,
|
||||
"{gamerule}", gamerule.getName(),
|
||||
"{count}", String.valueOf(worlds.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subcommand("list")
|
||||
@CommandPermission("multiverse.core.gamerule.list")
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=mvgamerulecommand @flags:groupName=mvgamerulecommand")
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerule.list.description}")
|
||||
void onGameruleListCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.gamerule.list.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerule.list.description.page}")
|
||||
String[] flags) {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
ContentDisplay.create()
|
||||
.addContent(MapContentProvider.forContent(getGameRuleMap(world.getBukkitWorld().getOrNull())) // TODO: Handle null
|
||||
.withKeyColor(ChatColor.AQUA)
|
||||
.withValueColor(ChatColor.WHITE))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(this.getListTitle(issuer, world.getBukkitWorld().getOrNull()))
|
||||
.doPagination(true)
|
||||
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
|
||||
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the gamerules and their values for a given world.
|
||||
*
|
||||
* @param world The world to find gamerules for.
|
||||
* @return A map of the gamerules and their values
|
||||
*/
|
||||
private Map<String, String> getGameRuleMap(World world) {
|
||||
Map<String, String> gameRuleMap = new HashMap<>();
|
||||
|
||||
for (GameRule<?> gamerule : GameRule.values()) {
|
||||
Object gameruleValue = world.getGameRuleValue(gamerule);
|
||||
if (gameruleValue == null) {
|
||||
gameRuleMap.put(gamerule.getName(), "null");
|
||||
continue;
|
||||
}
|
||||
gameRuleMap.put(gamerule.getName(), gameruleValue.toString());
|
||||
}
|
||||
return gameRuleMap;
|
||||
}
|
||||
|
||||
private String getListTitle(CommandIssuer issuer, World world) {
|
||||
return this.commandManager.formatMessage(
|
||||
issuer,
|
||||
MessageType.INFO,
|
||||
MVCorei18n.GAMERULE_LIST_TITLE,
|
||||
"{world}", world.getName());
|
||||
}
|
||||
}
|
||||
+25
-53
@@ -1,23 +1,18 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -31,16 +26,18 @@ import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
|
||||
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
|
||||
/**
|
||||
* List all gamerules in your current or specified world.
|
||||
*/
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class GamerulesCommand extends MultiverseCommand {
|
||||
class GeneratorsCommand extends MultiverseCommand {
|
||||
|
||||
private final GeneratorProvider generatorProvider;
|
||||
|
||||
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
|
||||
.builder("--page", Integer.class)
|
||||
@@ -67,66 +64,41 @@ class GamerulesCommand extends MultiverseCommand {
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
GamerulesCommand(@NotNull MVCommandManager commandManager) {
|
||||
GeneratorsCommand(@NotNull MVCommandManager commandManager, @NotNull GeneratorProvider generatorProvider) {
|
||||
super(commandManager);
|
||||
this.generatorProvider = generatorProvider;
|
||||
}
|
||||
|
||||
@Subcommand("gamerules|rules")
|
||||
@CommandPermission("multiverse.core.gamerule.list")
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=mvgamerulescommand @flags:groupName=mvgamerulescommand")
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerules.description}")
|
||||
@Subcommand("generators|gens")
|
||||
@CommandPermission("multiverse.core.generator")
|
||||
@CommandCompletion("@flags:groupName=mvgeneratorscommand @flags:groupName=mvgeneratorscommand")
|
||||
@Syntax("")
|
||||
@Description("{@@mv-core.generators.description}")
|
||||
void onGamerulesCommand(
|
||||
@NotNull MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.gamerules.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerules.description.page}")
|
||||
@Description("{@@mv-core.generators.description.flags}")
|
||||
String[] flags) {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
// Get the generators loaded using the command suggestions
|
||||
List<String> generators = (List<String>) generatorProvider.suggestGeneratorString("");
|
||||
|
||||
// Tell the user if we cannot find any generator plugins, then abort
|
||||
if (generators.isEmpty()) {
|
||||
issuer.sendMessage(commandManager.formatMessage(issuer, MessageType.INFO, MVCorei18n.GENERATORS_EMPTY));
|
||||
return;
|
||||
}
|
||||
|
||||
ContentDisplay.create()
|
||||
.addContent(MapContentProvider.forContent(getGameRuleMap(world.getBukkitWorld().getOrNull())) // TODO: Handle null
|
||||
.withKeyColor(ChatColor.AQUA)
|
||||
.withValueColor(ChatColor.WHITE))
|
||||
.addContent(ListContentProvider.forContent(generators))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(this.getTitle(issuer, world.getBukkitWorld().getOrNull()))
|
||||
.withHeader("%s====[ Multiverse Generator List ]====", ChatColor.AQUA)
|
||||
.doPagination(true)
|
||||
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
|
||||
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the gamerules and their values for a given world.
|
||||
*
|
||||
* @param world The world to find gamerules for.
|
||||
* @return A map of the gamerules and their values
|
||||
*/
|
||||
private Map<String, String> getGameRuleMap(World world) {
|
||||
Map<String, String> gameRuleMap = new HashMap<>();
|
||||
|
||||
for (GameRule<?> gamerule : GameRule.values()) {
|
||||
Object gameruleValue = world.getGameRuleValue(gamerule);
|
||||
if (gameruleValue == null) {
|
||||
gameRuleMap.put(gamerule.getName(), "null");
|
||||
continue;
|
||||
}
|
||||
gameRuleMap.put(gamerule.getName(), gameruleValue.toString());
|
||||
}
|
||||
return gameRuleMap;
|
||||
}
|
||||
|
||||
private String getTitle(CommandIssuer issuer, World world) {
|
||||
return this.commandManager.formatMessage(
|
||||
issuer,
|
||||
MessageType.INFO,
|
||||
MVCorei18n.GAMERULES_TITLE,
|
||||
"{world}", world.getName());
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class ImportCommand extends MultiverseCommand {
|
||||
|
||||
@Subcommand("import")
|
||||
@CommandPermission("multiverse.core.import")
|
||||
@CommandCompletion("@mvworlds:scope=potential @flags:groupName=mvimportcommand")
|
||||
@CommandCompletion("@mvworlds:scope=potential @environments @flags:groupName=mvimportcommand")
|
||||
@Syntax("<name> <env> --generator [generator[:id]] --adjust-spawn")
|
||||
@Description("{@@mv-core.import.description")
|
||||
void onImportCommand(
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class InfoCommand extends MultiverseCommand {
|
||||
|
||||
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
|
||||
.builder("--page", Integer.class)
|
||||
.addAlias("-p")
|
||||
.context(value -> {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidCommandArgument("Invalid page number: " + value);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
|
||||
.builder("--filter", ContentFilter.class)
|
||||
.addAlias("-f")
|
||||
.context(value -> {
|
||||
try {
|
||||
return RegexContentFilter.fromString(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidCommandArgument("Invalid filter: " + value);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
private final LocationManipulation locationManipulation;
|
||||
private final MVEconomist economist;
|
||||
|
||||
@Inject
|
||||
InfoCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull LocationManipulation locationManipulation,
|
||||
@NotNull MVEconomist economist) {
|
||||
super(commandManager);
|
||||
this.locationManipulation = locationManipulation;
|
||||
this.economist = economist;
|
||||
}
|
||||
|
||||
// TODO: support info for unloaded worlds
|
||||
@Subcommand("info")
|
||||
@CommandPermission("multiverse.core.info")
|
||||
@CommandCompletion("@mvworlds:scope=both|@flags:groupName=mvinfocommand @flags:groupName=mvinfocommand")
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.info.description")
|
||||
public void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.info.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--page <page>]")
|
||||
@Description("{@@mv-core.info.description.page}")
|
||||
String[] flags) {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
ContentDisplay.create()
|
||||
.addContent(MapContentProvider.forContent(getInfo(world))
|
||||
.withKeyColor(ChatColor.AQUA)
|
||||
.withValueColor(ChatColor.WHITE))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(getTitle(world))
|
||||
.doPagination(true)
|
||||
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
|
||||
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
private String getTitle(MultiverseWorld world) {
|
||||
return "&a&l---- World Info: &f&l%s&a&l ----".formatted(world.getName());
|
||||
}
|
||||
|
||||
private Map<String, String> getInfo(LoadedMultiverseWorld world) {
|
||||
Map<String, String> outMap = new LinkedHashMap<>();
|
||||
|
||||
outMap.put("World Name", world.getName());
|
||||
outMap.put("World Alias", world.getAlias());
|
||||
outMap.put("World UID", world.getUID().toString());
|
||||
outMap.put("Game Mode: ", world.getGameMode().toString());
|
||||
outMap.put("Difficulty", world.getDifficulty().toString());
|
||||
outMap.put("Spawn Location", locationManipulation.strCoords(world.getSpawnLocation()));
|
||||
outMap.put("Seed", String.valueOf(world.getSeed()));
|
||||
getEntryFeeInfo(outMap, world); // Entry fee/reward
|
||||
outMap.put("Respawn World", world.getRespawnWorldName());
|
||||
outMap.put("World Type", world.getWorldType().get().toString());
|
||||
outMap.put("Generator", world.getGenerator());
|
||||
outMap.put("Generate Structures", world.canGenerateStructures().get().toString());
|
||||
outMap.put("World Scale", String.valueOf(world.getScale()));
|
||||
outMap.put("Weather Enabled", String.valueOf(world.getAllowWeather()));
|
||||
outMap.put("Hunger Depletes", String.valueOf(world.getHunger()));
|
||||
outMap.put("Keep Spawn In Memory", String.valueOf(world.getKeepSpawnInMemory()));
|
||||
outMap.put("PVP Enabled", String.valueOf(world.getPvp()));
|
||||
getAnimalSpawningInfo(outMap, world); // Animals that can spawn
|
||||
getMonsterSpawningInfo(outMap, world); // Monsters that can spawn
|
||||
|
||||
return outMap;
|
||||
}
|
||||
|
||||
private void getEntryFeeInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
|
||||
double price = world.getPrice();
|
||||
if (price == 0) {
|
||||
outMap.put("Entry Fee", "FREE!");
|
||||
} else if (price > 0) {
|
||||
outMap.put("Entry Fee", economist.formatPrice(-price, world.getCurrency()));
|
||||
} else if (price < 0) {
|
||||
outMap.put("Entry Reward", economist.formatPrice(price, world.getCurrency()));
|
||||
}
|
||||
}
|
||||
|
||||
private void getAnimalSpawningInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
|
||||
if (world.getSpawningAnimals()) {
|
||||
outMap.put("Spawning Animals", "ALL");
|
||||
} else {
|
||||
if (!world.getSpawningAnimalsExceptions().isEmpty()) {
|
||||
outMap.put("Spawning Animals", world.getSpawningAnimalsExceptions().toString());
|
||||
} else {
|
||||
outMap.put("Spawning Animals", "NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getMonsterSpawningInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
|
||||
if (world.getSpawningMonsters()) {
|
||||
outMap.put("Spawning Monsters", "ALL");
|
||||
} else {
|
||||
if (!world.getSpawningMonstersExceptions().isEmpty()) {
|
||||
outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString());
|
||||
} else {
|
||||
outMap.put("Spawning Monsters", "NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class ModifyCommand extends MultiverseCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
|
||||
@Inject
|
||||
ModifyCommand(@NotNull MVCommandManager commandManager, WorldManager worldManager) {
|
||||
super(commandManager);
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
@Subcommand("modify")
|
||||
@CommandPermission("multiverse.core.modify")
|
||||
@CommandCompletion("@mvworlds:scope=both @propsmodifyaction @mvworldpropsname @mvworldpropsvalue")
|
||||
@Syntax("[world] <set|add|remove|reset> <property> <value>")
|
||||
@Description("")
|
||||
void onModifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[world]")
|
||||
@Description("")
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<set|add|remove|reset>")
|
||||
@Description("")
|
||||
PropertyModifyAction action,
|
||||
|
||||
@Syntax("<property>")
|
||||
@Description("")
|
||||
String propertyName,
|
||||
|
||||
@Optional
|
||||
@Syntax("[value]")
|
||||
@Description("")
|
||||
String propertyValue) {
|
||||
StringPropertyHandle worldPropertyHandle = world.getStringPropertyHandle();
|
||||
worldPropertyHandle.modifyProperty(propertyName, propertyValue, action).onSuccess(ignore -> {
|
||||
issuer.sendMessage("Property %s set to %s for world %s.".formatted(
|
||||
propertyName,
|
||||
worldPropertyHandle.getProperty(propertyName).getOrNull(),
|
||||
world.getName()));
|
||||
worldManager.saveWorldsConfig();
|
||||
}).onFailure(exception -> {
|
||||
issuer.sendMessage("Failed to %s property %s to %s for world %s.".formatted(
|
||||
action.name().toLowerCase(),
|
||||
propertyName,
|
||||
propertyValue,
|
||||
world.getName()));
|
||||
issuer.sendMessage(exception.getMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import co.aikar.commands.MessageType;
|
||||
@@ -13,6 +14,7 @@ import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -24,8 +26,10 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
|
||||
|
||||
@Service
|
||||
@@ -33,6 +37,7 @@ import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
|
||||
class RegenCommand extends MultiverseCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
|
||||
private final CommandValueFlag<String> SEED_FLAG = flag(CommandValueFlag.builder("--seed", String.class)
|
||||
.addAlias("-s")
|
||||
@@ -51,10 +56,18 @@ class RegenCommand extends MultiverseCommand {
|
||||
.addAlias("-wb")
|
||||
.build());
|
||||
|
||||
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
|
||||
.addAlias("-r")
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
RegenCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
|
||||
RegenCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
|
||||
super(commandManager);
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
}
|
||||
|
||||
@Subcommand("regen")
|
||||
@@ -77,23 +90,43 @@ class RegenCommand extends MultiverseCommand {
|
||||
|
||||
this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand(
|
||||
issuer.getIssuer(),
|
||||
() -> {
|
||||
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName());
|
||||
worldManager.regenWorld(RegenWorldOptions.world(world)
|
||||
.randomSeed(parsedFlags.hasFlag(SEED_FLAG))
|
||||
.seed(parsedFlags.flagValue(SEED_FLAG))
|
||||
.keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG))
|
||||
.keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG))
|
||||
.keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG))
|
||||
).onSuccess(newWorld -> {
|
||||
Logging.fine("World regen success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, "{world}", newWorld.getName());
|
||||
}).onFailure(failure -> {
|
||||
Logging.fine("World regen failure: " + failure);
|
||||
issuer.sendError(failure.getFailureMessage());
|
||||
});
|
||||
},
|
||||
() -> runRegenCommand(issuer, world, parsedFlags),
|
||||
this.commandManager.formatMessage(
|
||||
issuer, MessageType.INFO, MVCorei18n.REGEN_PROMPT, "{world}", world.getName())));
|
||||
}
|
||||
|
||||
private void runRegenCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName());
|
||||
List<Player> worldPlayers = world.getPlayers().getOrElse(Collections.emptyList());
|
||||
|
||||
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
: Async.completedFuture(Collections.emptyList());
|
||||
|
||||
future.thenRun(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers));
|
||||
}
|
||||
|
||||
private void doWorldRegening(
|
||||
MVCommandIssuer issuer,
|
||||
LoadedMultiverseWorld world,
|
||||
ParsedCommandFlags parsedFlags,
|
||||
List<Player> worldPlayers) {
|
||||
RegenWorldOptions regenWorldOptions = RegenWorldOptions.world(world)
|
||||
.randomSeed(parsedFlags.hasFlag(SEED_FLAG))
|
||||
.seed(parsedFlags.flagValue(SEED_FLAG))
|
||||
.keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG))
|
||||
.keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG))
|
||||
.keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG));
|
||||
|
||||
worldManager.regenWorld(regenWorldOptions).onSuccess(newWorld -> {
|
||||
Logging.fine("World regen success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, "{world}", newWorld.getName());
|
||||
if (parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)) {
|
||||
playerWorldTeleporter.teleportPlayersToWorld(worldPlayers, newWorld);
|
||||
}
|
||||
}).onFailure(failure -> {
|
||||
Logging.fine("World regen failure: " + failure);
|
||||
issuer.sendError(failure.getFailureMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Conditions;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
@@ -16,19 +19,33 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class RemoveCommand extends MultiverseCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
|
||||
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
|
||||
.addAlias("-r")
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
RemoveCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
|
||||
RemoveCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
|
||||
super(commandManager);
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
}
|
||||
|
||||
@Subcommand("remove")
|
||||
@@ -40,14 +57,31 @@ class RemoveCommand extends MultiverseCommand {
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Single
|
||||
@Conditions("mvworlds:scope=both")
|
||||
@Conditions("mvworlds:scope=both @flags:groupName=mvremovecommand")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.remove.world.description}")
|
||||
String worldName) {
|
||||
worldManager.removeWorld(worldName)
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Description(/* TODO */"")
|
||||
String[] flags) {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
|
||||
? worldManager.getLoadedWorld(world)
|
||||
.map(playerWorldTeleporter::removeFromWorld)
|
||||
.getOrElse(Async.completedFuture(Collections.emptyList()))
|
||||
: Async.completedFuture(Collections.emptyList());
|
||||
|
||||
future.thenRun(() -> doWorldRemoving(issuer, world));
|
||||
}
|
||||
|
||||
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world) {
|
||||
worldManager.removeWorld(world)
|
||||
.onSuccess(removedWorldName -> {
|
||||
Logging.fine("World remove success: " + removedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.REMOVEWORLD_REMOVED, "{world}", removedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.REMOVE_SUCCESS, "{world}", removedWorldName);
|
||||
}).onFailure(failure -> {
|
||||
Logging.fine("World remove failure: " + failure);
|
||||
issuer.sendError(failure.getFailureMessage());
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.List;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
@@ -14,24 +12,32 @@ import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class TeleportCommand extends MultiverseCommand {
|
||||
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final CorePermissionsChecker permissionsChecker;
|
||||
private final AsyncSafetyTeleporter safetyTeleporter;
|
||||
|
||||
@Inject
|
||||
TeleportCommand(MVCommandManager commandManager, DestinationsProvider destinationsProvider) {
|
||||
TeleportCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull CorePermissionsChecker permissionsChecker,
|
||||
@NotNull AsyncSafetyTeleporter safetyTeleporter) {
|
||||
super(commandManager);
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.permissionsChecker = permissionsChecker;
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
}
|
||||
|
||||
@CommandAlias("mvtp")
|
||||
@@ -40,7 +46,7 @@ class TeleportCommand extends MultiverseCommand {
|
||||
@Syntax("[player] <destination>")
|
||||
@Description("{@@mv-core.teleport.description}")
|
||||
void onTeleportCommand(
|
||||
BukkitCommandIssuer issuer,
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[player]")
|
||||
@@ -56,17 +62,25 @@ class TeleportCommand extends MultiverseCommand {
|
||||
? issuer.getPlayer() == players[0] ? "you" : players[0].getName()
|
||||
: players.length + " players";
|
||||
|
||||
// TODO: Multi player permission checking
|
||||
if (!permissionsChecker.checkTeleportPermissions(issuer.getIssuer(), players[0], destination)) {
|
||||
issuer.sendMessage("You do not have teleport permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
"{player}", playerName, "{destination}", destination.toString());
|
||||
|
||||
CompletableFuture.allOf(Arrays.stream(players)
|
||||
.map(player -> this.destinationsProvider.playerTeleportAsync(issuer, player, destination))
|
||||
.toArray(CompletableFuture[]::new))
|
||||
.thenRun(() -> Logging.finer("Async teleport completed."));
|
||||
safetyTeleporter.teleportSafely(issuer.getIssuer(), List.of(players), destination)
|
||||
.thenAccept(attempts -> Logging.fine("Async teleport completed: %s", attempts))
|
||||
.exceptionally(throwable -> {
|
||||
Logging.severe("Error while teleporting %s to %s: %s",
|
||||
playerName, destination, throwable.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandIssuer issuer) {
|
||||
return this.destinationsProvider.hasAnyTeleportPermission(issuer);
|
||||
return permissionsChecker.hasAnyTeleportPermission(issuer.getIssuer());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
@@ -18,8 +20,10 @@ import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
|
||||
@Service
|
||||
@@ -27,6 +31,7 @@ import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
class UnloadCommand extends MultiverseCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
|
||||
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
|
||||
.addAlias("-r")
|
||||
@@ -37,9 +42,13 @@ class UnloadCommand extends MultiverseCommand {
|
||||
.build());
|
||||
|
||||
@Inject
|
||||
UnloadCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
|
||||
UnloadCommand(
|
||||
@NotNull MVCommandManager commandManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
|
||||
super(commandManager);
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
}
|
||||
|
||||
@Subcommand("unload")
|
||||
@@ -61,8 +70,16 @@ class UnloadCommand extends MultiverseCommand {
|
||||
ParsedCommandFlags parsedFlags = parseFlags(flags);
|
||||
|
||||
issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, "{world}", world.getAlias());
|
||||
|
||||
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
: Async.completedFuture(Collections.emptyList());
|
||||
|
||||
future.thenRun(() -> doWorldUnloading(issuer, world, parsedFlags));
|
||||
}
|
||||
|
||||
private void doWorldUnloading(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
UnloadWorldOptions unloadWorldOptions = UnloadWorldOptions.world(world)
|
||||
.removePlayers(parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG))
|
||||
.saveBukkitWorld(!parsedFlags.hasFlag(NO_SAVE_FLAG));
|
||||
worldManager.unloadWorld(unloadWorldOptions)
|
||||
.onSuccess(loadedWorld -> {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.utils.MVCorei18n;
|
||||
|
||||
@Service
|
||||
@CommandAlias("mv")
|
||||
class VersionCommand extends MultiverseCommand {
|
||||
|
||||
private final MultiverseCore plugin;
|
||||
|
||||
@Inject
|
||||
VersionCommand(@NotNull MVCommandManager commandManager, MultiverseCore plugin) {
|
||||
super(commandManager);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Subcommand("version")
|
||||
@CommandPermission("multiverse.core.version")
|
||||
@Description("{@@mv-core.version.description}")
|
||||
void versionCommand(BukkitCommandIssuer issuer) {
|
||||
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_MV, "{version}", plugin.getDescription().getVersion());
|
||||
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_AUTHORS, "{authors}", String.join(", ", plugin.getDescription().getAuthors()));
|
||||
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_SECRETCODE); // An in joke I don't get...
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.commandtools;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -18,11 +19,15 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Difficulty;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
@@ -30,33 +35,44 @@ import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@Service
|
||||
public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
class MVCommandCompletions extends PaperCommandCompletions {
|
||||
|
||||
protected final MVCommandManager commandManager;
|
||||
private final MVCommandManager commandManager;
|
||||
private final WorldManager worldManager;
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final MVCoreConfig config;
|
||||
|
||||
@Inject
|
||||
public MVCommandCompletions(
|
||||
MVCommandCompletions(
|
||||
@NotNull MVCommandManager mvCommandManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull MVCoreConfig config
|
||||
) {
|
||||
@NotNull MVCoreConfig config) {
|
||||
super(mvCommandManager);
|
||||
this.commandManager = mvCommandManager;
|
||||
this.worldManager = worldManager;
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.config = config;
|
||||
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
registerStaticCompletion("mvconfigs", config.getNodes().getNames());
|
||||
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames());
|
||||
registerAsyncCompletion("mvconfigvalues", this::suggestMVConfigValues);
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
|
||||
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
|
||||
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
|
||||
|
||||
setDefaultCompletion("destinations", ParsedDestination.class);
|
||||
setDefaultCompletion("difficulties", Difficulty.class);
|
||||
setDefaultCompletion("environments", World.Environment.class);
|
||||
setDefaultCompletion("flags", String[].class);
|
||||
setDefaultCompletion("gamemodes", GameMode.class);
|
||||
setDefaultCompletion("gamerules", GameRule.class);
|
||||
setDefaultCompletion("mvworlds", LoadedMultiverseWorld.class);
|
||||
}
|
||||
@@ -94,7 +110,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
|
||||
return this.destinationsProvider
|
||||
.suggestDestinations((BukkitCommandIssuer)context.getIssuer(), context.getInput());
|
||||
.suggestDestinations((BukkitCommandIssuer) context.getIssuer(), context.getInput());
|
||||
}
|
||||
|
||||
private Collection<String> suggestFlags(@NotNull BukkitCommandCompletionContext context) {
|
||||
@@ -109,6 +125,13 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
return Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(String.class))
|
||||
.map(propertyName -> config.getStringPropertyHandle()
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET))
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorlds(BukkitCommandCompletionContext context) {
|
||||
if (context.hasConfig("playerOnly") && !context.getIssuer().isPlayer()) {
|
||||
return Collections.emptyList();
|
||||
@@ -152,4 +175,28 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
Logging.severe("Invalid MVWorld scope: " + scope);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
|
||||
return world.getStringPropertyHandle().getModifiablePropertyNames(action);
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
|
||||
String propertyName = context.getContextValue(String.class);
|
||||
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action);
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private <T extends Enum<T>> Collection<String> suggestEnums(Class<T> enumClass) {
|
||||
return EnumSet.allOf(enumClass).stream()
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,13 @@ import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.PaperCommandContexts;
|
||||
import co.aikar.commands.contexts.ContextResolver;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.commandtools.context.GameRuleValue;
|
||||
import org.mvplugins.multiverse.core.commandtools.context.MVConfigValue;
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.configuration.node.Node;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
@@ -27,10 +23,11 @@ import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@Service
|
||||
public class MVCommandContexts extends PaperCommandContexts {
|
||||
class MVCommandContexts extends PaperCommandContexts {
|
||||
|
||||
private final MVCommandManager mvCommandManager;
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
@@ -38,12 +35,11 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
private final MVCoreConfig config;
|
||||
|
||||
@Inject
|
||||
public MVCommandContexts(
|
||||
MVCommandContexts(
|
||||
MVCommandManager mvCommandManager,
|
||||
DestinationsProvider destinationsProvider,
|
||||
WorldManager worldManager,
|
||||
MVCoreConfig config
|
||||
) {
|
||||
MVCoreConfig config) {
|
||||
super(mvCommandManager);
|
||||
this.mvCommandManager = mvCommandManager;
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
@@ -56,9 +52,9 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
registerContext(ParsedDestination.class, this::parseDestination);
|
||||
registerContext(GameRule.class, this::parseGameRule);
|
||||
registerContext(GameRuleValue.class, this::parseGameRuleValue);
|
||||
registerContext(MVConfigValue.class, this::parseMVConfigValue);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseMVWorld);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseMVWorldArray);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseLoadedMultiverseWorld);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedMultiverseWorldArray);
|
||||
registerIssuerAwareContext(MultiverseWorld.class, this::parseMultiverseWorld);
|
||||
registerIssuerAwareContext(Player.class, this::parsePlayer);
|
||||
registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
|
||||
}
|
||||
@@ -129,40 +125,7 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
return new GameRuleValue(resolvedValue);
|
||||
}
|
||||
|
||||
private MVConfigValue parseMVConfigValue(BukkitCommandExecutionContext context) {
|
||||
String configName = (String) context.getResolvedArg(String.class);
|
||||
if (Strings.isNullOrEmpty(configName)) {
|
||||
throw new InvalidCommandArgument("No config name specified.");
|
||||
}
|
||||
Option<Node> node = config.getNodes().findNode(configName);
|
||||
if (node.isEmpty()) {
|
||||
throw new InvalidCommandArgument("The config " + configName + " is not valid.");
|
||||
}
|
||||
|
||||
String valueString = context.getFirstArg();
|
||||
if (Strings.isNullOrEmpty(valueString)) {
|
||||
throw new InvalidCommandArgument("No config value specified.");
|
||||
}
|
||||
|
||||
if (!(node.get() instanceof ValueNode)) {
|
||||
context.popFirstArg();
|
||||
return new MVConfigValue(valueString);
|
||||
}
|
||||
|
||||
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(((ValueNode<?>) node.get()).getType());
|
||||
if (resolver == null) {
|
||||
context.popFirstArg();
|
||||
return new MVConfigValue(valueString);
|
||||
}
|
||||
|
||||
Object resolvedValue = resolver.getContext(context);
|
||||
if (resolvedValue == null) {
|
||||
throw new InvalidCommandArgument("The config value " + valueString + " is not valid for config " + configName + ".");
|
||||
}
|
||||
return new MVConfigValue(resolvedValue);
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld parseMVWorld(BukkitCommandExecutionContext context) {
|
||||
private LoadedMultiverseWorld parseLoadedMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
@@ -205,7 +168,7 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld[] parseMVWorldArray(BukkitCommandExecutionContext context) {
|
||||
private LoadedMultiverseWorld[] parseLoadedMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
LoadedMultiverseWorld playerWorld = null;
|
||||
@@ -265,6 +228,49 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
throw new InvalidCommandArgument("World " + worldStrings + " is not a loaded multiverse world.");
|
||||
}
|
||||
|
||||
private MultiverseWorld parseMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldName = context.getFirstArg();
|
||||
MultiverseWorld world = worldManager.getWorld(worldName).getOrNull();
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Player is not in a Multiverse World.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
|
||||
private Player parsePlayer(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.commandtools;
|
||||
|
||||
import co.aikar.commands.MessageKeys;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.commands.OpenBukkitCommandIssuer;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
@@ -7,6 +8,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.utils.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.message.MessageReplacement;
|
||||
|
||||
public class MVCommandIssuer extends OpenBukkitCommandIssuer {
|
||||
|
||||
@@ -22,6 +24,58 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
public void sendError(String message) {
|
||||
sendMessage(MessageType.INFO, MessageKeys.INFO_MESSAGE, "{message}", message);
|
||||
}
|
||||
|
||||
public void sendSyntax(String message) {
|
||||
sendMessage(MessageType.SYNTAX, MessageKeys.INFO_MESSAGE, "{message}", message);
|
||||
}
|
||||
|
||||
public void sendInfo(String message) {
|
||||
sendMessage(MessageType.INFO, MessageKeys.INFO_MESSAGE, "{message}", message);
|
||||
}
|
||||
|
||||
public void sendMessage(MessageType messageType, String message) {
|
||||
var formatter = getManager().getFormat(messageType);
|
||||
if (formatter != null) {
|
||||
sendMessage(formatter.format(message));
|
||||
} else {
|
||||
sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendError(MessageKeyProvider key) {
|
||||
sendMessage(MessageType.ERROR, key, new String[0]);
|
||||
}
|
||||
|
||||
public void sendSyntax(MessageKeyProvider key) {
|
||||
sendMessage(MessageType.SYNTAX, key, new String[0]);
|
||||
}
|
||||
|
||||
public void sendInfo(MessageKeyProvider key) {
|
||||
sendMessage(MessageType.INFO, key, new String[0]);
|
||||
}
|
||||
|
||||
public void sendError(MessageKeyProvider key, MessageReplacement... replacements) {
|
||||
sendMessage(MessageType.ERROR, key, replacements);
|
||||
}
|
||||
|
||||
public void sendSyntax(MessageKeyProvider key, MessageReplacement... replacements) {
|
||||
sendMessage(MessageType.SYNTAX, key, replacements);
|
||||
}
|
||||
|
||||
public void sendInfo(MessageKeyProvider key, MessageReplacement... replacements) {
|
||||
sendMessage(MessageType.INFO, key, replacements);
|
||||
}
|
||||
|
||||
private void sendMessage(MessageType messageType, MessageKeyProvider key, MessageReplacement... replacements) {
|
||||
sendMessage(messageType, Message.of(
|
||||
key,
|
||||
"{error_key: %s}".formatted(key.getMessageKey().getKey()),
|
||||
replacements));
|
||||
}
|
||||
|
||||
public void sendError(Message message) {
|
||||
sendMessage(MessageType.ERROR, message);
|
||||
}
|
||||
@@ -34,7 +88,7 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
|
||||
sendMessage(MessageType.INFO, message);
|
||||
}
|
||||
|
||||
private void sendMessage(MessageType messageType, Message message) {
|
||||
public void sendMessage(MessageType messageType, Message message) {
|
||||
if (message instanceof MessageKeyProvider) {
|
||||
sendMessage(messageType, (MessageKeyProvider) message,
|
||||
message.getReplacements(getManager().getLocales(), this));
|
||||
|
||||
@@ -11,15 +11,26 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandFlagGroup;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
|
||||
|
||||
/**
|
||||
* Base class for all Multiverse commands.
|
||||
*/
|
||||
@Contract
|
||||
public abstract class MultiverseCommand extends BaseCommand {
|
||||
public abstract class MultiverseCommand extends BaseCommand {
|
||||
|
||||
/**
|
||||
* The command manager with multiverse specific methods.
|
||||
*/
|
||||
protected final MVCommandManager commandManager;
|
||||
/**
|
||||
* The flags manager for the above command manager.
|
||||
*/
|
||||
protected final CommandFlagsManager flagsManager;
|
||||
private String flagGroupName;
|
||||
private CommandFlagGroup.Builder flagGroupBuilder;
|
||||
|
||||
protected MultiverseCommand(@NotNull MVCommandManager commandManager) {
|
||||
this.commandManager = commandManager;
|
||||
this.flagsManager = commandManager.getFlagsManager();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@@ -30,10 +41,22 @@ public abstract class MultiverseCommand extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
protected CommandFlagsManager getFlagsManager() {
|
||||
return commandManager.getFlagsManager();
|
||||
private void registerFlagGroup(@NotNull CommandFlagGroup flagGroup) {
|
||||
if (flagGroupName != null) {
|
||||
throw new IllegalStateException("Flag group already registered! (name: " + flagGroupName + ")");
|
||||
}
|
||||
flagsManager.registerFlagGroup(flagGroup);
|
||||
flagGroupName = flagGroup.getName();
|
||||
Logging.finest("Registered flag group: " + flagGroupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new flag to the flag builder.
|
||||
*
|
||||
* @param flag The flag to add.
|
||||
* @param <T> The type of the flag.
|
||||
* @return The flag.
|
||||
*/
|
||||
protected <T extends CommandFlag> T flag(T flag) {
|
||||
if (flagGroupBuilder == null) {
|
||||
flagGroupBuilder = CommandFlagGroup.builder("mv" + getClass().getSimpleName().toLowerCase());
|
||||
@@ -43,16 +66,13 @@ public abstract class MultiverseCommand extends BaseCommand {
|
||||
return flag;
|
||||
}
|
||||
|
||||
protected void registerFlagGroup(@NotNull CommandFlagGroup flagGroup) {
|
||||
if (flagGroupName != null) {
|
||||
throw new IllegalStateException("Flag group already registered! (name: " + flagGroupName + ")");
|
||||
}
|
||||
getFlagsManager().registerFlagGroup(flagGroup);
|
||||
flagGroupName = flagGroup.getName();
|
||||
Logging.fine("Registered flag group: " + flagGroupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses flags.
|
||||
*
|
||||
* @param flags The raw string array to parse into flags.
|
||||
* @return The parsed flags.
|
||||
*/
|
||||
protected @NotNull ParsedCommandFlags parseFlags(@NotNull String[] flags) {
|
||||
return getFlagsManager().parse(flagGroupName, flags);
|
||||
return flagsManager.parse(flagGroupName, flags);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.commandtools.context;
|
||||
|
||||
public class MVConfigValue {
|
||||
private final Object value;
|
||||
|
||||
public MVConfigValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,13 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.api.MVConfig;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.CommentedYamlConfigHandle;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.BooleanMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.IntegerMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.InvertBoolMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.MoveMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.VersionMigrator;
|
||||
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
|
||||
@Service
|
||||
public class MVCoreConfig implements MVConfig {
|
||||
@@ -32,14 +32,14 @@ public class MVCoreConfig implements MVConfig {
|
||||
private final Path configPath;
|
||||
private final MVCoreConfigNodes configNodes;
|
||||
private final CommentedYamlConfigHandle configHandle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
|
||||
@Inject
|
||||
MVCoreConfig(@NotNull MultiverseCore core, @NotNull PluginManager pluginManager) {
|
||||
this.configPath = Path.of(core.getDataFolder().getPath(), CONFIG_FILENAME);
|
||||
this.configNodes = new MVCoreConfigNodes(pluginManager);
|
||||
this.configHandle = CommentedYamlConfigHandle.builder(configPath)
|
||||
this.configHandle = CommentedYamlConfigHandle.builder(configPath, configNodes.getNodes())
|
||||
.logger(Logging.getLogger())
|
||||
.nodes(configNodes.getNodes())
|
||||
.migrator(ConfigMigrator.builder(configNodes.VERSION)
|
||||
.addVersionMigrator(VersionMigrator.builder(5.0)
|
||||
.addAction(MoveMigratorAction.of("multiverse-configuration.enforceaccess", "world.enforce-access"))
|
||||
@@ -70,7 +70,7 @@ public class MVCoreConfig implements MVConfig {
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
this.stringPropertyHandle = new StringPropertyHandle(configHandle);
|
||||
load();
|
||||
save();
|
||||
}
|
||||
@@ -108,18 +108,8 @@ public class MVCoreConfig implements MVConfig {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeGroup getNodes() {
|
||||
return configNodes.getNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<Object> getProperty(String name) {
|
||||
return configHandle.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<Void> setProperty(String name, Object value) {
|
||||
return configHandle.set(name, value);
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Provides default serializers for common types.
|
||||
*/
|
||||
public final class DefaultSerializerProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeSerializer<?>> SERIALIZERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default serializer for the given type.
|
||||
*
|
||||
* @param type The type.
|
||||
* @param serializer The serializer.
|
||||
* @param <T> The type.
|
||||
*/
|
||||
public static <T> void addDefaultSerializer(@NotNull Class<T> type, @NotNull NodeSerializer<T> serializer) {
|
||||
SERIALIZERS.put(type, serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default serializer for the given type.
|
||||
*
|
||||
* @param type The type.
|
||||
* @param <T> The type.
|
||||
* @return The default serializer for the given type, or null if no default serializer exists.
|
||||
*/
|
||||
public static <T> @Nullable NodeSerializer<T> getDefaultSerializer(Class<T> type) {
|
||||
if (type.isEnum()) {
|
||||
// Special case for enums
|
||||
return (NodeSerializer<T>) ENUM_SERIALIZER;
|
||||
}
|
||||
return (NodeSerializer<T>) SERIALIZERS.get(type);
|
||||
}
|
||||
|
||||
private static final NodeSerializer<Enum> ENUM_SERIALIZER = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Enum<?> deserialize(Object object, Class<Enum> type) {
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Enum object, Class<Enum> type) {
|
||||
return object.name();
|
||||
}
|
||||
};
|
||||
|
||||
private static final NodeSerializer<Boolean> BOOLEAN_SERIALIZER = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Boolean deserialize(Object object, Class<Boolean> type) {
|
||||
if (object instanceof Boolean) {
|
||||
return (Boolean) object;
|
||||
}
|
||||
return ACFUtil.isTruthy(String.valueOf(object));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Boolean object, Class<Boolean> type) {
|
||||
return object;
|
||||
}
|
||||
};
|
||||
|
||||
static {
|
||||
addDefaultSerializer(Boolean.class, BOOLEAN_SERIALIZER);
|
||||
}
|
||||
|
||||
private DefaultSerializerProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import io.vavr.control.Try;
|
||||
|
||||
/**
|
||||
* Provides default string parsers for common types.
|
||||
*/
|
||||
public final class DefaultStringParserProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeStringParser<?>> PARSERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default string parser for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param parser The string parser.
|
||||
*/
|
||||
public static void addDefaultStringParser(Class<?> clazz, NodeStringParser<?> parser) {
|
||||
PARSERS.put(clazz, parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default string parser for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param <T> The type.
|
||||
* @return The default string parser for the given type, or null if no default string parser exists.
|
||||
*/
|
||||
public static <T> NodeStringParser<T> getDefaultStringParser(Class<T> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
// Special case for enums
|
||||
return (NodeStringParser<T>) ENUM_STRING_PARSER;
|
||||
}
|
||||
return (NodeStringParser<T>) PARSERS.get(clazz);
|
||||
}
|
||||
|
||||
private static final NodeStringParser<Enum> ENUM_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> Enum.valueOf(type, input.toUpperCase()));
|
||||
|
||||
private static final NodeStringParser<String> STRING_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> input);
|
||||
|
||||
private static final NodeStringParser<Boolean> BOOLEAN_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.isTruthy(String.valueOf(input).toLowerCase()));
|
||||
|
||||
private static final NodeStringParser<Integer> INTEGER_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseInt(input));
|
||||
|
||||
private static final NodeStringParser<Double> DOUBLE_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseDouble(input));
|
||||
|
||||
private static final NodeStringParser<Float> FLOAT_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseFloat(input));
|
||||
|
||||
private static final NodeStringParser<Long> LONG_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseLong(input));
|
||||
|
||||
static {
|
||||
addDefaultStringParser(String.class, STRING_STRING_PARSER);
|
||||
addDefaultStringParser(Boolean.class, BOOLEAN_STRING_PARSER);
|
||||
addDefaultStringParser(Integer.class, INTEGER_STRING_PARSER);
|
||||
addDefaultStringParser(Double.class, DOUBLE_STRING_PARSER);
|
||||
addDefaultStringParser(Float.class, FLOAT_STRING_PARSER);
|
||||
addDefaultStringParser(Long.class, LONG_STRING_PARSER);
|
||||
}
|
||||
|
||||
private DefaultStringParserProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Provides default suggestions for common types.
|
||||
*/
|
||||
public final class DefaultSuggesterProvider {
|
||||
|
||||
private static final Map<Class<?>, NodeSuggester> SUGGESTERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a default suggester for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @param suggester The suggester.
|
||||
*/
|
||||
public static void addDefaultSuggester(Class<?> clazz, NodeSuggester suggester) {
|
||||
SUGGESTERS.put(clazz, suggester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default suggester for the given type.
|
||||
*
|
||||
* @param clazz The type.
|
||||
* @return The default suggester for the given type, or null if no default suggester exists.
|
||||
*/
|
||||
public static @Nullable NodeSuggester getDefaultSuggester(Class<?> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
// Special case for enums
|
||||
return enumSuggester(clazz);
|
||||
}
|
||||
return SUGGESTERS.get(clazz);
|
||||
}
|
||||
|
||||
private static NodeSuggester enumSuggester(Class<?> clazz) {
|
||||
return input -> Arrays.stream(clazz.getEnumConstants())
|
||||
.map(Object::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static final NodeSuggester BOOLEAN_SUGGESTER = input -> List.of("true", "false");
|
||||
|
||||
private static final NodeSuggester INTEGER_SUGGESTER = input -> IntStream.range(1, 10)
|
||||
.boxed()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
static {
|
||||
addDefaultSuggester(Boolean.class, BOOLEAN_SUGGESTER);
|
||||
addDefaultSuggester(Integer.class, INTEGER_SUGGESTER);
|
||||
}
|
||||
|
||||
private DefaultSuggesterProvider() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
/**
|
||||
* A function that serializes and deserializes objects to and from YAML.
|
||||
*
|
||||
* @param <T> The type of the object to serialize and deserialize.
|
||||
*/
|
||||
public interface NodeSerializer<T> {
|
||||
/**
|
||||
* Deserializes an object from YAML.
|
||||
*
|
||||
* @param object The object to deserialize.
|
||||
* @param type The type of the object to deserialize.
|
||||
* @return The deserialized typed value.
|
||||
*/
|
||||
T deserialize(Object object, Class<T> type);
|
||||
|
||||
/**
|
||||
* Serializes an object to YAML.
|
||||
*
|
||||
* @param object The object to serialize.
|
||||
* @param type The type of the object to serialize.
|
||||
* @return The serialized object.
|
||||
*/
|
||||
Object serialize(T object, Class<T> type);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that parses a string into a node value object of type {@link T}.
|
||||
*
|
||||
* @param <T> The type of the object to parse.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeStringParser<T> {
|
||||
/**
|
||||
* Parses a string into a node value object of type {@link T}.
|
||||
*
|
||||
* @param string The string to parse.
|
||||
* @param type The type of the object to parse.
|
||||
* @return The parsed object, or {@link Try.Failure} if the string could not be parsed.
|
||||
*/
|
||||
@NotNull Try<T> parse(@Nullable String string, @NotNull Class<T> type);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeSuggester {
|
||||
/**
|
||||
* Suggests possible values for a node value. Generated based on the current user input.
|
||||
*
|
||||
* @param input The current partial user input
|
||||
* @return The possible values.
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package org.mvplugins.multiverse.core.configuration.functions;
|
||||
+7
-6
@@ -24,16 +24,17 @@ public class CommentedYamlConfigHandle extends FileConfigHandle<CommentedConfigu
|
||||
* Creates a new builder for a {@link CommentedYamlConfigHandle}.
|
||||
*
|
||||
* @param configPath The path to the config file.
|
||||
* @param nodes The nodes.
|
||||
* @return The builder.
|
||||
*/
|
||||
public static @NotNull Builder builder(@NotNull Path configPath) {
|
||||
return new Builder(configPath);
|
||||
public static @NotNull Builder builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
|
||||
return new Builder(configPath, nodes);
|
||||
}
|
||||
|
||||
protected CommentedYamlConfigHandle(
|
||||
@NotNull Path configPath,
|
||||
@Nullable Logger logger,
|
||||
@Nullable NodeGroup nodes,
|
||||
@NotNull NodeGroup nodes,
|
||||
@Nullable ConfigMigrator migrator) {
|
||||
super(configPath, logger, nodes, migrator);
|
||||
}
|
||||
@@ -76,7 +77,7 @@ public class CommentedYamlConfigHandle extends FileConfigHandle<CommentedConfigu
|
||||
valueNode.getDefaultValue())).onFailure(e -> {
|
||||
Logging.warning("Failed to set node " + valueNode.getPath()
|
||||
+ " to " + valueNode.getDefaultValue());
|
||||
setDefault(valueNode);
|
||||
reset(valueNode);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -96,8 +97,8 @@ public class CommentedYamlConfigHandle extends FileConfigHandle<CommentedConfigu
|
||||
*/
|
||||
public static class Builder extends FileConfigHandle.Builder<CommentedConfiguration, Builder> {
|
||||
|
||||
protected Builder(@NotNull Path configPath) {
|
||||
super(configPath);
|
||||
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
|
||||
super(configPath, nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-7
@@ -18,16 +18,19 @@ public class ConfigurationSectionHandle extends GenericConfigHandle<Configuratio
|
||||
* Creates a new builder for a {@link ConfigurationSectionHandle}.
|
||||
*
|
||||
* @param configurationSection The configuration section.
|
||||
* @param nodes The nodes.
|
||||
* @return The builder.
|
||||
*/
|
||||
public static Builder<? extends Builder> builder(@NotNull ConfigurationSection configurationSection) {
|
||||
return new Builder<>(configurationSection);
|
||||
public static Builder<? extends Builder> builder(
|
||||
@NotNull ConfigurationSection configurationSection, @NotNull NodeGroup nodes) {
|
||||
return new Builder<>(configurationSection, nodes);
|
||||
}
|
||||
|
||||
protected ConfigurationSectionHandle(@NotNull ConfigurationSection configurationSection,
|
||||
@Nullable Logger logger,
|
||||
@Nullable NodeGroup nodes,
|
||||
@Nullable ConfigMigrator migrator) {
|
||||
protected ConfigurationSectionHandle(
|
||||
@NotNull ConfigurationSection configurationSection,
|
||||
@Nullable Logger logger,
|
||||
@NotNull NodeGroup nodes,
|
||||
@Nullable ConfigMigrator migrator) {
|
||||
super(logger, nodes, migrator);
|
||||
this.config = configurationSection;
|
||||
}
|
||||
@@ -51,7 +54,8 @@ public class ConfigurationSectionHandle extends GenericConfigHandle<Configuratio
|
||||
public static class Builder<B extends Builder<B>> extends GenericConfigHandle.Builder<ConfigurationSection, B> {
|
||||
private final ConfigurationSection configurationSection;
|
||||
|
||||
protected Builder(@NotNull ConfigurationSection configurationSection) {
|
||||
protected Builder(@NotNull ConfigurationSection configurationSection, @NotNull NodeGroup nodes) {
|
||||
super(nodes);
|
||||
this.configurationSection = configurationSection;
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -16,14 +16,19 @@ import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
|
||||
/**
|
||||
* Generic configuration handle for file based configurations.
|
||||
*
|
||||
* @param <C> The configuration type.
|
||||
*/
|
||||
abstract class FileConfigHandle<C extends FileConfiguration> extends GenericConfigHandle<C> {
|
||||
public abstract class FileConfigHandle<C extends FileConfiguration> extends GenericConfigHandle<C> {
|
||||
|
||||
protected final @NotNull Path configPath;
|
||||
protected final @NotNull File configFile;
|
||||
|
||||
protected FileConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) {
|
||||
protected FileConfigHandle(
|
||||
@NotNull Path configPath,
|
||||
@Nullable Logger logger,
|
||||
@NotNull NodeGroup nodes,
|
||||
@Nullable ConfigMigrator migrator) {
|
||||
super(logger, nodes, migrator);
|
||||
this.configPath = configPath;
|
||||
this.configFile = configPath.toFile();
|
||||
@@ -95,11 +100,13 @@ abstract class FileConfigHandle<C extends FileConfiguration> extends GenericConf
|
||||
* @param <C> The configuration type.
|
||||
* @param <B> The builder type.
|
||||
*/
|
||||
public static abstract class Builder<C extends FileConfiguration, B extends Builder<C, B>> extends GenericConfigHandle.Builder<C, B> {
|
||||
public abstract static class Builder<C extends FileConfiguration, B extends Builder<C, B>>
|
||||
extends GenericConfigHandle.Builder<C, B> {
|
||||
|
||||
protected @NotNull Path configPath;
|
||||
|
||||
protected Builder(@NotNull Path configPath) {
|
||||
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
|
||||
super(nodes);
|
||||
this.configPath = configPath;
|
||||
}
|
||||
|
||||
|
||||
+76
-44
@@ -1,5 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.configuration.handle;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
@@ -9,21 +10,24 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ConfigNodeNotFoundException;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ListValueNode;
|
||||
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
|
||||
|
||||
/**
|
||||
* Generic configuration handle for all ConfigurationSection types.
|
||||
*
|
||||
* @param <C> The configuration type.
|
||||
*/
|
||||
public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
|
||||
protected final @Nullable Logger logger;
|
||||
protected final @Nullable NodeGroup nodes;
|
||||
protected final @NotNull NodeGroup nodes;
|
||||
protected final @Nullable ConfigMigrator migrator;
|
||||
|
||||
protected C config;
|
||||
|
||||
protected GenericConfigHandle(@Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) {
|
||||
protected GenericConfigHandle(@Nullable Logger logger, @NotNull NodeGroup nodes, @Nullable ConfigMigrator migrator) {
|
||||
this.logger = logger;
|
||||
this.nodes = nodes;
|
||||
this.migrator = migrator;
|
||||
@@ -67,17 +71,6 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
|
||||
* @param name The name of the node.
|
||||
* @return The value of the node.
|
||||
*/
|
||||
public Try<Object> get(@Nullable String name) {
|
||||
return nodes.findNode(name, ValueNode.class)
|
||||
.toTry(() -> new ConfigNodeNotFoundException(name))
|
||||
.map(node -> get((ValueNode<Object>) node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
|
||||
*
|
||||
@@ -91,26 +84,13 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
return node.getSerializer().deserialize(config.get(node.getPath(), node.getDefaultValue()), node.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node, if the validator is not null, it will be tested first.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The value to set.
|
||||
* @return True if the value was set, false otherwise.
|
||||
*/
|
||||
public Try<Void> set(@Nullable String name, Object value) {
|
||||
return nodes.findNode(name, ValueNode.class)
|
||||
.toTry(() -> new ConfigNodeNotFoundException(name))
|
||||
.flatMap(node -> set(node, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node, if the validator is not null, it will be tested first.
|
||||
*
|
||||
* @param node The node to set the value of.
|
||||
* @param value The value to set.
|
||||
* @return True if the value was set, false otherwise.
|
||||
* @param <T> The type of the node value.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public <T> Try<Void> set(@NotNull ValueNode<T> node, T value) {
|
||||
return node.validate(value).map(ignore -> {
|
||||
@@ -126,13 +106,74 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to a list node.
|
||||
*
|
||||
* @param node The list node to add the item to.
|
||||
* @param itemValue The value of the item to add.
|
||||
* @param <I> The type of the list item.
|
||||
* @return Empty try if the item was added, try containing an error otherwise.
|
||||
*/
|
||||
public <I> Try<Void> add(@NotNull ListValueNode<I> node, I itemValue) {
|
||||
return node.validateItem(itemValue).map(ignore -> {
|
||||
var serialized = node.getItemSerializer() != null
|
||||
? node.getItemSerializer().serialize(itemValue, node.getItemType())
|
||||
: itemValue;
|
||||
List valueList = config.getList(node.getPath());
|
||||
if (valueList == null) {
|
||||
throw new IllegalArgumentException("Cannot add item to non-list node");
|
||||
}
|
||||
valueList.add(serialized);
|
||||
config.set(node.getPath(), valueList);
|
||||
node.onSetItemValue(null, itemValue);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from a list node.
|
||||
*
|
||||
* @param node The list node to remove the item from.
|
||||
* @param itemValue The value of the item to remove.
|
||||
* @param <I> The type of the list item.
|
||||
* @return Empty try if the item was removed, try containing an error otherwise.
|
||||
*/
|
||||
public <I> Try<Void> remove(@NotNull ListValueNode<I> node, I itemValue) {
|
||||
return node.validateItem(itemValue).map(ignore -> {
|
||||
var serialized = node.getItemSerializer() != null
|
||||
? node.getItemSerializer().serialize(itemValue, node.getItemType())
|
||||
: itemValue;
|
||||
List valueList = config.getList(node.getPath());
|
||||
if (valueList == null) {
|
||||
throw new IllegalArgumentException("Cannot remove item from non-list node");
|
||||
}
|
||||
if (!valueList.remove(serialized)) {
|
||||
throw new IllegalArgumentException("Cannot remove item from list node");
|
||||
}
|
||||
config.set(node.getPath(), valueList);
|
||||
node.onSetItemValue(itemValue, null);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of a node.
|
||||
*
|
||||
* @param node The node to set the default value of.
|
||||
* @param <T> The type of the node value.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public void setDefault(@NotNull ValueNode node) {
|
||||
config.set(node.getPath(), node.getDefaultValue());
|
||||
public <T> Try<Void> reset(@NotNull ValueNode<T> node) {
|
||||
return Try.run(() -> config.set(node.getPath(), node.getDefaultValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration. Mainly used for {@link StringPropertyHandle}.
|
||||
*
|
||||
* @return The configuration.
|
||||
*/
|
||||
@NotNull NodeGroup getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,13 +182,15 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
* @param <C> The configuration type.
|
||||
* @param <B> The builder type.
|
||||
*/
|
||||
public static abstract class Builder<C extends ConfigurationSection, B extends GenericConfigHandle.Builder<C, B>> {
|
||||
public abstract static class Builder<C extends ConfigurationSection, B extends GenericConfigHandle.Builder<C, B>> {
|
||||
|
||||
protected final @NotNull NodeGroup nodes;
|
||||
protected @Nullable Logger logger;
|
||||
protected @Nullable NodeGroup nodes;
|
||||
protected @Nullable ConfigMigrator migrator;
|
||||
|
||||
protected Builder() {}
|
||||
protected Builder(@NotNull NodeGroup nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logger.
|
||||
@@ -171,17 +214,6 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nodes.
|
||||
*
|
||||
* @param nodes The nodes.
|
||||
* @return The builder.
|
||||
*/
|
||||
public B nodes(@Nullable NodeGroup nodes) {
|
||||
this.nodes = nodes;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the migrator.
|
||||
*
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.configuration.handle;
|
||||
|
||||
/**
|
||||
* The type of modification to a config.
|
||||
*/
|
||||
public enum PropertyModifyAction {
|
||||
SET,
|
||||
ADD,
|
||||
REMOVE,
|
||||
RESET
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
package org.mvplugins.multiverse.core.configuration.handle;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.node.ConfigNodeNotFoundException;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ListValueNode;
|
||||
import org.mvplugins.multiverse.core.configuration.node.Node;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ValueNode;
|
||||
|
||||
/**
|
||||
* Handles setting of config with string names and values.
|
||||
*/
|
||||
public class StringPropertyHandle {
|
||||
private final @NotNull GenericConfigHandle<?> handle;
|
||||
|
||||
/**
|
||||
* Creates a new string property handle.
|
||||
*
|
||||
* @param handle The handle to wrap.
|
||||
*/
|
||||
public StringPropertyHandle(@NotNull GenericConfigHandle<?> handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of all properties in this handle.
|
||||
*
|
||||
* @return The names of all properties in this handle.
|
||||
*/
|
||||
public Collection<String> getAllPropertyNames() {
|
||||
return handle.getNodes().getNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of all properties in this handle that can be modified by the given action.
|
||||
*
|
||||
* @param action The action to perform.
|
||||
* @return The names of all properties in this handle that can be modified by the given action.
|
||||
*/
|
||||
public Collection<String> getModifiablePropertyNames(PropertyModifyAction action) {
|
||||
return switch (action) {
|
||||
case SET, RESET -> handle.getNodes().getNames();
|
||||
|
||||
case ADD, REMOVE -> handle.getNodes().stream()
|
||||
.filter(node -> node instanceof ListValueNode)
|
||||
.map(node -> ((ValueNode<?>) node).getName())
|
||||
.filter(Option::isDefined)
|
||||
.map(Option::get)
|
||||
.toList();
|
||||
|
||||
default -> Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of property.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @return The type of the property, or an error if the property was not found.
|
||||
*/
|
||||
public Try<Class<?>> getPropertyType(@Nullable String name) {
|
||||
return findNode(name, ValueNode.class).map(ValueNode::getType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-complete suggestions for a property.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param input The current user input.
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
public Collection<String> getSuggestedPropertyValue(
|
||||
@Nullable String name, @Nullable String input, @NotNull PropertyModifyAction action) {
|
||||
return switch (action) {
|
||||
case SET -> findNode(name, ValueNode.class)
|
||||
.map(node -> node.suggest(input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case ADD -> findNode(name, ListValueNode.class)
|
||||
.map(node -> node.suggestItem(input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case REMOVE -> findNode(name, ListValueNode.class)
|
||||
.map(node -> handle.get((ListValueNode<?>) node))
|
||||
.map(valueList -> valueList.stream()
|
||||
.map(String::valueOf)
|
||||
.toList())
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
default -> Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a node, if the node has a default value, it will be returned if the node is not found.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @return The value of the node, or an error if the node was not found.
|
||||
*/
|
||||
public Try<Object> getProperty(@Nullable String name) {
|
||||
return findNode(name, ValueNode.class).map(node -> handle.get(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The value to set.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> setProperty(@Nullable String name, @Nullable Object value) {
|
||||
return findNode(name, ValueNode.class).flatMap(node -> handle.set(node, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a list node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The value to add.
|
||||
* @return Empty try if the value was added, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> addProperty(@Nullable String name, @Nullable Object value) {
|
||||
return findNode(name, ListValueNode.class).flatMap(node -> handle.add(node, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a list node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The value to remove.
|
||||
* @return Empty try if the value was removed, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> removeProperty(@Nullable String name, @Nullable Object value) {
|
||||
return findNode(name, ListValueNode.class).flatMap(node -> handle.remove(node, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the value of a node to its default value.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @return Empty try if the value was reset, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> resetProperty(@Nullable String name) {
|
||||
return findNode(name, ValueNode.class).flatMap(node -> handle.reset(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the value of a node based on the given action.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The value to modify.
|
||||
* @param action The action to perform.
|
||||
* @return Empty try if the value was modified, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> modifyProperty(
|
||||
@Nullable String name, @Nullable Object value, @NotNull PropertyModifyAction action) {
|
||||
return switch (action) {
|
||||
case SET -> setProperty(name, value);
|
||||
case ADD -> addProperty(name, value);
|
||||
case REMOVE -> removeProperty(name, value);
|
||||
case RESET -> resetProperty(name);
|
||||
default -> Try.failure(new IllegalArgumentException("Unknown action: " + action));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string value of a node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The string value to set.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> setPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.flatMap(node -> node.parseFromString(value)
|
||||
.flatMap(parsedValue -> handle.set(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string value to a list node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The string value to add.
|
||||
* @return Empty try if the value was added, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> addPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(parsedValue -> handle.add(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a string value from a list node.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The string value to remove.
|
||||
* @return Empty try if the value was removed, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> removePropertyString(@Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(parsedValue -> handle.remove(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the value of a node based on the given action.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param value The string value to modify.
|
||||
* @param action The action to perform.
|
||||
* @return Empty try if the value was modified, try containing an error otherwise.
|
||||
*/
|
||||
public Try<Void> modifyPropertyString(
|
||||
@Nullable String name, @Nullable String value, @NotNull PropertyModifyAction action) {
|
||||
return switch (action) {
|
||||
case SET -> setPropertyString(name, value);
|
||||
case ADD -> addPropertyString(name, value);
|
||||
case REMOVE -> removePropertyString(name, value);
|
||||
case RESET -> resetProperty(name);
|
||||
default -> Try.failure(new IllegalArgumentException("Unknown action: " + action));
|
||||
};
|
||||
}
|
||||
|
||||
private <T extends Node> Try<T> findNode(@Nullable String name, @NotNull Class<T> type) {
|
||||
return handle.getNodes().findNode(name, type)
|
||||
.toTry(() -> new ConfigNodeNotFoundException(name));
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -22,13 +22,18 @@ public class YamlConfigHandle extends FileConfigHandle<YamlConfiguration> {
|
||||
* Creates a new builder for {@link YamlConfigHandle}.
|
||||
*
|
||||
* @param configPath The path to the config file.
|
||||
* @param nodes The nodes.
|
||||
* @return The builder.
|
||||
*/
|
||||
public static @NotNull Builder<? extends Builder> builder(@NotNull Path configPath) {
|
||||
return new Builder<>(configPath);
|
||||
public static @NotNull Builder<? extends Builder> builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
|
||||
return new Builder<>(configPath, nodes);
|
||||
}
|
||||
|
||||
protected YamlConfigHandle(@NotNull Path configPath, @Nullable Logger logger, @Nullable NodeGroup nodes, @Nullable ConfigMigrator migrator) {
|
||||
protected YamlConfigHandle(
|
||||
@NotNull Path configPath,
|
||||
@Nullable Logger logger,
|
||||
@NotNull NodeGroup nodes,
|
||||
@Nullable ConfigMigrator migrator) {
|
||||
super(configPath, logger, nodes, migrator);
|
||||
}
|
||||
|
||||
@@ -51,12 +56,13 @@ public class YamlConfigHandle extends FileConfigHandle<YamlConfiguration> {
|
||||
|
||||
/**
|
||||
* Builder for {@link YamlConfigHandle}.
|
||||
*
|
||||
* @param <B> The type of the builder.
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends FileConfigHandle.Builder<YamlConfiguration, B> {
|
||||
|
||||
protected Builder(@NotNull Path configPath) {
|
||||
super(configPath);
|
||||
protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) {
|
||||
super(configPath, nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.functions.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSuggester;
|
||||
|
||||
/**
|
||||
* A node that contains a value.
|
||||
*
|
||||
* @param <T> The type of the value.
|
||||
*/
|
||||
public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@@ -19,38 +30,48 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
*
|
||||
* @param path The path of the node.
|
||||
* @param type The type of the value.
|
||||
* @return The new builder.
|
||||
* @param <T> The type of the value.
|
||||
* @return The new builder.
|
||||
*/
|
||||
public static @NotNull <T> ConfigNode.Builder<T, ? extends ConfigNode.Builder<T, ?>> builder(
|
||||
@NotNull String path,
|
||||
@NotNull Class<T> type
|
||||
) {
|
||||
@NotNull Class<T> type) {
|
||||
return new ConfigNode.Builder<>(path, type);
|
||||
}
|
||||
|
||||
protected final @Nullable String name;
|
||||
protected final @NotNull Class<T> type;
|
||||
protected final @Nullable T defaultValue;
|
||||
protected final @Nullable NodeSerializer<T> serializer;
|
||||
protected final @Nullable Function<T, Try<Void>> validator;
|
||||
protected final @Nullable BiConsumer<T, T> onSetValue;
|
||||
protected final @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
|
||||
protected ConfigNode(
|
||||
@NotNull String path,
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<T> type,
|
||||
@Nullable T defaultValue,
|
||||
@Nullable Supplier<T> defaultValue,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<T> stringParser,
|
||||
@Nullable NodeSerializer<T> serializer,
|
||||
@Nullable Function<T, Try<Void>> validator,
|
||||
@Nullable BiConsumer<T, T> onSetValue
|
||||
) {
|
||||
@Nullable BiConsumer<T, T> onSetValue) {
|
||||
super(path, comments);
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
this.serializer = serializer;
|
||||
this.suggester = (suggester != null)
|
||||
? suggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(type);
|
||||
this.stringParser = (stringParser != null)
|
||||
? stringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(type);
|
||||
this.serializer = (serializer != null)
|
||||
? serializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(type);
|
||||
this.validator = validator;
|
||||
this.onSetValue = onSetValue;
|
||||
}
|
||||
@@ -76,9 +97,37 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T getDefaultValue() {
|
||||
return defaultValue;
|
||||
if (defaultValue != null) {
|
||||
return defaultValue.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggest(@Nullable String input) {
|
||||
if (suggester != null) {
|
||||
return suggester.suggest(input);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<T> parseFromString(@Nullable String input) {
|
||||
if (stringParser != null) {
|
||||
return stringParser.parse(input, type);
|
||||
}
|
||||
return Try.failure(new UnsupportedOperationException("No string parser for type " + type.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public @Nullable NodeSerializer<T> getSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
@@ -111,11 +160,12 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* @param <B> The type of the builder.
|
||||
*/
|
||||
public static class Builder<T, B extends ConfigNode.Builder<T, B>> extends ConfigHeaderNode.Builder<B> {
|
||||
private static final NodeSerializer<?> ENUM_NODE_SERIALIZER = new EnumNodeSerializer<>();
|
||||
|
||||
protected @Nullable String name;
|
||||
protected @NotNull final Class<T> type;
|
||||
protected @Nullable T defaultValue;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
@@ -130,9 +180,6 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
super(path);
|
||||
this.name = path;
|
||||
this.type = type;
|
||||
if (type.isEnum()) {
|
||||
this.serializer = (NodeSerializer<T>) ENUM_NODE_SERIALIZER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,8 +189,19 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B defaultValue(@NotNull T defaultValue) {
|
||||
this.defaultValue = () -> defaultValue;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value for this node.
|
||||
*
|
||||
* @param defaultValue The default value supplier.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B defaultValue(@NotNull Supplier<T> defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return (B) this;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,17 +212,51 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
*/
|
||||
public @NotNull B name(@Nullable String name) {
|
||||
this.name = name;
|
||||
return (B) this;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node.
|
||||
*
|
||||
* @param suggester The suggester for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B suggester(@NotNull NodeSuggester suggester) {
|
||||
this.suggester = suggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node.
|
||||
*
|
||||
* @param stringParser The string parser for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B stringParser(@NotNull NodeStringParser<T> stringParser) {
|
||||
this.stringParser = stringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for this node.
|
||||
*
|
||||
* @param serializer The serializer for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B serializer(@NotNull NodeSerializer<T> serializer) {
|
||||
this.serializer = serializer;
|
||||
return (B) this;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the validator for this node.
|
||||
*
|
||||
* @param validator The validator for this node.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B validator(@NotNull Function<T, Try<Void>> validator) {
|
||||
this.validator = validator;
|
||||
return (B) this;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +267,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
*/
|
||||
public @NotNull B onSetValue(@NotNull BiConsumer<T, T> onSetValue) {
|
||||
this.onSetValue = onSetValue;
|
||||
return (B) this;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +275,13 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
*/
|
||||
@Override
|
||||
public @NotNull ConfigNode<T> build() {
|
||||
return new ConfigNode<>(path, comments.toArray(new String[0]), name, type, defaultValue, serializer, validator, onSetValue);
|
||||
return new ConfigNode<>(path, comments.toArray(new String[0]),
|
||||
name, type, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
}
|
||||
|
||||
protected @NotNull B self() {
|
||||
//noinspection unchecked
|
||||
return (B) this;
|
||||
}
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
public class EnumNodeSerializer<T extends Enum<T>> implements NodeSerializer<T> {
|
||||
|
||||
@Override
|
||||
public T deserialize(Object object, Class<T> type) {
|
||||
return Enum.valueOf(type, object.toString().toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(T object, Class<T> type) {
|
||||
return object.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.Value;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSuggester;
|
||||
|
||||
/**
|
||||
* A config node that contains a list of values.
|
||||
*
|
||||
* @param <I> The type of the value.
|
||||
*/
|
||||
public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueNode<I> {
|
||||
|
||||
/**
|
||||
* Creates a new builder for a {@link ConfigNode}.
|
||||
*
|
||||
* @param path The path of the node.
|
||||
* @param type The type of the value.
|
||||
* @param <I> The type of the value.
|
||||
* @return The new builder.
|
||||
*/
|
||||
public static @NotNull <I> Builder<I, ? extends Builder<I, ?>> listBuilder(
|
||||
@NotNull String path,
|
||||
@NotNull Class<I> type) {
|
||||
return new Builder<>(path, type);
|
||||
}
|
||||
|
||||
protected final Class<I> itemType;
|
||||
protected final NodeSuggester itemSuggester;
|
||||
protected final NodeStringParser<I> itemStringParser;
|
||||
protected final NodeSerializer<I> itemSerializer;
|
||||
protected final Function<I, Try<Void>> itemValidator;
|
||||
protected final BiConsumer<I, I> onSetItemValue;
|
||||
|
||||
protected ListConfigNode(
|
||||
@NotNull String path,
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<List<I>> type,
|
||||
@Nullable Supplier<List<I>> defaultValueSupplier,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<List<I>> stringParser,
|
||||
@Nullable NodeSerializer<List<I>> serializer,
|
||||
@Nullable Function<List<I>, Try<Void>> validator,
|
||||
@Nullable BiConsumer<List<I>, List<I>> onSetValue,
|
||||
@NotNull Class<I> itemType,
|
||||
@Nullable NodeSuggester itemSuggester,
|
||||
@Nullable NodeStringParser<I> itemStringParser,
|
||||
@Nullable NodeSerializer<I> itemSerializer,
|
||||
@Nullable Function<I, Try<Void>> itemValidator,
|
||||
@Nullable BiConsumer<I, I> onSetItemValue) {
|
||||
super(path, comments, name, type, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onSetValue);
|
||||
this.itemType = itemType;
|
||||
this.itemSuggester = itemSuggester != null
|
||||
? itemSuggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(itemType);
|
||||
this.itemStringParser = itemStringParser != null
|
||||
? itemStringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(itemType);
|
||||
this.itemSerializer = itemSerializer;
|
||||
this.itemValidator = itemValidator;
|
||||
this.onSetItemValue = onSetItemValue;
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
private void setDefaults() {
|
||||
if (this.itemSuggester != null && this.suggester == null) {
|
||||
setDefaultSuggester();
|
||||
}
|
||||
if (this.itemStringParser != null && this.stringParser == null) {
|
||||
setDefaultStringParser();
|
||||
}
|
||||
if (this.itemValidator != null && this.validator == null) {
|
||||
setDefaultValidator();
|
||||
}
|
||||
if (this.itemSerializer != null && this.serializer == null) {
|
||||
setDefaultSerialiser();
|
||||
}
|
||||
if (this.onSetItemValue != null && this.onSetValue == null) {
|
||||
setDefaultOnSetValue();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultSuggester() {
|
||||
this.suggester = input -> {
|
||||
int lastIndexOf = input == null ? -1 : input.lastIndexOf(',');
|
||||
if (lastIndexOf == -1) {
|
||||
return itemSuggester.suggest(input);
|
||||
}
|
||||
|
||||
String lastInput = input.substring(lastIndexOf + 1);
|
||||
String inputBeforeLast = input.substring(0, lastIndexOf + 1);
|
||||
Set<String> inputs = Set.of(inputBeforeLast.split(","));
|
||||
return itemSuggester.suggest(lastInput).stream()
|
||||
.filter(item -> !inputs.contains(item))
|
||||
.map(item -> inputBeforeLast + item)
|
||||
.toList();
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultStringParser() {
|
||||
this.stringParser = (input, type) -> {
|
||||
if (input == null) {
|
||||
return Try.failure(new IllegalArgumentException("Input cannot be null"));
|
||||
}
|
||||
return Try.sequence(Arrays.stream(input.split(","))
|
||||
.map(inputItem -> itemStringParser.parse(inputItem, itemType))
|
||||
.toList()).map(Value::toJavaList);
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultValidator() {
|
||||
this.validator = value -> {
|
||||
if (value != null) {
|
||||
return Try.sequence(value.stream().map(itemValidator).toList()).map(v -> null);
|
||||
}
|
||||
return Try.success(null);
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultSerialiser() {
|
||||
this.serializer = new NodeSerializer<>() {
|
||||
@Override
|
||||
public List<I> deserialize(Object object, Class<List<I>> type) {
|
||||
if (object instanceof List list) {
|
||||
return list.stream().map(item -> itemSerializer.deserialize(item, itemType)).toList();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(List<I> object, Class<List<I>> type) {
|
||||
if (object != null) {
|
||||
return object.stream().map(item -> itemSerializer.serialize(item, itemType)).toList();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultOnSetValue() {
|
||||
this.onSetValue = (oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.stream()
|
||||
.filter(value -> !newValue.contains(value))
|
||||
.forEach(item -> onSetItemValue.accept(item, null));
|
||||
}
|
||||
newValue.forEach(item -> onSetItemValue.accept(null, item));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Class<I> getItemType() {
|
||||
return itemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggestItem(@Nullable String input) {
|
||||
if (itemSuggester != null) {
|
||||
return itemSuggester.suggest(input);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<I> parseItemFromString(@Nullable String input) {
|
||||
if (itemStringParser != null) {
|
||||
return itemStringParser.parse(input, itemType);
|
||||
}
|
||||
return Try.failure(new UnsupportedOperationException("No item string parser for type " + itemType));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable NodeSerializer<I> getItemSerializer() {
|
||||
return itemSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Try<Void> validateItem(@Nullable I value) {
|
||||
if (itemValidator != null) {
|
||||
return itemValidator.apply(value);
|
||||
}
|
||||
return Try.success(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onSetItemValue(@Nullable I oldValue, @Nullable I newValue) {
|
||||
if (onSetItemValue != null) {
|
||||
onSetItemValue.accept(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder<I, B extends ListConfigNode.Builder<I, B>> extends ConfigNode.Builder<List<I>, B> {
|
||||
|
||||
protected final @NotNull Class<I> itemType;
|
||||
protected @Nullable NodeSuggester itemSuggester;
|
||||
protected @Nullable NodeStringParser<I> itemStringParser;
|
||||
protected @Nullable NodeSerializer<I> itemSerializer;
|
||||
protected @Nullable Function<I, Try<Void>> itemValidator;
|
||||
protected @Nullable BiConsumer<I, I> onSetItemValue;
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*
|
||||
* @param path The path of the node.
|
||||
* @param itemType The type of the item value in list.
|
||||
*/
|
||||
protected Builder(@NotNull String path, @NotNull Class<I> itemType) {
|
||||
//noinspection unchecked
|
||||
super(path, (Class<List<I>>) (Object) List.class);
|
||||
this.itemType = itemType;
|
||||
this.defaultValue = () -> (List<I>) new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for an individual item in the list.
|
||||
*
|
||||
* @param itemSuggester The suggester.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B itemSuggester(@NotNull NodeSuggester itemSuggester) {
|
||||
this.itemSuggester = itemSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for an individual item in the list.
|
||||
*
|
||||
* @param itemStringParser The string parser.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B itemStringParser(@NotNull NodeStringParser<I> itemStringParser) {
|
||||
this.itemStringParser = itemStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for an individual item in the list.
|
||||
*
|
||||
* @param serializer The serializer.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B itemSerializer(@NotNull NodeSerializer<I> serializer) {
|
||||
this.itemSerializer = serializer;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the validator for an individual item in the list.
|
||||
*
|
||||
* @param itemValidator The validator.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B itemValidator(@NotNull Function<I, Try<Void>> itemValidator) {
|
||||
this.itemValidator = itemValidator;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the onSetValue for an individual item in the list.
|
||||
*
|
||||
* @param onSetItemValue The onSetValue.
|
||||
* @return This builder.
|
||||
*/
|
||||
public @NotNull B onSetItemValue(@Nullable BiConsumer<I, I> onSetItemValue) {
|
||||
this.onSetItemValue = onSetItemValue;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull ListConfigNode<I> build() {
|
||||
return new ListConfigNode<>(
|
||||
path,
|
||||
comments.toArray(new String[0]),
|
||||
name,
|
||||
type,
|
||||
defaultValue,
|
||||
suggester,
|
||||
stringParser,
|
||||
serializer,
|
||||
validator,
|
||||
onSetValue,
|
||||
itemType,
|
||||
itemSuggester, itemStringParser, itemSerializer,
|
||||
itemValidator,
|
||||
onSetItemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
|
||||
|
||||
/**
|
||||
* A node that holds a list of values of a specific type.
|
||||
*
|
||||
* @param <I> The type of list item.
|
||||
*/
|
||||
public interface ListValueNode<I> extends ValueNode<List<I>> {
|
||||
|
||||
/**
|
||||
* Gets the class type of list item.
|
||||
*
|
||||
* @return The class type of list item.
|
||||
*/
|
||||
@NotNull Class<I> getItemType();
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node.
|
||||
*
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
@NotNull Collection<String> suggestItem(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link I}. Used for property set by user input.
|
||||
*
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*/
|
||||
@NotNull Try<I> parseItemFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
* @return The serializer for this node.
|
||||
*/
|
||||
@Nullable NodeSerializer<I> getItemSerializer();
|
||||
|
||||
/**
|
||||
* Validates the value of this node.
|
||||
*
|
||||
* @param value The value to validate.
|
||||
* @return True if the value is valid, false otherwise.
|
||||
*/
|
||||
Try<Void> validateItem(@Nullable I value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is set.
|
||||
*
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*/
|
||||
void onSetItemValue(@Nullable I oldValue, @Nullable I newValue);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.Map;
|
||||
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
|
||||
import io.vavr.control.Option;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A collection of {@link CommentedNode}s, with mappings to nodes by name.
|
||||
@@ -17,24 +18,32 @@ public class NodeGroup implements Collection<Node> {
|
||||
private final Collection<Node> nodes;
|
||||
private final Map<String, Node> nodesMap;
|
||||
|
||||
/**
|
||||
* Creates a new empty node group.
|
||||
*/
|
||||
public NodeGroup() {
|
||||
this.nodes = new ArrayList<>();
|
||||
this.nodesMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public NodeGroup(Collection<Node> nodes) {
|
||||
/**
|
||||
* Creates a new node group with the given nodes.
|
||||
*
|
||||
* @param nodes The nodes to make up the group.
|
||||
*/
|
||||
public NodeGroup(@NotNull Collection<Node> nodes) {
|
||||
this.nodes = nodes;
|
||||
this.nodesMap = new HashMap<>(nodes.size());
|
||||
nodes.forEach(this::addNodeIndex);
|
||||
}
|
||||
|
||||
private void addNodeIndex(Node node) {
|
||||
private void addNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(name -> nodesMap.put(name, node));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNodeIndex(Node node) {
|
||||
private void removeNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(nodesMap::remove);
|
||||
}
|
||||
@@ -45,32 +54,29 @@ public class NodeGroup implements Collection<Node> {
|
||||
*
|
||||
* @return The names of all nodes in this group.
|
||||
*/
|
||||
public Collection<String> getNames() {
|
||||
public @NotNull Collection<String> getNames() {
|
||||
return nodesMap.keySet();
|
||||
}
|
||||
|
||||
public Map<String, Node> getNodesMap() {
|
||||
return nodesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node with the given name.
|
||||
*
|
||||
* @param name The name of the node to get.
|
||||
* @return The node with the given name, or {@link Option.None} if no node with the given name exists.
|
||||
*/
|
||||
public Option<Node> findNode(String name) {
|
||||
public @NotNull Option<Node> findNode(@Nullable String name) {
|
||||
return Option.of(nodesMap.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node with the given name.
|
||||
*
|
||||
* @param name The name of the node to get.
|
||||
* @param type The type of the node to get.
|
||||
* @param name The name of node to get.
|
||||
* @param type The type of node to get.
|
||||
* @param <T> The type of node.
|
||||
* @return The node with the given name, or {@link Option.None} if no node with the given name exists.
|
||||
*/
|
||||
public <T extends Node> Option<T> findNode(String name, Class<T> type) {
|
||||
public <T extends Node> @NotNull Option<T> findNode(@Nullable String name, @NotNull Class<T> type) {
|
||||
return Option.of(nodesMap.get(name)).map(node -> type.isAssignableFrom(node.getClass()) ? (T) node : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
public interface NodeSerializer<T> {
|
||||
T deserialize(Object object, Class<T> type);
|
||||
Object serialize(T object, Class<T> type);
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.mvplugins.multiverse.core.configuration.node;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.functions.NodeSerializer;
|
||||
|
||||
public interface ValueNode<T> extends Node {
|
||||
|
||||
/**
|
||||
@@ -28,6 +32,22 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@Nullable T getDefaultValue();
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Generated based on the current user input.
|
||||
*
|
||||
* @param input The current partial user input
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T}. Used for property set by user input.
|
||||
*
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*/
|
||||
@NotNull Try<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
@@ -39,7 +59,8 @@ public interface ValueNode<T> extends Node {
|
||||
* Validates the value of this node.
|
||||
*
|
||||
* @param value The value to validate.
|
||||
* @return True if the value is valid, false otherwise.
|
||||
* @return An empty {@link Try} if the value is valid, or a {@link Try} containing an exception if the value is
|
||||
* invalid.
|
||||
*/
|
||||
Try<Void> validate(@Nullable T value);
|
||||
|
||||
|
||||
@@ -3,14 +3,10 @@ package org.mvplugins.multiverse.core.destination;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,9 +15,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
|
||||
|
||||
/**
|
||||
* Provides destinations for teleportation.
|
||||
@@ -32,13 +25,11 @@ public class DestinationsProvider {
|
||||
private static final String PERMISSION_PREFIX = "multiverse.teleport.";
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final SafeTTeleporter safetyTeleporter;
|
||||
private final Map<String, Destination<?>> destinationMap;
|
||||
|
||||
@Inject
|
||||
DestinationsProvider(@NotNull PluginManager pluginManager, @NotNull SafeTTeleporter safetyTeleporter) {
|
||||
DestinationsProvider(@NotNull PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
this.destinationMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -123,93 +114,11 @@ public class DestinationsProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the teleportee to the destination.
|
||||
* Gets all registered destinations.
|
||||
*
|
||||
* @param teleporter The teleporter.
|
||||
* @param teleportee The teleportee.
|
||||
* @param destination The destination.
|
||||
* @return The async teleport result.
|
||||
* @return A collection of destinations.
|
||||
*/
|
||||
public CompletableFuture<TeleportResult> playerTeleportAsync(
|
||||
@NotNull BukkitCommandIssuer teleporter,
|
||||
@NotNull Player teleportee,
|
||||
@NotNull ParsedDestination<?> destination) {
|
||||
if (!checkTeleportPermissions(teleporter, teleportee, destination)) {
|
||||
return CompletableFuture.completedFuture(TeleportResult.FAIL_PERMISSION);
|
||||
}
|
||||
return teleportAsync(teleporter, teleportee, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the teleportee to the destination.
|
||||
*
|
||||
* @param teleporter The teleporter.
|
||||
* @param teleportee The teleportee.
|
||||
* @param destination The destination.
|
||||
* @return The async teleport result.
|
||||
*/
|
||||
public CompletableFuture<TeleportResult> teleportAsync(
|
||||
@NotNull BukkitCommandIssuer teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull ParsedDestination<?> destination) {
|
||||
Teleporter teleportHandler = destination.getDestination().getTeleporter();
|
||||
if (teleportHandler == null) {
|
||||
teleportHandler = safetyTeleporter;
|
||||
}
|
||||
return teleportHandler.teleportAsync(teleporter, teleportee, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the teleporter has permission to teleport the teleportee to the destination.
|
||||
*
|
||||
* @param teleporter The teleporter.
|
||||
* @param teleportee The teleportee.
|
||||
* @param destination The destination.
|
||||
* @return True if the teleporter has permission, false otherwise.
|
||||
*/
|
||||
public boolean checkTeleportPermissions(
|
||||
CommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
|
||||
// TODO: Move permission checking to a separate class
|
||||
String permission = PERMISSION_PREFIX
|
||||
+ (teleportee.equals(teleporter.getIssuer()) ? "self" : "other") + "."
|
||||
+ destination.getDestination().getIdentifier();
|
||||
if (!teleporter.hasPermission(permission)) {
|
||||
teleporter.sendMessage("You don't have permission to teleport to this destination.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Config whether to use finer permission
|
||||
String finerPermissionSuffix = destination.getDestinationInstance().getFinerPermissionSuffix();
|
||||
if (finerPermissionSuffix == null || finerPermissionSuffix.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String finerPermission = permission + "." + finerPermissionSuffix;
|
||||
if (!teleporter.hasPermission(finerPermission)) {
|
||||
teleporter.sendMessage("You don't have permission to teleport to this destination.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the issuer has permission to teleport to at least one destination.
|
||||
*
|
||||
* @param issuer The issuer.
|
||||
* @return True if the issuer has permission, false otherwise.
|
||||
*/
|
||||
public boolean hasAnyTeleportPermission(CommandIssuer issuer) {
|
||||
for (Destination<?> destination : this.destinationMap.values()) {
|
||||
String permission = PERMISSION_PREFIX + "self." + destination.getIdentifier();
|
||||
if (issuer.hasPermission(permission)) {
|
||||
return true;
|
||||
}
|
||||
permission = PERMISSION_PREFIX + "other." + destination.getIdentifier();
|
||||
if (issuer.hasPermission(permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public @NotNull Collection<Destination<?>> getDestinations() {
|
||||
return this.destinationMap.values();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
|
||||
/**
|
||||
* {@link Destination} implementation for anchors.
|
||||
@@ -61,12 +60,4 @@ public class AnchorDestination implements Destination<AnchorDestinationInstance>
|
||||
public boolean checkTeleportSafety() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
|
||||
/**
|
||||
@@ -62,12 +61,4 @@ public class BedDestination implements Destination<BedDestinationInstance> {
|
||||
public boolean checkTeleportSafety() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@@ -97,12 +96,4 @@ public class CannonDestination implements Destination<CannonDestinationInstance>
|
||||
public boolean checkTeleportSafety() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@@ -100,12 +99,4 @@ public class ExactDestination implements Destination<ExactDestinationInstance> {
|
||||
public boolean checkTeleportSafety() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
|
||||
/**
|
||||
@@ -60,12 +59,4 @@ public class PlayerDestination implements Destination<PlayerDestinationInstance>
|
||||
public boolean checkTeleportSafety() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.api.Teleporter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@@ -76,12 +75,4 @@ public class WorldDestination implements Destination<WorldDestinationInstance> {
|
||||
public boolean checkTeleportSafety() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Teleporter getTeleporter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -2,9 +2,9 @@ package org.mvplugins.multiverse.core.display.parsers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import com.google.common.base.Strings;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -45,8 +45,15 @@ public class MapContentProvider<K, V> implements ContentProvider {
|
||||
@Override
|
||||
public Collection<String> parse(@NotNull BukkitCommandIssuer issuer) {
|
||||
return map.entrySet().stream()
|
||||
.map(e -> String.format(format, keyColor, e.getKey(), separator, valueColor, e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
.map(e -> String.format(format, keyColor, e.getKey(), separator, valueColor, formatValue(e.getValue())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String formatValue(V value) {
|
||||
if (value instanceof String stringValue) {
|
||||
return Strings.isNullOrEmpty(stringValue) ? "&7&onull" : stringValue;
|
||||
}
|
||||
return value == null ? "&7&onull" : String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,8 +14,8 @@ import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
|
||||
/**
|
||||
* Event that gets called when a player use the /mvtp command.
|
||||
@@ -89,8 +89,8 @@ public class MVTeleportEvent extends Event implements Cancellable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks if this {@link MVTeleportEvent} is using the {@link SafeTTeleporter}.
|
||||
* @return True if this {@link MVTeleportEvent} is using the {@link SafeTTeleporter}.
|
||||
* Looks if this {@link MVTeleportEvent} is using the {@link AsyncSafetyTeleporter}.
|
||||
* @return True if this {@link MVTeleportEvent} is using the {@link AsyncSafetyTeleporter}.
|
||||
*/
|
||||
public boolean isUsingSafeTTeleporter() {
|
||||
return useSafeTeleport;
|
||||
|
||||
@@ -31,10 +31,11 @@ public class MVChatListener implements InjectableListener {
|
||||
|
||||
/**
|
||||
* This handles a {@link AsyncPlayerChatEvent}.
|
||||
*
|
||||
* @param event The {@link AsyncPlayerChatEvent}.
|
||||
*/
|
||||
@EventHandler
|
||||
public void playerChat(AsyncPlayerChatEvent event) {
|
||||
public void asyncPlayerChat(AsyncPlayerChatEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public class MVEntityListener implements InjectableListener {
|
||||
|
||||
/**
|
||||
* This method is called when an entity's food level goes higher or lower.
|
||||
*
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventHandler
|
||||
@@ -52,6 +53,7 @@ public class MVEntityListener implements InjectableListener {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(player.getWorld())
|
||||
.peek(world -> {
|
||||
if (!world.getHunger() && event.getFoodLevel() < player.getFoodLevel()) {
|
||||
@@ -62,6 +64,7 @@ public class MVEntityListener implements InjectableListener {
|
||||
|
||||
/**
|
||||
* This method is called when an entity's health goes up or down.
|
||||
*
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventHandler
|
||||
@@ -69,6 +72,7 @@ public class MVEntityListener implements InjectableListener {
|
||||
if (event.isCancelled() || event.getRegainReason() != RegainReason.REGEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(event.getEntity().getWorld())
|
||||
.peek(world -> {
|
||||
if (!world.getAutoHeal()) {
|
||||
@@ -79,6 +83,7 @@ public class MVEntityListener implements InjectableListener {
|
||||
|
||||
/**
|
||||
* Handle Animal/Monster Spawn settings, seems like a more concrete method than using CraftBukkit.
|
||||
*
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
|
||||
@@ -31,8 +31,9 @@ import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
@@ -40,6 +41,7 @@ import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.inject.InjectableListener;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue;
|
||||
import org.mvplugins.multiverse.core.utils.result.ResultChain;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
@@ -56,7 +58,8 @@ public class MVPlayerListener implements InjectableListener {
|
||||
private final Plugin plugin;
|
||||
private final MVCoreConfig config;
|
||||
private final Provider<WorldManager> worldManagerProvider;
|
||||
private final SafeTTeleporter safetyTeleporter;
|
||||
private final BlockSafety blockSafety;
|
||||
private final AsyncSafetyTeleporter safetyTeleporter;
|
||||
private final Server server;
|
||||
private final TeleportQueue teleportQueue;
|
||||
private final MVEconomist economist;
|
||||
@@ -72,7 +75,8 @@ public class MVPlayerListener implements InjectableListener {
|
||||
MultiverseCore plugin,
|
||||
MVCoreConfig config,
|
||||
Provider<WorldManager> worldManagerProvider,
|
||||
SafeTTeleporter safetyTeleporter,
|
||||
BlockSafety blockSafety,
|
||||
AsyncSafetyTeleporter safetyTeleporter,
|
||||
Server server,
|
||||
TeleportQueue teleportQueue,
|
||||
MVEconomist economist,
|
||||
@@ -83,6 +87,7 @@ public class MVPlayerListener implements InjectableListener {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.worldManagerProvider = worldManagerProvider;
|
||||
this.blockSafety = blockSafety;
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
this.server = server;
|
||||
this.teleportQueue = teleportQueue;
|
||||
@@ -211,7 +216,7 @@ public class MVPlayerListener implements InjectableListener {
|
||||
}
|
||||
|
||||
// Finally, teleport the player
|
||||
safetyTeleporter.teleportAsync(getCommandManager().getCommandIssuer(player), player, joinDestination);
|
||||
safetyTeleporter.teleportSafely(player, player, joinDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,7 +305,7 @@ public class MVPlayerListener implements InjectableListener {
|
||||
// REMEMBER! getTo MAY be NULL HERE!!!
|
||||
// If the player was actually outside of the portal, adjust the from location
|
||||
if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() != Material.NETHER_PORTAL) {
|
||||
Location newloc = this.safetyTeleporter.findPortalBlockNextTo(event.getFrom());
|
||||
Location newloc = blockSafety.findPortalBlockNextTo(event.getFrom());
|
||||
// TODO: Fix this. Currently, we only check for PORTAL blocks. I'll have to figure out what
|
||||
// TODO: we want to do here.
|
||||
if (newloc != null) {
|
||||
@@ -352,7 +357,7 @@ public class MVPlayerListener implements InjectableListener {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
safetyTeleporter.safelyTeleportAsync(getCommandManager().getCommandIssuer(player), player, parsedDestination);
|
||||
safetyTeleporter.teleportSafely(player, player, parsedDestination);
|
||||
}
|
||||
}, 1L);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
import org.mvplugins.multiverse.core.inject.InjectableListener;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import static org.bukkit.PortalType.CUSTOM;
|
||||
|
||||
/**
|
||||
* A custom listener for portal related events.
|
||||
*/
|
||||
@@ -42,49 +43,51 @@ public class MVPortalListener implements InjectableListener {
|
||||
|
||||
/**
|
||||
* This is called when a portal is formed.
|
||||
*
|
||||
* @param event The event where a portal was created or formed due to a world link
|
||||
*/
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void portalForm(PortalCreateEvent event) {
|
||||
Logging.fine("Attempting to create portal at '%s' with reason: %s", event.getWorld().getName(), event.getReason());
|
||||
public void portalCreate(PortalCreateEvent event) {
|
||||
Logging.fine("Attempting to create portal at '%s' with reason: %s",
|
||||
event.getWorld().getName(), event.getReason());
|
||||
|
||||
LoadedMultiverseWorld world = this.worldManager.getLoadedWorld(event.getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
Logging.fine("World '%s' is not managed by Multiverse! Ignoring at PortalCreateEvent.", event.getWorld().getName());
|
||||
return;
|
||||
}
|
||||
this.worldManager.getLoadedWorld(event.getWorld()).peek(world -> {
|
||||
PortalType targetType = getPortalType(event);
|
||||
if (targetType == PortalType.CUSTOM) {
|
||||
return;
|
||||
}
|
||||
if (!world.getPortalForm().isPortalAllowed(targetType)) {
|
||||
Logging.fine("Cancelling creation of %s portal because portalForm disallows.", targetType);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}).onEmpty(() -> {
|
||||
Logging.fine("World '%s' is not managed by Multiverse! Ignoring at PortalCreateEvent.",
|
||||
event.getWorld().getName());
|
||||
});
|
||||
}
|
||||
|
||||
PortalType targetType;
|
||||
switch (event.getReason()) {
|
||||
case FIRE:
|
||||
private PortalType getPortalType(PortalCreateEvent event) {
|
||||
return switch (event.getReason()) {
|
||||
case FIRE -> {
|
||||
// Ensure portal by flint and steel actually creates nether
|
||||
boolean isNether = false;
|
||||
for (BlockState block : event.getBlocks()) {
|
||||
if (block.getType() == Material.NETHER_PORTAL) {
|
||||
isNether = true;
|
||||
break;
|
||||
yield PortalType.NETHER;
|
||||
}
|
||||
}
|
||||
if (!isNether) {
|
||||
return;
|
||||
}
|
||||
targetType = PortalType.NETHER;
|
||||
break;
|
||||
case NETHER_PAIR:
|
||||
targetType = PortalType.NETHER;
|
||||
break;
|
||||
case END_PLATFORM:
|
||||
targetType = PortalType.ENDER;
|
||||
break;
|
||||
default:
|
||||
yield CUSTOM;
|
||||
}
|
||||
case NETHER_PAIR -> {
|
||||
yield PortalType.NETHER;
|
||||
}
|
||||
case END_PLATFORM -> {
|
||||
yield PortalType.ENDER;
|
||||
}
|
||||
default -> {
|
||||
Logging.fine("Portal created is not NETHER or ENDER type. Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!world.getPortalForm().isPortalAllowed(targetType)) {
|
||||
Logging.fine("Cancelling creation of %s portal because portalForm disallows.", targetType);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
yield CUSTOM;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,28 +96,28 @@ public class MVPortalListener implements InjectableListener {
|
||||
* @param event The player interact event.
|
||||
*/
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void portalForm(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
if (event.getClickedBlock() == null || event.getClickedBlock().getType() != Material.END_PORTAL_FRAME) {
|
||||
return;
|
||||
}
|
||||
if (event.getItem() == null || event.getItem().getType() != Material.ENDER_EYE) {
|
||||
public void playerInteract(PlayerInteractEvent event) {
|
||||
if (isCreateEndPortalInteraction(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoadedMultiverseWorld world = this.worldManager.getLoadedWorld(event.getPlayer().getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
this.worldManager.getLoadedWorld(event.getPlayer().getWorld()).peek(world -> {
|
||||
if (!world.getPortalForm().isPortalAllowed(PortalType.ENDER)) {
|
||||
Logging.fine("Cancelling creation of ENDER portal because portalForm disallows.");
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}).onEmpty(() -> {
|
||||
Logging.fine("World '%s' is not managed by Multiverse! Ignoring at PlayerInteractEvent.",
|
||||
event.getPlayer().getWorld().getName());
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!world.getPortalForm().isPortalAllowed(PortalType.ENDER)) {
|
||||
Logging.fine("Cancelling creation of ENDER portal because portalForm disallows.");
|
||||
event.setCancelled(true);
|
||||
}
|
||||
private boolean isCreateEndPortalInteraction(PlayerInteractEvent event) {
|
||||
return event.getAction() != Action.RIGHT_CLICK_BLOCK
|
||||
|| event.getClickedBlock() == null
|
||||
|| event.getClickedBlock().getType() != Material.END_PORTAL_FRAME
|
||||
|| event.getItem() == null
|
||||
|| event.getItem().getType() != Material.ENDER_EYE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +130,7 @@ public class MVPortalListener implements InjectableListener {
|
||||
if (event.isCancelled() || event.getTo() == null) {
|
||||
return;
|
||||
}
|
||||
if (!config.isUsingCustomPortalSearch()) {
|
||||
if (config.isUsingCustomPortalSearch()) {
|
||||
event.setSearchRadius(config.getCustomPortalSearchRadius());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class MVWorldListener implements InjectableListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void unloadWorld(WorldUnloadEvent event) {
|
||||
public void worldUnload(WorldUnloadEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class MVWorldListener implements InjectableListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void loadWorld(WorldLoadEvent event) {
|
||||
public void worldLoad(WorldLoadEvent event) {
|
||||
worldManager.getUnloadedWorld(event.getWorld().getName())
|
||||
.peek(world -> {
|
||||
Logging.fine("Loading world: " + world.getName());
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/******************************************************************************
|
||||
* Multiverse 2 Copyright (c) the Multiverse Team 2011. *
|
||||
* Multiverse 2 is licensed under the BSD License. *
|
||||
* For more information please check the README.md file included *
|
||||
* with this project. *
|
||||
******************************************************************************/
|
||||
|
||||
package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import org.mvplugins.multiverse.core.event.MVConfigReloadEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVPlayerTouchedPortalEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVTeleportEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVVersionEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVWorldDeleteEvent;
|
||||
import org.mvplugins.multiverse.core.event.MVWorldPropertyChangeEvent;
|
||||
import org.mvplugins.multiverse.core.inject.InjectableListener;
|
||||
|
||||
/**
|
||||
* Subclasses of this listener can be used to conveniently listen to MultiverseCore-events.
|
||||
*/
|
||||
public abstract class MultiverseCoreListener implements InjectableListener {
|
||||
/**
|
||||
* Called when a {@link MVWorldPropertyChangeEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void worldPropertyChange(MVWorldPropertyChangeEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVWorldDeleteEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void worldDelete(MVWorldDeleteEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVVersionEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void versionRequest(MVVersionEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVTeleportEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void playerTeleport(MVTeleportEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVRespawnEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void playerRespawn(MVRespawnEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVPlayerTouchedPortalEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void playerTouchedPortal(MVPlayerTouchedPortalEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a {@link MVConfigReloadEvent} is fired.
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void configReload(MVConfigReloadEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,32 @@
|
||||
package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
public class CorePermissions {
|
||||
public static String WORLD_ACCESS = "multiverse.access";
|
||||
public static String WORLD_EXEMPT = "multiverse.exempt";
|
||||
public static String GAMEMODE_BYPASS = "mv.bypass.gamemode";
|
||||
public static String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit";
|
||||
final class CorePermissions {
|
||||
/**
|
||||
* Permission to access a world.
|
||||
*/
|
||||
static final String WORLD_ACCESS = "multiverse.access";
|
||||
|
||||
/**
|
||||
* Permission to bypass the entry fee of a world.
|
||||
*/
|
||||
static final String WORLD_EXEMPT = "multiverse.exempt";
|
||||
|
||||
/**
|
||||
* Permission to bypass the gamemode of a world.
|
||||
*/
|
||||
static final String GAMEMODE_BYPASS = "mv.bypass.gamemode";
|
||||
|
||||
/**
|
||||
* Permission to bypass the player limit of a world.
|
||||
*/
|
||||
static final String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit";
|
||||
|
||||
/**
|
||||
* Permission to teleport to a destination.
|
||||
*/
|
||||
static final String TELEPORT = "multiverse.teleport";
|
||||
|
||||
private CorePermissions() {
|
||||
// Prevent instantiation as this is a static utility class
|
||||
}
|
||||
}
|
||||
+65
-1
@@ -1,15 +1,28 @@
|
||||
package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
@Service
|
||||
public class CorePermissionsChecker {
|
||||
|
||||
private DestinationsProvider destinationsProvider;
|
||||
|
||||
@Inject
|
||||
CorePermissionsChecker(@NotNull DestinationsProvider destinationsProvider) {
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
}
|
||||
|
||||
public boolean hasWorldAccessPermission(@NotNull CommandSender sender, @NotNull MultiverseWorld world) {
|
||||
return hasPermission(sender, concatPermission(CorePermissions.WORLD_ACCESS, world.getName()));
|
||||
}
|
||||
@@ -26,7 +39,58 @@ public class CorePermissionsChecker {
|
||||
return hasPermission(sender, concatPermission(CorePermissions.GAMEMODE_BYPASS, world.getName()));
|
||||
}
|
||||
|
||||
private String concatPermission(String permission, String...child) {
|
||||
/**
|
||||
* Checks if the teleporter has permission to teleport the teleportee to the destination.
|
||||
*
|
||||
* @param teleporter The teleporter.
|
||||
* @param teleportee The teleportee.
|
||||
* @param destination The destination.
|
||||
* @return True if the teleporter has permission, false otherwise.
|
||||
*/
|
||||
public boolean checkTeleportPermissions(
|
||||
@NotNull CommandSender teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull ParsedDestination<?> destination) {
|
||||
|
||||
String permission = concatPermission(
|
||||
CorePermissions.TELEPORT,
|
||||
teleportee.equals(teleporter) ? "self" : "other",
|
||||
destination.getDestination().getIdentifier());
|
||||
if (!hasPermission(teleporter, permission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Config whether to use finer permission
|
||||
String finerPermissionSuffix = destination.getDestinationInstance().getFinerPermissionSuffix();
|
||||
if (finerPermissionSuffix == null || finerPermissionSuffix.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String finerPermission = concatPermission(permission, finerPermissionSuffix);
|
||||
return hasPermission(teleporter, finerPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the issuer has permission to teleport to at least one destination.
|
||||
*
|
||||
* @param sender The sender to check.
|
||||
* @return True if the issuer has permission, false otherwise.
|
||||
*/
|
||||
public boolean hasAnyTeleportPermission(CommandSender sender) {
|
||||
for (Destination<?> destination : destinationsProvider.getDestinations()) {
|
||||
String permission = concatPermission(CorePermissions.TELEPORT, "self", destination.getIdentifier());
|
||||
if (hasPermission(sender, permission)) {
|
||||
return true;
|
||||
}
|
||||
permission = concatPermission(CorePermissions.TELEPORT, "other", destination.getIdentifier());
|
||||
if (hasPermission(sender, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String concatPermission(String permission, String... child) {
|
||||
return permission + "." + String.join(".", child);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
|
||||
/**
|
||||
* Teleports entities safely and asynchronously.
|
||||
*/
|
||||
@Service
|
||||
public class AsyncSafetyTeleporter {
|
||||
private final BlockSafety blockSafety;
|
||||
private final TeleportQueue teleportQueue;
|
||||
|
||||
@Inject
|
||||
AsyncSafetyTeleporter(
|
||||
BlockSafety blockSafety,
|
||||
TeleportQueue teleportQueue) {
|
||||
this.blockSafety = blockSafety;
|
||||
this.teleportQueue = teleportQueue;
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleportSafely(
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
return teleportSafely(null, teleportee, destination);
|
||||
}
|
||||
|
||||
public <T extends Entity> Async<List<Attempt<Void, TeleportResult.Failure>>> teleportSafely(
|
||||
@Nullable CommandSender teleporter,
|
||||
@NotNull List<T> teleportees,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
return AsyncAttempt.allOf(teleportees.stream()
|
||||
.map(teleportee -> teleportSafely(teleporter, teleportee, destination))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleportSafely(
|
||||
@Nullable CommandSender teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
if (destination == null) {
|
||||
return AsyncAttempt.failure(TeleportResult.Failure.NULL_DESTINATION);
|
||||
}
|
||||
return destination.getDestination().checkTeleportSafety()
|
||||
? teleportSafely(teleporter, teleportee, destination.getLocation(teleportee))
|
||||
: teleport(teleporter, teleportee, destination.getLocation(teleportee));
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleportSafely(
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable Location location) {
|
||||
return teleportSafely(null, teleportee, location);
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleportSafely(
|
||||
@Nullable CommandSender teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable Location location) {
|
||||
if (location == null) {
|
||||
return AsyncAttempt.failure(TeleportResult.Failure.NULL_LOCATION);
|
||||
}
|
||||
Location safeLocation = blockSafety.getSafeLocation(location);
|
||||
if (safeLocation == null) {
|
||||
return AsyncAttempt.failure(TeleportResult.Failure.UNSAFE_LOCATION);
|
||||
}
|
||||
return teleport(teleporter, teleportee, safeLocation);
|
||||
}
|
||||
|
||||
public <T extends Entity> Async<List<Attempt<Void, TeleportResult.Failure>>> teleport(
|
||||
@NotNull List<T> teleportees,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
return AsyncAttempt.allOf(teleportees.stream()
|
||||
.map(teleportee -> teleport(teleportee, destination))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleport(
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
return teleport(null, teleportee, destination);
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleport(
|
||||
@Nullable CommandSender teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable ParsedDestination<?> destination) {
|
||||
if (destination == null) {
|
||||
return AsyncAttempt.failure(TeleportResult.Failure.NULL_DESTINATION);
|
||||
}
|
||||
return teleport(teleporter, teleportee, destination.getLocation(teleportee));
|
||||
}
|
||||
|
||||
public <T extends Entity> Async<List<Attempt<Void, TeleportResult.Failure>>> teleport(
|
||||
@NotNull List<T> teleportees,
|
||||
@Nullable Location location) {
|
||||
return AsyncAttempt.allOf(teleportees.stream()
|
||||
.map(teleportee -> teleport(teleportee, location))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleport(
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable Location location) {
|
||||
return teleport(null, teleportee, location);
|
||||
}
|
||||
|
||||
public AsyncAttempt<Void, TeleportResult.Failure> teleport(
|
||||
@Nullable CommandSender teleporter,
|
||||
@NotNull Entity teleportee,
|
||||
@Nullable Location location) {
|
||||
if (location == null) {
|
||||
return AsyncAttempt.failure(TeleportResult.Failure.NULL_LOCATION);
|
||||
}
|
||||
|
||||
boolean shouldAddToQueue = teleporter != null && teleportee instanceof Player;
|
||||
if (shouldAddToQueue) {
|
||||
teleportQueue.addToQueue(teleporter.getName(), teleportee.getName());
|
||||
}
|
||||
|
||||
return doAsyncTeleport(teleportee, location, shouldAddToQueue);
|
||||
}
|
||||
|
||||
private AsyncAttempt<Void, TeleportResult.Failure> doAsyncTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location,
|
||||
boolean shouldAddToQueue) {
|
||||
return AsyncAttempt.of(PaperLib.teleportAsync(teleportee, location), exception -> {
|
||||
Logging.warning("Failed to teleport %s to %s: %s",
|
||||
teleportee.getName(), location, exception.getMessage());
|
||||
return Attempt.failure(TeleportResult.Failure.TELEPORT_FAILED_EXCEPTION);
|
||||
}).mapAttempt(result -> {
|
||||
Logging.finer("Teleported async %s to %s", teleportee.getName(), location);
|
||||
if (result) {
|
||||
if (shouldAddToQueue) {
|
||||
teleportQueue.popFromQueue(teleportee.getName());
|
||||
}
|
||||
return Attempt.success(null);
|
||||
}
|
||||
return Attempt.failure(TeleportResult.Failure.TELEPORT_FAILED);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -16,11 +15,14 @@ import jakarta.inject.Inject;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Bed;
|
||||
import org.bukkit.entity.Minecart;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
@@ -31,9 +33,13 @@ import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
*/
|
||||
@Service
|
||||
public class SimpleBlockSafety implements BlockSafety {
|
||||
private final LocationManipulation locationManipulation;
|
||||
private static final Vector DEFAULT_VECTOR = new Vector();
|
||||
private static final int DEFAULT_TOLERANCE = 6;
|
||||
private static final int DEFAULT_RADIUS = 9;
|
||||
private static final Set<BlockFace> AROUND_BLOCK = EnumSet.noneOf(BlockFace.class);
|
||||
|
||||
private final LocationManipulation locationManipulation;
|
||||
|
||||
static {
|
||||
AROUND_BLOCK.add(BlockFace.NORTH);
|
||||
AROUND_BLOCK.add(BlockFace.NORTH_EAST);
|
||||
@@ -46,7 +52,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public SimpleBlockSafety(LocationManipulation locationManipulation) {
|
||||
SimpleBlockSafety(LocationManipulation locationManipulation) {
|
||||
this.locationManipulation = locationManipulation;
|
||||
}
|
||||
|
||||
@@ -113,6 +119,197 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Location findPortalBlockNextTo(Location location) {
|
||||
Block b = location.getWorld().getBlockAt(location);
|
||||
Location foundLocation = null;
|
||||
if (b.getType() == Material.NETHER_PORTAL) {
|
||||
return location;
|
||||
}
|
||||
if (b.getRelative(BlockFace.NORTH).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(location, b.getRelative(BlockFace.NORTH).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.SOUTH).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(location, b.getRelative(BlockFace.SOUTH).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.EAST).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(location, b.getRelative(BlockFace.EAST).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.WEST).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(location, b.getRelative(BlockFace.WEST).getLocation(), foundLocation);
|
||||
}
|
||||
return foundLocation;
|
||||
}
|
||||
|
||||
private static Location getCloserBlock(Location source, Location blockA, Location blockB) {
|
||||
// If B wasn't given, return a.
|
||||
if (blockB == null) {
|
||||
return blockA;
|
||||
}
|
||||
// Center our calculations
|
||||
blockA.add(.5, 0, .5);
|
||||
blockB.add(.5, 0, .5);
|
||||
|
||||
// Retrieve the distance to the normalized blocks
|
||||
double testA = source.distance(blockA);
|
||||
double testB = source.distance(blockB);
|
||||
|
||||
// Compare and return
|
||||
if (testA <= testB) {
|
||||
return blockA;
|
||||
}
|
||||
return blockB;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Location getSafeLocation(Location location) {
|
||||
return this.getSafeLocation(location, DEFAULT_TOLERANCE, DEFAULT_RADIUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Location getSafeLocation(Location location, int tolerance, int radius) {
|
||||
// Check around the player first in a configurable radius:
|
||||
// TODO: Make this configurable
|
||||
Location safe = checkAboveAndBelowLocation(location, tolerance, radius);
|
||||
if (safe != null) {
|
||||
safe.setX(safe.getBlockX() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck
|
||||
safe.setZ(safe.getBlockZ() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck
|
||||
Logging.fine("Hey! I found one: " + locationManipulation.strCoordsRaw(safe));
|
||||
} else {
|
||||
Logging.fine("Uh oh! No safe place found!");
|
||||
}
|
||||
return safe;
|
||||
}
|
||||
|
||||
private Location checkAboveAndBelowLocation(Location l, int tolerance, int radius) {
|
||||
// Tolerance must be an even number:
|
||||
if (tolerance % 2 != 0) {
|
||||
tolerance += 1;
|
||||
}
|
||||
// We want half of it, so we can go up and down
|
||||
tolerance /= 2;
|
||||
Logging.finer("Given Location of: " + locationManipulation.strCoordsRaw(l));
|
||||
Logging.finer("Checking +-" + tolerance + " with a radius of " + radius);
|
||||
|
||||
// For now this will just do a straight up block.
|
||||
Location locToCheck = l.clone();
|
||||
// Check the main level
|
||||
Location safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
// We've already checked zero right above this.
|
||||
int currentLevel = 1;
|
||||
while (currentLevel <= tolerance) {
|
||||
// Check above
|
||||
locToCheck = l.clone();
|
||||
locToCheck.add(0, currentLevel, 0);
|
||||
safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
|
||||
// Check below
|
||||
locToCheck = l.clone();
|
||||
locToCheck.subtract(0, currentLevel, 0);
|
||||
safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
currentLevel++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* For my crappy algorithm, radius MUST be odd.
|
||||
*/
|
||||
private Location checkAroundLocation(Location l, int diameter) {
|
||||
if (diameter % 2 == 0) {
|
||||
diameter += 1;
|
||||
}
|
||||
Location checkLoc = l.clone();
|
||||
|
||||
// Start at 3, the min diameter around a block
|
||||
int loopcounter = 3;
|
||||
while (loopcounter <= diameter) {
|
||||
boolean foundSafeArea = checkAroundSpecificDiameter(checkLoc, loopcounter);
|
||||
// If a safe area was found:
|
||||
if (foundSafeArea) {
|
||||
// Return the checkLoc, it is the safe location.
|
||||
return checkLoc;
|
||||
}
|
||||
// Otherwise, let's reset our location
|
||||
checkLoc = l.clone();
|
||||
// And increment the radius
|
||||
loopcounter += 2;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkAroundSpecificDiameter(Location checkLoc, int circle) {
|
||||
// Adjust the circle to get how many blocks to step out.
|
||||
// A radius of 3 makes the block step 1
|
||||
// A radius of 5 makes the block step 2
|
||||
// A radius of 7 makes the block step 3
|
||||
// ...
|
||||
int adjustedCircle = ((circle - 1) / 2);
|
||||
checkLoc.add(adjustedCircle, 0, 0);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
// Now we go to the right that adjustedCircle many
|
||||
for (int i = 0; i < adjustedCircle; i++) {
|
||||
checkLoc.add(0, 0, 1);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then down adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(-1, 0, 0);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then left adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(0, 0, -1);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then up Then left adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(1, 0, 0);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then finish up by doing adjustedCircle - 1
|
||||
for (int i = 0; i < adjustedCircle - 1; i++) {
|
||||
checkLoc.add(0, 0, 1);
|
||||
if (playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -122,7 +319,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
if (l == null) {
|
||||
return null;
|
||||
}
|
||||
final Location trySpawn = this.getSafeSpawnAroundABlock(l);
|
||||
final Location trySpawn = this.getSafeSpawnAroundBlock(l);
|
||||
if (trySpawn != null) {
|
||||
return trySpawn;
|
||||
}
|
||||
@@ -131,18 +328,17 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
return null;
|
||||
}
|
||||
// Now we have 2 locations, check around each, if the type is bed, skip it.
|
||||
return this.getSafeSpawnAroundABlock(otherBlock);
|
||||
return this.getSafeSpawnAroundBlock(otherBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a safe spawn around a location. (N,S,E,W,NE,NW,SE,SW)
|
||||
*
|
||||
* @param l Location to check around
|
||||
* @return A safe location, or none if it wasn't found.
|
||||
*/
|
||||
private Location getSafeSpawnAroundABlock(Location l) {
|
||||
Iterator<BlockFace> checkblock = AROUND_BLOCK.iterator();
|
||||
while (checkblock.hasNext()) {
|
||||
final BlockFace face = checkblock.next();
|
||||
private Location getSafeSpawnAroundBlock(Location l) {
|
||||
for (BlockFace face : AROUND_BLOCK) {
|
||||
if (this.playerCanSpawnHereSafely(l.getBlock().getRelative(face).getLocation())) {
|
||||
// Don't forget to center the player.
|
||||
return l.getBlock().getRelative(face).getLocation().add(.5, 0, .5);
|
||||
@@ -158,16 +354,15 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
*/
|
||||
private Location findOtherBedPiece(Location checkLoc) {
|
||||
BlockData data = checkLoc.getBlock().getBlockData();
|
||||
if (!(data instanceof Bed)) {
|
||||
if (!(data instanceof Bed bed)) {
|
||||
return null;
|
||||
}
|
||||
Bed b = (Bed) data;
|
||||
|
||||
if (b.getPart() == Bed.Part.HEAD) {
|
||||
return checkLoc.getBlock().getRelative(b.getFacing().getOppositeFace()).getLocation();
|
||||
if (bed.getPart() == Bed.Part.HEAD) {
|
||||
return checkLoc.getBlock().getRelative(bed.getFacing().getOppositeFace()).getLocation();
|
||||
}
|
||||
// We shouldn't ever be looking at the foot, but here's the code for it.
|
||||
return checkLoc.getBlock().getRelative(b.getFacing()).getLocation();
|
||||
return checkLoc.getBlock().getRelative(bed.getFacing()).getLocation();
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +401,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
/*
|
||||
* If someone has a better way of this... Please either tell us, or submit a pull request!
|
||||
*/
|
||||
public static boolean isSolidBlock(Material type) {
|
||||
private static boolean isSolidBlock(Material type) {
|
||||
return type.isSolid();
|
||||
}
|
||||
|
||||
@@ -237,7 +432,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
if (oneBelow.getBlock().getType() == Material.WATER) {
|
||||
Location twoBelow = oneBelow.clone();
|
||||
twoBelow.subtract(0, 1, 0);
|
||||
return (oneBelow.getBlock().getType() == Material.WATER);
|
||||
return oneBelow.getBlock().getType() == Material.WATER;
|
||||
}
|
||||
if (oneBelow.getBlock().getType() != Material.AIR) {
|
||||
return false;
|
||||
@@ -253,10 +448,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
if (this.isBlockAboveAir(cart.getLocation())) {
|
||||
return true;
|
||||
}
|
||||
if (this.isEntitiyOnTrack(locationManipulation.getNextBlock(cart))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return this.isEntitiyOnTrack(locationManipulation.getNextBlock(cart));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,10 +456,7 @@ public class SimpleBlockSafety implements BlockSafety {
|
||||
*/
|
||||
@Override
|
||||
public boolean canSpawnVehicleSafely(Vehicle vehicle) {
|
||||
if (this.isBlockAboveAir(vehicle.getLocation())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return this.isBlockAboveAir(vehicle.getLocation());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/******************************************************************************
|
||||
* Multiverse 2 Copyright (c) the Multiverse Team 2011. *
|
||||
* Multiverse 2 is licensed under the BSD License. *
|
||||
* For more information please check the README.md file included *
|
||||
* with this project. *
|
||||
******************************************************************************/
|
||||
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Minecart;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.api.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.destination.ParsedDestination;
|
||||
|
||||
/**
|
||||
* The default-implementation of {@link SafeTTeleporter}.
|
||||
*/
|
||||
@Service
|
||||
public class SimpleSafeTTeleporter implements SafeTTeleporter {
|
||||
private final MultiverseCore plugin;
|
||||
private final LocationManipulation locationManipulation;
|
||||
private final BlockSafety blockSafety;
|
||||
private final TeleportQueue teleportQueue;
|
||||
|
||||
@Inject
|
||||
public SimpleSafeTTeleporter(
|
||||
MultiverseCore plugin,
|
||||
LocationManipulation locationManipulation,
|
||||
BlockSafety blockSafety,
|
||||
TeleportQueue teleportQueue
|
||||
) {
|
||||
this.plugin = plugin;
|
||||
this.locationManipulation = locationManipulation;
|
||||
this.blockSafety = blockSafety;
|
||||
this.teleportQueue = teleportQueue;
|
||||
}
|
||||
|
||||
private static final Vector DEFAULT_VECTOR = new Vector();
|
||||
private static final int DEFAULT_TOLERANCE = 6;
|
||||
private static final int DEFAULT_RADIUS = 9;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Location getSafeLocation(Location l) {
|
||||
return this.getSafeLocation(l, DEFAULT_TOLERANCE, DEFAULT_RADIUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Location getSafeLocation(Location l, int tolerance, int radius) {
|
||||
// Check around the player first in a configurable radius:
|
||||
// TODO: Make this configurable
|
||||
Location safe = checkAboveAndBelowLocation(l, tolerance, radius);
|
||||
if (safe != null) {
|
||||
safe.setX(safe.getBlockX() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck
|
||||
safe.setZ(safe.getBlockZ() + .5); // SUPPRESS CHECKSTYLE: MagicNumberCheck
|
||||
Logging.fine("Hey! I found one: " + locationManipulation.strCoordsRaw(safe));
|
||||
} else {
|
||||
Logging.fine("Uh oh! No safe place found!");
|
||||
}
|
||||
return safe;
|
||||
}
|
||||
|
||||
private Location checkAboveAndBelowLocation(Location l, int tolerance, int radius) {
|
||||
// Tolerance must be an even number:
|
||||
if (tolerance % 2 != 0) {
|
||||
tolerance += 1;
|
||||
}
|
||||
// We want half of it, so we can go up and down
|
||||
tolerance /= 2;
|
||||
Logging.finer("Given Location of: " + locationManipulation.strCoordsRaw(l));
|
||||
Logging.finer("Checking +-" + tolerance + " with a radius of " + radius);
|
||||
|
||||
// For now this will just do a straight up block.
|
||||
Location locToCheck = l.clone();
|
||||
// Check the main level
|
||||
Location safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
// We've already checked zero right above this.
|
||||
int currentLevel = 1;
|
||||
while (currentLevel <= tolerance) {
|
||||
// Check above
|
||||
locToCheck = l.clone();
|
||||
locToCheck.add(0, currentLevel, 0);
|
||||
safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
|
||||
// Check below
|
||||
locToCheck = l.clone();
|
||||
locToCheck.subtract(0, currentLevel, 0);
|
||||
safe = this.checkAroundLocation(locToCheck, radius);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
currentLevel++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* For my crappy algorithm, radius MUST be odd.
|
||||
*/
|
||||
private Location checkAroundLocation(Location l, int diameter) {
|
||||
if (diameter % 2 == 0) {
|
||||
diameter += 1;
|
||||
}
|
||||
Location checkLoc = l.clone();
|
||||
|
||||
// Start at 3, the min diameter around a block
|
||||
int loopcounter = 3;
|
||||
while (loopcounter <= diameter) {
|
||||
boolean foundSafeArea = checkAroundSpecificDiameter(checkLoc, loopcounter);
|
||||
// If a safe area was found:
|
||||
if (foundSafeArea) {
|
||||
// Return the checkLoc, it is the safe location.
|
||||
return checkLoc;
|
||||
}
|
||||
// Otherwise, let's reset our location
|
||||
checkLoc = l.clone();
|
||||
// And increment the radius
|
||||
loopcounter += 2;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkAroundSpecificDiameter(Location checkLoc, int circle) {
|
||||
// Adjust the circle to get how many blocks to step out.
|
||||
// A radius of 3 makes the block step 1
|
||||
// A radius of 5 makes the block step 2
|
||||
// A radius of 7 makes the block step 3
|
||||
// ...
|
||||
int adjustedCircle = ((circle - 1) / 2);
|
||||
checkLoc.add(adjustedCircle, 0, 0);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
// Now we go to the right that adjustedCircle many
|
||||
for (int i = 0; i < adjustedCircle; i++) {
|
||||
checkLoc.add(0, 0, 1);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then down adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(-1, 0, 0);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then left adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(0, 0, -1);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then up Then left adjustedCircle *2
|
||||
for (int i = 0; i < adjustedCircle * 2; i++) {
|
||||
checkLoc.add(1, 0, 0);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then finish up by doing adjustedCircle - 1
|
||||
for (int i = 0; i < adjustedCircle - 1; i++) {
|
||||
checkLoc.add(0, 0, 1);
|
||||
if (blockSafety.playerCanSpawnHereSafely(checkLoc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public TeleportResult safelyTeleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
|
||||
return safelyTeleportAsync(teleporter, teleportee, destination).join();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TeleportResult> safelyTeleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
|
||||
if (destination == null) {
|
||||
Logging.finer("Entity tried to teleport to an invalid destination");
|
||||
return CompletableFuture.completedFuture(TeleportResult.FAIL_INVALID);
|
||||
}
|
||||
|
||||
Player teleporteePlayer = null;
|
||||
if (teleportee instanceof Player) {
|
||||
teleporteePlayer = ((Player) teleportee);
|
||||
} else if (teleportee.getPassenger() instanceof Player) {
|
||||
teleporteePlayer = ((Player) teleportee.getPassenger());
|
||||
}
|
||||
|
||||
if (teleporteePlayer == null) {
|
||||
return CompletableFuture.completedFuture(TeleportResult.FAIL_INVALID);
|
||||
}
|
||||
|
||||
teleportQueue.addToQueue(teleporter.getIssuer().getName(), teleporteePlayer.getName());
|
||||
|
||||
Location safeLoc = destination.getLocation(teleportee);
|
||||
if (destination.getDestination().checkTeleportSafety()) {
|
||||
safeLoc = this.getSafeLocation(teleportee, destination.getDestinationInstance());
|
||||
}
|
||||
|
||||
if (safeLoc == null) {
|
||||
return CompletableFuture.completedFuture(TeleportResult.FAIL_UNSAFE);
|
||||
}
|
||||
|
||||
CompletableFuture<TeleportResult> future = new CompletableFuture<>();
|
||||
|
||||
PaperLib.teleportAsync(teleportee, safeLoc).thenAccept(result -> {
|
||||
if (!result) {
|
||||
future.complete(TeleportResult.FAIL_OTHER);
|
||||
return;
|
||||
}
|
||||
Vector v = destination.getDestinationInstance().getVelocity(teleportee);
|
||||
if (v != null && !DEFAULT_VECTOR.equals(v)) {
|
||||
Bukkit.getScheduler().runTaskLater(this.plugin, () -> {
|
||||
teleportee.setVelocity(v);
|
||||
}, 1);
|
||||
}
|
||||
future.complete(TeleportResult.SUCCESS);
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public TeleportResult safelyTeleport(CommandSender teleporter, Entity teleportee, Location location, boolean safely) {
|
||||
if (safely) {
|
||||
location = this.getSafeLocation(location);
|
||||
}
|
||||
|
||||
if (location != null) {
|
||||
if (teleportee.teleport(location)) {
|
||||
return TeleportResult.SUCCESS;
|
||||
}
|
||||
return TeleportResult.FAIL_OTHER;
|
||||
}
|
||||
return TeleportResult.FAIL_UNSAFE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getSafeLocation(Entity entity, DestinationInstance destination) {
|
||||
Location l = destination.getLocation(entity);
|
||||
if (blockSafety.playerCanSpawnHereSafely(l)) {
|
||||
Logging.fine("The first location you gave me was safe.");
|
||||
return l;
|
||||
}
|
||||
if (entity instanceof Minecart) {
|
||||
Minecart m = (Minecart) entity;
|
||||
if (!blockSafety.canSpawnCartSafely(m)) {
|
||||
return null;
|
||||
}
|
||||
} else if (entity instanceof Vehicle) {
|
||||
Vehicle v = (Vehicle) entity;
|
||||
if (!blockSafety.canSpawnVehicleSafely(v)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Location safeLocation = this.getSafeLocation(l);
|
||||
if (safeLocation != null) {
|
||||
// Add offset to account for a vehicle on dry land!
|
||||
if (entity instanceof Minecart && !blockSafety.isEntitiyOnTrack(safeLocation)) {
|
||||
safeLocation.setY(safeLocation.getBlockY() + .5);
|
||||
Logging.finer("Player was inside a minecart. Offsetting Y location.");
|
||||
}
|
||||
Logging.finer("Had to look for a bit, but I found a safe place for ya!");
|
||||
return safeLocation;
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
Player p = (Player) entity;
|
||||
p.sendMessage("No safe locations found!");
|
||||
Logging.finer("No safe location found for " + p.getName());
|
||||
} else if (entity.getPassenger() instanceof Player) {
|
||||
Player p = (Player) entity.getPassenger();
|
||||
p.sendMessage("No safe locations found!");
|
||||
Logging.finer("No safe location found for " + p.getName());
|
||||
}
|
||||
Logging.fine("Sorry champ, you're basically trying to teleport into a minefield. I should just kill you now.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Location findPortalBlockNextTo(Location l) {
|
||||
Block b = l.getWorld().getBlockAt(l);
|
||||
Location foundLocation = null;
|
||||
if (b.getType() == Material.NETHER_PORTAL) {
|
||||
return l;
|
||||
}
|
||||
if (b.getRelative(BlockFace.NORTH).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(l, b.getRelative(BlockFace.NORTH).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.SOUTH).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(l, b.getRelative(BlockFace.SOUTH).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.EAST).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(l, b.getRelative(BlockFace.EAST).getLocation(), foundLocation);
|
||||
}
|
||||
if (b.getRelative(BlockFace.WEST).getType() == Material.NETHER_PORTAL) {
|
||||
foundLocation = getCloserBlock(l, b.getRelative(BlockFace.WEST).getLocation(), foundLocation);
|
||||
}
|
||||
return foundLocation;
|
||||
}
|
||||
|
||||
private static Location getCloserBlock(Location source, Location blockA, Location blockB) {
|
||||
// If B wasn't given, return a.
|
||||
if (blockB == null) {
|
||||
return blockA;
|
||||
}
|
||||
// Center our calculations
|
||||
blockA.add(.5, 0, .5);
|
||||
blockB.add(.5, 0, .5);
|
||||
|
||||
// Retrieve the distance to the normalized blocks
|
||||
double testA = source.distance(blockA);
|
||||
double testB = source.distance(blockB);
|
||||
|
||||
// Compare and return
|
||||
if (testA <= testB) {
|
||||
return blockA;
|
||||
}
|
||||
return blockB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeleportResult teleport(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
|
||||
return safelyTeleport(teleporter, teleportee, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TeleportResult> teleportAsync(BukkitCommandIssuer teleporter, Entity teleportee, ParsedDestination<?> destination) {
|
||||
return safelyTeleportAsync(teleporter, teleportee, destination);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class TeleportQueue {
|
||||
|
||||
private final Map<String, String> teleportQueue;
|
||||
|
||||
public TeleportQueue() {
|
||||
TeleportQueue() {
|
||||
teleportQueue = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
/******************************************************************************
|
||||
* Multiverse 2 Copyright (c) the Multiverse Team 2011. *
|
||||
* Multiverse 2 is licensed under the BSD License. *
|
||||
* For more information please check the README.md file included *
|
||||
* with this project. *
|
||||
******************************************************************************/
|
||||
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
/**
|
||||
* An enum containing possible teleport-results.
|
||||
*/
|
||||
public enum TeleportResult {
|
||||
/**
|
||||
* Insufficient permissions.
|
||||
*/
|
||||
FAIL_PERMISSION,
|
||||
/**
|
||||
* The teleport was unsafe.
|
||||
*/
|
||||
FAIL_UNSAFE,
|
||||
/**
|
||||
* The player was to poor.
|
||||
*/
|
||||
FAIL_TOO_POOR,
|
||||
/**
|
||||
* The teleport was invalid.
|
||||
*/
|
||||
FAIL_INVALID,
|
||||
/**
|
||||
* Unknown reason.
|
||||
*/
|
||||
FAIL_OTHER,
|
||||
/**
|
||||
* The player was successfully teleported.
|
||||
*/
|
||||
SUCCESS
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.utils.result.SuccessReason;
|
||||
|
||||
public class TeleportResult {
|
||||
public enum Success implements SuccessReason {
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
public enum Failure implements FailureReason {
|
||||
NULL_DESTINATION,
|
||||
NULL_LOCATION,
|
||||
UNSAFE_LOCATION,
|
||||
TELEPORT_FAILED,
|
||||
TELEPORT_FAILED_EXCEPTION,
|
||||
PLAYER_OFFLINE,
|
||||
}
|
||||
}
|
||||
@@ -43,21 +43,32 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
// delete command
|
||||
DELETE_DELETING,
|
||||
DELETE_PROMPT,
|
||||
DELETE_SUCCESS,
|
||||
|
||||
// Dumps command
|
||||
DUMPS_DESCRIPTION,
|
||||
DUMPS_URL_LIST,
|
||||
|
||||
// gamerule command
|
||||
GAMERULE_FAILED,
|
||||
GAMERULE_SUCCESS_SINGLE,
|
||||
GAMERULE_SUCCESS_MULTIPLE,
|
||||
// gamerule set command
|
||||
GAMERULE_SET_FAILED,
|
||||
GAMERULE_SET_SUCCESS_SINGLE,
|
||||
GAMERULE_SET_SUCCESS_MULTIPLE,
|
||||
|
||||
// Gamerules command
|
||||
GAMERULES_DESCRIPTION,
|
||||
GAMERULES_DESCRIPTION_PAGE,
|
||||
GAMERULES_DESCRIPTION_WORLD,
|
||||
GAMERULES_TITLE,
|
||||
// gamerule reset command
|
||||
GAMERULE_RESET_FAILED,
|
||||
GAMERULE_RESET_SUCCESS_SINGLE,
|
||||
GAMERULE_RESET_SUCCESS_MULTIPLE,
|
||||
|
||||
// gamerule list command
|
||||
GAMERULE_LIST_DESCRIPTION,
|
||||
GAMERULE_LIST_DESCRIPTION_PAGE,
|
||||
GAMERULE_LIST_DESCRIPTION_WORLD,
|
||||
GAMERULE_LIST_TITLE,
|
||||
|
||||
// Generators command
|
||||
GENERATORS_DESCRIPTION,
|
||||
GENERATORS_DESCRIPTION_FLAGS,
|
||||
GENERATORS_EMPTY,
|
||||
|
||||
// import command
|
||||
IMPORT_IMPORTING,
|
||||
@@ -76,6 +87,9 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
RELOAD_RELOADING,
|
||||
RELOAD_SUCCESS,
|
||||
|
||||
// remove command
|
||||
REMOVE_SUCCESS,
|
||||
|
||||
// root MV command
|
||||
ROOT_TITLE,
|
||||
ROOT_HELP,
|
||||
@@ -87,6 +101,11 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
UNLOAD_UNLOADING,
|
||||
UNLOAD_SUCCESS,
|
||||
|
||||
// version command
|
||||
VERSION_MV,
|
||||
VERSION_AUTHORS,
|
||||
VERSION_SECRETCODE,
|
||||
|
||||
// debug command
|
||||
DEBUG_INFO_OFF,
|
||||
DEBUG_INFO_ON,
|
||||
@@ -115,7 +134,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
CREATEWORLD_WORLDEXISTLOADED,
|
||||
CREATEWORLD_BUKKITCREATIONFAILED,
|
||||
|
||||
DELETE_SUCCESS,
|
||||
DELETEWORLD_WORLDNONEXISTENT,
|
||||
DELETEWORLD_LOADFAILED,
|
||||
DELETEWORLD_WORLDFOLDERNOTFOUND,
|
||||
@@ -133,7 +151,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
LOADWORLD_WORLDEXISTLOADED,
|
||||
LOADWORLD_BUKKITCREATIONFAILED,
|
||||
|
||||
REMOVEWORLD_REMOVED,
|
||||
REMOVEWORLD_WORLDNONEXISTENT,
|
||||
|
||||
UNLOADWORLD_WORLDALREADYUNLOADING,
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig;
|
||||
|
||||
/**
|
||||
* Wraps calls that could result in exceptions that are not Multiverse's fault.
|
||||
*/
|
||||
@Service
|
||||
public class UnsafeCallWrapper {
|
||||
private final MVCoreConfig config;
|
||||
|
||||
@Inject
|
||||
public UnsafeCallWrapper(MVCoreConfig configProvider) {
|
||||
this.config = configProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps calls that could result in exceptions that are not Multiverse's fault.
|
||||
*
|
||||
* @param callable The potentially unsafe call.
|
||||
* @param plugin The plugin that's probably the culprit.
|
||||
* @param action What MV was attempting to do (error message, format string).
|
||||
* @param formatArgs The formatting arguments for the error message.
|
||||
* The exception that was thrown will be appended to these objects.
|
||||
* @param <T> The type of the return value.
|
||||
* @return The return value or null if the call failed.
|
||||
*/
|
||||
public <T> T wrap(Callable<T> callable, String plugin, String action, Object... formatArgs) {
|
||||
try {
|
||||
// We're ready to catch you! JUMP!
|
||||
return callable.call();
|
||||
} catch (Throwable t) {
|
||||
Object[] actualFormatArgs = new Object[formatArgs.length + 1];
|
||||
System.arraycopy(formatArgs, 0, actualFormatArgs, 0, formatArgs.length);
|
||||
actualFormatArgs[formatArgs.length] = t;
|
||||
Logging.warning(action, actualFormatArgs);
|
||||
Logging.warning("This is a bug in %s, NOT a bug in Multiverse!", plugin);
|
||||
if (config.getGlobalDebug() >= 1)
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.mvplugins.multiverse.core.utils.result;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Represents the result of an asynchronous operation that wraps a {@link CompletableFuture}.
|
||||
*
|
||||
* @param <T> The type of the value.
|
||||
*/
|
||||
public final class Async<T> {
|
||||
|
||||
/**
|
||||
* Returns a new AsyncResult that is completed when all of the given AsyncResult complete.
|
||||
*
|
||||
* @param results The results to wait for.
|
||||
* @return A new AsyncResult that is completed when all of the given AsyncResult complete.
|
||||
*/
|
||||
public static Async<Void> allOf(Async<?>... results) {
|
||||
return new Async<>(CompletableFuture.allOf(Arrays.stream(results)
|
||||
.map(result -> result.future)
|
||||
.toArray(CompletableFuture[]::new)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new AsyncResult that is completed when all of the given AsyncResult with the same type complete.
|
||||
*
|
||||
* @param results The results to wait for.
|
||||
* @param <T> The type of the AsyncResult.
|
||||
* @return A new AsyncResult that is completed when all of the given AsyncResult complete.
|
||||
*/
|
||||
public static <T> Async<List<T>> allOf(List<Async<T>> results) {
|
||||
return new Async<>(CompletableFuture.allOf(results.stream()
|
||||
.map(result -> result.future)
|
||||
.toArray(CompletableFuture[]::new))
|
||||
.thenApply(v -> results.stream()
|
||||
.map(result -> result.future.join())
|
||||
.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a CompletableFuture in an AsyncResult.
|
||||
*
|
||||
* @param future The future to wrap.
|
||||
* @param <T> The type of the future.
|
||||
* @return A new AsyncResult that is completed when the given future completes.
|
||||
*/
|
||||
public static <T> Async<T> of(CompletableFuture<T> future) {
|
||||
return new Async<>(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new AsyncResult that is already completed with the given value.
|
||||
*
|
||||
* @param value The value to complete the AsyncResult with.
|
||||
* @param <T> The type of the value.
|
||||
* @return The completed AsyncResult.
|
||||
*/
|
||||
public static <T> Async<T> completedFuture(T value) {
|
||||
return new Async<>(CompletableFuture.completedFuture(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new CompletableFuture that is already completed exceptionally with the given exception.
|
||||
*
|
||||
* @param throwable The exception to complete the AsyncResult with.
|
||||
* @param <T> The type of the value.
|
||||
* @return The completed AsyncResult.
|
||||
*/
|
||||
public static <T> Async<T> failedFuture(Throwable throwable) {
|
||||
return new Async<>(CompletableFuture.failedFuture(throwable));
|
||||
}
|
||||
|
||||
private final CompletableFuture<T> future;
|
||||
|
||||
/**
|
||||
* Creates a new AsyncResult.
|
||||
*/
|
||||
public Async() {
|
||||
this(new CompletableFuture<>());
|
||||
}
|
||||
|
||||
private Async(CompletableFuture<T> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
/**
|
||||
* If not already completed, sets the value returned by related methods to the given value.
|
||||
*
|
||||
* @param value The value to complete the AsyncResult with.
|
||||
* @return true if this invocation caused this AsyncResult to transition to a completed state, else false.
|
||||
*/
|
||||
public boolean complete(T value) {
|
||||
return future.complete(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If not already completed, causes invocations of related methods to throw the given exception.
|
||||
*
|
||||
* @param throwable The exception to complete the AsyncResult with.
|
||||
* @return true if this invocation caused this AsyncResult to transition to a completed state, else false.
|
||||
*/
|
||||
public boolean completeExceptionally(Throwable throwable) {
|
||||
return future.completeExceptionally(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action when this AsyncResult completes.
|
||||
*
|
||||
* @param consumer The action to perform.
|
||||
* @return This AsyncResult.
|
||||
*/
|
||||
public Async<Void> thenAccept(Consumer<T> consumer) {
|
||||
return new Async<>(future.thenAccept(consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action when this AsyncResult completes.
|
||||
*
|
||||
* @param runnable The action to perform.
|
||||
* @return This AsyncResult.
|
||||
*/
|
||||
public Async<Void> thenRun(Runnable runnable) {
|
||||
return new Async<>(future.thenRun(runnable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action when this AsyncResult completes and returns a new AsyncResult with the new value.
|
||||
*
|
||||
* @param function The action to perform.
|
||||
* @param <U> The type of the new value.
|
||||
* @return A new AsyncResult with the new value.
|
||||
*/
|
||||
public <U> Async<U> thenApply(Function<T, U> function) {
|
||||
return new Async<>(future.thenApply(function));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action when this AsyncResult completes with an exception.
|
||||
*
|
||||
* @param consumer The action to perform.
|
||||
* @return This AsyncResult.
|
||||
*/
|
||||
public Async<T> exceptionally(Consumer<Throwable> consumer) {
|
||||
return new Async<>(future.exceptionally(throwable -> {
|
||||
consumer.accept(throwable);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given action when this AsyncResult completes with an exception and returns a new AsyncResult with the new value.
|
||||
*
|
||||
* @param function The action to perform.
|
||||
* @return A new AsyncResult with the new value.
|
||||
*/
|
||||
public Async<T> exceptionally(Function<Throwable, T> function) {
|
||||
return new Async<>(future.exceptionally(function));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.mvplugins.multiverse.core.utils.result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.mvplugins.multiverse.core.utils.message.MessageReplacement;
|
||||
|
||||
public final class AsyncAttempt<T, F extends FailureReason> {
|
||||
|
||||
public static <T, F extends FailureReason> Async<List<Attempt<T, F>>> allOf(List<AsyncAttempt<T, F>> attempts) {
|
||||
return Async.of(CompletableFuture.allOf(attempts.stream()
|
||||
.map(attempt -> attempt.future)
|
||||
.toArray(CompletableFuture[]::new))
|
||||
.thenApply(v -> attempts.stream()
|
||||
.map(attempt -> attempt.future.join())
|
||||
.toList()));
|
||||
}
|
||||
|
||||
public static <T, F extends FailureReason> AsyncAttempt<T, F> of(
|
||||
CompletableFuture<T> future,
|
||||
BiFunction<T, Throwable, Attempt<T, F>> completionHandler) {
|
||||
return new AsyncAttempt<>(future.handle(completionHandler));
|
||||
}
|
||||
|
||||
public static <T, F extends FailureReason> AsyncAttempt<T, F> of(
|
||||
CompletableFuture<T> future,
|
||||
Function<Throwable, Attempt<T, F>> exceptionHandler) {
|
||||
BiFunction<T, Throwable, Attempt<T, F>> completionHandler = (result, exception) -> exception != null
|
||||
? exceptionHandler.apply(exception)
|
||||
: Attempt.success(result);
|
||||
return of(future, completionHandler);
|
||||
}
|
||||
|
||||
public static <F extends FailureReason> AsyncAttempt<Void, F> success() {
|
||||
return new AsyncAttempt<>(CompletableFuture.completedFuture(null));
|
||||
}
|
||||
|
||||
public static <F extends FailureReason> AsyncAttempt<Void, F> failure(
|
||||
F failureReason,
|
||||
MessageReplacement... messageReplacements) {
|
||||
return new AsyncAttempt<>(CompletableFuture.completedFuture(
|
||||
Attempt.failure(failureReason, messageReplacements)));
|
||||
}
|
||||
|
||||
private final CompletableFuture<Attempt<T, F>> future;
|
||||
|
||||
private AsyncAttempt(CompletableFuture<Attempt<T, F>> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
public <U> AsyncAttempt<U, F> map(Function<? super T, ? extends U> mapper) {
|
||||
return new AsyncAttempt<>(future.thenApply(attempt -> attempt.map(mapper)));
|
||||
}
|
||||
|
||||
public <U> AsyncAttempt<U, F> mapAttempt(Function<? super T, Attempt<U, F>> mapper) {
|
||||
return new AsyncAttempt<>(future.thenApply(attempt -> attempt.mapAttempt(mapper)));
|
||||
}
|
||||
|
||||
public <U> AsyncAttempt<U, F> mapAsyncAttempt(Function<? super T, AsyncAttempt<U, F>> mapper) {
|
||||
return new AsyncAttempt<>(future.thenApplyAsync(
|
||||
attempt -> attempt.mapAttempt(rasult -> mapper.apply(rasult).toAttempt())));
|
||||
}
|
||||
|
||||
public AsyncAttempt<T, F> onSuccess(Runnable runnable) {
|
||||
return new AsyncAttempt<>(future.thenApply(attempt -> attempt.onSuccess(runnable)));
|
||||
}
|
||||
|
||||
public Attempt<T, F> toAttempt() {
|
||||
return future.join();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.config.NullLocation;
|
||||
import org.mvplugins.multiverse.core.world.config.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.config.WorldConfig;
|
||||
@@ -29,19 +28,16 @@ public class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
private final UUID worldUid;
|
||||
|
||||
private final BlockSafety blockSafety;
|
||||
private final SafeTTeleporter safetyTeleporter;
|
||||
private final LocationManipulation locationManipulation;
|
||||
|
||||
LoadedMultiverseWorld(
|
||||
@NotNull World world,
|
||||
@NotNull WorldConfig worldConfig,
|
||||
@NotNull BlockSafety blockSafety,
|
||||
@NotNull SafeTTeleporter safetyTeleporter,
|
||||
@NotNull LocationManipulation locationManipulation) {
|
||||
super(world.getName(), worldConfig);
|
||||
this.worldUid = world.getUID();
|
||||
this.blockSafety = blockSafety;
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
this.locationManipulation = locationManipulation;
|
||||
|
||||
setupWorldConfig(world);
|
||||
@@ -82,7 +78,7 @@ public class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
// The location is not safe, so we need to find a better one.
|
||||
Logging.warning("Spawn location from world.dat file was unsafe. Adjusting...");
|
||||
Logging.warning("Original Location: " + locationManipulation.strCoordsRaw(location));
|
||||
Location newSpawn = safetyTeleporter.getSafeLocation(location,
|
||||
Location newSpawn = blockSafety.getSafeLocation(location,
|
||||
SPAWN_LOCATION_SEARCH_TOLERANCE, SPAWN_LOCATION_SEARCH_RADIUS);
|
||||
// I think we could also do this, as I think this is what Notch does.
|
||||
// Not sure how it will work in the nether...
|
||||
@@ -107,6 +103,15 @@ public class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Unique ID of this world.
|
||||
*
|
||||
* @return Unique ID of this world.
|
||||
*/
|
||||
public UUID getUID() {
|
||||
return worldUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Bukkit world object that this world describes.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -13,6 +12,7 @@ import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.world.config.AllowedPortalType;
|
||||
import org.mvplugins.multiverse.core.world.config.WorldConfig;
|
||||
|
||||
@@ -57,34 +57,12 @@ public class MultiverseWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties that can be configured on this world. Can be used for {@link #getProperty(String)} and
|
||||
* {@link #setProperty(String, Object)}.
|
||||
* Gets the properties handler of this world.
|
||||
*
|
||||
* @return A collection of property names.
|
||||
* @return The properties handler of this world.
|
||||
*/
|
||||
public Collection<String> getConfigurablePropertyNames() {
|
||||
return worldConfig.getConfigurablePropertyNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property on this world.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @return The value of the property.
|
||||
*/
|
||||
public Try<Object> getProperty(String name) {
|
||||
return worldConfig.getProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property on this world.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The value of the property.
|
||||
* @return Result of setting property.
|
||||
*/
|
||||
public Try<Void> setProperty(String name, Object value) {
|
||||
return worldConfig.setProperty(name, value);
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return worldConfig.getStringPropertyHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.utils.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
@@ -75,7 +74,6 @@ public class WorldManager {
|
||||
private final PlayerWorldTeleporter playerWorldActions;
|
||||
private final FilesManipulator filesManipulator;
|
||||
private final BlockSafety blockSafety;
|
||||
private final SafeTTeleporter safetyTeleporter;
|
||||
private final LocationManipulation locationManipulation;
|
||||
|
||||
@Inject
|
||||
@@ -86,7 +84,6 @@ public class WorldManager {
|
||||
@NotNull PlayerWorldTeleporter playerWorldActions,
|
||||
@NotNull FilesManipulator filesManipulator,
|
||||
@NotNull BlockSafety blockSafety,
|
||||
@NotNull SafeTTeleporter safetyTeleporter,
|
||||
@NotNull LocationManipulation locationManipulation) {
|
||||
this.worldsMap = new HashMap<>();
|
||||
this.loadedWorldsMap = new HashMap<>();
|
||||
@@ -99,7 +96,6 @@ public class WorldManager {
|
||||
this.playerWorldActions = playerWorldActions;
|
||||
this.filesManipulator = filesManipulator;
|
||||
this.blockSafety = blockSafety;
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
this.locationManipulation = locationManipulation;
|
||||
}
|
||||
|
||||
@@ -297,7 +293,6 @@ public class WorldManager {
|
||||
world,
|
||||
worldConfig,
|
||||
blockSafety,
|
||||
safetyTeleporter,
|
||||
locationManipulation);
|
||||
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
|
||||
saveWorldsConfig();
|
||||
@@ -354,7 +349,6 @@ public class WorldManager {
|
||||
world,
|
||||
worldConfig,
|
||||
blockSafety,
|
||||
safetyTeleporter,
|
||||
locationManipulation);
|
||||
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
|
||||
saveWorldsConfig();
|
||||
@@ -377,10 +371,6 @@ public class WorldManager {
|
||||
return worldActionResult(UnloadFailureReason.WORLD_ALREADY_UNLOADING, world.getName());
|
||||
}
|
||||
|
||||
if (options.removePlayers()) {
|
||||
playerWorldActions.removeFromWorld(world);
|
||||
}
|
||||
|
||||
return unloadBukkitWorld(world.getBukkitWorld().getOrNull(), options.saveBukkitWorld()).fold(
|
||||
exception -> worldActionResult(UnloadFailureReason.BUKKIT_UNLOAD_FAILED,
|
||||
world.getName(), exception),
|
||||
@@ -432,7 +422,7 @@ public class WorldManager {
|
||||
*/
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull LoadedMultiverseWorld loadedWorld) {
|
||||
// TODO: Config option on removePlayers
|
||||
return unloadWorld(UnloadWorldOptions.world(loadedWorld).removePlayers(true))
|
||||
return unloadWorld(UnloadWorldOptions.world(loadedWorld))
|
||||
.transform(RemoveFailureReason.UNLOAD_FAILED)
|
||||
.mapAttempt(this::removeWorldFromConfig);
|
||||
}
|
||||
@@ -594,7 +584,6 @@ public class WorldManager {
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull RegenWorldOptions options) {
|
||||
LoadedMultiverseWorld world = options.world();
|
||||
List<Player> playersInWorld = world.getPlayers().getOrElse(Collections.emptyList());
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, world);
|
||||
boolean shouldKeepSpawnLocation = options.keepWorldConfig() && options.seed() == world.getSeed();
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
@@ -617,7 +606,6 @@ public class WorldManager {
|
||||
// different seed.
|
||||
newWorld.setSpawnLocation(spawnLocation);
|
||||
}
|
||||
playerWorldActions.teleportPlayersToWorld(playersInWorld, newWorld);
|
||||
saveWorldsConfig();
|
||||
});
|
||||
}
|
||||
@@ -681,12 +669,13 @@ public class WorldManager {
|
||||
unloadTracker.add(world.getName());
|
||||
if (!Bukkit.unloadWorld(world, save)) {
|
||||
// TODO: Localize this, maybe with MultiverseException
|
||||
if (!world.getPlayers().isEmpty()) {
|
||||
throw new Exception("There are still players in the world! Please use --remove-players flag to "
|
||||
+ "your command if wish to teleport all players out of the world.");
|
||||
}
|
||||
throw new Exception("Is this the default world? You can't unload the default world!");
|
||||
}
|
||||
Logging.fine("Bukkit unloaded world: " + world.getName());
|
||||
}).onFailure(exception -> {
|
||||
Logging.severe("Failed to unload bukkit world: " + world.getName());
|
||||
exception.printStackTrace();
|
||||
}).andFinally(() -> unloadTracker.remove(world.getName()));
|
||||
}
|
||||
|
||||
@@ -737,6 +726,17 @@ public class WorldManager {
|
||||
return !isLoadedWorld(worldName) && isWorld(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a world that may or may not be loaded. It will an {@link LoadedMultiverseWorld} if the world is loaded,
|
||||
* otherwise returns an {@link MultiverseWorld} instance.
|
||||
*
|
||||
* @param world The bukkit world to get.
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getWorld(@Nullable World world) {
|
||||
return Option.of(world).flatMap(this::getWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a world that may or may not be loaded. It will an {@link LoadedMultiverseWorld} if the world is loaded,
|
||||
* otherwise returns an {@link MultiverseWorld} instance.
|
||||
|
||||
@@ -14,7 +14,9 @@ import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.ConfigurationSectionHandle;
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.BooleanMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.IntegerMigratorAction;
|
||||
@@ -23,7 +25,6 @@ import org.mvplugins.multiverse.core.configuration.migration.MoveMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.NullStringMigratorAction;
|
||||
import org.mvplugins.multiverse.core.configuration.migration.VersionMigrator;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
|
||||
/**
|
||||
* Represents a world configuration.
|
||||
@@ -33,20 +34,21 @@ public final class WorldConfig {
|
||||
private final String worldName;
|
||||
private final WorldConfigNodes configNodes;
|
||||
private final ConfigurationSectionHandle configHandle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
|
||||
WorldConfig(
|
||||
@NotNull String worldName,
|
||||
@NotNull ConfigurationSection configSection,
|
||||
@NotNull EnforcementHandler enforcementHandler) {
|
||||
@NotNull MultiverseCore multiverseCore) {
|
||||
this.worldName = worldName;
|
||||
this.configNodes = new WorldConfigNodes(enforcementHandler);
|
||||
this.configHandle = ConfigurationSectionHandle.builder(configSection)
|
||||
this.configNodes = new WorldConfigNodes(multiverseCore);
|
||||
this.configHandle = ConfigurationSectionHandle.builder(configSection, configNodes.getNodes())
|
||||
.logger(Logging.getLogger())
|
||||
.nodes(configNodes.getNodes())
|
||||
.migrator(ConfigMigrator.builder(configNodes.VERSION)
|
||||
.addVersionMigrator(initialVersionMigrator())
|
||||
.build())
|
||||
.build();
|
||||
this.stringPropertyHandle = new StringPropertyHandle(configHandle);
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -110,22 +112,14 @@ public final class WorldConfig {
|
||||
return configHandle.load(section);
|
||||
}
|
||||
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
public Collection<String> getConfigurablePropertyNames() {
|
||||
return configNodes.getNodes().getNames();
|
||||
}
|
||||
|
||||
public Try<Object> getProperty(String name) {
|
||||
return configHandle.get(name);
|
||||
}
|
||||
|
||||
public Try<Void> setProperty(String name, Object value) {
|
||||
return configHandle.set(name, value);
|
||||
}
|
||||
|
||||
public boolean getAdjustSpawn() {
|
||||
return configHandle.get(configNodes.ADJUST_SPAWN);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.world.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Difficulty;
|
||||
import org.bukkit.GameMode;
|
||||
@@ -10,10 +9,11 @@ import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ConfigNode;
|
||||
import org.mvplugins.multiverse.core.configuration.node.ListConfigNode;
|
||||
import org.mvplugins.multiverse.core.configuration.node.Node;
|
||||
import org.mvplugins.multiverse.core.configuration.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.world.config.AllowedPortalType;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
|
||||
@@ -27,8 +27,8 @@ public class WorldConfigNodes {
|
||||
private EnforcementHandler enforcementHandler;
|
||||
private LoadedMultiverseWorld world = null;
|
||||
|
||||
WorldConfigNodes(@NotNull EnforcementHandler enforcementHandler) {
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
|
||||
this.enforcementHandler = multiverseCore.getService(EnforcementHandler.class);
|
||||
}
|
||||
|
||||
LoadedMultiverseWorld getWorld() {
|
||||
@@ -212,8 +212,8 @@ public class WorldConfigNodes {
|
||||
})
|
||||
.build());
|
||||
|
||||
final ConfigNode<List> SPAWNING_ANIMALS_EXCEPTIONS = node(ConfigNode
|
||||
.builder("spawning.animals.exceptions", List.class)
|
||||
final ListConfigNode<String> SPAWNING_ANIMALS_EXCEPTIONS = node(ListConfigNode
|
||||
.listBuilder("spawning.animals.exceptions", String.class)
|
||||
.defaultValue(new ArrayList<>())
|
||||
.name("spawning-animals-exceptions")
|
||||
.build());
|
||||
@@ -238,14 +238,13 @@ public class WorldConfigNodes {
|
||||
})
|
||||
.build());
|
||||
|
||||
final ConfigNode<List> SPAWNING_MONSTERS_EXCEPTIONS = node(ConfigNode
|
||||
.builder("spawning.monsters.exceptions", List.class)
|
||||
final ListConfigNode<String> SPAWNING_MONSTERS_EXCEPTIONS = node(ListConfigNode
|
||||
.listBuilder("spawning.monsters.exceptions", String.class)
|
||||
.defaultValue(new ArrayList<>())
|
||||
.name("spawning-monsters-exceptions")
|
||||
.build());
|
||||
|
||||
final ConfigNode<List> WORLD_BLACKLIST = node(ConfigNode.builder("world-blacklist", List.class)
|
||||
.defaultValue(new ArrayList<>())
|
||||
final ListConfigNode<String> WORLD_BLACKLIST = node(ListConfigNode.listBuilder("world-blacklist", String.class)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Double> VERSION = node(ConfigNode.builder("version", Double.class)
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
|
||||
/**
|
||||
* Manages the worlds.yml file.
|
||||
@@ -35,14 +34,14 @@ public final class WorldsConfigManager {
|
||||
private final File worldConfigFile;
|
||||
private YamlConfiguration worldsConfig;
|
||||
|
||||
private final EnforcementHandler enforcementHandler;
|
||||
private final MultiverseCore multiverseCore;
|
||||
|
||||
@Inject
|
||||
WorldsConfigManager(@NotNull MultiverseCore core, @NotNull EnforcementHandler enforcementHandler) {
|
||||
WorldsConfigManager(@NotNull MultiverseCore core, @NotNull MultiverseCore multiverseCore) {
|
||||
worldConfigMap = new HashMap<>();
|
||||
worldConfigFile = core.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
|
||||
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
this.multiverseCore = multiverseCore;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,7 +127,7 @@ public final class WorldsConfigManager {
|
||||
WorldConfig newWorldConfig = new WorldConfig(
|
||||
worldName,
|
||||
getWorldConfigSection(worldName),
|
||||
enforcementHandler);
|
||||
multiverseCore);
|
||||
worldConfigMap.put(worldName, newWorldConfig);
|
||||
newWorldsAdded.add(newWorldConfig);
|
||||
});
|
||||
@@ -183,7 +182,7 @@ public final class WorldsConfigManager {
|
||||
if (worldConfigMap.containsKey(worldName)) {
|
||||
throw new IllegalArgumentException("WorldConfig for world " + worldName + " already exists.");
|
||||
}
|
||||
WorldConfig worldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName), enforcementHandler);
|
||||
WorldConfig worldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName), multiverseCore);
|
||||
worldConfigMap.put(worldName, worldConfig);
|
||||
return worldConfig;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
/**
|
||||
@@ -90,7 +91,8 @@ public interface DataStore<T> {
|
||||
@Override
|
||||
public WorldConfigStore copyFrom(LoadedMultiverseWorld world) {
|
||||
this.configMap = new HashMap<>();
|
||||
world.getConfigurablePropertyNames().forEach(name -> world.getProperty(name)
|
||||
StringPropertyHandle worldPropertyHandler = world.getStringPropertyHandle();
|
||||
worldPropertyHandler.getAllPropertyNames().forEach(name -> worldPropertyHandler.getProperty(name)
|
||||
.peek(value -> configMap.put(name, value)).onFailure(e -> {
|
||||
Logging.warning("Failed to get property " + name + " from world "
|
||||
+ world.getName() + ": " + e.getMessage());
|
||||
@@ -106,12 +108,11 @@ public interface DataStore<T> {
|
||||
if (configMap == null) {
|
||||
return this;
|
||||
}
|
||||
configMap.forEach((name, value) -> {
|
||||
world.setProperty(name, value).onFailure(e -> {
|
||||
Logging.warning("Failed to set property %s to %s for world %s: %s",
|
||||
name, value, world.getName(), e.getMessage());
|
||||
});
|
||||
});
|
||||
StringPropertyHandle worldPropertyHandler = world.getStringPropertyHandle();
|
||||
configMap.forEach((name, value) -> worldPropertyHandler.setProperty(name, value).onFailure(e -> {
|
||||
Logging.warning("Failed to set property %s to %s for world %s: %s",
|
||||
name, value, world.getName(), e.getMessage());
|
||||
}));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+35
-26
@@ -2,7 +2,6 @@ package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -11,7 +10,10 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
|
||||
import org.mvplugins.multiverse.core.utils.result.Async;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
/**
|
||||
@@ -19,10 +21,10 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
*/
|
||||
@Service
|
||||
public class PlayerWorldTeleporter {
|
||||
private final SafeTTeleporter safetyTeleporter;
|
||||
private final AsyncSafetyTeleporter safetyTeleporter;
|
||||
|
||||
@Inject
|
||||
PlayerWorldTeleporter(@NotNull SafeTTeleporter safetyTeleporter) {
|
||||
PlayerWorldTeleporter(@NotNull AsyncSafetyTeleporter safetyTeleporter) {
|
||||
this.safetyTeleporter = safetyTeleporter;
|
||||
}
|
||||
|
||||
@@ -30,11 +32,12 @@ public class PlayerWorldTeleporter {
|
||||
* Removes all players from the given world.
|
||||
*
|
||||
* @param world The world to remove all players from.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*/
|
||||
public void removeFromWorld(@NotNull LoadedMultiverseWorld world) {
|
||||
public Async<List<Attempt<Void, TeleportResult.Failure>>> removeFromWorld(@NotNull LoadedMultiverseWorld world) {
|
||||
// TODO: Better handling of fallback world
|
||||
World toWorld = Bukkit.getWorlds().get(0);
|
||||
transferFromWorldTo(world, toWorld);
|
||||
return transferFromWorldTo(world, toWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,9 +45,12 @@ public class PlayerWorldTeleporter {
|
||||
*
|
||||
* @param from The world to transfer players from.
|
||||
* @param to The location to transfer players to.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*/
|
||||
public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull LoadedMultiverseWorld to) {
|
||||
transferAllFromWorldToLocation(from, to.getSpawnLocation());
|
||||
public Async<List<Attempt<Void, TeleportResult.Failure>>> transferFromWorldTo(
|
||||
@NotNull LoadedMultiverseWorld from,
|
||||
@NotNull LoadedMultiverseWorld to) {
|
||||
return transferAllFromWorldToLocation(from, to.getSpawnLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,24 +58,28 @@ public class PlayerWorldTeleporter {
|
||||
*
|
||||
* @param from The world to transfer players from.
|
||||
* @param to The world to transfer players to.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*/
|
||||
public void transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull World to) {
|
||||
transferAllFromWorldToLocation(from, to.getSpawnLocation());
|
||||
public Async<List<Attempt<Void, TeleportResult.Failure>>> transferFromWorldTo(
|
||||
@NotNull LoadedMultiverseWorld from,
|
||||
@NotNull World to) {
|
||||
return transferAllFromWorldToLocation(from, to.getSpawnLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers all players from the given world to the given location.
|
||||
*
|
||||
* @param world The world to transfer players from.
|
||||
* @param location The location to transfer players to.
|
||||
* @param world The world to transfer players from.
|
||||
* @param location The location to transfer players to.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*/
|
||||
public void transferAllFromWorldToLocation(@NotNull LoadedMultiverseWorld world, @NotNull Location location) {
|
||||
world.getPlayers().peek(players -> players.forEach(player -> {
|
||||
if (player.isOnline()) {
|
||||
Logging.fine("Teleporting player '%s' to world spawn: %s", player.getName(), location);
|
||||
safetyTeleporter.safelyTeleport(null, player, location, true);
|
||||
}
|
||||
}));
|
||||
public Async<List<Attempt<Void, TeleportResult.Failure>>> transferAllFromWorldToLocation(
|
||||
@NotNull LoadedMultiverseWorld world,
|
||||
@NotNull Location location) {
|
||||
return world.getPlayers()
|
||||
.map(players -> safetyTeleporter.teleport(players, location))
|
||||
.getOrElse(() -> Async.failedFuture(
|
||||
new IllegalStateException("Unable to get players from world" + world.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,13 +87,12 @@ public class PlayerWorldTeleporter {
|
||||
*
|
||||
* @param players The players to teleport.
|
||||
* @param world The world to teleport players to.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*/
|
||||
public void teleportPlayersToWorld(@NotNull List<Player> players, @NotNull LoadedMultiverseWorld world) {
|
||||
players.forEach(player -> {
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
if (player.isOnline()) {
|
||||
safetyTeleporter.safelyTeleport(null, player, spawnLocation, true);
|
||||
}
|
||||
});
|
||||
public Async<List<Attempt<Void, TeleportResult.Failure>>> teleportPlayersToWorld(
|
||||
@NotNull List<Player> players,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
return safetyTeleporter.teleport(players, spawnLocation);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ public class UnloadWorldOptions {
|
||||
}
|
||||
|
||||
private final LoadedMultiverseWorld world;
|
||||
private boolean removePlayers = false;
|
||||
private boolean saveBukkitWorld = true;
|
||||
|
||||
UnloadWorldOptions(LoadedMultiverseWorld world) {
|
||||
@@ -34,26 +33,6 @@ public class UnloadWorldOptions {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to teleport the players out from the world before unloading.
|
||||
*
|
||||
* @param removePlayersInput Whether to remove players from the world before unloading.
|
||||
* @return This {@link UnloadWorldOptions} instance.
|
||||
*/
|
||||
public UnloadWorldOptions removePlayers(boolean removePlayersInput) {
|
||||
this.removePlayers = removePlayersInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to teleport the players out from the world before unloading.
|
||||
*
|
||||
* @return Whether to remove players from the world before unloading.
|
||||
*/
|
||||
public boolean removePlayers() {
|
||||
return removePlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to save the bukkit world before unloading.
|
||||
*
|
||||
|
||||
@@ -60,20 +60,33 @@ mv-core.delete.success=&aWorld '{world}' deleted!
|
||||
mv-core.dumps.description=Dumps version info to the console or paste services
|
||||
mv-core.dumps.url.list={service} : {link}
|
||||
|
||||
# /mv gamerule
|
||||
mv-core.gamerule.description=Changes a gamerule in one or more worlds
|
||||
mv-core.gamerule.gamerule.description=Gamerule to set
|
||||
mv-core.gamerule.value.description=Value of gamerule
|
||||
mv-core.gamerule.world.description=World to apply gamerule to, current world by default
|
||||
mv-core.gamerule.failed=Failed to set gamerule {gamerule} to {value} in {world}. &fIt should be a {type}.
|
||||
mv-core.gamerule.success.single=&aSuccessfully set {gamerule} to {value} in {world}.
|
||||
mv-core.gamerule.success.multiple=&aSuccessfully set {gamerule} to {value} in {count} worlds.
|
||||
# /mv gamerule set
|
||||
mv-core.gamerule.set.description=Changes a gamerule in one or more worlds.
|
||||
mv-core.gamerule.set.gamerule.description=Gamerule to set.
|
||||
mv-core.gamerule.set.value.description=Value of gamerule.
|
||||
mv-core.gamerule.set.world.description=World to apply gamerule to, current player's world by default.
|
||||
mv-core.gamerule.set.failed=Failed to set gamerule {gamerule} to {value} in {world}. &fIt should be a {type}.
|
||||
mv-core.gamerule.set.success.single=&aSuccessfully set {gamerule} to {value} in {world}.
|
||||
mv-core.gamerule.set.success.multiple=&aSuccessfully set {gamerule} to {value} in {count} worlds.
|
||||
|
||||
# /mv gamerules
|
||||
mv-core.gamerules.description=Lists gamerules for the specified world
|
||||
mv-core.gamerules.description.page=The page to view
|
||||
mv-core.gamerules.description.world=The world to list gamerules in
|
||||
mv-core.gamerules.title= --- Gamerules for {world} ---
|
||||
# /mv gamerule reset
|
||||
mv-core.gamerule.reset.description=Resets a gamerule in one or more worlds to it's default value.
|
||||
mv-core.gamerule.reset.gamerule.description=Gamerule to reset.
|
||||
mv-core.gamerule.reset.world.description=World to reset gamerule of, current player's world by default.
|
||||
mv-core.gamerule.reset.failed=Failed to reset gamerule {gamerule} in {world}.
|
||||
mv-core.gamerule.reset.success.single=&aSuccessfully reset {gamerule} in {world}.
|
||||
mv-core.gamerule.reset.success.multiple=&aSuccessfully reset {gamerule} in {count} worlds.
|
||||
|
||||
# /mv gamerule list
|
||||
mv-core.gamerule.list.description=Lists gamerules for the specified world.
|
||||
mv-core.gamerule.list.description.page=The page to view.
|
||||
mv-core.gamerule.list.description.world=The world to list gamerules in.
|
||||
mv-core.gamerule.list.title= --- Gamerules for {world} ---
|
||||
|
||||
# /mv generators
|
||||
mv-core.generators.description=Lists generators known to Multiverse
|
||||
mv-core.generators.description.flags=Filter - only shows entries matching this. Page - the page to show
|
||||
mv-core.generators.empty=&cNo Generator Plugins found.
|
||||
|
||||
# /mv import
|
||||
mv-core.import.description=Imports an existing world folder.
|
||||
@@ -126,6 +139,12 @@ mv-core.unload.success=&aWorld '{world}' unloaded!
|
||||
# /mv usage
|
||||
mv-core.usage.description=Show Multiverse-Core command usage.
|
||||
|
||||
# /mv version
|
||||
mv-core.version.description=Displays version and authors
|
||||
mv-core.version.mv=Multiverse Core Version &fv{version}
|
||||
mv-core.version.authors=Multiverse Core Authors &f{authors}
|
||||
mv-core.version.secretcode=Special Code: &fFRN002
|
||||
|
||||
# commands error
|
||||
mv-core.commands.error.playersonly=&cThis command can only be used by players
|
||||
mv-core.commands.error.multiverseworldonly=&cThis can only be used in multiverse worlds
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Multiverse-Core
|
||||
main: org.mvplugins.multiverse.core.MultiverseCore
|
||||
authors: ['dumptruckman', 'Rigby', 'fernferret', 'lithium3141', 'main--']
|
||||
authors: ['dumptruckman', 'Rigby', 'fernferret', 'lithium3141', 'main--', 'benwoo1110', 'Zax71']
|
||||
website: 'https://dev.bukkit.org/projects/multiverse-core'
|
||||
softdepend: ['Vault', 'PlaceholderAPI']
|
||||
api-version: 1.13
|
||||
|
||||
@@ -52,14 +52,14 @@ class ConfigTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `Getting existing config property with getProperty returns expected value`() {
|
||||
assertEquals(false, config.getProperty("enforce-access").get())
|
||||
assertEquals("world", config.getProperty("first-spawn-location").get())
|
||||
assertEquals(false, config.stringPropertyHandle.getProperty("enforce-access").get())
|
||||
assertEquals("world", config.stringPropertyHandle.getProperty("first-spawn-location").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Getting non-existing config property with getProperty returns null`() {
|
||||
assertTrue(config.getProperty("invalid-property").isFailure)
|
||||
assertTrue(config.getProperty("version").isFailure)
|
||||
assertTrue(config.stringPropertyHandle.getProperty("invalid-property").isFailure)
|
||||
assertTrue(config.stringPropertyHandle.getProperty("version").isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -70,19 +70,31 @@ class ConfigTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `Updating an existing config property with setProperty reflects the changes in getProperty`() {
|
||||
assertTrue(config.setProperty("enforce-access", true).isSuccess)
|
||||
assertEquals(true, config.getProperty("enforce-access").get())
|
||||
assertTrue(config.stringPropertyHandle.setProperty("enforce-access", true).isSuccess)
|
||||
assertEquals(true, config.stringPropertyHandle.getProperty("enforce-access").get())
|
||||
|
||||
assertTrue(config.setProperty("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.getProperty("first-spawn-location").get())
|
||||
assertTrue(config.stringPropertyHandle.setProperty("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.stringPropertyHandle.getProperty("first-spawn-location").get())
|
||||
|
||||
assertTrue(config.setProperty("global-debug", 1).isSuccess)
|
||||
assertEquals(1, config.getProperty("global-debug").get())
|
||||
assertTrue(config.stringPropertyHandle.setProperty("global-debug", 1).isSuccess)
|
||||
assertEquals(1, config.stringPropertyHandle.getProperty("global-debug").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Updating an existing config property with setPropertyString reflects the changes in getProperty`() {
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("enforce-access", "true").isSuccess)
|
||||
assertEquals(true, config.stringPropertyHandle.getProperty("enforce-access").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.stringPropertyHandle.getProperty("first-spawn-location").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("global-debug", "1").isSuccess)
|
||||
assertEquals(1, config.stringPropertyHandle.getProperty("global-debug").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Updating a non-existing property with setProperty returns false`() {
|
||||
assertTrue(config.setProperty("invalid-property", false).isFailure)
|
||||
assertTrue(config.setProperty("version", 1.1).isFailure)
|
||||
assertTrue(config.stringPropertyHandle.setProperty("invalid-property", false).isFailure)
|
||||
assertTrue(config.stringPropertyHandle.setProperty("version", 1.1).isFailure)
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,16 @@ import org.mvplugins.multiverse.core.anchor.AnchorManager
|
||||
import org.mvplugins.multiverse.core.api.BlockSafety
|
||||
import org.mvplugins.multiverse.core.api.Destination
|
||||
import org.mvplugins.multiverse.core.api.LocationManipulation
|
||||
import org.mvplugins.multiverse.core.api.SafeTTeleporter
|
||||
import org.mvplugins.multiverse.core.commandtools.MVCommandManager
|
||||
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand
|
||||
import org.mvplugins.multiverse.core.commandtools.PluginLocales
|
||||
import org.mvplugins.multiverse.core.config.MVCoreConfig
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist
|
||||
import org.mvplugins.multiverse.core.listeners.*
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter
|
||||
import org.mvplugins.multiverse.core.teleportation.SimpleBlockSafety
|
||||
import org.mvplugins.multiverse.core.teleportation.SimpleLocationManipulation
|
||||
import org.mvplugins.multiverse.core.teleportation.SimpleSafeTTeleporter
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue
|
||||
import org.mvplugins.multiverse.core.utils.UnsafeCallWrapper
|
||||
import org.mvplugins.multiverse.core.utils.metrics.MetricsConfigurator
|
||||
import org.mvplugins.multiverse.core.world.WorldManager
|
||||
import kotlin.test.*
|
||||
@@ -28,6 +26,11 @@ class InjectionTest : TestWithMockBukkit() {
|
||||
assertNotNull(multiverseCore.getService(AnchorManager::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AsyncSafetyTeleporter is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(AsyncSafetyTeleporter::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BlockSafety is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(BlockSafety::class.java))
|
||||
@@ -50,23 +53,11 @@ class InjectionTest : TestWithMockBukkit() {
|
||||
assertNotNull(multiverseCore.getService(SimpleLocationManipulation::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SafeTTeleporter is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(SafeTTeleporter::class.java))
|
||||
assertNotNull(multiverseCore.getService(SimpleSafeTTeleporter::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TeleportQueue is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(TeleportQueue::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun `UnsafeCallWrapper is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(UnsafeCallWrapper::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MVWorldManager is available as a service`() {
|
||||
assertNotNull(multiverseCore.getService(WorldManager::class.java))
|
||||
|
||||
@@ -68,9 +68,9 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
|
||||
val worldConfig = worldConfigManager.getWorldConfig("world").orNull
|
||||
assertNotNull(worldConfig)
|
||||
|
||||
worldConfig.setProperty("adjust-spawn", true)
|
||||
worldConfig.setProperty("alias", "newalias")
|
||||
worldConfig.setProperty("spawn-location", SpawnLocation(-64.0, 64.0, 48.0))
|
||||
worldConfig.stringPropertyHandle.setProperty("adjust-spawn", true)
|
||||
worldConfig.stringPropertyHandle.setProperty("alias", "newalias")
|
||||
worldConfig.stringPropertyHandle.setProperty("spawn-location", SpawnLocation(-64.0, 64.0, 48.0))
|
||||
assertTrue(worldConfigManager.save().isSuccess)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,14 +30,14 @@ class WorldConfigTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `Getting existing world property with getProperty returns expected value`() {
|
||||
assertEquals("my world", worldConfig.getProperty("alias").get())
|
||||
assertEquals(false, worldConfig.getProperty("hidden").get())
|
||||
assertEquals("my world", worldConfig.stringPropertyHandle.getProperty("alias").get())
|
||||
assertEquals(false, worldConfig.stringPropertyHandle.getProperty("hidden").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Getting non-existing world property with getProperty returns null`() {
|
||||
assertTrue(worldConfig.getProperty("invalid-property").isFailure)
|
||||
assertTrue(worldConfig.getProperty("version").isFailure)
|
||||
assertTrue(worldConfig.stringPropertyHandle.getProperty("invalid-property").isFailure)
|
||||
assertTrue(worldConfig.stringPropertyHandle.getProperty("version").isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -48,23 +48,23 @@ class WorldConfigTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `Updating an existing world property with setProperty reflects the changes in getProperty`() {
|
||||
assertTrue(worldConfig.setProperty("adjust-spawn", true).isSuccess)
|
||||
assertEquals(true, worldConfig.getProperty("adjust-spawn").get())
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("adjust-spawn", true).isSuccess)
|
||||
assertEquals(true, worldConfig.stringPropertyHandle.getProperty("adjust-spawn").get())
|
||||
|
||||
assertTrue(worldConfig.setProperty("alias", "abc").isSuccess)
|
||||
assertEquals("abc", worldConfig.getProperty("alias").get())
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("alias", "abc").isSuccess)
|
||||
assertEquals("abc", worldConfig.stringPropertyHandle.getProperty("alias").get())
|
||||
|
||||
assertTrue(worldConfig.setProperty("scale", 2.0).isSuccess)
|
||||
assertEquals(2.0, worldConfig.getProperty("scale").get())
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("scale", 2.0).isSuccess)
|
||||
assertEquals(2.0, worldConfig.stringPropertyHandle.getProperty("scale").get())
|
||||
|
||||
val blacklists = listOf("a", "b", "c")
|
||||
assertTrue(worldConfig.setProperty("world-blacklist", blacklists).isSuccess)
|
||||
assertEquals(blacklists, worldConfig.getProperty("world-blacklist").get())
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("world-blacklist", blacklists).isSuccess)
|
||||
assertEquals(blacklists, worldConfig.stringPropertyHandle.getProperty("world-blacklist").get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Updating a non-existing property with setProperty returns false`() {
|
||||
assertTrue(worldConfig.setProperty("invalid-property", false).isFailure)
|
||||
assertTrue(worldConfig.setProperty("version", 1.1).isFailure)
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("invalid-property", false).isFailure)
|
||||
assertTrue(worldConfig.stringPropertyHandle.setProperty("version", 1.1).isFailure)
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,9 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
val world = worldManager.getLoadedWorld("world_nether").get()
|
||||
assertNotNull(world)
|
||||
assertEquals("world_nether", world.name)
|
||||
assertEquals(World.Environment.NETHER, world.getProperty("environment").get())
|
||||
assertEquals("", world.getProperty("generator").get())
|
||||
assertEquals(1234L, world.getProperty("seed").get())
|
||||
assertEquals(World.Environment.NETHER, world.environment)
|
||||
assertEquals("", world.generator)
|
||||
assertEquals(1234L, world.seed)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user