diff --git a/build.gradle b/build.gradle index 6b188da1a..bb56e4a1c 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ repositories { configure(apiDependencies) { serverApiVersion = '1.21.11-R0.1-SNAPSHOT' mockBukkitServerApiVersion = '1.21' - mockBukkitVersion = '4.100.0' + mockBukkitVersion = '4.110.0' } dependencies { diff --git a/src/main/java/org/mvplugins/multiverse/core/MultiverseCoreApi.java b/src/main/java/org/mvplugins/multiverse/core/MultiverseCoreApi.java index b185613ce..91f966844 100644 --- a/src/main/java/org/mvplugins/multiverse/core/MultiverseCoreApi.java +++ b/src/main/java/org/mvplugins/multiverse/core/MultiverseCoreApi.java @@ -24,7 +24,7 @@ /** * Provides access to the MultiverseCore API. */ -public class MultiverseCoreApi { +public final class MultiverseCoreApi { private static MultiverseCoreApi instance; private static final List> whenLoadedCallbacks = new ArrayList<>(); diff --git a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java index 167ef6c5f..2fbeddf9a 100644 --- a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java +++ b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java @@ -52,6 +52,7 @@ import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin; import org.mvplugins.multiverse.core.world.generators.GeneratorProvider; +import org.mvplugins.multiverse.core.world.helpers.PotentialWorldFinder; import static org.mvplugins.multiverse.core.utils.StringFormatter.addOnToCommaSeparated; @@ -65,6 +66,7 @@ public class MVCommandCompletions extends PaperCommandCompletions { private final CorePermissionsChecker corePermissionsChecker; private final AnchorManager anchorManager; private final GeneratorProvider generatorProvider; + private final PotentialWorldFinder potentialWorldFinder; @Inject MVCommandCompletions( @@ -74,7 +76,8 @@ public class MVCommandCompletions extends PaperCommandCompletions { @NotNull CoreConfig config, @NotNull CorePermissionsChecker corePermissionsChecker, @NotNull AnchorManager anchorManager, - @NotNull GeneratorProvider generatorProvider + @NotNull GeneratorProvider generatorProvider, + @NotNull PotentialWorldFinder potentialWorldFinder ) { super(mvCommandManager); this.commandManager = mvCommandManager; @@ -84,6 +87,7 @@ public class MVCommandCompletions extends PaperCommandCompletions { this.corePermissionsChecker = corePermissionsChecker; this.anchorManager = anchorManager; this.generatorProvider = generatorProvider; + this.potentialWorldFinder = potentialWorldFinder; registerAsyncCompletion("anchornames", this::suggestAnchorNames); registerAsyncCompletion("commands", this::suggestCommands); @@ -297,7 +301,7 @@ private Collection suggestMVWorlds(BukkitCommandCompletionContext contex .toList(); } case "potential" -> { - return worldManager.getPotentialWorlds(); + return potentialWorldFinder.findPotentialWorlds(); } } Logging.severe("Invalid MVWorld scope: " + scope); diff --git a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandContexts.java b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandContexts.java index 19ce84675..616f1d613 100644 --- a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandContexts.java +++ b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandContexts.java @@ -42,6 +42,7 @@ import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.PlayerFinder; import org.mvplugins.multiverse.core.utils.REPatterns; +import org.mvplugins.multiverse.core.utils.tick.TickDuration; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -93,6 +94,7 @@ public class MVCommandContexts extends PaperCommandContexts { registerIssuerAwareContext(PlayerArrayValue.class, playerArrayContextBuilder().generateContext(PlayerArrayValue::new)); registerIssuerAwareContext(PlayerLocation.class, this::parsePlayerLocation); registerContext(SpawnCategory[].class, this::parseSpawnCategories); + registerContext(TickDuration.class, this::parseTickDuration); } private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) { @@ -393,4 +395,13 @@ private SpawnCategory[] parseSpawnCategories(BukkitCommandExecutionContext conte } return categories.toArray(new SpawnCategory[0]); } + + private TickDuration parseTickDuration(BukkitCommandExecutionContext context) { + String arg = context.popFirstArg(); + return TickDuration.parseString(arg) + .getOrElseThrow(failure -> + new InvalidCommandArgument("Invalid time duration format: \"" +arg + "\". Use a number " + + "followed by an optional suffix (s for seconds, d for game days) or just a number " + + "for ticks. Examples: 100, 5s, 2d")); + } } diff --git a/src/main/java/org/mvplugins/multiverse/core/command/flag/CommandValueFlag.java b/src/main/java/org/mvplugins/multiverse/core/command/flag/CommandValueFlag.java index 24f0f67df..85b689cdb 100644 --- a/src/main/java/org/mvplugins/multiverse/core/command/flag/CommandValueFlag.java +++ b/src/main/java/org/mvplugins/multiverse/core/command/flag/CommandValueFlag.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.Locale; import java.util.function.Function; +import java.util.function.Supplier; import co.aikar.commands.InvalidCommandArgument; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,34 +42,34 @@ public class CommandValueFlag extends CommandFlag { private final Class type; private final boolean optional; - private final T defaultValue; + private final Supplier defaultValueSupplier; private final Function context; private final Function> completion; /** * Creates a new flag. * - * @param key The key for the new flag. - * @param aliases The aliases that also refer to this flag. - * @param type The type of the value. - * @param optional Allow for flag without value. - * @param defaultValue The default value if optional is true and user does not specify a value. - * @param context Function to parse string into value type. - * @param completion Function to get completion for this flag. + * @param key The key for the new flag. + * @param aliases The aliases that also refer to this flag. + * @param type The type of the value. + * @param optional Allow for flag without value. + * @param defaultValueSupplier The default value if optional is true and user does not specify a value. + * @param context Function to parse string into value type. + * @param completion Function to get completion for this flag. */ protected CommandValueFlag( @NotNull String key, @NotNull List aliases, @NotNull Class type, boolean optional, - @Nullable T defaultValue, + @Nullable Supplier defaultValueSupplier, @Nullable Function context, @Nullable Function> completion ) { super(key, aliases); this.type = type; this.optional = optional; - this.defaultValue = defaultValue; + this.defaultValueSupplier = defaultValueSupplier; this.context = context; this.completion = completion; } @@ -96,7 +98,7 @@ public boolean isOptional() { * @return The default value. */ public @Nullable T getDefaultValue() { - return defaultValue; + return defaultValueSupplier == null ? null : defaultValueSupplier.get(); } /** @@ -126,7 +128,7 @@ public boolean isOptional() { public static class Builder> extends CommandFlag.Builder { protected final Class type; protected boolean optional = false; - protected T defaultValue = null; + protected Supplier defaultValueSupplier = null; protected Function context = null; protected Function> completion = null; @@ -158,7 +160,21 @@ public Builder(@NotNull String key, @NotNull Class type) { * @return The builder. */ public @NotNull S defaultValue(@NotNull T defaultValue) { - this.defaultValue = defaultValue; + return defaultValue(() -> defaultValue); + } + + /** + * Set the default value supplier. Used if optional is true and user does not specify a value. + * Supplier is only called when command is executed with the flag is present in input. + * + * @param defaultValueSupplier The default value supplier + * @return The builder + * + * @since 5.7 + */ + @ApiStatus.AvailableSince("5.7") + public @NotNull S defaultValue(@NotNull Supplier defaultValueSupplier) { + this.defaultValueSupplier = defaultValueSupplier; return (S) this; } @@ -194,7 +210,7 @@ public Builder(@NotNull String key, @NotNull Class type) { if (context == null && !String.class.equals(type)) { throw new IllegalStateException("Context is required for non-string value flags"); } - return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion); + return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion); } } @@ -207,7 +223,7 @@ public Builder(@NotNull String key, @NotNull Class type) { public static class EnumBuilder, S extends EnumBuilder> extends CommandFlag.Builder { protected final Class type; protected boolean optional = false; - protected T defaultValue = null; + protected Supplier defaultValueSupplier = null; protected Function context = null; protected Function> completion = null; @@ -253,7 +269,21 @@ private void setEnumCompletion() { * @return The builder. */ public @NotNull S defaultValue(@NotNull T defaultValue) { - this.defaultValue = defaultValue; + return defaultValue(() -> defaultValue); + } + + /** + * Set the default value to supply. Used if optional is true and user does not specify a value. + * Supplier is only called when command is executed with the flag is present in input. + * + * @param defaultValueSupplier The default value supplier. + * @return The builder. + * + * @since 5.7 + */ + @ApiStatus.AvailableSince("5.7") + public @NotNull S defaultValue(@NotNull Supplier defaultValueSupplier) { + this.defaultValueSupplier = defaultValueSupplier; return (S) this; } @@ -264,7 +294,7 @@ private void setEnumCompletion() { */ @Override public @NotNull CommandValueFlag build() { - return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion); + return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion); } } } diff --git a/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerDestinationFlags.java b/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerDestinationFlags.java new file mode 100644 index 000000000..a2f7fb875 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerDestinationFlags.java @@ -0,0 +1,65 @@ +package org.mvplugins.multiverse.core.command.flags; + +import co.aikar.commands.InvalidCommandArgument; +import jakarta.inject.Inject; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.CommandValueFlag; +import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.destination.core.WorldDestination; +import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument; +import org.mvplugins.multiverse.core.world.WorldManager; + +@ApiStatus.AvailableSince("5.7") +@Service +public class RemovePlayerDestinationFlags extends FlagBuilder { + + public static final String NAME = "removeplayer"; + + private WorldManager worldManager; + private DestinationsProvider destinationsProvider; + private WorldDestination worldDestination; + + protected RemovePlayerDestinationFlags( + @NotNull String name, + @NotNull CommandFlagsManager flagsManager, + @NotNull WorldManager worldManager, + @NotNull DestinationsProvider destinationsProvider, + @NotNull WorldDestination worldDestination + ) { + super(name, flagsManager); + this.worldManager = worldManager; + this.destinationsProvider = destinationsProvider; + this.worldDestination = worldDestination; + } + + @Inject + private RemovePlayerDestinationFlags( + @NotNull CommandFlagsManager flagsManager, + @NotNull WorldManager worldManager, + @NotNull DestinationsProvider destinationsProvider, + @NotNull WorldDestination worldDestination + ) { + super(NAME, flagsManager); + this.destinationsProvider = destinationsProvider; + this.worldManager = worldManager; + this.worldDestination = worldDestination; + } + + public final CommandValueFlag removePlayers = flag(CommandValueFlag.builder("--remove-players", DestinationInstance.class) + .addAlias("-r") + .defaultValue(() -> worldManager.getDefaultWorld() + .map(defaultWorld -> worldDestination.fromWorld(defaultWorld)) + .getOrElseThrow(() -> new InvalidCommandArgument("No default world found, so the --remove-players flag requires a destination argument."))) //TODO: locale + .completion(input -> destinationsProvider.suggestDestinationStrings(Bukkit.getConsoleSender(), input)) + .context(input -> destinationsProvider.parseDestination(input) + .getOrThrow(failure -> + MVInvalidCommandArgument.of(failure.getFailureMessage()))) + .optional() + .build()); +} diff --git a/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerFlags.java b/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerFlags.java index 11c37fa3f..0f16b54b7 100644 --- a/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerFlags.java +++ b/src/main/java/org/mvplugins/multiverse/core/command/flags/RemovePlayerFlags.java @@ -1,12 +1,19 @@ package org.mvplugins.multiverse.core.command.flags; import jakarta.inject.Inject; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.command.flag.CommandFlag; import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +/** + * @deprecated The --remove-players flag is being removed in favor of a more flexible system that allows for specifying + * a destination for players to be teleported to when a world is unloaded. See {@link RemovePlayerDestinationFlags}. + */ +@Deprecated(forRemoval = true, since = "5.7") +@ApiStatus.ScheduledForRemoval(inVersion = "6.0") @Service public class RemovePlayerFlags extends FlagBuilder { diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/CreateCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/CreateCommand.java index 7be73b0a6..bd21ada4c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/CreateCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/CreateCommand.java @@ -11,6 +11,7 @@ import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; import com.dumptruckman.minecraft.util.Logging; +import io.vavr.control.Try; import jakarta.inject.Inject; import org.bukkit.World; import org.bukkit.WorldType; @@ -24,9 +25,11 @@ import org.mvplugins.multiverse.core.command.flag.CommandValueFlag; import org.mvplugins.multiverse.core.command.flag.FlagBuilder; import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.core.utils.position.EntityPosition; import org.mvplugins.multiverse.core.utils.result.Attempt.Failure; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -52,8 +55,9 @@ class CreateCommand extends CoreCommand { @Subcommand("create") @CommandPermission("multiverse.core.create") @CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME) - @Syntax(" [--seed --generator --world-type --adjust-spawn " - + "--no-structures --biome --properties ]") + @Syntax(" [--seed --generator --world-type " + + "--adjust-spawn --no-structures --generate-bonus-chest --force-spawn-position " + + "--biome --generator-settings --properties ]") @Description("{@@mv-core.create.description}") void onCreateCommand( MVCommandIssuer issuer, @@ -67,8 +71,9 @@ void onCreateCommand( World.Environment environment, @Optional - @Syntax("[--seed --generator --world-type --adjust-spawn " - + "--no-structures --biome --properties ]") + @Syntax("[--seed --generator --world-type --adjust-spawn " + + "--no-structures --generate-bonus-chest --force-spawn-position --biome " + + "--generator-settings --properties ]") @Description("{@@mv-core.create.flags.description}") String[] flagArray) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); @@ -79,14 +84,16 @@ void onCreateCommand( worldManager.createWorld(CreateWorldOptions.worldName(worldName) .biome(parsedFlags.flagValue(flags.biome, "")) + .bonusChest(parsedFlags.hasFlag(flags.bonusChest)) .environment(environment) - .seed(parsedFlags.flagValue(flags.seed)) - .worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL)) - .useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)) + .forcedSpawnPosition(parsedFlags.flagValue(flags.forceSpawnPosition)) .generator(parsedFlags.flagValue(flags.generator, "")) .generatorSettings(parsedFlags.flagValue(flags.generatorSettings, "")) .generateStructures(!parsedFlags.hasFlag(flags.noStructures)) - .worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties)))) + .seed(parsedFlags.flagValue(flags.seed)) + .useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)) + .worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties))) + .worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))) .onSuccess(newWorld -> messageSuccess(issuer, newWorld)) .onFailure(failure -> messageFailure(issuer, failure)); } @@ -185,6 +192,15 @@ private Flags( private final CommandValueFlag properties = flag(CommandValueFlag.builder("--properties", String.class) .addAlias("-p") .build()); + + private final CommandFlag bonusChest = flag(CommandFlag.builder("--generate-bonus-chest") + .addAlias("-c") + .build()); + + private final CommandValueFlag forceSpawnPosition = flag(CommandValueFlag.builder("--force-spawn-position", EntityPosition.class) + .addAlias("-f") + .context(input -> Try.of(() -> EntityPosition.fromString(input)).getOrElseThrow(MVInvalidCommandArgument::causeBy)) + .build()); } @Service diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java index 7b6c86b9d..c5119e535 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/DeleteCommand.java @@ -15,11 +15,11 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; -import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags; +import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags; import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; @@ -31,6 +31,8 @@ import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions; +import java.util.Objects; + @Service class DeleteCommand extends CoreCommand { @@ -38,7 +40,7 @@ class DeleteCommand extends CoreCommand { private final WorldManager worldManager; private final PlayerWorldTeleporter playerWorldTeleporter; private final WorldTickDeferrer worldTickDeferrer; - private final RemovePlayerFlags flags; + private final RemovePlayerDestinationFlags flags; @Inject DeleteCommand( @@ -46,7 +48,7 @@ class DeleteCommand extends CoreCommand { @NotNull WorldManager worldManager, @NotNull PlayerWorldTeleporter playerWorldTeleporter, @NotNull WorldTickDeferrer worldTickDeferrer, - @NotNull RemovePlayerFlags flags + @NotNull RemovePlayerDestinationFlags flags ) { this.commandQueueManager = commandQueueManager; this.worldManager = worldManager; @@ -57,8 +59,8 @@ class DeleteCommand extends CoreCommand { @Subcommand("delete") @CommandPermission("multiverse.core.delete") - @CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME) - @Syntax("") + @CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerDestinationFlags.NAME) + @Syntax(" [--remove-players [destination]]") @Description("{@@mv-core.delete.description}") void onDeleteCommand( MVCommandIssuer issuer, @@ -69,7 +71,7 @@ void onDeleteCommand( MultiverseWorld world, @Optional - @Syntax("[--remove-players]") + @Syntax("[--remove-players [destination]]") @Description("") String[] flagArray) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); @@ -84,10 +86,11 @@ void onDeleteCommand( private void runDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) { issuer.sendInfo(MVCorei18n.DELETE_DELETING, Replace.WORLD.with(world.getName())); - var future = parsedFlags.hasFlag(flags.removePlayers) + DestinationInstance removeToDestination = parsedFlags.flagValue(flags.removePlayers); + var future = Objects.nonNull(removeToDestination) && world.isLoaded() && world instanceof LoadedMultiverseWorld loadedWorld - ? playerWorldTeleporter.removeFromWorld(loadedWorld) + ? playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination) : AsyncAttemptsAggregate.emptySuccess(); future.onSuccess(() -> worldTickDeferrer.deferWorldTick(() -> doWorldDeleting(issuer, world))) @@ -113,7 +116,7 @@ private static final class LegacyAlias extends DeleteCommand implements LegacyAl @NotNull WorldManager worldManager, @NotNull PlayerWorldTeleporter playerWorldTeleporter, @NotNull WorldTickDeferrer worldTickDeferrer, - @NotNull RemovePlayerFlags flags) { + @NotNull RemovePlayerDestinationFlags flags) { super(commandQueueManager, worldManager, playerWorldTeleporter, worldTickDeferrer, flags); } diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/ImportCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/ImportCommand.java index b0261aa44..1da3fcec4 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/ImportCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/ImportCommand.java @@ -48,7 +48,7 @@ class ImportCommand extends CoreCommand { void onImportCommand( MVCommandIssuer issuer, - @Conditions("worldname:scope=new") + // @Conditions("worldname:scope=new") @Syntax("") @Description("{@@mv-core.import.name.description}") String worldName, @@ -67,7 +67,8 @@ void onImportCommand( worldManager.importWorld(ImportWorldOptions.worldName(worldName) .biome(parsedFlags.flagValue(flags.biome, "")) .environment(environment) - .generator(parsedFlags.flagValue(flags.generator, String.class)) + .generator(parsedFlags.flagValue(flags.generator)) + .generatorSettings(parsedFlags.flagValue(flags.generatorSettings, "")) .useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)) .doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck))) .onSuccess(newWorld -> { @@ -110,6 +111,11 @@ private Flags(@NotNull CommandFlagsManager flagsManager, @NotNull GeneratorProvi private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check") .addAlias("-f") .build()); + + private final CommandValueFlag generatorSettings = flag(CommandValueFlag + .builder("--generator-settings", String.class) + .addAlias("-gs") + .build()); } @Service diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/InfoCommand.java index ef1e65e95..66c0b94ed 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/InfoCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/InfoCommand.java @@ -87,6 +87,7 @@ public void onInfoCommand( private Map getInfo(LoadedMultiverseWorld world) { Map outMap = new LinkedHashMap<>(); + outMap.put("World Key", world.getKey().toString()); outMap.put("World Name", world.getName()); outMap.put("World Alias", world.getAlias()); outMap.put("World UID", world.getUID().toString()); @@ -103,6 +104,7 @@ private Map getInfo(LoadedMultiverseWorld world) { outMap.put("World Type", world.getWorldType().map(WorldType::getName).getOrNull()); outMap.put("Biome", world.getBiome()); outMap.put("Generator", world.getGenerator()); + outMap.put("Generator Settings", world.getGeneratorSettings()); outMap.put("Generate Structures", world.canGenerateStructures().map(String::valueOf).getOrNull()); outMap.put("Auto Load", String.valueOf(world.isAutoLoad())); @@ -112,13 +114,12 @@ private Map getInfo(LoadedMultiverseWorld world) { outMap.put("World Scale", String.valueOf(world.getScale())); outMap.put("Weather Enabled", String.valueOf(world.isAllowWeather())); outMap.put("Allow Flight", String.valueOf(world.isAllowFlight())); + outMap.put("Grant Advancements", String.valueOf(world.isAllowAdvancementGrant())); + outMap.put("Auto Heal", String.valueOf(world.getAutoHeal())); outMap.put("Hunger Depletes", String.valueOf(world.isHunger())); - outMap.put("Keep Spawn In Memory", String.valueOf(world.isKeepSpawnInMemory())); outMap.put("PVP Enabled", String.valueOf(world.getPvp())); outMap.put("Portal Form", String.valueOf(world.getPortalForm())); outMap.put("Player Limit", String.valueOf(world.getPlayerLimit())); -// getAnimalSpawningInfo(outMap, world); -// getMonsterSpawningInfo(outMap, world); outMap.put("World Blacklist", String.join(", ", world.getWorldBlacklist())); return outMap; @@ -135,30 +136,6 @@ private void getEntryFeeInfo(Map outMap, MultiverseWorld world) } } -// private void getAnimalSpawningInfo(Map outMap, MultiverseWorld world) { -// if (world.isSpawningAnimals()) { -// 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 outMap, MultiverseWorld world) { -// if (world.isSpawningMonsters()) { -// outMap.put("Spawning Monsters", "ALL"); -// } else { -// if (!world.getSpawningMonstersExceptions().isEmpty()) { -// outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString()); -// } else { -// outMap.put("Spawning Monsters", "NONE"); -// } -// } -// } - @Service private static final class LegacyAlias extends InfoCommand implements LegacyAliasCommand { @Inject diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java index 745ffe93d..971aa197c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/RegenCommand.java @@ -2,6 +2,7 @@ import java.util.Collections; import java.util.List; +import java.util.Objects; import co.aikar.commands.ACFUtil; import co.aikar.commands.annotation.CommandAlias; @@ -23,9 +24,12 @@ import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; import org.mvplugins.multiverse.core.command.flag.CommandValueFlag; import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; -import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags; +import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags; import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.destination.core.WorldDestination; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; @@ -65,7 +69,8 @@ class RegenCommand extends CoreCommand { @Subcommand("regen") @CommandPermission("multiverse.core.regen") @CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + Flags.NAME) - @Syntax(" [--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]") + @Syntax(" [--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " + + "[--remove-players [destination]]") @Description("{@@mv-core.regen.description}") void onRegenCommand( MVCommandIssuer issuer, @@ -75,7 +80,8 @@ void onRegenCommand( LoadedMultiverseWorld world, @Optional - @Syntax("[--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]") + @Syntax("[--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " + + "[--remove-players [destination]]") @Description("{@@mv-core.regen.other.description}") String[] flagArray) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); @@ -91,8 +97,9 @@ private void runRegenCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, Replace.WORLD.with(world.getName())); List worldPlayers = world.getPlayers().getOrElse(Collections.emptyList()); - var future = parsedFlags.hasFlag(flags.removePlayers) - ? playerWorldTeleporter.removeFromWorld(world) + DestinationInstance removeToDestination = parsedFlags.flagValue(flags.removePlayers); + var future = Objects.nonNull(removeToDestination) + ? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination) : AsyncAttemptsAggregate.emptySuccess(); // todo: using future will hide stacktrace @@ -128,13 +135,18 @@ private void doWorldRegening( } @Service - private static final class Flags extends RemovePlayerFlags { + private static final class Flags extends RemovePlayerDestinationFlags { private static final String NAME = "mvregen"; @Inject - private Flags(@NotNull CommandFlagsManager flagsManager) { - super(NAME, flagsManager); + private Flags( + @NotNull CommandFlagsManager flagsManager, + @NotNull WorldManager worldManager, + @NotNull DestinationsProvider destinationsProvider, + @NotNull WorldDestination worldDestination + ) { + super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination); } private final CommandValueFlag seed = flag(CommandValueFlag.builder("--seed", String.class) diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java index 0054cfd2c..22d4be28b 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/RemoveCommand.java @@ -14,11 +14,13 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.flag.CommandFlag; import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; -import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags; +import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.destination.core.WorldDestination; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate; @@ -27,6 +29,8 @@ import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions; +import java.util.Objects; + @Service class RemoveCommand extends CoreCommand { @@ -48,7 +52,7 @@ class RemoveCommand extends CoreCommand { @Subcommand("remove") @CommandPermission("multiverse.core.remove") @CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME) - @Syntax("") + @Syntax(" [--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]") @Description("{@@mv-core.remove.description}") void onRemoveCommand( MVCommandIssuer issuer, @@ -58,13 +62,16 @@ void onRemoveCommand( MultiverseWorld world, @Optional - @Syntax("[--remove-players]") + @Syntax("[--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]") @Description("") String[] flagArray) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); - var future = parsedFlags.hasFlag(flags.removePlayers) - ? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess) + DestinationInstance removeToDestination = parsedFlags.flagValue(flags.removePlayers); + var future = Objects.nonNull(removeToDestination) + ? world.asLoadedWorld() + .map(loadedWorld -> playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination)) + .getOrElse(AsyncAttemptsAggregate::emptySuccess) : AsyncAttemptsAggregate.emptySuccess(); future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags)) @@ -85,13 +92,18 @@ private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world, Pars } @Service - private static final class Flags extends RemovePlayerFlags { + private static final class Flags extends RemovePlayerDestinationFlags { private static final String NAME = "mvremove"; @Inject - private Flags(@NotNull CommandFlagsManager flagsManager) { - super(NAME, flagsManager); + private Flags( + @NotNull CommandFlagsManager flagsManager, + @NotNull WorldManager worldManager, + @NotNull DestinationsProvider destinationsProvider, + @NotNull WorldDestination worldDestination + ) { + super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination); } private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world") diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java index ee639b7ae..403d2b58c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/UnloadCommand.java @@ -14,11 +14,13 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.command.MVCommandIssuer; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.command.flag.CommandFlag; import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; -import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags; +import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.destination.core.WorldDestination; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate; @@ -27,6 +29,8 @@ import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter; import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions; +import java.util.Objects; + @Service class UnloadCommand extends CoreCommand { @@ -48,7 +52,7 @@ class UnloadCommand extends CoreCommand { @Subcommand("unload") @CommandPermission("multiverse.core.unload") @CommandCompletion("@mvworlds @flags:groupName=" + Flags.NAME) - @Syntax("") + @Syntax(" [--remove-players [destination]] [--no-save]") @Description("{@@mv-core.unload.description}") void onUnloadCommand( MVCommandIssuer issuer, @@ -58,15 +62,16 @@ void onUnloadCommand( LoadedMultiverseWorld world, @Optional - @Syntax("[--remove-players] [--no-save]") + @Syntax("[--remove-players [destination]] [--no-save]") @Description("{@@mv-core.gamerules.description.page}") String[] flagArray) { ParsedCommandFlags parsedFlags = flags.parse(flagArray); issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, Replace.WORLD.with(world.getAliasOrName())); - var future = parsedFlags.hasFlag(flags.removePlayers) - ? playerWorldTeleporter.removeFromWorld(world) + DestinationInstance removeToDestination = parsedFlags.flagValue(flags.removePlayers); + var future = Objects.nonNull(removeToDestination) + ? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination) : AsyncAttemptsAggregate.emptySuccess(); future.onSuccess(() -> doWorldUnloading(issuer, world, parsedFlags)) @@ -88,13 +93,18 @@ private void doWorldUnloading(MVCommandIssuer issuer, LoadedMultiverseWorld worl } @Service - private static final class Flags extends RemovePlayerFlags { + private static final class Flags extends RemovePlayerDestinationFlags { private static final String NAME = "mvunload"; @Inject - private Flags(@NotNull CommandFlagsManager flagsManager) { - super(NAME, flagsManager); + private Flags( + @NotNull CommandFlagsManager flagsManager, + @NotNull WorldManager worldManager, + @NotNull DestinationsProvider destinationsProvider, + @NotNull WorldDestination worldDestination + ) { + super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination); } private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world") diff --git a/src/main/java/org/mvplugins/multiverse/core/commands/WorldBorderCommand.java b/src/main/java/org/mvplugins/multiverse/core/commands/WorldBorderCommand.java index 94e1e8cb2..06bf70850 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commands/WorldBorderCommand.java +++ b/src/main/java/org/mvplugins/multiverse/core/commands/WorldBorderCommand.java @@ -12,8 +12,11 @@ import org.mvplugins.multiverse.core.command.MVCommandIssuer; import org.mvplugins.multiverse.core.locale.MVCorei18n; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; +import org.mvplugins.multiverse.core.utils.compatibility.WorldBorderCompatibility; +import org.mvplugins.multiverse.core.utils.tick.TickDuration; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import java.time.temporal.ChronoUnit; import java.util.function.Consumer; import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @@ -33,15 +36,14 @@ void onWorldBorderAdd( @Optional @Default("0") @Syntax("[time]") - int time, + TickDuration duration, @Flags("resolve=issuerAware,maxArgForAware=0") @Syntax("[world]") LoadedMultiverseWorld world ) { - worldBorderAction(issuer, world, worldBorder -> { - onWorldBorderSet(issuer, worldBorder.getSize() + size, time, world); - }); + worldBorderAction(issuer, world, worldBorder -> + onWorldBorderSet(issuer, worldBorder.getSize() + size, duration, world)); } @Subcommand("center") @@ -145,7 +147,7 @@ void onWorldBorderSet( @Optional @Default("0") @Syntax("[time]") - int time, + TickDuration duration, @Flags("resolve=issuerAware,maxArgForAware=0") @Syntax("[world]") @@ -158,15 +160,18 @@ void onWorldBorderSet( Replace.WORLD.with(world.getAliasOrName())); return; } - worldBorder.setSize(size, time); - if (time <= 0) { + if (!WorldBorderCompatibility.supportsChangeSizeInTicks() && !duration.isExactTo(ChronoUnit.SECONDS)) { + setRoundOffError(issuer, duration); + } + WorldBorderCompatibility.changeSizeDuration(worldBorder, size, duration); + if (duration.toTicks() <= 0) { issuer.sendMessage(MVCorei18n.WORLDBORDER_SET_IMMEDIATE, replace("{size}").with(worldBorder.getSize()), Replace.WORLD.with(world.getAliasOrName())); } else { issuer.sendMessage(previousSize > size ? MVCorei18n.WORLDBORDER_SET_GROWING : MVCorei18n.WORLDBORDER_SET_SHRINKING, replace("{size}").with(size), - replace("{time}").with(time), + replace("{time}").with(String.format("%.2f", duration.toSeconds())), Replace.WORLD.with(world.getAliasOrName())); } }); @@ -201,25 +206,34 @@ void onWorldBorderWarningTime( MVCommandIssuer issuer, @Syntax("