diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/HyperCloudCommandManager.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/HyperCloudCommandManager.java index 7695ee5e..f012f6f9 100644 --- a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/HyperCloudCommandManager.java +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/HyperCloudCommandManager.java @@ -20,11 +20,13 @@ import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.StaticArgument; import cloud.commandframework.arguments.flags.CommandFlag; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.LongArgument; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.bukkit.CloudBukkitCapabilities; +import cloud.commandframework.bukkit.parsers.PlayerArgument; import cloud.commandframework.context.CommandContext; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.meta.CommandMeta; @@ -62,6 +64,7 @@ import se.hyperver.hyperverse.Hyperverse; import se.hyperver.hyperverse.commands.parser.EnumParser; import se.hyperver.hyperverse.commands.parser.GameRuleParser; +import se.hyperver.hyperverse.commands.parser.HyperWorldParser; import se.hyperver.hyperverse.commands.parser.WorldFlagParser; import se.hyperver.hyperverse.commands.parser.WorldStructureSettingParser; import se.hyperver.hyperverse.configuration.FileHyperConfiguration; @@ -201,7 +204,8 @@ public HyperCloudCommandManager( // Start building the hypervere command var builder = this.commandManager.commandBuilder("hyperverse", "hv"); this.registerCommandCreateWorld(this.commandManager, builder) - .registerCommandImport(this.commandManager, builder); + .registerCommandImport(this.commandManager, builder) + .registerCommandTeleport(this.commandManager, builder); } @@ -637,6 +641,78 @@ public void handleList(CommandContext context) { }); } + private void registerCommandTeleport( + @NonNull final CommandManager commandManager, + final Command.@NonNull Builder builder + ) { + HyperWorldParser hyperWorldParser = new HyperWorldParser<>(this.worldManager, + HyperWorldParser.WorldState.LOADED, true + ); + var commandTeleportSinglePlayer = builder.literal("teleport", "tp") + .argument(CommandArgument.ofType(HyperWorld.class, "world") + .withSuggestionsProvider(hyperWorldParser::suggestions)) + .senderType(Player.class) + .handler(this::handleTeleportSinglePlayer) + .permission("hyperverse.teleport") + .build(); + var commandTeleport = builder.literal("teleport", "tp") + .argument(CommandArgument.ofType(HyperWorld.class, "world") + .withSuggestionsProvider(hyperWorldParser::suggestions)) + .senderType(Player.class) + .handler(this::handleTeleportSingle) + .permission("hyperverse.teleport") + .build(); + var commandTeleportSinglePlayerProxy = commandManager.commandBuilder("hvtp") + .proxies(commandTeleportSinglePlayer).build(); + var commandTeleportProxy = commandManager.commandBuilder("hvtp") + .proxies(commandTeleport).build(); + commandManager.command(commandTeleport) + .command(commandTeleportSinglePlayer) + .command(commandTeleportProxy) + .command(commandTeleportSinglePlayerProxy); + } + + private void handleTeleportSinglePlayer(CommandContext context) { + CommandSender sender = context.getSender(); + if (!(sender instanceof Player player)) { + throw new IllegalArgumentException("Command can only be used by a player"); + } + HyperWorld world = context.getOrDefault("world", null); + if (world == null) { + MessageUtil.sendMessage(player, Messages.messageNoSuchWorld); + return; + } + if (!world.isLoaded()) { + MessageUtil.sendMessage(player, Messages.messageWorldNotLoaded); + return; + } + if (world.getBukkitWorld() == player.getWorld()) { + MessageUtil.sendMessage(player, Messages.messageAlreadyInWorld); + return; + } + MessageUtil.sendMessage(player, Messages.messageTeleporting, "%world%", world.getDisplayName()); + world.teleportPlayer(player); + } + + private void handleTeleportSingle(CommandContext context) { + Player player = context.get("player"); + HyperWorld world = context.getOrDefault("world", null); + if (world == null) { + MessageUtil.sendMessage(player, Messages.messageNoSuchWorld); + return; + } + if (!world.isLoaded()) { + MessageUtil.sendMessage(player, Messages.messageWorldNotLoaded); + return; + } + if (world.getBukkitWorld() == player.getWorld()) { + MessageUtil.sendMessage(player, Messages.messageAlreadyInWorld); + return; + } + MessageUtil.sendMessage(player, Messages.messageTeleporting, "%world%", world.getDisplayName()); + world.teleportPlayer(player); + } + @Subcommand("teleport|tp") @CommandAlias("hvtp") @CommandPermission("hyperverse.teleport") diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/EnumParser.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/EnumParser.java index 88a0dadb..c6e5f481 100644 --- a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/EnumParser.java +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/EnumParser.java @@ -3,6 +3,7 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Arrays; @@ -57,7 +58,7 @@ public EnumParser( @NonNull final Queue<@NonNull String> inputQueue ) { if (inputQueue.isEmpty()) { - return ArgumentParseResult.failure(new IllegalStateException("Input queue is empty")); + return ArgumentParseResult.failure(new NoInputProvidedException(getClass(), commandContext)); } return this.fromStringMapper.apply(inputQueue.poll()) .map(ArgumentParseResult::success) diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/GameRuleParser.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/GameRuleParser.java index 5d9ad44e..3c17d3f7 100644 --- a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/GameRuleParser.java +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/GameRuleParser.java @@ -3,6 +3,7 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import org.bukkit.GameRule; import org.checkerframework.checker.nullness.qual.NonNull; import se.hyperver.hyperverse.configuration.Messages; @@ -19,7 +20,7 @@ public class GameRuleParser implements ArgumentParser> { @NonNull final Queue<@NonNull String> inputQueue ) { if (inputQueue.isEmpty()) { - return ArgumentParseResult.failure(new IllegalStateException("Input queue is empty")); + return ArgumentParseResult.failure(new NoInputProvidedException(getClass(), commandContext)); } return java.util.Optional.ofNullable(GameRule.getByName(inputQueue.poll())) .>>map(ArgumentParseResult::success) diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/HyperWorldParser.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/HyperWorldParser.java new file mode 100644 index 00000000..a514dcec --- /dev/null +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/HyperWorldParser.java @@ -0,0 +1,100 @@ +package se.hyperver.hyperverse.commands.parser; + +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; +import org.bukkit.entity.Entity; +import org.checkerframework.checker.nullness.qual.NonNull; +import se.hyperver.hyperverse.configuration.Messages; +import se.hyperver.hyperverse.world.HyperWorld; +import se.hyperver.hyperverse.world.WorldManager; + +import java.util.Comparator; +import java.util.List; +import java.util.Queue; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class HyperWorldParser implements ArgumentParser { + + private final WorldManager worldManager; + + private final boolean filterSameWorld; + private final WorldState allowedWorldState; + + public enum WorldState { + ANY, + LOADED, + UNLOADED, + } + + public HyperWorldParser(@NonNull final WorldManager worldManager) { + this(worldManager, WorldState.ANY, false); + } + + public HyperWorldParser( + @NonNull final WorldManager worldManager, + @NonNull final WorldState allowedWorldState, + final boolean filterSameWorld + ) { + this.worldManager = worldManager; + this.allowedWorldState = allowedWorldState; + this.filterSameWorld = filterSameWorld; + } + + @Override + public @NonNull ArgumentParseResult<@NonNull HyperWorld> parse( + @NonNull final CommandContext<@NonNull C> commandContext, + @NonNull final Queue<@NonNull String> inputQueue + ) { + if (inputQueue.isEmpty()) { + return ArgumentParseResult.failure(new NoInputProvidedException(getClass(), commandContext)); + } + final String world = inputQueue.poll(); + final HyperWorld hyperWorld = this.worldManager.getWorld(world); + if (hyperWorld == null) { + return ArgumentParseResult.failure(new IllegalArgumentException(Messages.messageNoSuchWorld.withoutColorCodes())); + } + if (!this.filterSameWorld && this.allowedWorldState == WorldState.ANY) { + return ArgumentParseResult.success(hyperWorld); + } + if (this.allowedWorldState == WorldState.LOADED && !hyperWorld.isLoaded()) { + return ArgumentParseResult.failure(new IllegalArgumentException(Messages.messageWorldNotLoaded.withoutColorCodes())); + } else if (this.allowedWorldState == WorldState.UNLOADED && hyperWorld.isLoaded()) { + return ArgumentParseResult.failure(new IllegalArgumentException(Messages.messageWorldAlreadyLoaded.withoutColorCodes())); + } + // Must guard calls to commandContext behind filterSameWorld check otherwise we are violating + // the contextFree contract below in #isContextFree + if (filterSameWorld && (commandContext.getSender() instanceof Entity entity)) { + HyperWorld currentWorld = this.worldManager.getWorld(entity.getWorld()); + if (currentWorld != null && currentWorld.equals(hyperWorld)) { + return ArgumentParseResult.failure(new IllegalArgumentException(Messages.messagePlayerAlreadyInWorld.withoutColorCodes())); + } + } + return ArgumentParseResult.success(hyperWorld); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + @NonNull final CommandContext commandContext, + @NonNull final String input + ) { + Stream stream = this.worldManager.getWorlds().stream(); + if (this.allowedWorldState == WorldState.LOADED) { + stream = stream.filter(HyperWorld::isLoaded); + } else if (this.allowedWorldState == WorldState.UNLOADED) { + stream = stream.filter(Predicate.not(HyperWorld::isLoaded)); + } + return stream.map(HyperWorld::getDisplayName) + .filter(name -> name.startsWith(input)) + .sorted(Comparator.naturalOrder()) + .toList(); + } + + @Override + public boolean isContextFree() { + return !this.filterSameWorld; + } + +} diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldFlagParser.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldFlagParser.java index f48dc90e..75893f16 100644 --- a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldFlagParser.java +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldFlagParser.java @@ -3,6 +3,7 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import org.checkerframework.checker.nullness.qual.NonNull; import se.hyperver.hyperverse.configuration.Messages; import se.hyperver.hyperverse.flags.GlobalWorldFlagContainer; @@ -27,7 +28,7 @@ public WorldFlagParser(@NonNull final GlobalWorldFlagContainer flagContainer) { @NonNull final Queue<@NonNull String> inputQueue ) { if (inputQueue.isEmpty()) { - return ArgumentParseResult.failure(new IllegalStateException("Input queue is empty")); + return ArgumentParseResult.failure(new NoInputProvidedException(getClass(), commandContext)); } final WorldFlag flag = this.flagContainer.getFlagFromString(inputQueue.poll().toLowerCase(Locale.ENGLISH)); if (flag == null) { diff --git a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldStructureSettingParser.java b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldStructureSettingParser.java index 4753132a..01fdfe6b 100644 --- a/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldStructureSettingParser.java +++ b/hyperverse-core/src/main/java/se/hyperver/hyperverse/commands/parser/WorldStructureSettingParser.java @@ -3,6 +3,7 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import org.checkerframework.checker.nullness.qual.NonNull; import se.hyperver.hyperverse.configuration.Messages; import se.hyperver.hyperverse.world.WorldStructureSetting; @@ -22,7 +23,7 @@ public class WorldStructureSettingParser implements ArgumentParser inputQueue ) { if (inputQueue.isEmpty()) { - return ArgumentParseResult.failure(new IllegalArgumentException("Input queue is empty")); + return ArgumentParseResult.failure(new NoInputProvidedException(getClass(), commandContext)); } return switch (inputQueue.poll().toLowerCase(Locale.ENGLISH)) { case "yes", "true", "generate_structures", "structures" ->