From cbfaa35f36ac53e399a3b6d1383aa2a60b10dbf8 Mon Sep 17 00:00:00 2001 From: Callum Seabrook Date: Tue, 15 Aug 2023 18:00:45 +0100 Subject: [PATCH] Service abstraction upgrade --- build.gradle.kts | 7 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../minestom/core/module/MinestomModule.java | 2 +- .../minestom/core/module/chat/ChatModule.java | 2 +- .../minestom/core/module/core/CoreModule.java | 18 ++- .../core/badge/BadgeAdminSubcommand.java | 144 ++++++------------ .../core/module/core/badge/BadgeCommand.java | 78 ++++------ .../core/module/core/badge/BadgeGui.java | 101 ++++++------ .../module/kubernetes/KubernetesModule.java | 11 +- .../command/agones/AgonesCommand.java | 3 +- .../currentserver/CurrentServerCommand.java | 40 +++-- .../module/liveconfig/LiveConfigModule.java | 2 +- .../module/matchmaker/MatchmakerModule.java | 56 +++---- .../matchmaker/commands/DequeueCommand.java | 69 +++------ .../matchmaker/commands/QueueCommand.java | 102 ++++--------- .../DefaultMatchmakingSessionImpl.java | 4 +- .../session/MatchmakingSession.java | 11 +- .../session/MatchmakingSessionManager.java | 93 +++++------ .../module/messaging/MessagingModule.java | 2 +- .../module/monitoring/MonitoringModule.java | 3 +- .../module/permissions/PermissionCache.java | 71 ++++----- .../module/permissions/PermissionModule.java | 9 +- .../permissions/PermissionUpdateListener.java | 2 +- .../core/utils/command/ExtraConditions.java | 15 -- .../utils/command/argument/ArgumentBadge.java | 42 ++--- .../command/argument/ArgumentMcPlayer.java | 74 ++++----- .../matchmaker/KurushimiMinestomUtils.java | 100 ++++++++++++ 27 files changed, 483 insertions(+), 580 deletions(-) delete mode 100644 src/main/java/dev/emortal/minestom/core/utils/command/ExtraConditions.java create mode 100644 src/main/java/dev/emortal/minestom/core/utils/matchmaker/KurushimiMinestomUtils.java diff --git a/build.gradle.kts b/build.gradle.kts index b7d78a0..b1e8ae4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,11 +26,10 @@ dependencies { implementation("ch.qos.logback:logback-classic:1.4.8") // APIs - api("dev.emortal.api:module-system:16353c8") + api("dev.emortal.api:module-system:e69aa43") api("dev.emortal.api:agones-sdk:1.0.7") - api("dev.emortal.api:common-proto-sdk:da7a48c") - api("dev.emortal.api:live-config-parser:a9fc46f") - api("dev.emortal.api:kurushimi-sdk:82c14c3") + api("dev.emortal.api:common-proto-sdk:4135280") + api("dev.emortal.api:live-config-parser:89c56a9") api("io.kubernetes:client-java:18.0.0") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87..84a0b92 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/dev/emortal/minestom/core/module/MinestomModule.java b/src/main/java/dev/emortal/minestom/core/module/MinestomModule.java index 63f6f21..7fdb8aa 100644 --- a/src/main/java/dev/emortal/minestom/core/module/MinestomModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/MinestomModule.java @@ -2,7 +2,7 @@ import dev.emortal.api.modules.Module; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; import net.minestom.server.MinecraftServer; import net.minestom.server.event.Event; import net.minestom.server.event.EventNode; diff --git a/src/main/java/dev/emortal/minestom/core/module/chat/ChatModule.java b/src/main/java/dev/emortal/minestom/core/module/chat/ChatModule.java index e74403b..7b2ab20 100644 --- a/src/main/java/dev/emortal/minestom/core/module/chat/ChatModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/chat/ChatModule.java @@ -4,7 +4,7 @@ import dev.emortal.api.message.messagehandler.ChatMessageCreatedMessage; import dev.emortal.api.model.messagehandler.ChatMessage; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; import dev.emortal.api.utils.kafka.FriendlyKafkaProducer; import dev.emortal.minestom.core.module.MinestomModule; import dev.emortal.minestom.core.module.messaging.MessagingModule; diff --git a/src/main/java/dev/emortal/minestom/core/module/core/CoreModule.java b/src/main/java/dev/emortal/minestom/core/module/core/CoreModule.java index 4029658..51cd88c 100644 --- a/src/main/java/dev/emortal/minestom/core/module/core/CoreModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/core/CoreModule.java @@ -1,17 +1,23 @@ package dev.emortal.minestom.core.module.core; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; +import dev.emortal.api.service.badges.BadgeService; +import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.api.utils.resolvers.PlayerResolver; import dev.emortal.minestom.core.module.MinestomModule; import dev.emortal.minestom.core.module.core.badge.BadgeCommand; import dev.emortal.minestom.core.module.core.performance.PerformanceCommand; import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandManager; import net.minestom.server.entity.Player; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @ModuleData(name = "core", required = false) public final class CoreModule extends MinestomModule { + private static final Logger LOGGER = LoggerFactory.getLogger(CoreModule.class); public CoreModule(@NotNull ModuleEnvironment environment) { super(environment); @@ -26,9 +32,15 @@ public boolean onLoad() { return new PlayerResolver.CachedMcPlayer(player.getUuid(), player.getUsername(), player.isOnline()); }); - var commandManager = MinecraftServer.getCommandManager(); + CommandManager commandManager = MinecraftServer.getCommandManager(); commandManager.register(new PerformanceCommand(this.eventNode)); - commandManager.register(new BadgeCommand()); + + BadgeService badgeService = GrpcStubCollection.getBadgeManagerService().orElse(null); + if (badgeService != null) { + commandManager.register(new BadgeCommand(badgeService)); + } else { + LOGGER.warn("Badge service unavailable. Badges will not work."); + } return true; } diff --git a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeAdminSubcommand.java b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeAdminSubcommand.java index 400be0e..f1e09ab 100644 --- a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeAdminSubcommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeAdminSubcommand.java @@ -1,18 +1,14 @@ package dev.emortal.minestom.core.module.core.badge; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.rpc.Status; -import dev.emortal.api.grpc.badge.BadgeManagerGrpc; -import dev.emortal.api.grpc.badge.BadgeManagerProto; import dev.emortal.api.grpc.mcplayer.McPlayerProto; import dev.emortal.api.model.mcplayer.McPlayer; -import dev.emortal.api.utils.GrpcStubCollection; -import dev.emortal.minestom.core.utils.command.ExtraConditions; +import dev.emortal.api.service.badges.AddBadgeToPlayerResult; +import dev.emortal.api.service.badges.BadgeService; +import dev.emortal.api.service.badges.RemoveBadgeFromPlayerResult; +import dev.emortal.api.service.mcplayer.McPlayerService; import dev.emortal.minestom.core.utils.command.argument.ArgumentBadge; import dev.emortal.minestom.core.utils.command.argument.ArgumentMcPlayer; -import io.grpc.protobuf.StatusProto; +import io.grpc.StatusRuntimeException; import net.kyori.adventure.text.Component; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Command; @@ -22,123 +18,69 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ForkJoinPool; +import java.util.UUID; +import java.util.function.Supplier; -public final class BadgeAdminSubcommand extends Command { +final class BadgeAdminSubcommand extends Command { private static final Logger LOGGER = LoggerFactory.getLogger(BadgeAdminSubcommand.class); - private final BadgeManagerGrpc.BadgeManagerFutureStub badgeManager; + private final BadgeService badgeService; - public BadgeAdminSubcommand(@NotNull BadgeManagerGrpc.BadgeManagerFutureStub badgeManager) { + BadgeAdminSubcommand(@NotNull BadgeService badgeService, @NotNull McPlayerService mcPlayerService) { super("admin"); - this.badgeManager = badgeManager; + this.badgeService = badgeService; - this.setCondition(ExtraConditions.hasPermission("command.badge.admin")); + this.setCondition((sender, $) -> sender.hasPermission("command.badge.admin")); var addArgument = new ArgumentLiteral("add"); var removeArgument = new ArgumentLiteral("remove"); - var playerArgument = ArgumentMcPlayer.create("player", - GrpcStubCollection.getPlayerService().orElse(null), - McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod.NONE - ); - var badgeArgument = ArgumentBadge.create(badgeManager, "badge", false); + var playerArgument = ArgumentMcPlayer.create("player", mcPlayerService, McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod.NONE); + var badgeArgument = ArgumentBadge.create(badgeService, "badge", false); this.addSyntax(this::executeAddBadgeToPlayer, addArgument, playerArgument, badgeArgument); this.addSyntax(this::executeRemoveBadgeFromPlayer, removeArgument, playerArgument, badgeArgument); } - private void executeAddBadgeToPlayer(CommandSender sender, CommandContext context) { - CompletableFuture playerFuture = context.get("player"); + private void executeAddBadgeToPlayer(@NotNull CommandSender sender, @NotNull CommandContext context) { + McPlayer player = getPlayer(context); String badgeId = context.get("badge"); - playerFuture.thenAccept(mcPlayer -> { - var request = BadgeManagerProto.AddBadgeToPlayerRequest.newBuilder() - .setBadgeId(badgeId) - .setPlayerId(mcPlayer.getId()) - .build(); - - Futures.addCallback(this.badgeManager.addBadgeToPlayer(request), new AddBadgeToPlayerCallback(sender), ForkJoinPool.commonPool()); - }); - } - - private record AddBadgeToPlayerCallback(@NotNull CommandSender sender) implements FutureCallback { - - @Override - public void onSuccess(@NotNull BadgeManagerProto.AddBadgeToPlayerResponse result) { - this.sender.sendMessage(Component.text("Added badge to player")); + AddBadgeToPlayerResult result; + try { + result = this.badgeService.addBadgeToPlayer(UUID.fromString(player.getId()), badgeId); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to add badge to player", exception); + sender.sendMessage(Component.text("Failed to add badge to player")); + return; } - @Override - public void onFailure(@NotNull Throwable throwable) { - Status status = StatusProto.fromThrowable(throwable); - if (status == null || status.getDetailsCount() == 0) { - LOGGER.error("Failed to add badge to player", throwable); - return; - } - - final BadgeManagerProto.AddBadgeToPlayerErrorResponse response; - try { - response = status.getDetails(0).unpack(BadgeManagerProto.AddBadgeToPlayerErrorResponse.class); - } catch (InvalidProtocolBufferException exception) { - LOGGER.error("Failed to add badge to player", exception); - return; - } - - switch (response.getReason()) { - case PLAYER_ALREADY_HAS_BADGE -> this.sender.sendMessage(Component.text("Player already has that badge")); - default -> { - LOGGER.error("Failed to add badge to player", throwable); - this.sender.sendMessage(Component.text("Failed to add badge to player")); - } - } + switch (result) { + case SUCCESS -> sender.sendMessage(Component.text("Added badge to player")); + case PLAYER_ALREADY_HAS_BADGE -> sender.sendMessage(Component.text("Player already has that badge")); } } - private void executeRemoveBadgeFromPlayer(CommandSender sender, CommandContext context) { - CompletableFuture playerFuture = context.get("player"); + private void executeRemoveBadgeFromPlayer(@NotNull CommandSender sender, @NotNull CommandContext context) { + McPlayer player = getPlayer(context); String badgeId = context.get("badge"); - playerFuture.thenAccept(mcPlayer -> { - var request = BadgeManagerProto.RemoveBadgeFromPlayerRequest.newBuilder() - .setBadgeId(badgeId) - .setPlayerId(mcPlayer.getId()) - .build(); - - Futures.addCallback(this.badgeManager.removeBadgeFromPlayer(request), new RemoveBadgeFromPlayerCallback(sender), ForkJoinPool.commonPool()); - }); - } - - private record RemoveBadgeFromPlayerCallback(@NotNull CommandSender sender) implements FutureCallback { - - @Override - public void onSuccess(@NotNull BadgeManagerProto.RemoveBadgeFromPlayerResponse result) { - this.sender.sendMessage(Component.text("Removed badge from player")); + RemoveBadgeFromPlayerResult result; + try { + result = this.badgeService.removeBadgeFromPlayer(UUID.fromString(player.getId()), badgeId); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to remove badge from player", exception); + sender.sendMessage(Component.text("Failed to remove badge from player")); + return; } - @Override - public void onFailure(@NotNull Throwable throwable) { - Status status = StatusProto.fromThrowable(throwable); - if (status == null || status.getDetailsCount() == 0) { - LOGGER.error("Failed to remove badge from player", throwable); - return; - } - - final BadgeManagerProto.RemoveBadgeFromPlayerErrorResponse response; - try { - response = status.getDetails(0).unpack(BadgeManagerProto.RemoveBadgeFromPlayerErrorResponse.class); - } catch (InvalidProtocolBufferException exception) { - LOGGER.error("Failed to remove badge from player", exception); - return; - } - - switch (response.getReason()) { - case PLAYER_DOESNT_HAVE_BADGE -> this.sender.sendMessage(Component.text("Player doesn't have that badge")); - default -> { - LOGGER.error("Failed to remove badge from player", throwable); - this.sender.sendMessage(Component.text("Failed to remove badge from player")); - } - } + switch (result) { + case SUCCESS -> sender.sendMessage(Component.text("Removed badge from player")); + case PLAYER_DOESNT_HAVE_BADGE -> sender.sendMessage(Component.text("Player doesn't have that badge")); } } + + private static @NotNull McPlayer getPlayer(@NotNull CommandContext context) { + Supplier supplier = context.get("player"); + return supplier.get(); + } } diff --git a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeCommand.java b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeCommand.java index 1d93648..823dda8 100644 --- a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeCommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeCommand.java @@ -1,14 +1,11 @@ package dev.emortal.minestom.core.module.core.badge; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.rpc.Status; -import dev.emortal.api.grpc.badge.BadgeManagerGrpc; -import dev.emortal.api.grpc.badge.BadgeManagerProto; +import dev.emortal.api.service.badges.BadgeService; +import dev.emortal.api.service.badges.SetActiveBadgeResult; +import dev.emortal.api.service.mcplayer.McPlayerService; import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.minestom.core.utils.command.argument.ArgumentBadge; -import io.grpc.protobuf.StatusProto; +import io.grpc.StatusRuntimeException; import net.kyori.adventure.text.Component; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Command; @@ -20,69 +17,46 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ForkJoinPool; - public final class BadgeCommand extends Command { private static final Logger LOGGER = LoggerFactory.getLogger(BadgeCommand.class); - private final BadgeManagerGrpc.BadgeManagerFutureStub badgeManager = GrpcStubCollection.getBadgeManagerService().orElse(null); + private final BadgeService badgeService; - public BadgeCommand() { + public BadgeCommand(@NotNull BadgeService badgeService) { super("badge"); + this.badgeService = badgeService; this.setCondition(Conditions::playerOnly); - this.setDefaultExecutor((sender, context) -> new BadgeGui((Player) sender)); + this.setDefaultExecutor((sender, context) -> new BadgeGui((Player) sender, badgeService)); var setArgument = new ArgumentLiteral("set"); - var badgeArgument = ArgumentBadge.create(this.badgeManager, "badge", true); - + var badgeArgument = ArgumentBadge.create(this.badgeService, "badge", true); this.addConditionalSyntax(Conditions::playerOnly, this::executeSetCurrentBadge, setArgument, badgeArgument); - this.addSubcommand(new BadgeAdminSubcommand(this.badgeManager)); + + McPlayerService mcPlayerService = GrpcStubCollection.getPlayerService().orElse(null); + if (mcPlayerService != null) { + this.addSubcommand(new BadgeAdminSubcommand(this.badgeService, mcPlayerService)); + } else { + LOGGER.warn("MC player service unavailable. Badge admin command will not be registered."); + } } private void executeSetCurrentBadge(CommandSender sender, CommandContext context) { String badgeId = context.get("badge"); Player player = (Player) sender; - var request = BadgeManagerProto.SetActivePlayerBadgeRequest.newBuilder() - .setBadgeId(badgeId) - .setPlayerId(player.getUuid().toString()) - .build(); - - Futures.addCallback(this.badgeManager.setActivePlayerBadge(request), new SetCurrentBadgeCallback(sender, badgeId), ForkJoinPool.commonPool()); - } - - private record SetCurrentBadgeCallback(@NotNull CommandSender sender, - @NotNull String badgeId) implements FutureCallback { - - @Override - public void onSuccess(@NotNull BadgeManagerProto.SetActivePlayerBadgeResponse result) { - this.sender.sendMessage(Component.text("Set your badge to " + this.badgeId)); + SetActiveBadgeResult result; + try { + result = this.badgeService.setActiveBadge(player.getUuid(), badgeId); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to set badge", exception); + sender.sendMessage(Component.text("Failed to set badge")); + return; } - @Override - public void onFailure(@NotNull Throwable throwable) { - Status status = StatusProto.fromThrowable(throwable); - if (status == null || status.getDetailsCount() == 0) { - LOGGER.error("Failed to set badge", throwable); - return; - } - - final BadgeManagerProto.SetActivePlayerBadgeErrorResponse response; - try { - response = status.getDetails(0).unpack(BadgeManagerProto.SetActivePlayerBadgeErrorResponse.class); - } catch (InvalidProtocolBufferException exception) { - LOGGER.error("Failed to set badge", throwable); - return; - } - - switch (response.getReason()) { - case PLAYER_DOESNT_HAVE_BADGE -> this.sender.sendMessage(Component.text("You don't have that badge")); - default -> { - LOGGER.error("Failed to set badge", throwable); - this.sender.sendMessage(Component.text("Failed to set badge")); - } - } + switch (result) { + case SUCCESS -> sender.sendMessage(Component.text("Set your badge to " + badgeId)); + case PLAYER_DOESNT_HAVE_BADGE -> sender.sendMessage(Component.text("You don't have that badge")); } } } diff --git a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeGui.java b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeGui.java index 985927c..11f09ea 100644 --- a/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeGui.java +++ b/src/main/java/dev/emortal/minestom/core/module/core/badge/BadgeGui.java @@ -1,11 +1,10 @@ package dev.emortal.minestom.core.module.core.badge; -import com.google.common.util.concurrent.Futures; -import dev.emortal.api.grpc.badge.BadgeManagerGrpc; import dev.emortal.api.grpc.badge.BadgeManagerProto; import dev.emortal.api.model.badge.Badge; -import dev.emortal.api.utils.GrpcStubCollection; -import dev.emortal.api.utils.callback.FunctionalFutureCallback; +import dev.emortal.api.service.badges.BadgeService; +import dev.emortal.api.service.badges.SetActiveBadgeResult; +import io.grpc.StatusRuntimeException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -26,11 +25,9 @@ import java.util.Comparator; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; -public final class BadgeGui { +final class BadgeGui { private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); private static final Logger LOGGER = LoggerFactory.getLogger(BadgeGui.class); @@ -48,36 +45,26 @@ public final class BadgeGui { private static final Tag BADGE_ACTIVE_TAG = Tag.Boolean("badge_active"); private static final Tag BADGE_UNLOCKED_TAG = Tag.Boolean("badge_unlocked"); - private final BadgeManagerGrpc.BadgeManagerFutureStub badgeService = GrpcStubCollection.getBadgeManagerService().orElse(null); - - private final @NotNull Inventory inventory; + private final Inventory inventory; + private final BadgeService badgeService; private final boolean canChangeActive; - public BadgeGui(@NotNull Player player) { + BadgeGui(@NotNull Player player, @NotNull BadgeService badgeService) { this.inventory = new Inventory(InventoryType.CHEST_4_ROW, TITLE); + this.badgeService = badgeService; - var badgesRequest = BadgeManagerProto.GetBadgesRequest.newBuilder().build(); - var playerBadgesRequest = BadgeManagerProto.GetPlayerBadgesRequest.newBuilder().setPlayerId(player.getUuid().toString()).build(); - - final BadgeManagerProto.GetBadgesResponse badges; - final BadgeManagerProto.GetPlayerBadgesResponse playerBadges; - try { - badges = this.badgeService.getBadges(badgesRequest).get(); - playerBadges = this.badgeService.getPlayerBadges(playerBadgesRequest).get(); - } catch (final InterruptedException | ExecutionException exception) { - throw new RuntimeException(exception); - } + List badges = this.badgeService.getAllBadges(); + BadgeManagerProto.GetPlayerBadgesResponse playerBadges = this.badgeService.getPlayerBadges(player.getUuid()); Set ownedBadgeIds = playerBadges.getBadgesList().stream() .map(Badge::getId) .collect(Collectors.toUnmodifiableSet()); boolean canChangeActive = true; - for (var badge : badges.getBadgesList()) { - if (badge.getRequired() && ownedBadgeIds.contains(badge.getId())) { // badge is required and player owns it - canChangeActive = false; - break; - } + for (Badge badge : badges) { + if (!badge.getRequired() || !ownedBadgeIds.contains(badge.getId())) continue; // badge is not required or player doesn't own it + canChangeActive = false; + break; } this.canChangeActive = canChangeActive; @@ -86,14 +73,13 @@ public BadgeGui(@NotNull Player player) { player.openInventory(this.inventory); } - private void drawInventory(@NotNull BadgeManagerProto.GetBadgesResponse badgesResp, - @NotNull Set ownedBadgeIds, String activeBadgeId) { - List badges = badgesResp.getBadgesList().stream() + private void drawInventory(@NotNull List allBadges, @NotNull Set ownedBadgeIds, @NotNull String activeBadgeId) { + List sortedBadges = allBadges.stream() .sorted(Comparator.comparingLong(Badge::getPriority)) .toList(); - for (int i = 0; i < badges.size(); i++) { - Badge badge = badges.get(i); + for (int i = 0; i < sortedBadges.size(); i++) { + Badge badge = sortedBadges.get(i); boolean isOwned = ownedBadgeIds.contains(badge.getId()); boolean isActive = badge.getId().equals(activeBadgeId); @@ -102,7 +88,7 @@ private void drawInventory(@NotNull BadgeManagerProto.GetBadgesResponse badgesRe } } - private void onClick(Player clicker, int slot, ClickType clickType, InventoryConditionResult result) { + private void onClick(@NotNull Player clicker, int slot, @NotNull ClickType clickType, @NotNull InventoryConditionResult result) { if (slot < 0 || slot >= this.inventory.getSize()) return; ItemStack clickedItem = this.inventory.getItemStack(slot); @@ -120,40 +106,38 @@ private void onClick(Player clicker, int slot, ClickType clickType, InventoryCon if (!isUnlocked || isActive) return; String badgeId = clickedItem.meta().getTag(BADGE_ID_TAG); - var setPlayerBadgeRequest = BadgeManagerProto.SetActivePlayerBadgeRequest.newBuilder() - .setPlayerId(clicker.getUuid().toString()) - .setBadgeId(badgeId) - .build(); + SetActiveBadgeResult setResult; + try { + setResult = this.badgeService.setActiveBadge(clicker.getUuid(), badgeId); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to set active badge for {}", clicker.getUsername(), exception); + clicker.sendMessage(Component.text("Failed to set active badge", NamedTextColor.RED)); + return; + } - Futures.addCallback(this.badgeService.setActivePlayerBadge(setPlayerBadgeRequest), FunctionalFutureCallback.create( - unused -> { - String badgeName = clickedItem.meta().getTag(BADGE_NAME_TAG); - clicker.sendMessage(Component.text("Set active badge to " + badgeName, NamedTextColor.GREEN)); - new BadgeGui(clicker); // Reopen the gui - }, - throwable -> { - LOGGER.error("Failed to set active badge for {}: {}", clicker.getUsername(), throwable); - clicker.sendMessage(Component.text("Failed to set active badge", NamedTextColor.RED)); - } - ), ForkJoinPool.commonPool()); + switch (setResult) { + case SUCCESS -> { + String badgeName = clickedItem.meta().getTag(BADGE_NAME_TAG); + clicker.sendMessage(Component.text("Set active badge to " + badgeName, NamedTextColor.GREEN)); + new BadgeGui(clicker, this.badgeService); // Reopen the gui + } + case PLAYER_DOESNT_HAVE_BADGE -> {} // do nothing + } } private @NotNull ItemStack createItemStack(@NotNull Badge badge, boolean isOwned, boolean isActive) { Badge.GuiItem guiItem = badge.getGuiItem(); - - final List lore = new ArrayList<>(); + List lore = new ArrayList<>(); // Unlocked: Yes/No lore.add(MINI_MESSAGE.deserialize(isOwned ? UNLOCKED_LINE : NOT_UNLOCKED_LINE)); - if (isOwned) { // Active: Yes/No lore.add(MINI_MESSAGE.deserialize(isActive ? ACTIVE_LINE : NOT_ACTIVE_LINE)); } - lore.add(Component.empty()); - for (var line : guiItem.getLoreList()) { + for (String line : guiItem.getLoreList()) { lore.add(MINI_MESSAGE.deserialize(line)); } @@ -162,15 +146,20 @@ private void onClick(Player clicker, int slot, ClickType clickType, InventoryCon lore.add(MINI_MESSAGE.deserialize(REQUIRED_LINE)); } - return ItemStack.builder(Material.fromNamespaceId(guiItem.getMaterial())) + Material material = Material.fromNamespaceId(guiItem.getMaterial()); + if (material == null) { + LOGGER.error("Failed to find material {} for badge {}", guiItem.getMaterial(), badge.getId()); + return ItemStack.AIR; + } + + return ItemStack.builder(material) .displayName(MINI_MESSAGE.deserialize(guiItem.getDisplayName())) .lore(lore) .meta(builder -> builder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES) .set(BADGE_ID_TAG, badge.getId()) .set(BADGE_NAME_TAG, badge.getFriendlyName()) .set(BADGE_ACTIVE_TAG, isActive) - .set(BADGE_UNLOCKED_TAG, isOwned) - ) + .set(BADGE_UNLOCKED_TAG, isOwned)) .build(); } } diff --git a/src/main/java/dev/emortal/minestom/core/module/kubernetes/KubernetesModule.java b/src/main/java/dev/emortal/minestom/core/module/kubernetes/KubernetesModule.java index 563cf2c..7ef9a03 100644 --- a/src/main/java/dev/emortal/minestom/core/module/kubernetes/KubernetesModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/kubernetes/KubernetesModule.java @@ -6,7 +6,9 @@ import dev.emortal.api.agonessdk.IgnoredStreamObserver; import dev.emortal.api.modules.Module; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; +import dev.emortal.api.service.playertracker.PlayerTrackerService; +import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.minestom.core.Environment; import dev.emortal.minestom.core.module.kubernetes.command.agones.AgonesCommand; import dev.emortal.minestom.core.module.kubernetes.command.currentserver.CurrentServerCommand; @@ -68,7 +70,12 @@ public boolean onLoad() { } // player tracker - MinecraftServer.getCommandManager().register(new CurrentServerCommand()); + PlayerTrackerService playerTracker = GrpcStubCollection.getPlayerTrackerService().orElse(null); + if (playerTracker != null) { + MinecraftServer.getCommandManager().register(new CurrentServerCommand(playerTracker)); + } else { + LOGGER.warn("Player tracker service unavailable. Current server command will not work."); + } // agones if (AGONES_SDK_ENABLED) this.loadAgones(); diff --git a/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/agones/AgonesCommand.java b/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/agones/AgonesCommand.java index bb77d72..5f3f738 100644 --- a/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/agones/AgonesCommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/agones/AgonesCommand.java @@ -1,7 +1,6 @@ package dev.emortal.minestom.core.module.kubernetes.command.agones; import dev.agones.sdk.SDKGrpc; -import dev.emortal.minestom.core.utils.command.ExtraConditions; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.arguments.ArgumentLiteral; @@ -13,7 +12,7 @@ public final class AgonesCommand extends Command { public AgonesCommand(@NotNull SDKGrpc.SDKStub sdk) { super("magones"); - this.setCondition(ExtraConditions.hasPermission("command.agones")); + this.setCondition((source, $) -> source.hasPermission("command.agones")); var sdkSubs = new SdkSubCommands(sdk); diff --git a/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/currentserver/CurrentServerCommand.java b/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/currentserver/CurrentServerCommand.java index d4d1b3e..6ce783c 100644 --- a/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/currentserver/CurrentServerCommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/kubernetes/command/currentserver/CurrentServerCommand.java @@ -1,11 +1,8 @@ package dev.emortal.minestom.core.module.kubernetes.command.currentserver; -import com.google.common.util.concurrent.Futures; -import dev.emortal.api.grpc.mcplayer.McPlayerProto; -import dev.emortal.api.grpc.mcplayer.PlayerTrackerGrpc; import dev.emortal.api.model.mcplayer.CurrentServer; -import dev.emortal.api.utils.GrpcStubCollection; -import dev.emortal.api.utils.callback.FunctionalFutureCallback; +import dev.emortal.api.service.playertracker.PlayerTrackerService; +import io.grpc.StatusRuntimeException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; @@ -22,8 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ForkJoinPool; - public final class CurrentServerCommand extends Command { private static final Logger LOGGER = LoggerFactory.getLogger(CurrentServerCommand.class); @@ -36,10 +31,11 @@ public final class CurrentServerCommand extends Command { Instance: %s Position: %s"""; - private final PlayerTrackerGrpc.PlayerTrackerFutureStub playerTracker = GrpcStubCollection.getPlayerTrackerService().orElse(null); + private final PlayerTrackerService playerTracker; - public CurrentServerCommand() { + public CurrentServerCommand(@NotNull PlayerTrackerService playerTracker) { super("whereami"); + this.playerTracker = playerTracker; this.setCondition(Conditions::playerOnly); this.setDefaultExecutor(this::onExecute); @@ -53,21 +49,21 @@ private void onExecute(CommandSender sender, CommandContext context) { return; } - var request = McPlayerProto.GetPlayerServersRequest.newBuilder().addPlayerIds(player.getUuid().toString()).build(); - Futures.addCallback(this.playerTracker.getPlayerServers(request), FunctionalFutureCallback.create( - response -> { - CurrentServer currentServer = response.getPlayerServersMap().get(player.getUuid().toString()); + CurrentServer server; + try { + server = this.playerTracker.getServer(player.getUuid()); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to retrieve player server", exception); + return; + } - var serverId = Placeholder.unparsed("server_id", currentServer.getServerId()); - var proxyId = Placeholder.unparsed("proxy_id", currentServer.getProxyId()); + var serverId = Placeholder.unparsed("server_id", server.getServerId()); + var proxyId = Placeholder.unparsed("proxy_id", server.getProxyId()); - var message = MiniMessage.miniMessage().deserialize(MESSAGE, serverId, proxyId) - .clickEvent(ClickEvent.copyToClipboard(createCopyableData(currentServer, player))) - .hoverEvent(HoverEvent.showText(Component.text("Click to copy", NamedTextColor.GREEN))); - sender.sendMessage(message); - }, - exception -> LOGGER.error("Failed to retrieve player server", exception) - ), ForkJoinPool.commonPool()); + var message = MiniMessage.miniMessage().deserialize(MESSAGE, serverId, proxyId) + .clickEvent(ClickEvent.copyToClipboard(this.createCopyableData(server, player))) + .hoverEvent(HoverEvent.showText(Component.text("Click to copy", NamedTextColor.GREEN))); + sender.sendMessage(message); } private String createCopyableData(@NotNull CurrentServer server, @NotNull Player player) { diff --git a/src/main/java/dev/emortal/minestom/core/module/liveconfig/LiveConfigModule.java b/src/main/java/dev/emortal/minestom/core/module/liveconfig/LiveConfigModule.java index 2a123a7..c4257d0 100644 --- a/src/main/java/dev/emortal/minestom/core/module/liveconfig/LiveConfigModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/liveconfig/LiveConfigModule.java @@ -3,7 +3,7 @@ import dev.emortal.api.liveconfigparser.configs.LiveConfigCollection; import dev.emortal.api.modules.Module; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; import dev.emortal.minestom.core.module.kubernetes.KubernetesModule; import io.kubernetes.client.openapi.ApiClient; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/MatchmakerModule.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/MatchmakerModule.java index f708201..8af0985 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/MatchmakerModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/MatchmakerModule.java @@ -1,13 +1,10 @@ package dev.emortal.minestom.core.module.matchmaker; -import dev.emortal.api.kurushimi.KurushimiStubCollection; -import dev.emortal.api.kurushimi.KurushimiUtils; -import dev.emortal.api.kurushimi.MatchmakerGrpc; -import dev.emortal.api.kurushimi.Ticket; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeCollection; -import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; +import dev.emortal.api.service.matchmaker.MatchmakerService; +import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.minestom.core.module.MinestomModule; import dev.emortal.minestom.core.module.liveconfig.LiveConfigModule; import dev.emortal.minestom.core.module.matchmaker.commands.DequeueCommand; @@ -17,8 +14,6 @@ import dev.emortal.minestom.core.module.matchmaker.session.MatchmakingSessionManager; import dev.emortal.minestom.core.module.messaging.MessagingModule; import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.Player; -import org.apache.commons.lang3.function.TriFunction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,44 +22,51 @@ public final class MatchmakerModule extends MinestomModule { private static final Logger LOGGER = LoggerFactory.getLogger(MatchmakerModule.class); - private final MatchmakerGrpc.MatchmakerFutureStub matchmaker = KurushimiStubCollection.getFutureStub().orElse(null); + private final MatchmakingSession.Creator sessionCreator; - private final MessagingModule messaging; - private final LiveConfigModule liveConfig; + private MatchmakerService matchmaker; - private final TriFunction sessionCreator; + public MatchmakerModule(@NotNull ModuleEnvironment environment, @NotNull MatchmakingSession.Creator sessionCreator) { + super(environment); + this.sessionCreator = sessionCreator; + } public MatchmakerModule(@NotNull ModuleEnvironment environment) { this(environment, DefaultMatchmakingSessionImpl::new); } - public MatchmakerModule(@NotNull ModuleEnvironment environment, @NotNull TriFunction sessionCreator) { - super(environment); - this.sessionCreator = sessionCreator; - - KurushimiUtils.registerParserRegistry(); - this.messaging = this.getModule(MessagingModule.class); - this.liveConfig = this.getModule(LiveConfigModule.class); + // If you can access this method, we assume the module was loaded and the matchmaker is not null. + public @NotNull MatchmakerService getMatchmaker() { + return this.matchmaker; } @Override public boolean onLoad() { - if (this.matchmaker == null) { - LOGGER.error("Matchmaker gRPC stub is not present but is required for MatchmakerModule"); + MessagingModule messaging = this.getModule(MessagingModule.class); + if (messaging == null) { + LOGGER.warn("Messaging unavailable. Matchmaking will not work."); + return false; + } + + LiveConfigModule liveConfig = this.getModule(LiveConfigModule.class); + if (liveConfig == null || liveConfig.getConfigCollection().gameModes() == null) { + LOGGER.warn("Live config parser unavailable. Matchmaking will not work."); return false; } + GameModeCollection gameModes = liveConfig.getConfigCollection().gameModes(); - GameModeCollection gameModes = this.liveConfig.getConfigCollection().gameModes(); - if (gameModes == null) { - LOGGER.error("GameModeCollection is not present in LiveConfigModule but is required for MatchmakerModule"); + MatchmakerService matchmaker = GrpcStubCollection.getMatchmakerService().orElse(null); + if (matchmaker == null) { + LOGGER.warn("Matchmaker service unavailable. Matchmaking will not work."); return false; } + this.matchmaker = matchmaker; var commandManager = MinecraftServer.getCommandManager(); - commandManager.register(new QueueCommand(this.matchmaker, gameModes)); - commandManager.register(new DequeueCommand(this.matchmaker)); + commandManager.register(new QueueCommand(matchmaker, gameModes)); + commandManager.register(new DequeueCommand(matchmaker)); - new MatchmakingSessionManager(this.eventNode, this.matchmaker, this.messaging, gameModes, this.sessionCreator); + new MatchmakingSessionManager(this.eventNode, matchmaker, messaging, gameModes, this.sessionCreator); return true; } diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/DequeueCommand.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/DequeueCommand.java index cc72943..f5b6015 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/DequeueCommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/DequeueCommand.java @@ -1,15 +1,9 @@ package dev.emortal.minestom.core.module.matchmaker.commands; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.rpc.Status; -import dev.emortal.api.kurushimi.DequeueByPlayerErrorResponse; -import dev.emortal.api.kurushimi.DequeueByPlayerRequest; -import dev.emortal.api.kurushimi.DequeueByPlayerResponse; -import dev.emortal.api.kurushimi.MatchmakerGrpc; +import dev.emortal.api.service.matchmaker.DequeuePlayerResult; +import dev.emortal.api.service.matchmaker.MatchmakerService; import dev.emortal.minestom.core.module.matchmaker.CommonMatchmakerError; -import io.grpc.protobuf.StatusProto; +import io.grpc.StatusRuntimeException; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; @@ -19,14 +13,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ForkJoinPool; - public final class DequeueCommand extends Command { private static final Logger LOGGER = LoggerFactory.getLogger(DequeueCommand.class); - private final MatchmakerGrpc.MatchmakerFutureStub matchmaker; + private final MatchmakerService matchmaker; - public DequeueCommand(@NotNull MatchmakerGrpc.MatchmakerFutureStub matchmaker) { + public DequeueCommand(@NotNull MatchmakerService matchmaker) { super("dequeue"); this.matchmaker = matchmaker; @@ -36,45 +28,22 @@ public DequeueCommand(@NotNull MatchmakerGrpc.MatchmakerFutureStub matchmaker) { private void execute(CommandSender sender, CommandContext context) { Player player = (Player) sender; - var request = DequeueByPlayerRequest.newBuilder().setPlayerId(player.getUuid().toString()).build(); - Futures.addCallback(this.matchmaker.dequeueByPlayer(request), new DequeueCallback(player), ForkJoinPool.commonPool()); - } - - private record DequeueCallback(@NotNull Player player) implements FutureCallback { - @Override - public void onSuccess(@NotNull DequeueByPlayerResponse result) { - this.player.sendMessage(CommonMatchmakerError.DEQUEUE_SUCCESS); + DequeuePlayerResult result; + try { + result = this.matchmaker.dequeuePlayer(player.getUuid()); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to dequeue player for unknown reason (id: {})", player.getUuid(), exception); + player.sendMessage(CommonMatchmakerError.DEQUEUE_ERR_UNKNOWN); + return; } - @Override - public void onFailure(@NotNull Throwable throwable) { - Status status = StatusProto.fromThrowable(throwable); - if (status == null || status.getDetailsCount() == 0) { - this.player.sendMessage(CommonMatchmakerError.DEQUEUE_ERR_UNKNOWN); - LOGGER.error("Failed to dequeue player (id: {})", this.player.getUuid(), throwable); - return; - } - - final DequeueByPlayerErrorResponse response; - try { - response = status.getDetails(0).unpack(DequeueByPlayerErrorResponse.class); - } catch (InvalidProtocolBufferException exception) { - this.player.sendMessage(CommonMatchmakerError.DEQUEUE_ERR_UNKNOWN); - LOGGER.error("Failed to dequeue player (id: {})", this.player.getUuid(), throwable); - return; - } - - var message = switch (response.getReason()) { - case NOT_IN_QUEUE -> CommonMatchmakerError.DEQUEUE_ERR_NOT_IN_QUEUE; - case NO_PERMISSION -> CommonMatchmakerError.DEQUEUE_ERR_NO_PERMISSION; - case ALREADY_MARKED_FOR_DEQUEUE -> CommonMatchmakerError.DEQUEUE_ERR_ALREADY_MARKED; - default -> { - LOGGER.error("Failed to dequeue player for unknown reason (id: {}, errorResponse: {})", this.player.getUuid(), response, throwable); - yield CommonMatchmakerError.DEQUEUE_ERR_UNKNOWN; - } - }; - this.player.sendMessage(message); - } + var message = switch (result) { + case SUCCESS -> CommonMatchmakerError.DEQUEUE_SUCCESS; + case NOT_IN_QUEUE -> CommonMatchmakerError.DEQUEUE_ERR_NOT_IN_QUEUE; + case NO_PERMISSION -> CommonMatchmakerError.DEQUEUE_ERR_NO_PERMISSION; + case ALREADY_MARKED_FOR_DEQUEUE -> CommonMatchmakerError.DEQUEUE_ERR_ALREADY_MARKED; + }; + player.sendMessage(message); } } diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/QueueCommand.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/QueueCommand.java index 91fcb5c..15611e8 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/QueueCommand.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/commands/QueueCommand.java @@ -1,17 +1,11 @@ package dev.emortal.minestom.core.module.matchmaker.commands; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.rpc.Status; -import dev.emortal.api.kurushimi.MatchmakerGrpc; -import dev.emortal.api.kurushimi.QueueByPlayerErrorResponse; -import dev.emortal.api.kurushimi.QueueByPlayerRequest; -import dev.emortal.api.kurushimi.QueueByPlayerResponse; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeCollection; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; +import dev.emortal.api.service.matchmaker.MatchmakerService; +import dev.emortal.api.service.matchmaker.QueuePlayerResult; import dev.emortal.minestom.core.module.matchmaker.CommonMatchmakerError; -import io.grpc.protobuf.StatusProto; +import io.grpc.StatusRuntimeException; import java.util.Collection; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -26,18 +20,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collection; -import java.util.concurrent.ForkJoinPool; import java.util.stream.Stream; public class QueueCommand extends Command { private static final Logger LOGGER = LoggerFactory.getLogger(QueueCommand.class); private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); - private final MatchmakerGrpc.MatchmakerFutureStub matchmaker; + private final MatchmakerService matchmaker; private final Collection configs; - public QueueCommand(@NotNull MatchmakerGrpc.MatchmakerFutureStub matchmaker, @NotNull GameModeCollection gameModeCollection) { + public QueueCommand(@NotNull MatchmakerService matchmaker, @NotNull GameModeCollection gameModeCollection) { super("play", "queue"); this.matchmaker = matchmaker; this.configs = gameModeCollection.getAllConfigs(); @@ -78,66 +70,38 @@ private void execute(@NotNull CommandSender sender, @NotNull CommandContext cont return; } - var request = QueueByPlayerRequest.newBuilder() - .setPlayerId(player.getUuid().toString()) - .setGameModeId(mode.id()) - .build(); - - Futures.addCallback(this.matchmaker.queueByPlayer(request), new QueueCallback(sender, mode), ForkJoinPool.commonPool()); - } + var modeNamePlaceholder = Placeholder.unparsed("mode", mode.friendlyName()); - private record QueueCallback(@NotNull CommandSender sender, @NotNull GameModeConfig mode) implements FutureCallback { - - @Override - public void onSuccess(@NotNull QueueByPlayerResponse result) { - var modeName = Placeholder.unparsed("mode", this.mode.friendlyName()); - this.sender.sendMessage(MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_SUCCESS, modeName)); + QueuePlayerResult result; + try { + result = this.matchmaker.queuePlayer(mode.id(), player.getUuid()); + } catch (StatusRuntimeException exception) { + LOGGER.error("An unknown error occurred while queuing for " + mode.friendlyName(), exception); + player.sendMessage(MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeNamePlaceholder)); + return; } - @Override - public void onFailure(@NotNull Throwable throwable) { - Status status = StatusProto.fromThrowable(throwable); - if (status == null || status.getDetailsCount() == 0) { - this.sender.sendMessage("An unknown error occurred while queuing for " + this.mode.friendlyName()); - LOGGER.error("An unknown error occurred while queuing for " + this.mode.friendlyName(), throwable); - return; + var message = switch (result) { + case SUCCESS -> MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_SUCCESS, modeNamePlaceholder); + case ALREADY_IN_QUEUE -> CommonMatchmakerError.QUEUE_ERR_ALREADY_IN_QUEUE; + case INVALID_GAME_MODE -> { + LOGGER.error("Invalid gamemode " + mode.friendlyName()); + yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeNamePlaceholder); } - - final QueueByPlayerErrorResponse response; - try { - response = status.getDetails(0).unpack(QueueByPlayerErrorResponse.class); - } catch (InvalidProtocolBufferException exception) { - this.sender.sendMessage("An unknown error occurred while queuing for " + this.mode.friendlyName()); - LOGGER.error("An unknown error occurred while queuing for " + this.mode.friendlyName(), exception); - return; + case GAME_MODE_DISABLED -> { + LOGGER.error("Gamemode " + mode.friendlyName() + " is disabled"); + yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeNamePlaceholder); } - - var modeName = Placeholder.unparsed("mode", this.mode.friendlyName()); - var message = switch (response.getReason()) { - case ALREADY_IN_QUEUE -> CommonMatchmakerError.QUEUE_ERR_ALREADY_IN_QUEUE; - case NO_PERMISSION -> MINI_MESSAGE.deserialize(CommonMatchmakerError.PLAYER_PERMISSION_DENIED); - case INVALID_MAP -> { - LOGGER.error("Invalid map for gamemode " + this.mode.friendlyName()); - yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeName); - } - case PARTY_TOO_LARGE -> { - final var max = Placeholder.unparsed("max", String.valueOf(this.mode.partyRestrictions().maxSize())); - yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_PARTY_TOO_LARGE, modeName, max); - } - case INVALID_GAME_MODE -> { - LOGGER.error("Invalid gamemode " + this.mode.friendlyName()); - yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeName); - } - case GAME_MODE_DISABLED -> { - LOGGER.error("Gamemode " + this.mode.friendlyName() + " is disabled"); - yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeName); - } - default -> { - LOGGER.error("An unknown error occurred while queuing for " + this.mode.friendlyName(), throwable); - yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeName); - } - }; - this.sender.sendMessage(message); - } + case INVALID_MAP -> { + LOGGER.error("Invalid map for gamemode " + mode.friendlyName()); + yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_UNKNOWN, modeNamePlaceholder); + } + case PARTY_TOO_LARGE -> { + final var max = Placeholder.unparsed("max", String.valueOf(mode.partyRestrictions().maxSize())); + yield MINI_MESSAGE.deserialize(CommonMatchmakerError.QUEUE_ERR_PARTY_TOO_LARGE, modeNamePlaceholder, max); + } + case NO_PERMISSION -> MINI_MESSAGE.deserialize(CommonMatchmakerError.PLAYER_PERMISSION_DENIED); + }; + player.sendMessage(message); } } diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/DefaultMatchmakingSessionImpl.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/DefaultMatchmakingSessionImpl.java index 9b71c33..d225000 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/DefaultMatchmakingSessionImpl.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/DefaultMatchmakingSessionImpl.java @@ -1,7 +1,7 @@ package dev.emortal.minestom.core.module.matchmaker.session; -import dev.emortal.api.kurushimi.PendingMatch; -import dev.emortal.api.kurushimi.Ticket; +import dev.emortal.api.model.matchmaker.PendingMatch; +import dev.emortal.api.model.matchmaker.Ticket; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; import dev.emortal.api.utils.ProtoTimestampConverter; import net.kyori.adventure.text.Component; diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSession.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSession.java index bbc95ac..5c7b0f5 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSession.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSession.java @@ -1,7 +1,8 @@ package dev.emortal.minestom.core.module.matchmaker.session; -import dev.emortal.api.kurushimi.PendingMatch; -import dev.emortal.api.kurushimi.Ticket; +import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; +import dev.emortal.api.model.matchmaker.PendingMatch; +import dev.emortal.api.model.matchmaker.Ticket; import net.minestom.server.entity.Player; import org.jetbrains.annotations.NotNull; @@ -49,4 +50,10 @@ public enum DeleteReason { public void setTicket(@NotNull Ticket ticket) { this.ticket = ticket; } + + @FunctionalInterface + public interface Creator { + + @NotNull MatchmakingSession create(@NotNull Player player, @NotNull GameModeConfig gameModeConfig, @NotNull Ticket ticket); + } } diff --git a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSessionManager.java b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSessionManager.java index 232c754..ec7ff19 100644 --- a/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSessionManager.java +++ b/src/main/java/dev/emortal/minestom/core/module/matchmaker/session/MatchmakingSessionManager.java @@ -1,12 +1,6 @@ package dev.emortal.minestom.core.module.matchmaker.session; -import com.google.common.util.concurrent.Futures; -import com.google.rpc.Code; -import com.google.rpc.Status; -import dev.emortal.api.kurushimi.GetPlayerQueueInfoRequest; -import dev.emortal.api.kurushimi.MatchmakerGrpc; -import dev.emortal.api.kurushimi.PendingMatch; -import dev.emortal.api.kurushimi.Ticket; +import dev.emortal.api.grpc.matchmaker.MatchmakerProto; import dev.emortal.api.kurushimi.messages.MatchCreatedMessage; import dev.emortal.api.kurushimi.messages.PendingMatchCreatedMessage; import dev.emortal.api.kurushimi.messages.PendingMatchDeletedMessage; @@ -16,9 +10,11 @@ import dev.emortal.api.kurushimi.messages.TicketUpdatedMessage; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeCollection; import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; -import dev.emortal.api.utils.callback.FunctionalFutureCallback; +import dev.emortal.api.model.matchmaker.PendingMatch; +import dev.emortal.api.model.matchmaker.Ticket; +import dev.emortal.api.service.matchmaker.MatchmakerService; import dev.emortal.minestom.core.module.messaging.MessagingModule; -import io.grpc.protobuf.StatusProto; +import io.grpc.StatusRuntimeException; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.minestom.server.MinecraftServer; @@ -27,7 +23,6 @@ import net.minestom.server.event.EventNode; import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerLoginEvent; -import org.apache.commons.lang3.function.TriFunction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +30,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ForkJoinPool; import java.util.function.BiConsumer; public final class MatchmakingSessionManager { @@ -44,8 +38,8 @@ public final class MatchmakingSessionManager { private static final String QUEUE_RESTORED_MESSAGE = "Your queue for has been transferred!"; private static final String QUEUE_RESTORE_FAILED_MESSAGE = "Your queue for could not be transferred! Please tell a staff member."; - private final MatchmakerGrpc.MatchmakerFutureStub matchmaker; - private final TriFunction sessionCreator; + private final MatchmakerService matchmaker; + private final MatchmakingSession.Creator sessionCreator; private final Map sessions = new ConcurrentHashMap<>(); private final Map ticketCache = new ConcurrentHashMap<>(); @@ -53,21 +47,21 @@ public final class MatchmakingSessionManager { private final GameModeCollection gameModeCollection; // TODO: note that tickets technically memory leak but it's so small and cleaned up when the ticket is deleted. - public MatchmakingSessionManager(@NotNull EventNode eventNode, @NotNull MatchmakerGrpc.MatchmakerFutureStub matchmaker, + public MatchmakingSessionManager(@NotNull EventNode eventNode, @NotNull MatchmakerService matchmaker, @NotNull MessagingModule messaging, @NotNull GameModeCollection gameModeCollection, - @NotNull TriFunction sessionCreator) { + @NotNull MatchmakingSession.Creator sessionCreator) { this.matchmaker = matchmaker; this.sessionCreator = sessionCreator; this.gameModeCollection = gameModeCollection; - eventNode.addListener(PlayerLoginEvent.class, this::handlePlayerLogin); + eventNode.addListener(PlayerLoginEvent.class, event -> Thread.startVirtualThread(() -> this.handlePlayerLogin(event))); eventNode.addListener(PlayerDisconnectEvent.class, event -> { UUID playerId = event.getPlayer().getUuid(); MatchmakingSession session = this.sessions.remove(playerId); if (session == null) return; - destroySession(session); + this.destroySession(session); }); messaging.addListener(TicketCreatedMessage.class, message -> this.onTicketCreate(message.getTicket())); @@ -108,7 +102,12 @@ private void onTicketCreate(Ticket ticket) { if (player == null) continue; GameModeConfig gameMode = this.gameModeCollection.getConfig(ticket.getGameModeId()); - MatchmakingSession session = sessionCreator.apply(player, gameMode, ticket); + if (gameMode == null) { + LOGGER.warn("Received ticket for unknown game mode: {}", ticket.getGameModeId()); + continue; + } + + MatchmakingSession session = this.sessionCreator.create(player, gameMode, ticket); this.sessions.put(uuid, session); shouldCache = true; } @@ -137,6 +136,10 @@ private void onTicketDelete(Ticket ticket, TicketDeletedMessage.Reason messageRe private void onTicketUpdated(Ticket newTicket) { Ticket oldTicket = this.ticketCache.get(newTicket.getId()); GameModeConfig gameMode = this.gameModeCollection.getConfig(newTicket.getGameModeId()); + if (gameMode == null) { + LOGGER.warn("Received ticket for unknown game mode: {}", newTicket.getGameModeId()); + return; + } // Perform adding operations and update existing MatchmakingSessions for (String playerId : newTicket.getPlayerIdsList()) { @@ -150,7 +153,7 @@ private void onTicketUpdated(Ticket newTicket) { Player player = MinecraftServer.getConnectionManager().getPlayer(uuid); if (player == null) continue; - session = sessionCreator.apply(player, gameMode, newTicket); + session = sessionCreator.create(player, gameMode, newTicket); this.sessions.put(uuid, session); } @@ -204,32 +207,30 @@ private void destroySession(@NotNull MatchmakingSession session) { private void handlePlayerLogin(@NotNull PlayerLoginEvent event) { Player player = event.getPlayer(); UUID playerId = player.getUuid(); - var infoRequest = GetPlayerQueueInfoRequest.newBuilder().setPlayerId(playerId.toString()).build(); - - Futures.addCallback(this.matchmaker.getPlayerQueueInfo(infoRequest), FunctionalFutureCallback.create( - response -> { - Ticket ticket = response.getTicket(); - GameModeConfig mode = this.gameModeCollection.getConfig(ticket.getGameModeId()); - - var modeName = Placeholder.unparsed("mode", mode == null ? ticket.getGameModeId() : mode.friendlyName()); - if (mode == null) { - LOGGER.error("Failed to get game mode config for player " + playerId + " with game mode ID " + ticket.getGameModeId()); - player.sendMessage(MiniMessage.miniMessage().deserialize(QUEUE_RESTORE_FAILED_MESSAGE, modeName)); - return; - } - - MatchmakingSession session = this.sessionCreator.apply(player, mode, ticket); - this.sessions.put(playerId, session); - this.ticketCache.put(ticket.getId(), ticket); - - player.sendMessage(MiniMessage.miniMessage().deserialize(QUEUE_RESTORED_MESSAGE, modeName)); - }, - throwable -> { - Status status = StatusProto.fromThrowable(throwable); - if (status.getCode() == Code.NOT_FOUND_VALUE) return; // Player is not in a queue - - LOGGER.error("Failed to get player queue info for player " + playerId, throwable); - } - ), ForkJoinPool.commonPool()); + + MatchmakerProto.GetPlayerQueueInfoResponse queueInfo; + try { + queueInfo = this.matchmaker.getPlayerQueueInfo(playerId); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to get queue info for player " + playerId, exception); + return; + } + if (queueInfo == null) return; // Not in a queue + + Ticket ticket = queueInfo.getTicket(); + GameModeConfig mode = this.gameModeCollection.getConfig(ticket.getGameModeId()); + + var modeName = Placeholder.unparsed("mode", mode == null ? ticket.getGameModeId() : mode.friendlyName()); + if (mode == null) { + LOGGER.error("Failed to get game mode config for player " + playerId + " with game mode ID " + ticket.getGameModeId()); + player.sendMessage(MiniMessage.miniMessage().deserialize(QUEUE_RESTORE_FAILED_MESSAGE, modeName)); + return; + } + + MatchmakingSession session = this.sessionCreator.create(player, mode, ticket); + this.sessions.put(playerId, session); + this.ticketCache.put(ticket.getId(), ticket); + + player.sendMessage(MiniMessage.miniMessage().deserialize(QUEUE_RESTORED_MESSAGE, modeName)); } } diff --git a/src/main/java/dev/emortal/minestom/core/module/messaging/MessagingModule.java b/src/main/java/dev/emortal/minestom/core/module/messaging/MessagingModule.java index 9438f5b..06692a1 100644 --- a/src/main/java/dev/emortal/minestom/core/module/messaging/MessagingModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/messaging/MessagingModule.java @@ -3,7 +3,7 @@ import com.google.protobuf.AbstractMessage; import dev.emortal.api.modules.Module; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; import dev.emortal.api.utils.kafka.FriendlyKafkaConsumer; import dev.emortal.api.utils.kafka.FriendlyKafkaProducer; import dev.emortal.api.utils.kafka.KafkaSettings; diff --git a/src/main/java/dev/emortal/minestom/core/module/monitoring/MonitoringModule.java b/src/main/java/dev/emortal/minestom/core/module/monitoring/MonitoringModule.java index 1dd891b..fb2fd1b 100644 --- a/src/main/java/dev/emortal/minestom/core/module/monitoring/MonitoringModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/monitoring/MonitoringModule.java @@ -1,9 +1,8 @@ package dev.emortal.minestom.core.module.monitoring; import com.sun.net.httpserver.HttpServer; -import dev.emortal.api.modules.Module; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; import dev.emortal.minestom.core.Environment; import dev.emortal.minestom.core.module.MinestomModule; import io.micrometer.core.instrument.Metrics; diff --git a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionCache.java b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionCache.java index fae4316..ce59679 100644 --- a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionCache.java +++ b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionCache.java @@ -1,10 +1,10 @@ package dev.emortal.minestom.core.module.permissions; import com.google.common.collect.Sets; -import dev.emortal.api.grpc.permission.PermissionProto; -import dev.emortal.api.grpc.permission.PermissionServiceGrpc; import dev.emortal.api.model.permission.PermissionNode; import dev.emortal.api.model.permission.Role; +import dev.emortal.api.service.permission.PermissionService; +import io.grpc.StatusRuntimeException; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; @@ -19,23 +19,23 @@ import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -public class PermissionCache { +public final class PermissionCache { private static final Logger LOGGER = LoggerFactory.getLogger(PermissionCache.class); - private final PermissionServiceGrpc.PermissionServiceFutureStub permissionService; + private final PermissionService permissionService; private final Map roleCache = Collections.synchronizedMap(new LinkedHashMap<>()); private final Map userCache = new ConcurrentHashMap<>(); - public PermissionCache(PermissionServiceGrpc.PermissionServiceFutureStub permissionService, EventNode eventNode) { + public PermissionCache(@NotNull PermissionService permissionService, @NotNull EventNode eventNode) { this.permissionService = permissionService; eventNode.addListener(PlayerDisconnectEvent.class, this::onDisconnect); @@ -45,14 +45,16 @@ public PermissionCache(PermissionServiceGrpc.PermissionServiceFutureStub permiss } private void loadRoles() { + List roles; try { - var response = this.permissionService.getAllRoles(PermissionProto.GetAllRolesRequest.getDefaultInstance()).get(); + roles = this.permissionService.getAllRoles(); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to load roles from permission service!", exception); + return; + } - for (Role role : response.getRolesList()) { - this.addRole(role); - } - } catch (InterruptedException | ExecutionException exception) { - LOGGER.error("Couldn't load roles", exception); + for (Role role : roles) { + this.addRole(role); } } @@ -62,26 +64,20 @@ private void loadRoles() { * @param player the player to load */ private void loadUser(@NotNull Player player) { - try { - var request = PermissionProto.GetPlayerRolesRequest.newBuilder().setPlayerId(player.getUuid().toString()).build(); - - Set roleIds = Sets.newConcurrentHashSet(this.permissionService.getPlayerRoles(request).get().getRoleIdsList()); - User user = new User(player.getUuid(), roleIds); - this.userCache.put(player.getUuid(), user); + Set roleIds = Sets.newConcurrentHashSet(this.permissionService.getPlayerRoles(player.getUuid()).getRoleIdsList()); + User user = new User(player.getUuid(), roleIds); + this.userCache.put(player.getUuid(), user); - Set permissions = new HashSet<>(); - for (String roleId : roleIds) { - CachedRole role = this.roleCache.get(roleId); - if (role == null) continue; - - permissions.addAll(role.permissions()); - } + Set permissions = new HashSet<>(); + for (String roleId : roleIds) { + CachedRole role = this.roleCache.get(roleId); + if (role == null) continue; - player.getAllPermissions().clear(); - player.getAllPermissions().addAll(permissions); - } catch (InterruptedException | ExecutionException exception) { - throw new RuntimeException(exception); + permissions.addAll(role.permissions()); } + + player.getAllPermissions().clear(); + player.getAllPermissions().addAll(permissions); } private void updateUserPermissions(@NotNull User user) { @@ -128,21 +124,18 @@ public Optional getUser(UUID id) { */ void addRole(@NotNull Role roleResponse) { CachedRole role = CachedRole.fromRole(roleResponse); - this.roleCache.put(roleResponse.getId(), role); for (User user : this.userCache.values()) { - if (user.roleIds().contains(roleResponse.getId())) { - this.updateUserPermissions(user); - } + if (!user.roleIds().contains(roleResponse.getId())) continue; + this.updateUserPermissions(user); } } void removeRole(@NotNull String id) { - for (User user : this.userCache.values()) { - if (user.roleIds().contains(id)) { - this.removeRoleFromUser(user.id(), id); - } + for (User(UUID uuid, Set roleIds) : this.userCache.values()) { + if (!roleIds.contains(id)) continue; + this.removeRoleFromUser(uuid, id); } this.roleCache.remove(id); @@ -188,13 +181,13 @@ private void onLogin(@NotNull PlayerLoginEvent event) { this.loadUser(event.getPlayer()); } - public record User(UUID id, Set roleIds) { + public record User(@NotNull UUID id, @NotNull Set roleIds) { } public record CachedRole(@NotNull String id, @NotNull Set permissions, int priority, @NotNull String displayName) implements Comparable { - static CachedRole fromRole(@NotNull Role role) { + static @NotNull CachedRole fromRole(@NotNull Role role) { return new CachedRole( role.getId(), role.getPermissionsList().stream() diff --git a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionModule.java b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionModule.java index d07822f..89159c8 100644 --- a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionModule.java +++ b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionModule.java @@ -1,7 +1,8 @@ package dev.emortal.minestom.core.module.permissions; import dev.emortal.api.modules.ModuleData; -import dev.emortal.api.modules.ModuleEnvironment; +import dev.emortal.api.modules.env.ModuleEnvironment; +import dev.emortal.api.service.permission.PermissionService; import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.minestom.core.Environment; import dev.emortal.minestom.core.module.MinestomModule; @@ -17,7 +18,6 @@ public final class PermissionModule extends MinestomModule { private static final Logger LOGGER = LoggerFactory.getLogger(PermissionModule.class); - private static final boolean ENABLED = Environment.isProduction() || GrpcStubCollection.getPermissionService().isPresent(); // Grants all permissions if the permission service is not available private static final boolean GRANT_ALL_PERMISSIONS = Boolean.parseBoolean(System.getenv("GRANT_ALL_PERMISSIONS")); @@ -29,7 +29,8 @@ public PermissionModule(@NotNull ModuleEnvironment environment) { @Override public boolean onLoad() { - if (!ENABLED) { + PermissionService permissionService = GrpcStubCollection.getPermissionService().orElse(null); + if (!Environment.isProduction() || permissionService == null) { if (GRANT_ALL_PERMISSIONS) { LOGGER.warn("Permission service is not available, granting all permissions"); this.eventNode.addListener(PlayerLoginEvent.class, event -> event.getPlayer().addPermission(new Permission("*"))); @@ -40,7 +41,7 @@ public boolean onLoad() { return true; } - this.permissionCache = new PermissionCache(GrpcStubCollection.getPermissionService().orElse(null), this.eventNode); + this.permissionCache = new PermissionCache(permissionService, this.eventNode); MessagingModule messagingModule = this.environment.moduleProvider().getModule(MessagingModule.class); if (messagingModule != null) { diff --git a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionUpdateListener.java b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionUpdateListener.java index fd9b13c..0289d66 100644 --- a/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionUpdateListener.java +++ b/src/main/java/dev/emortal/minestom/core/module/permissions/PermissionUpdateListener.java @@ -10,7 +10,7 @@ import java.util.UUID; -public class PermissionUpdateListener { +public final class PermissionUpdateListener { private static final Logger LOGGER = LoggerFactory.getLogger(PermissionUpdateListener.class); private final PermissionCache permissionCache; diff --git a/src/main/java/dev/emortal/minestom/core/utils/command/ExtraConditions.java b/src/main/java/dev/emortal/minestom/core/utils/command/ExtraConditions.java deleted file mode 100644 index 1015e0e..0000000 --- a/src/main/java/dev/emortal/minestom/core/utils/command/ExtraConditions.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.emortal.minestom.core.utils.command; - -import net.minestom.server.command.builder.condition.CommandCondition; -import org.jetbrains.annotations.NotNull; - -public final class ExtraConditions { - - public static @NotNull CommandCondition hasPermission(@NotNull String permission) { - return (sender, commandName) -> sender.hasPermission(permission); - } - - private ExtraConditions() { - throw new AssertionError("This class cannot be instantiated."); - } -} diff --git a/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentBadge.java b/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentBadge.java index fa0a950..6318779 100644 --- a/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentBadge.java +++ b/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentBadge.java @@ -1,11 +1,11 @@ package dev.emortal.minestom.core.utils.command.argument; -import dev.emortal.api.grpc.badge.BadgeManagerGrpc; -import dev.emortal.api.grpc.badge.BadgeManagerProto; import dev.emortal.api.model.badge.Badge; +import dev.emortal.api.service.badges.BadgeService; import net.kyori.adventure.text.Component; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.arguments.ArgumentWord; import net.minestom.server.command.builder.suggestion.Suggestion; import net.minestom.server.command.builder.suggestion.SuggestionEntry; @@ -15,30 +15,25 @@ import org.slf4j.LoggerFactory; import java.util.List; -import java.util.concurrent.ExecutionException; public final class ArgumentBadge { private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentBadge.class); - private final BadgeManagerGrpc.BadgeManagerFutureStub badgeManager; + public static @NotNull Argument create(@NotNull BadgeService badgeService, @NotNull String id, boolean onlyOwned) { + var handler = new ArgumentBadge(badgeService, id, onlyOwned); + return new ArgumentWord(id).setSuggestionCallback(handler::suggestionCallback); + } + private final BadgeService badgeService; private final String id; private final boolean onlyOwned; - public ArgumentBadge(@NotNull BadgeManagerGrpc.BadgeManagerFutureStub badgeManager, @NotNull String id, boolean onlyOwned) { - this.badgeManager = badgeManager; + private ArgumentBadge(@NotNull BadgeService badgeService, @NotNull String id, boolean onlyOwned) { + this.badgeService = badgeService; this.id = id; this.onlyOwned = onlyOwned; } - public static @NotNull ArgumentWord create(@NotNull BadgeManagerGrpc.BadgeManagerFutureStub badgeManager, @NotNull String id, boolean onlyOwned) { - var handlerArgument = new ArgumentBadge(badgeManager, id, onlyOwned); - var createdArgument = new ArgumentWord(id); - - createdArgument.setSuggestionCallback(handlerArgument::suggestionCallback); - return createdArgument; - } - public void suggestionCallback(@NotNull CommandSender sender, @NotNull CommandContext context, @NotNull Suggestion suggestion) { String input = context.getRaw(this.id); @@ -52,28 +47,19 @@ public void suggestionCallback(@NotNull CommandSender sender, @NotNull CommandCo } if (this.onlyOwned) { - handleOwnedBadgesSuggestion((Player) sender, input, suggestion); + this.handleOwnedBadgesSuggestion((Player) sender, input, suggestion); } else { - handleDefaultSuggestion(input, suggestion); + this.handleDefaultSuggestion(input, suggestion); } } private void handleDefaultSuggestion(@NotNull String input, @NotNull Suggestion suggestion) { - try { - var response = this.badgeManager.getBadges(BadgeManagerProto.GetBadgesRequest.getDefaultInstance()).get(); - addBadgeSuggestions(suggestion, response.getBadgesList(), input); - } catch (InterruptedException | ExecutionException exception) { - LOGGER.error("Failed to get badges", exception); - } + List badges = this.badgeService.getAllBadges(); + this.addBadgeSuggestions(suggestion, badges, input); } private void handleOwnedBadgesSuggestion(@NotNull Player sender, @NotNull String input, @NotNull Suggestion suggestion) { - try { - var request = BadgeManagerProto.GetPlayerBadgesRequest.newBuilder().setPlayerId(sender.getUuid().toString()).build(); - addBadgeSuggestions(suggestion, this.badgeManager.getPlayerBadges(request).get().getBadgesList(), input); - } catch (InterruptedException | ExecutionException exception) { - LOGGER.error("Failed to get badges", exception); - } + this.addBadgeSuggestions(suggestion, this.badgeService.getPlayerBadges(sender.getUuid()).getBadgesList(), input); } private void addBadgeSuggestions(@NotNull Suggestion suggestion, @NotNull List badges, @NotNull String filter) { diff --git a/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentMcPlayer.java b/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentMcPlayer.java index b63f4ae..8cee2e6 100644 --- a/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentMcPlayer.java +++ b/src/main/java/dev/emortal/minestom/core/utils/command/argument/ArgumentMcPlayer.java @@ -1,9 +1,9 @@ package dev.emortal.minestom.core.utils.command.argument; -import dev.emortal.api.grpc.mcplayer.McPlayerGrpc; -import dev.emortal.api.grpc.mcplayer.McPlayerProto; +import dev.emortal.api.grpc.mcplayer.McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod; import dev.emortal.api.model.common.Pageable; import dev.emortal.api.model.mcplayer.McPlayer; +import dev.emortal.api.service.mcplayer.McPlayerService; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.Argument; @@ -16,76 +16,54 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; +import java.util.List; +import java.util.function.Supplier; public final class ArgumentMcPlayer { private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentMcPlayer.class); + public static @NotNull Argument> create(@NotNull String id, @NotNull McPlayerService mcPlayerService, + @Nullable FilterMethod filterMethod) { + var argument = new ArgumentMcPlayer(id, mcPlayerService, filterMethod); + return new ArgumentWord(id).map(argument::mapInput).setSuggestionCallback(argument::suggestionCallback); + } + private final @NotNull String id; - private final McPlayerGrpc.McPlayerFutureStub mcPlayerService; - private final @Nullable McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod filterMethod; + private final @NotNull McPlayerService mcPlayerService; + private final @Nullable FilterMethod filterMethod; - private ArgumentMcPlayer(@NotNull String id, McPlayerGrpc.McPlayerFutureStub mcPlayerService, - @Nullable McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod filterMethod) { + private ArgumentMcPlayer(@NotNull String id, @NotNull McPlayerService mcPlayerService, @Nullable FilterMethod filterMethod) { this.id = id; this.mcPlayerService = mcPlayerService; this.filterMethod = filterMethod; } - public static Argument> create(@NotNull String id, - @NotNull McPlayerGrpc.McPlayerFutureStub mcPlayerService, - @Nullable McPlayerProto.SearchPlayersByUsernameRequest.FilterMethod filterMethod) { - - var argument = new ArgumentMcPlayer(id, mcPlayerService, filterMethod); - return new ArgumentWord(id).map(argument::mapInput).setSuggestionCallback(argument::suggestionCallback); - } - - private CompletableFuture mapInput(@Nullable String input) { + private @NotNull Supplier mapInput(@Nullable String input) { if (input == null || input.isEmpty() || input.length() == 1 && input.charAt(0) == 0) { - return CompletableFuture.completedFuture(null); + return () -> null; } - var playerReqFuture = this.mcPlayerService.getPlayerByUsername(McPlayerProto.PlayerUsernameRequest.newBuilder().setUsername(input).build()); - - return CompletableFuture.supplyAsync(() -> { + return () -> { try { - return playerReqFuture.get().getPlayer(); - } catch (final Exception exception) { - throw new CompletionException(exception); + return this.mcPlayerService.getPlayerByUsername(input); + } catch (Exception exception) { + LOGGER.error("Failed to retrieve McPlayer (input: {})", input, exception); + return null; } - }, ForkJoinPool.commonPool()).exceptionally(exception -> { - LOGGER.error("Failed to retrieve McPlayer (input: {})", input, exception); - return null; - }); + }; } - private void suggestionCallback(CommandSender sender, CommandContext context, Suggestion suggestion) { + private void suggestionCallback(@NotNull CommandSender sender, @NotNull CommandContext context, @NotNull Suggestion suggestion) { if (!(sender instanceof Player player)) return; String input = context.getRaw(this.id); if (input == null || input.length() <= 2) return; - var reqBuilder = McPlayerProto.SearchPlayersByUsernameRequest.newBuilder() - .setIssuerId(player.getUuid().toString()) - .setSearchUsername(input) - .setPageable(Pageable.newBuilder().setPage(0).setSize(15)); + Pageable pageable = Pageable.newBuilder().setPage(0).setSize(15).build(); + List results = this.mcPlayerService.searchPlayersByUsername(player.getUuid(), input, pageable, this.filterMethod); - if (this.filterMethod != null) { - reqBuilder.setFilterMethod(this.filterMethod); - } - - var searchReqFuture = this.mcPlayerService.searchPlayersByUsername(reqBuilder.build()); - - try { - final var response = searchReqFuture.get(); - for (McPlayer mcPlayer : response.getPlayersList()) { - suggestion.addEntry(new SuggestionEntry(mcPlayer.getCurrentUsername())); - } - } catch (ExecutionException | InterruptedException exception) { - LOGGER.error("Failed to get player suggestions (input: {})", input, exception); + for (McPlayer result : results) { + suggestion.addEntry(new SuggestionEntry(result.getCurrentUsername())); } } } diff --git a/src/main/java/dev/emortal/minestom/core/utils/matchmaker/KurushimiMinestomUtils.java b/src/main/java/dev/emortal/minestom/core/utils/matchmaker/KurushimiMinestomUtils.java new file mode 100644 index 0000000..329181f --- /dev/null +++ b/src/main/java/dev/emortal/minestom/core/utils/matchmaker/KurushimiMinestomUtils.java @@ -0,0 +1,100 @@ +package dev.emortal.minestom.core.utils.matchmaker; + +import dev.emortal.api.service.matchmaker.MatchmakerService; +import dev.emortal.api.utils.GrpcStubCollection; +import io.grpc.StatusRuntimeException; +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventFilter; +import net.minestom.server.event.EventNode; +import net.minestom.server.event.player.PlayerDisconnectEvent; +import net.minestom.server.event.trait.PlayerEvent; +import net.minestom.server.timer.Task; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class KurushimiMinestomUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(KurushimiMinestomUtils.class); + private static final EventNode EVENT_NODE = EventNode.type("kurushimi-utils", EventFilter.PLAYER); + + static { + MinecraftServer.getGlobalEventHandler().addChild(EVENT_NODE); + } + + /** + * @param players The player ids to queue for a lobby + * @param onSuccess A runnable to run when all players are connected to the lobby. + * @param onFailure A runnable to run when the sender gives up sending players. + */ + @SuppressWarnings("unused") + public static void sendToLobby(@NotNull Collection players, @NotNull Runnable onSuccess, @NotNull Runnable onFailure) { + sendToLobby(players, onSuccess, onFailure, 1); + } + + /** + * Note: The failure runnable is only run at the end of the time if players are not sent. + * If there are other errors, they may only affect one player and resolve with retries. + * + * @param players The player ids to queue for a lobby + * @param onSuccess A runnable to run when all players are connected to the lobby. + * @param onFailure A runnable to run when the sender gives up sending players. + * @param retries The amount of retries to send players to the lobby before giving up. + */ + // todo retries + // todo store player tickets so if we assume a cancellation, we delete the ticket + public static void sendToLobby(@NotNull Collection players, @NotNull Runnable onSuccess, + @NotNull Runnable onFailure, int retries) { + + MatchmakerService service = GrpcStubCollection.getMatchmakerService() + .orElseThrow(() -> new IllegalStateException("Kurushimi stub is not present.")); + + Set remainingPlayers = new HashSet<>(players); + AtomicBoolean finished = new AtomicBoolean(false); + + EventNode localNode = EventNode.type(UUID.randomUUID().toString(), EventFilter.PLAYER, ($, player) -> players.contains(player)); + EVENT_NODE.addChild(localNode); + + Task task = MinecraftServer.getSchedulerManager().buildTask(() -> { + boolean shouldRun = finished.compareAndSet(false, true); + if (shouldRun) { + onFailure.run(); + EVENT_NODE.removeChild(localNode); + } + }).delay(10, ChronoUnit.SECONDS).schedule(); + + localNode.addListener(PlayerDisconnectEvent.class, event -> { + remainingPlayers.remove(event.getPlayer()); + if (!remainingPlayers.isEmpty()) return; + + task.cancel(); + EVENT_NODE.removeChild(localNode); + onSuccess.run(); + }); + + for (Player player : players) { + Thread.startVirtualThread(() -> sendToLobby(service, player, () -> { + // failure + LOGGER.warn("Failed to create ticket to send player {} to lobby.", player.getUsername()); + })); + } + } + + private static void sendToLobby(@NotNull MatchmakerService service, @NotNull Player player, @NotNull Runnable onFailure) { + try { + service.sendPlayerToLobby(player.getUuid(), false); + } catch (StatusRuntimeException exception) { + onFailure.run(); + } + } + + private KurushimiMinestomUtils() { + } +}