From 179f94085ff97af04d9daa3c93bde12844ac412e Mon Sep 17 00:00:00 2001 From: kento2 Date: Mon, 23 Feb 2026 20:14:08 +0100 Subject: [PATCH] . --- README.md | 4 ++ .../java/de/kentoj/scrow/bukkit/ScrowAPI.java | 14 ++++-- .../command/execution/CommandExecutor.java | 4 +- .../command/type/PlayerArgumentType.java | 3 +- .../friends/FriendRequestUsageException.java | 12 +++++ .../services/friends/FriendsService.java | 33 +++++++++++++ .../bukkit/services/friends/Friendship.java | 5 ++ .../kentoj/scrow/bukkit/CoreImplPlugin.java | 9 ++-- .../kentoj/scrow/bukkit/ScrowAPISurface.java | 2 +- .../cmds2/parsing/ArgumentReaderImpl.java | 4 +- .../cmds2/parsing/CommandProcessor.java | 38 +++++++++------ .../economy/{command => }/CoinsCommand.java | 2 +- .../bukkit/friends/FriendshipService.java | 36 -------------- .../bukkit/friends/command/FriendCommand.java | 20 ++++++++ .../friends/command/FriendListLiteral.java | 48 +++++++++++++++++++ 15 files changed, 167 insertions(+), 67 deletions(-) create mode 100644 core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendRequestUsageException.java create mode 100644 core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendsService.java rename core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/{command => }/CoinsCommand.java (98%) delete mode 100644 core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/FriendshipService.java create mode 100644 core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java create mode 100644 core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java diff --git a/README.md b/README.md index e3648b4..2bfdac0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +TODO +- separate error handling from processing +- tab completion + environment ----------- diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java index f267154..721e7f5 100644 --- a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java @@ -3,14 +3,13 @@ package de.kentoj.scrow.bukkit; import com.mongodb.reactivestreams.client.MongoDatabase; import de.kentoj.scrow.bukkit.services.EconomyService; import de.kentoj.scrow.bukkit.services.command.CommandManager; +import de.kentoj.scrow.bukkit.services.friends.FriendsService; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ScrowAPI { @@ -18,11 +17,13 @@ public class ScrowAPI { @Getter private static @Nullable MongoDatabase database; @Getter - private static EconomyService economyService; - @Getter private static CommandManager commandManager; @Getter private static Scheduler minecraftScheduler; + @Getter + private static EconomyService economyService; + @Getter + private static FriendsService friendsService; @ApiStatus.Internal public static void setDatabase(@Nullable MongoDatabase database) { @@ -43,5 +44,10 @@ public class ScrowAPI { public static void setMinecraftScheduler(Scheduler minecraftScheduler) { ScrowAPI.minecraftScheduler = minecraftScheduler; } + + @ApiStatus.Internal + public static void setFriendsService(FriendsService friendsService) { + ScrowAPI.friendsService = friendsService; + } } diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/execution/CommandExecutor.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/execution/CommandExecutor.java index 6156f1e..f3700c7 100644 --- a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/execution/CommandExecutor.java +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/execution/CommandExecutor.java @@ -1,6 +1,8 @@ package de.kentoj.scrow.bukkit.services.command.execution; +import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; + public interface CommandExecutor { - void execute(CommandContext ctx); + void execute(CommandContext ctx) throws CommandInvocationException; } diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/type/PlayerArgumentType.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/type/PlayerArgumentType.java index f2ac631..be19e5d 100644 --- a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/type/PlayerArgumentType.java +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/command/type/PlayerArgumentType.java @@ -57,7 +57,7 @@ public class PlayerArgumentType { try { player = Bukkit.getOfflinePlayer(UUID.fromString(rawInput)); } catch (IllegalArgumentException ex) { - throw new CommandInvocationException("Not a UUID or name of an online player"); + throw new CommandInvocationException("Not a UUID or name of an online player -- " + rawInput); } } return player; @@ -75,6 +75,7 @@ public class PlayerArgumentType { @Override public void checkValue(CommandContext ctx, OfflinePlayer value, String rawInput) throws CommandInvocationException { + if (!value.hasPlayedBefore()) throw new CommandInvocationException("Player has never played before -- " + rawInput); } }; diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendRequestUsageException.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendRequestUsageException.java new file mode 100644 index 0000000..f4637df --- /dev/null +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendRequestUsageException.java @@ -0,0 +1,12 @@ +package de.kentoj.scrow.bukkit.services.friends; + +public class FriendRequestUsageException extends RuntimeException { + + public FriendRequestUsageException(String message) { + super(message); + } + + public FriendRequestUsageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendsService.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendsService.java new file mode 100644 index 0000000..f2f114b --- /dev/null +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/FriendsService.java @@ -0,0 +1,33 @@ +package de.kentoj.scrow.bukkit.services.friends; + +import org.jetbrains.annotations.NotNull; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +public interface FriendsService { + + Flux<@NotNull Friendship> getFriendships(UUID playerId); + + /** + * @param initiator id of player who wished to end the friendship + * @param other id of the player the initiator wants to end the friendship with + * @throws FriendRequestUsageException + */ + Mono<@NotNull Boolean> removeFriend(UUID initiator, UUID other); + + /** + * @param playerId + */ + Flux<@NotNull FriendRequest> getIncomingFriendRequests(UUID playerId); + + Flux<@NotNull FriendRequest> getOutgoingFriendRequests(UUID playerId); + + /** + * @param from id of player sending the friend request + * @param to id of the player the initiator wants to befriend + * @throws FriendRequestUsageException + */ + Mono<@NotNull Friendship> sendFriendRequest(UUID from, UUID to); +} diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/Friendship.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/Friendship.java index 7312c4f..b22eafa 100644 --- a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/Friendship.java +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/services/friends/Friendship.java @@ -8,4 +8,9 @@ import java.util.UUID; public interface Friendship { Tuple2 getPlayerIds(); Instant getCreatedAt(); + + default UUID getOther(UUID self) { + final var ids = getPlayerIds(); + return ids.getFirst() != self ? ids.getFirst() : ids.getSecond(); + } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java index 1123d67..439bccc 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java @@ -1,6 +1,7 @@ package de.kentoj.scrow.bukkit; -import de.kentoj.scrow.bukkit.economy.command.CoinsCommand; +import de.kentoj.scrow.bukkit.economy.CoinsCommand; +import de.kentoj.scrow.bukkit.friends.command.FriendCommand; import org.bukkit.plugin.java.JavaPlugin; import reactor.core.publisher.Mono; @@ -14,6 +15,7 @@ public class CoreImplPlugin extends JavaPlugin { { ScrowAPI.getCommandManager().register(new CoinsCommand().create(), this); + ScrowAPI.getCommandManager().register(new FriendCommand().create(), this); } { @@ -33,9 +35,4 @@ public class CoreImplPlugin extends JavaPlugin { } ScrowAPISurface.destroyScrowAPI(); } - - @Override - public void onDisable() { - - } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java index f794f71..d11e4ae 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java @@ -47,7 +47,7 @@ public class ScrowAPISurface { ScrowAPI.getDatabase() != null ? new MongoEconomyService(ScrowAPI.getDatabase()) : new InMemoryEconomyService() ); - + // TODO friends service Commands.setFactory(new CommandFactoryImpl()); ScrowAPI.setCommandManager(new BukkitCommandManager()); } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/ArgumentReaderImpl.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/ArgumentReaderImpl.java index b5d041f..8eed79c 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/ArgumentReaderImpl.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/ArgumentReaderImpl.java @@ -27,7 +27,7 @@ public class ArgumentReaderImpl implements ArgumentReader { final var str = new StringBuilder(); while (true) { if (buf.position() >= buf.limit()) break; - char c = buf.get(); + final char c = buf.get(); if (c == ' ') break; str.append(c); } @@ -40,7 +40,7 @@ public class ArgumentReaderImpl implements ArgumentReader { var quoting = false; while (true) { if (buf.position() >= buf.limit()) break; - char c = buf.get(); + final char c = buf.get(); if (c == '"') quoting = !quoting; if (c == ' ' && !quoting) break; str.append(c); diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/CommandProcessor.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/CommandProcessor.java index 36fda5a..cca2e1e 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/CommandProcessor.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/cmds2/parsing/CommandProcessor.java @@ -1,17 +1,18 @@ package de.kentoj.scrow.bukkit.cmds2.parsing; import de.kentoj.scrow.bukkit.cmds2.CommandContextImpl; -import de.kentoj.scrow.bukkit.services.command.literal.ArgumentReader; +import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; import de.kentoj.scrow.bukkit.services.command.execution.ArgumentData; +import de.kentoj.scrow.bukkit.services.command.literal.ArgumentReader; import de.kentoj.scrow.bukkit.services.command.literal.CommandArgument; import de.kentoj.scrow.bukkit.services.command.literal.Literal; import de.kentoj.scrow.bukkit.services.command.literal.RootLiteral; -import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; import lombok.RequiredArgsConstructor; import org.bukkit.command.CommandSender; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; @RequiredArgsConstructor public class CommandProcessor { @@ -22,28 +23,35 @@ public class CommandProcessor { private final ArgumentReader argumentReader; public void execute(RootLiteral rootLiteral) { - Literal lastLiteral = rootLiteral; + Literal literal = rootLiteral; do { - processArguments(lastLiteral); + processArguments(literal); if (argumentReader.isEOF()) { - if (lastLiteral.getExecutor() == null) throw new CommandInvocationException("Incomplete command -- literal expected"); - lastLiteral.getExecutor().execute(new CommandContextImpl(sender, arguments)); + if (literal.getExecutor() == null) { + throw new CommandInvocationException("Incomplete command -- literal expected"); + } + literal.getExecutor().execute(new CommandContextImpl(sender, arguments)); return; } final var literalName = argumentReader.readWord(); - lastLiteral = lastLiteral.getLiteral(literalName); - if (lastLiteral == null) throw new CommandInvocationException("Unknown literal -- " + literalName); + final var nextLiteral = literal.getLiteral(literalName); + if (nextLiteral == null) { + final String validLiterals = literal.getLiterals().stream().map(Literal::getName).collect(Collectors.joining(", ")); + throw new CommandInvocationException("Unknown literal -- " + literalName + + "\nValid literals: " + validLiterals); + } + literal = nextLiteral; - final boolean hasSenderPermission = lastLiteral.getRequiredPermission() == null || sender.hasPermission(lastLiteral.getRequiredPermission()); + final boolean hasSenderPermission = literal.getRequiredPermission() == null || sender.hasPermission(literal.getRequiredPermission()); if (!hasSenderPermission) throw new CommandInvocationException("No permission"); - } while (lastLiteral.hasLiteral()); + } while (literal.hasLiteral()); - processArguments(lastLiteral); + processArguments(literal); - if (lastLiteral.getExecutor() == null) throw new CommandInvocationException("Incomplete command."); - lastLiteral.getExecutor().execute(new CommandContextImpl(sender, arguments)); + if (literal.getExecutor() == null) throw new CommandInvocationException("Incomplete command."); + literal.getExecutor().execute(new CommandContextImpl(sender, arguments)); } private void processArguments(Literal node) { @@ -55,13 +63,13 @@ public class CommandProcessor { private ArgumentData parseArgument(CommandArgument argument) { if (argumentReader.isEOF()) { - var defaultValue = argument.getDefaultValue(new CommandContextImpl(sender, arguments)); + final var defaultValue = argument.getDefaultValue(new CommandContextImpl(sender, arguments)); if (defaultValue == null) throw new CommandInvocationException("Missing argument -- " + argument.getName()); return new ArgumentDataImpl<>(defaultValue, argument, null); } final var rawInput = argument.getType().readRawInput(argumentReader); - T value = argument.getType().parseInput(rawInput); + final T value = argument.getType().parseInput(rawInput); argument.getType().checkValue(new CommandContextImpl(sender, arguments), value, rawInput); return new ArgumentDataImpl<>(value, argument, rawInput); } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/command/CoinsCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java similarity index 98% rename from core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/command/CoinsCommand.java rename to core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java index 116472f..0c1ebc6 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/command/CoinsCommand.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java @@ -1,4 +1,4 @@ -package de.kentoj.scrow.bukkit.economy.command; +package de.kentoj.scrow.bukkit.economy; import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.services.command.literal.CommandArgument; diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/FriendshipService.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/FriendshipService.java deleted file mode 100644 index 804e245..0000000 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/FriendshipService.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.kentoj.scrow.bukkit.friends; - -import de.kentoj.scrow.bukkit.services.friends.FriendRequest; -import de.kentoj.scrow.bukkit.services.friends.Friendship; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.UUID; - -public interface FriendshipService { - - Flux<@NotNull Friendship> getFriendships(UUID playerId); - - /** - * @param initiator id of player who wished to end the friendship - * @param other id of the player the initiator wants to end the friendship with - * @return true on success, false if no such friendship existed. - */ - Mono<@NotNull Boolean> endFriendship(UUID initiator, UUID other); - - Flux<@NotNull FriendRequest> getIncomingFriendRequests(UUID playerId); - - Flux<@NotNull FriendRequest> getOutgoingFriendRequests(UUID playerId); - - /** - * @param initiator id of player sending the friend request - * @param to id of the player the initiator wants to befriend - * @return true on success, false if already requested - */ - Mono<@NotNull Boolean> sendFriendRequest(UUID initiator, UUID to); - - Mono<@NotNull Friendship> acceptFriendRequest(UUID from, UUID to); - - Mono<@NotNull Friendship> denyFriendRequest(UUID from, UUID to); -} diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java new file mode 100644 index 0000000..6e64a3b --- /dev/null +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java @@ -0,0 +1,20 @@ +package de.kentoj.scrow.bukkit.friends.command; + +import de.kentoj.scrow.bukkit.services.command.Commands; +import de.kentoj.scrow.bukkit.services.command.literal.RootLiteral; + +public class FriendCommand { + + private final RootLiteral rootLiteral; + + public FriendCommand() { + rootLiteral = Commands.root("friend"); + rootLiteral.addAliases("f", "friends"); + + rootLiteral.addLiteral(new FriendListLiteral().create()); + } + + public RootLiteral create() { + return rootLiteral; + } +} diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java new file mode 100644 index 0000000..b1857f2 --- /dev/null +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java @@ -0,0 +1,48 @@ +package de.kentoj.scrow.bukkit.friends.command; + +import de.kentoj.scrow.bukkit.ScrowAPI; +import de.kentoj.scrow.bukkit.services.command.Commands; +import de.kentoj.scrow.bukkit.services.command.execution.CommandContext; +import de.kentoj.scrow.bukkit.services.command.literal.Literal; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import reactor.core.scheduler.Schedulers; + +import java.util.UUID; + +public class FriendListLiteral { + + private final Literal literal; + + public FriendListLiteral() { + literal = Commands.literal("list"); + literal.setExecutor(this::listFriends); + } + + public Literal create() { + return literal; + } + + private void listFriends(CommandContext ctx) { + ScrowAPI.getFriendsService().getFriendships(ctx.getSenderPlayer().getUniqueId()) + .subscribeOn(Schedulers.boundedElastic()) + .collectList() + .publishOn(ScrowAPI.getMinecraftScheduler()) + .subscribe(friendships -> { + if (friendships.isEmpty()) { + ctx.getSender().sendMessage("You have no friends."); + } else { + ctx.getSender().sendMessage("You have " + friendships.size() + " friends:"); + friendships.forEach(friendship -> { + final UUID friendId = friendship.getOther(ctx.getSenderPlayer().getUniqueId()); + // TODO get name even if never online on the network + final OfflinePlayer friend = Bukkit.getOfflinePlayer(friendId); + ctx.getSender().sendMessage("- " + friend.getName()); + }); + } + }, error -> { + ctx.getSender().sendMessage("Error fetching friends"); + // TODO improve handling + }); + } +}