diff --git a/.idea/misc.xml b/.idea/misc.xml index 03872dd..ecb0c83 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d949516..c4b14e8 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,12 +5,59 @@ + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + { "customColor": "", "associatedIndex": 0 @@ -124,22 +180,29 @@ "Gradle.core [build].executor": "Run", "Gradle.core [clean].executor": "Run", "Gradle.core [publishAllPublicationsToScrowRepository].executor": "Run", + "Gradle.core [publishCoreLibPublicationToScrowRepository].executor": "Run", "Gradle.core:core-bukkit-api [build].executor": "Run", "Gradle.core:core-bukkit-api [clean].executor": "Run", + "Gradle.core:core-lib [publishAllPublicationsToScrowRepository].executor": "Run", + "Gradle.core:core-lib [publishCoreLibPublicationToMavenLocal].executor": "Run", + "Gradle.core:core-lib [publishCoreLibPublicationToScrowRepository].executor": "Run", "ModuleVcsDetector.initialDetectionPerformed": "true", "RunOnceActivity.ShowReadmeOnStart": "true", "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", "RunOnceActivity.git.unshallow": "true", "git-widget-placeholder": "master", "kotlin-language-version-configured": "true", - "last_opened_file_path": "/home/a/src/scrow/core/settings.gradle.kts", - "project.structure.last.edited": "Modules", + "last_opened_file_path": "/home/a/src/scrow", + "project.structure.last.edited": "Project", "project.structure.proportion": "0.15", "project.structure.side.proportion": "0.6268199", - "settings.editor.selected.configurable": "editor.preferences.smartKeys" + "settings.editor.selected.configurable": "preferences.pluginManager" } }]]> + + + @@ -183,7 +246,7 @@ @@ -200,10 +263,10 @@ false - + - + - + @@ -311,11 +374,20 @@ - \ No newline at end of file diff --git a/core-bukkit-api/build.gradle.kts b/core-bukkit-api/build.gradle.kts index b20cc6b..2c66d03 100644 --- a/core-bukkit-api/build.gradle.kts +++ b/core-bukkit-api/build.gradle.kts @@ -4,29 +4,29 @@ plugins { id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.java-library") + id("de.kentoj.scrow.reactor") } group = "de.kentoj.scrow" repositories { mavenCentral() - - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { - name = "spigotmc-repo" + maven("https://repo.papermc.io/repository/maven-public/") { + name = "papermc" } } dependencies { api(project(":core-lib")) - api("org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT") + api("io.papermc.paper:paper-api:1.21.11-rc3-R0.1-SNAPSHOT") // https://mvnrepository.com/artifact/io.nats/jnats api("io.nats:jnats:2.25.2") // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core - api("de.kentoj.scrow:kencommandapi-core:0.9") + api("de.kentoj.scrow:kencommandapi-core:0.21") // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-bukkit - api("de.kentoj.scrow:kencommandapi-bukkit:0.9") + api("de.kentoj.scrow:kencommandapi-bukkit:0.21") } publishing { diff --git a/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/PlayerManager.java b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/PlayerManager.java new file mode 100644 index 0000000..e19c90c --- /dev/null +++ b/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/PlayerManager.java @@ -0,0 +1,10 @@ +package de.kentoj.scrow.bukkit; + +import com.leakyabstractions.result.api.Result; + +import java.util.UUID; + +public interface PlayerManager { + + Result sendPlayer(UUID uuid, String instanceHandle); +} 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 775f6fd..c1d70b0 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 @@ -1,11 +1,11 @@ package de.kentoj.scrow.bukkit; -import de.kentoj.kencommandapi.BukkitKenCommandApi; +import de.kentoj.kencommandapi.CommandAPI; import de.kentoj.scrow.bukkit.friends.FriendRequestService; import de.kentoj.scrow.bukkit.friends.FriendshipService; import de.kentoj.scrow.bukkit.region.RegionManager; import de.kentoj.scrowlib.connection.DatabaseConnectionFactory; -import de.kentoj.scrowlib.servermanager.ServerManager; +import de.kentoj.scrowlib.servermanager.InstanceManager; import io.nats.client.Connection; import lombok.AccessLevel; import lombok.Getter; @@ -30,11 +30,13 @@ public class ScrowAPI { @Getter private static Connection nats; @Getter - private static ServerManager serverManager; + private static InstanceManager instanceManager; @Getter private static RegionManager regionManager; @Getter - private static final BukkitKenCommandApi commandApi = new BukkitKenCommandApi(); + private static CommandAPI commandApi; + @Getter + private static PlayerManager playerManager; @ApiStatus.Internal public static void setEconomyService(EconomyService economyService) { @@ -62,8 +64,8 @@ public class ScrowAPI { } @ApiStatus.Internal - public static void setServerManager(ServerManager serverManager) { - ScrowAPI.serverManager = serverManager; + public static void setInstanceManager(InstanceManager instanceManager) { + ScrowAPI.instanceManager = instanceManager; } @ApiStatus.Internal @@ -75,5 +77,15 @@ public class ScrowAPI { public static void setRegionManager(RegionManager regionManager) { ScrowAPI.regionManager = regionManager; } + + @ApiStatus.Internal + public static void setCommandApi(CommandAPI commandApi) { + ScrowAPI.commandApi = commandApi; + } + + @ApiStatus.Internal + public static void setPlayerManager(PlayerManager playerManager) { + ScrowAPI.playerManager = playerManager; + } } diff --git a/core-bukkit-impl/build.gradle.kts b/core-bukkit-impl/build.gradle.kts index a20e7e9..2bbf523 100644 --- a/core-bukkit-impl/build.gradle.kts +++ b/core-bukkit-impl/build.gradle.kts @@ -5,6 +5,7 @@ plugins { id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.database") + id("de.kentoj.scrow.reactor") } group = "de.kentoj.scrow" @@ -12,14 +13,17 @@ group = "de.kentoj.scrow" repositories { mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { - name = "spigotmc-repo" + maven("https://repo.papermc.io/repository/maven-public/") { + name = "papermc" } } dependencies { implementation(project(":core-bukkit-api")) + // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core + implementation("de.kentoj.scrow:kencommandapi-core:0.21") + // TODO remove, we use postgres now // https://www.mongodb.com/docs/languages/java/reactive-streams-driver/current/get-started/download-and-install/ implementation(platform("org.mongodb:mongodb-driver-bom:5.6.1")) 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 be88436..f7fd99f 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 @@ -10,6 +10,13 @@ import de.kentoj.scrow.bukkit.server.PlayCommand; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + public class CoreImplPlugin extends JavaPlugin { public static boolean ENABLE_DATABASE = false; @@ -32,8 +39,13 @@ public class CoreImplPlugin extends JavaPlugin { } private void registerServer() { - ScrowAPI.getServerManager().setState(System.getenv("SERVER_HANDLE"), "UP") - .subscribe(__ -> Bukkit.getLogger().info("registered self")); + var opts = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE}; + try (var file = FileChannel.open(Path.of("/proc/self/fd/3"), opts)) { + //noinspection ResultOfMethodCallIgnored + file.write(ByteBuffer.wrap(new byte[]{'\n'})); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override 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 fc35278..97acb0e 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 @@ -1,6 +1,6 @@ package de.kentoj.scrow.bukkit; -import de.kentoj.kencommandapi.BukkitKenCommandApi; +import de.kentoj.kencommandapi.CommandAPI; import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService; import de.kentoj.scrow.bukkit.economy.PostgresEconomyService; import de.kentoj.scrow.bukkit.friends.friendship.InMemoryFriendshipService; @@ -9,7 +9,8 @@ import de.kentoj.scrow.bukkit.friends.request.InMemoryFriendRequestService; import de.kentoj.scrow.bukkit.friends.request.PostgresFriendRequestService; import de.kentoj.scrow.bukkit.region.RegionManagerImpl; import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl; -import de.kentoj.scrowlib.servermanager.ServerManagerImpl; +import de.kentoj.scrowlib.servermanager.InstanceManagerImpl; +import de.kentoj.scrowlib.utils.EnvUtils; import io.nats.client.Nats; import io.nats.client.Options; import io.r2dbc.spi.ConnectionFactories; @@ -31,7 +32,7 @@ public class ScrowAPISurface { })); try { - String natsHost = System.getenv("HOST_NATS"); + String natsHost = EnvUtils.envOrThrow("HOST_NATS"); var options = Options.builder() .server(natsHost) .pedantic() @@ -42,12 +43,13 @@ public class ScrowAPISurface { } if (enableDatabase) { - ConnectionFactory factory = ConnectionFactories.get(System.getenv("HOST_POSTGRES")); + ConnectionFactory factory = ConnectionFactories.get(EnvUtils.envOrThrow("HOST_POSTGRES")); ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory)); } ScrowAPI.setRegionManager(new RegionManagerImpl()); - ScrowAPI.setServerManager(new ServerManagerImpl(ScrowAPI.getNats())); + ScrowAPI.setCommandApi(new CommandAPI(plugin)); + ScrowAPI.setInstanceManager(new InstanceManagerImpl(ScrowAPI.getNats())); ScrowAPI.setEconomyService(ScrowAPI.getDbConFactory() != null ? new PostgresEconomyService(ScrowAPI.getDbConFactory()) : new InMemoryEconomyService()); ScrowAPI.setFriendRequestService(ScrowAPI.getDbConFactory() != null ? diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java index d958c35..f94001f 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java @@ -1,20 +1,23 @@ package de.kentoj.scrow.bukkit.crown; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.scrow.bukkit.ScrowAPI; +import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.scrowlib.convention.ScrowMessageStyle; import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.CommandSender; public class CrownCommand { @Getter - private final CommandNode rootNode; + private final CommandNode rootNode; public CrownCommand() { - this.rootNode = ScrowAPI.getCommandApi().createNode("crown", "server-manager"); - this.rootNode.addLiteral(new CrownListServersLiteral().getRootNode()); - this.rootNode.addLiteral(new CrownDeployServerLiteral().getRootNode()); - this.rootNode.addLiteral(new CrownDestroyServerLiteral().getRootNode()); + this.rootNode = CommandNode.node("crown", "server-manager"); + var style = new ScrowMessageStyle(Component.text("crown") + .color(NamedTextColor.GOLD)); + this.rootNode.addLiteral(new CrownListServersLiteral(style).getNode()); + this.rootNode.addLiteral(new CrownDeployServerLiteral(style).getRootNode()); + this.rootNode.addLiteral(new CrownDestroyServerLiteral(style).getRootNode()); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java index 7c3fcb5..d51ea5b 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java @@ -1,35 +1,40 @@ package de.kentoj.scrow.bukkit.crown; -import de.kentoj.kencommandapi.BukkitCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.kencommandapi.api.types.StringArgumentType; +import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; import de.kentoj.scrow.bukkit.ScrowAPI; import lombok.Getter; import org.bukkit.command.CommandSender; -public class CrownDeployServerLiteral implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class CrownDeployServerLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument templateArg; + private final CommandNode rootNode; + private final CommandArgument templateArg; public CrownDeployServerLiteral() { - this.rootNode = ScrowAPI.getCommandApi().createNode("deploy-server"); + this.rootNode = CommandNode.node("deploy-server"); this.rootNode.setExecutor(this); { - this.templateArg = ScrowAPI.getCommandApi().createArgument("template", new StringArgumentType<>()); + this.templateArg = CommandArgument.arg("template", new StringArgumentType<>()); this.rootNode.addArgument(templateArg); } } @Override - public void execute(BukkitCommandContext ctx) { + public CompletableFuture> execute(CommandContext ctx) { var template = ctx.getArg(templateArg); - ScrowAPI.getServerManager().deployServer(template) - .subscribe(server -> - ctx.getSender().sendMessage("Deployed instance of " + template + " with handle " + server.getHandle()) - ); + return ScrowAPI.getInstanceManager().deployInstance(template) + .map(server -> { + ctx.getSender().sendMessage("Deployed instance of " + template + " with handle " + server.getHandle()); + // FIXME handle unknown server + return Result.success(); + }).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java index f3f520d..3c634da 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java @@ -1,31 +1,34 @@ package de.kentoj.scrow.bukkit.crown; -import de.kentoj.kencommandapi.BukkitCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.kencommandapi.api.types.StringArgumentType; +import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrowlib.servermanager.ServerInstance; import lombok.Getter; import org.bukkit.command.CommandSender; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -public class CrownDestroyServerLiteral implements CommandExecutor { +public class CrownDestroyServerLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument handleArg; + private final CommandNode rootNode; + private final CommandArgument handleArg; public CrownDestroyServerLiteral() { - this.rootNode = ScrowAPI.getCommandApi().createNode("destroy-server"); + this.rootNode = CommandNode.node("destroy-server"); this.rootNode.setExecutor(this); { - this.handleArg = ScrowAPI.getCommandApi().createArgument("template", new StringArgumentType<>()); + this.handleArg = CommandArgument.arg("template", new StringArgumentType<>()); this.handleArg.setSuggestionProvider(ctx -> { //noinspection CodeBlock2Expr - return ScrowAPI.getServerManager().getRunningServers() + return ScrowAPI.getInstanceManager().getInstances() .map(ServerInstance::getHandle) .collect(Collectors.toSet()) .toFuture(); @@ -35,11 +38,12 @@ public class CrownDestroyServerLiteral implements CommandExecutor> execute(CommandContext ctx) { var handle = ctx.getArg(handleArg); - ScrowAPI.getServerManager().destroyServer(handle) - .subscribe(__ -> - ctx.getSender().sendMessage("Destroyed server with handle " + handle) - ); + return ScrowAPI.getInstanceManager().destroyInstance(handle) + .map(__ -> { + ctx.getSender().sendMessage("Destroyed server with handle " + handle); + return Result.success(); + }).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java index cae17c3..aca4f2b 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java @@ -1,35 +1,41 @@ package de.kentoj.scrow.bukkit.crown; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.scrow.bukkit.ScrowAPI; +import de.kentoj.scrowlib.convention.ScrowMessageStyle; import lombok.Getter; +import net.kyori.adventure.text.Component; import org.bukkit.command.CommandSender; -public class CrownListServersLiteral implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class CrownListServersLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; + private final CommandNode node; + private final ScrowMessageStyle style; - public CrownListServersLiteral() { - this.rootNode = ScrowAPI.getCommandApi().createNode("list-servers"); - this.rootNode.setExecutor(this); + public CrownListServersLiteral(ScrowMessageStyle style) { + this.style = style; + this.node = CommandNode.node("list-servers"); + this.node.setExecutor(this); } @Override - public void execute(BukkitCommandContext ctx) { - ScrowAPI.getServerManager().getRunningServers() - .reduce(new StringBuilder("\n"), (acc, cur) -> acc.append("> ") - .append(cur.getHandle()) - .append(" on port ") - .append(cur.getPort()) - .append(": ") - .append(cur.getState()) - .append('\n') + public CompletableFuture> execute(CommandContext ctx) { + return ScrowAPI.getInstanceManager().getInstances() + .collectList() + .map(list -> style.list(Component.text("Running instances:"), + list.stream() + .map(inst -> + Component.text(inst.getHandle() + " on port " + inst.getPort() + ": " + inst.getState()) + ) + .toList()) ) - .subscribe(msg -> - ctx.getPlayerSender().sendMessage(msg.toString()) - ); + .doOnNext(ctx.getSender()::sendMessage) + .thenReturn(Result.success()).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java index 83e5520..4b3466c 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java @@ -1,9 +1,13 @@ package de.kentoj.scrow.bukkit.economy; -import de.kentoj.kencommandapi.BukkitCommandContext; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.types.IntegerArgumentType; +import de.kentoj.kencommandapi.api.argument.types.IntegerArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.scrow.bukkit.ScrowAPI; import lombok.Getter; @@ -12,67 +16,63 @@ import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import reactor.core.scheduler.Schedulers; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; public class CoinsCommand { @Getter - private final CommandNode rootNode; - private final CommandArgument playerArg; - private final CommandArgument amountArg; + private final CommandNode rootNode; + private final CommandArgument playerArg; + private final CommandArgument amountArg; public CoinsCommand() { - this.rootNode = ScrowAPI.getCommandApi().createNode("coins", "eco"); - this.playerArg = ScrowAPI.getCommandApi().createArgument("player", OfflinePlayerArgumentType.getInstance()); - this.amountArg = ScrowAPI.getCommandApi().createArgument("amount", new IntegerArgumentType<>()); + this.rootNode = CommandNode.node("coins", "eco"); + this.playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance()); + this.amountArg = CommandArgument.arg("amount", new IntegerArgumentType<>()); { - var setLiteral = ScrowAPI.getCommandApi().createNode("set"); + CommandNode setLiteral = CommandNode.node("set"); rootNode.addLiteral(setLiteral); setLiteral.addArgument(playerArg); setLiteral.addArgument(amountArg); setLiteral.setExecutor(this::setCoins); } { - var getLiteral = ScrowAPI.getCommandApi().createNode("get"); + CommandNode getLiteral = CommandNode.node("get"); rootNode.addLiteral(getLiteral); getLiteral.addArgument(playerArg); getLiteral.setExecutor(this::getCoins); } } - private void getCoins(BukkitCommandContext ctx) { + private CompletableFuture> getCoins(CommandContext ctx) { var player = ctx.getArg(playerArg); - - ScrowAPI.getEconomyService() + return ScrowAPI.getEconomyService() .getCoins(player.getUniqueId()) .subscribeOn(Schedulers.boundedElastic()) .publishOn(ScrowAPI.getMinecraftScheduler()) - .subscribe(amount -> { + .map(amount -> { if (player == ctx.getSender()) { ctx.getSender().sendMessage("You have " + amount + "$"); } else { ctx.getSender().sendMessage(player.getName() + " has " + amount + "$"); } - }, err -> { - ctx.getSender().sendMessage("Unable to get coins: " + err.getMessage()); - Bukkit.getLogger().log(Level.SEVERE, "Unable to get coins of " + player.getUniqueId(), err); - }); + return Result.success(); + }).toFuture(); } - private void setCoins(BukkitCommandContext ctx) { + private CompletableFuture> setCoins(CommandContext ctx) { var player = ctx.getArg(playerArg); var amount = ctx.getArg(amountArg); - ScrowAPI.getEconomyService() + return ScrowAPI.getEconomyService() .setCoins(player.getUniqueId(), amount) .subscribeOn(Schedulers.boundedElastic()) .publishOn(ScrowAPI.getMinecraftScheduler()) - .subscribe(__ -> { + .map(__ -> { ctx.getSender().sendMessage(player.getName() + " now has " + amount + "$"); - }, err -> { - ctx.getSender().sendMessage("Unable to set coins: " + err.getMessage()); - Bukkit.getLogger().log(Level.SEVERE, "Unable to set coins of " + player.getUniqueId() + " to " + amount, err); - }); + return Results.success(null); + }).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java index d28a752..9036ccb 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java @@ -1,33 +1,107 @@ package de.kentoj.scrow.bukkit.friends.command; -import de.kentoj.kencommandapi.BukkitCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.scrow.bukkit.ScrowAPI; -import de.kentoj.scrow.bukkit.friends.handler.FriendAddHandler; +import de.kentoj.scrow.bukkit.friends.friendship.FriendshipImpl; +import de.kentoj.scrow.bukkit.friends.request.FriendRequestImpl; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import reactor.core.publisher.Mono; -public class FriendAddLiteral implements CommandExecutor { +import java.time.Instant; +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +public class FriendAddLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument playerArg; + private final CommandNode rootNode; + private final CommandArgument playerArg; public FriendAddLiteral() { - var cmds = ScrowAPI.getCommandApi(); - playerArg = cmds.createArgument("player", OfflinePlayerArgumentType.getInstance()); - rootNode = cmds.createNode("add"); + playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance()); + rootNode = CommandNode.node("add"); rootNode.addArgument(playerArg); rootNode.setExecutor(this); } @Override - public void execute(BukkitCommandContext ctx) { - var handler = new FriendAddHandler(ctx.getPlayerSender(), ctx.getArg(playerArg)); - handler.handle(); + public CompletableFuture> execute(CommandContext ctx) { + var self = (Player) ctx.getSender(); + var other = ctx.getArg(playerArg); + return findAction(self, other) + .flatMap(action -> executeAction(self, other, action)) + .toFuture(); + } + + private Mono<@NotNull Result> executeAction(Player self, OfflinePlayer other, AddAction action) { + return switch (action) { + case ERR_CAN_NOT_ADD_SELF -> Mono.just(Result.error("You can not add yourself")); + case ERR_ALREADY_FRIENDS -> Mono.just(Result.error("You are already friends with " + other.getName())); + case ERR_ALREADY_REQUESTED -> + Mono.just(Result.error("You already requested " + other.getName() + " to be friends with you")); + case SEND_REQUEST -> sendRequest(self, other); + case ACCEPT_REQUEST -> acceptRequest(self, other); + }; + } + + private Mono<@NotNull Result> sendRequest(Player self, OfflinePlayer other) { + return ScrowAPI.getFriendRequestService().saveFriendRequest(new FriendRequestImpl(self.getUniqueId(), other.getUniqueId(), Instant.now())) + .map(success -> { + if (!success) return Result.error("You already sent " + other.getName() + " a friend request"); + + self.sendMessage("Successfully sent " + other.getName() + " a friend request."); + var otherPlayer = other.getPlayer(); + if (otherPlayer != null) + otherPlayer.sendMessage(" wants to be friends with you. Use '/f add " + self.getName() + "' to accept."); + return Result.success(); + }); + } + + private Mono<@NotNull Result> acceptRequest(Player self, OfflinePlayer other) { + return Mono.when( + ScrowAPI.getFriendRequestService().deleteFriendRequest(self.getUniqueId(), other.getUniqueId()), + ScrowAPI.getFriendRequestService().deleteFriendRequest(other.getUniqueId(), self.getUniqueId()), + ScrowAPI.getFriendshipService().saveFriendship(new FriendshipImpl(self.getUniqueId(), other.getUniqueId(), Instant.now())) + ) + .doOnNext(__ -> { + self.sendMessage("You are now friends with " + other.getName()); + if (other.getPlayer() != null) + other.getPlayer().sendMessage("You are now friends with " + self.getName()); + }) + .then(Mono.just(Result.success())); + } + + private Mono<@NotNull AddAction> findAction(Player self, OfflinePlayer other) { + if (self.getUniqueId() == other.getUniqueId()) return Mono.just(AddAction.ERR_CAN_NOT_ADD_SELF); + return ScrowAPI.getFriendshipService().getFriendship(self.getUniqueId(), other.getUniqueId()) + .map(f -> AddAction.ERR_ALREADY_FRIENDS) + .switchIfEmpty( + ScrowAPI.getFriendRequestService().getFriendRequest(self.getUniqueId(), other.getUniqueId()) + .map(r -> AddAction.ERR_ALREADY_REQUESTED) + .switchIfEmpty( + ScrowAPI.getFriendRequestService().getFriendRequest(other.getUniqueId(), self.getUniqueId()) + .map(r -> AddAction.ACCEPT_REQUEST) + .switchIfEmpty(Mono.just(AddAction.SEND_REQUEST)) + ) + ); + } + + private enum AddAction { + ERR_ALREADY_FRIENDS, + ERR_ALREADY_REQUESTED, + ERR_CAN_NOT_ADD_SELF, + SEND_REQUEST, + ACCEPT_REQUEST } } 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 index d92378b..a7fcc40 100644 --- 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 @@ -1,21 +1,25 @@ package de.kentoj.scrow.bukkit.friends.command; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.scrow.bukkit.ScrowAPI; +import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.node.RootCommandNode; +import de.kentoj.scrowlib.convention.ScrowMessageStyle; import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.CommandSender; public class FriendCommand { @Getter - private final CommandNode rootNode; + private final RootCommandNode rootNode; public FriendCommand() { - this.rootNode = ScrowAPI.getCommandApi().createNode("friends", "f", "friend"); - rootNode.addLiteral(new FriendListLiteral().getRootNode()); - rootNode.addLiteral(new FriendAddLiteral().getRootNode()); - rootNode.addLiteral(new FriendRemoveLiteral().getRootNode()); - rootNode.addLiteral(new FriendRequestsLiteral().getRootNode()); + var style = new ScrowMessageStyle(Component.text("Friends") + .color(NamedTextColor.GREEN)); + this.rootNode = CommandNode.rootNode(style, "f", "friend"); + rootNode.addLiteral(new FriendListLiteral(style).getRootNode()); + rootNode.addLiteral(new FriendAddLiteral(style).getRootNode()); + rootNode.addLiteral(new FriendRemoveLiteral(style).getRootNode()); + rootNode.addLiteral(new FriendRequestsLiteral(style).getRootNode()); } } 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 index e487dc8..172b891 100644 --- 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 @@ -1,36 +1,51 @@ package de.kentoj.scrow.bukkit.friends.command; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.scrow.bukkit.ScrowAPI; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; -public class FriendListLiteral { +import java.util.concurrent.CompletableFuture; + +public class FriendListLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; + private final CommandNode rootNode; public FriendListLiteral() { - this.rootNode = ScrowAPI.getCommandApi().createNode("list"); - rootNode.setExecutor(this::friendList); + this.rootNode = CommandNode.node("list"); + rootNode.setExecutor(this); } - private void friendList(BukkitCommandContext ctx) { - var ownUid = ctx.getPlayerSender().getUniqueId(); - ctx.getPlayerSender().sendMessage("friends:"); - ScrowAPI.getFriendshipService().getFriendships(ownUid) + @Override + public CompletableFuture> execute(CommandContext ctx) { + var sender = (Player) ctx.getSender(); + var ownUid = sender.getUniqueId(); + return ScrowAPI.getFriendshipService().getFriendships(ownUid) .map(friendship -> Bukkit.getOfflinePlayer(friendship.getUuids().getOther(ownUid))) .sort(this::compareByOnlineStatus) - .map(friendPlayer -> { - var onlineStatus = friendPlayer.isOnline() ? "§aonline" : "offline"; - return "- " + friendPlayer.getName() + ": " + onlineStatus; - }) - .doOnNext(line -> ctx.getSender().sendMessage(line)) - .doOnComplete(() -> ctx.getSender().sendMessage("EOF")) - .subscribe(); + .collectList() + .map(list -> { + if (list.isEmpty()) return Result.error("You have no friends. Use /friend add "); + + var msg = "§3§lFriends§r§7 ☆ You have §3" + list.size() + "§7 friend"; + if (list.size() > 1) msg += "s"; + msg += ":§r\n"; + for (var friend : list) { + var status = friend.isOnline() ? "(§aonline§7)" : "(offline)"; + msg += "§c≫ §7" + friend.getName() + " " + status + "\n"; + } + + sender.sendMessage(msg); + return Result.success(); + }).toFuture(); } private int compareByOnlineStatus(OfflinePlayer p1, OfflinePlayer p2) { diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java index dad9e52..0fa8cbf 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java @@ -1,34 +1,42 @@ package de.kentoj.scrow.bukkit.friends.command; -import de.kentoj.kencommandapi.BukkitCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.scrow.bukkit.ScrowAPI; import lombok.Getter; import org.bukkit.OfflinePlayer; -import org.bukkit.command.CommandException; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; -public class FriendRemoveLiteral { +import java.util.concurrent.CompletableFuture; + +public class FriendRemoveLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument playerArg; + private final CommandNode rootNode; + private final CommandArgument playerArg; public FriendRemoveLiteral() { - this.playerArg = ScrowAPI.getCommandApi().createArgument("player", OfflinePlayerArgumentType.getInstance()); - this.rootNode = ScrowAPI.getCommandApi().createNode("remove"); + this.playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance()); + this.rootNode = CommandNode.node("remove"); this.rootNode.addArgument(playerArg); - this.rootNode.setExecutor(this::removeFriend); + this.rootNode.setExecutor(this); } - private void removeFriend(BukkitCommandContext ctx) { + @Override + public CompletableFuture> execute(CommandContext ctx) { var friend = ctx.getArg(playerArg); - - ScrowAPI.getFriendshipService().deleteFriendship(friend.getUniqueId(), ctx.getPlayerSender().getUniqueId()) - .subscribe(wasDeleted -> { - if (wasDeleted) throw new CommandException("You were not friends with " + friend.getName() + " in the first place"); - ctx.getSender().sendMessage("You are no longer friends with " + friend.getName()); - }); + var player = (Player) ctx.getSender(); + return ScrowAPI.getFriendshipService().deleteFriendship(friend.getUniqueId(), player.getUniqueId()) + .map(wasAdded -> wasAdded + ? Result.success() : Result.error("You are not friends with " + friend.getName()) + ).doOnNext(success -> { + if (success.isSuccess()) + ctx.getSender().sendMessage("You are no longer friends with " + friend.getName()); + }).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java index 9d2efe2..769d731 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java @@ -1,32 +1,45 @@ package de.kentoj.scrow.bukkit.friends.command; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; +import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.scrow.bukkit.ScrowAPI; +import de.kentoj.scrow.bukkit.friends.FriendRequest; import lombok.Getter; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; -public class FriendRequestsLiteral { +import java.util.concurrent.CompletableFuture; + +public class FriendRequestsLiteral implements CommandExecutor { @Getter - private final CommandNode rootNode; + private final CommandNode rootNode; public FriendRequestsLiteral() { - this.rootNode = ScrowAPI.getCommandApi().createNode("requests", "list-requests"); - rootNode.setExecutor(this::friendRequestsList); + this.rootNode = CommandNode.node("requests", "list-requests"); + rootNode.setExecutor(this); } - private void friendRequestsList(BukkitCommandContext ctx) { - var ownUid = ctx.getPlayerSender().getUniqueId(); - ctx.getPlayerSender().sendMessage("incoming friends requests:"); - ScrowAPI.getFriendRequestService().getFriendRequestsTo(ownUid) - .map(friendRequest -> { - var fromUid = friendRequest.getFrom(); - var fromName = Bukkit.getOfflinePlayer(fromUid).getName(); - return "- " + fromName; - }).doOnNext(line -> ctx.getPlayerSender().sendMessage(line)) - .doOnComplete(() -> ctx.getPlayerSender().sendMessage("EOF")) - .subscribe(); + @Override + public CompletableFuture> execute(CommandContext ctx) { + var sender = (Player) ctx.getSender(); + return ScrowAPI.getFriendRequestService().getFriendRequestsTo(sender.getUniqueId()) + .collectList() + .map(list -> { + if (list.isEmpty()) return Result.error("You have no friend requests"); + + var msg = "§3§lFriends§r§7 ☆ You have §3" + list.size() + "§7 open friend request"; + if (list.size() > 1) msg += "s"; + msg += ":§r\n"; + for (FriendRequest friendRequest : list) { + msg += "§c≫ §7" + friendRequest.getFrom() + "\n"; + } + + sender.sendMessage(msg); + return Result.success(); + }).toFuture(); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java index 6a260f7..600fd6a 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java @@ -17,7 +17,7 @@ public class InMemoryFriendshipService implements FriendshipService { @Override public Mono<@NotNull Friendship> getFriendship(UUID uuid1, UUID uuid2) { - return Mono.just(friendships.get(new OrderedUUIDPair(uuid1, uuid2))); + return Mono.justOrEmpty(friendships.get(new OrderedUUIDPair(uuid1, uuid2))); } @Override diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/handler/FriendAddHandler.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/handler/FriendAddHandler.java deleted file mode 100644 index ecfcbe1..0000000 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/handler/FriendAddHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package de.kentoj.scrow.bukkit.friends.handler; - -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.scrow.bukkit.ScrowAPI; -import de.kentoj.scrow.bukkit.friends.friendship.FriendshipImpl; -import de.kentoj.scrow.bukkit.friends.request.FriendRequestImpl; -import lombok.RequiredArgsConstructor; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -import java.time.Instant; - -@RequiredArgsConstructor -public class FriendAddHandler { - - private final Player self; - private final OfflinePlayer other; - - public void handle() { - findAction() - .flatMap(this::executeAction) - .doOnError(CommandException.class, e -> { - ScrowAPI.getCommandApi().sendErrorMessage(self, e); - }) - .onErrorComplete(CommandException.class) - .subscribe(); - } - - private Mono executeAction(AddAction action) { - return switch (action) { - case ERR_CAN_NOT_ADD_SELF -> throw new CommandException("You can not add yourself"); - case ERR_ALREADY_FRIENDS -> throw new CommandException("You are already friends with " + other.getName()); - case ERR_ALREADY_REQUESTED -> - throw new CommandException("You already requested " + other.getName() + " to be friends with you"); - case SEND_REQUEST -> sendRequest(); - case ACCEPT_REQUEST -> acceptRequest(); - }; - } - - private Mono sendRequest() { - return ScrowAPI.getFriendRequestService().saveFriendRequest(new FriendRequestImpl(self.getUniqueId(), other.getUniqueId(), Instant.now())) - .flatMap(success -> { - if (success) { - sendMessage(self.getPlayer(), "Successfully sent " + other.getName() + " a friend request."); - return Mono.just(true); - } else { - sendMessage(self.getPlayer(), "You already sent " + other.getName() + " a friend request"); - return Mono.just(Mono.empty()); - } - }) - .doOnNext(success -> { - var otherPlayer = other.getPlayer(); - if (otherPlayer != null) - sendMessage(otherPlayer, self.getName() + " wants to be friends with you. Use '/f add " + self.getName() + "' to accept.").subscribe(); - }) - .doOnError(this::handleError); - - } - - private Mono acceptRequest() { - return Mono.when( - ScrowAPI.getFriendRequestService().deleteFriendRequest(self.getUniqueId(), other.getUniqueId()), - ScrowAPI.getFriendRequestService().deleteFriendRequest(other.getUniqueId(), self.getUniqueId()), - ScrowAPI.getFriendshipService().saveFriendship(new FriendshipImpl(self.getUniqueId(), other.getUniqueId(), Instant.now())) - ) - .then(sendMessage(self, "You are now friends with " + other.getName())) - .then(Mono.fromRunnable(() -> { - var otherPlayer = other.getPlayer(); - if (otherPlayer != null) - sendMessage(otherPlayer, "You are now friends with " + self.getName()).subscribe(); - })) - .doOnError(this::handleError); - } - - private Mono<@NotNull AddAction> findAction() { - if (self.getUniqueId() == other.getUniqueId()) return Mono.just(AddAction.ERR_CAN_NOT_ADD_SELF); - return ScrowAPI.getFriendshipService().getFriendship(self.getUniqueId(), other.getUniqueId()) - .map(f -> AddAction.ERR_ALREADY_FRIENDS) - .switchIfEmpty( - ScrowAPI.getFriendRequestService().getFriendRequest(self.getUniqueId(), other.getUniqueId()) - .map(r -> AddAction.ERR_ALREADY_REQUESTED) - .switchIfEmpty( - ScrowAPI.getFriendRequestService().getFriendRequest(other.getUniqueId(), self.getUniqueId()) - .map(r -> AddAction.ACCEPT_REQUEST) - .switchIfEmpty(Mono.just(AddAction.SEND_REQUEST)) - ) - ); - } - - private Mono sendMessage(Player player, String msg) { - return Mono.fromRunnable(() -> player.sendMessage(msg)) - .subscribeOn(ScrowAPI.getMinecraftScheduler()); - } - - private void handleError(Throwable e) { - sendMessage(self.getPlayer(), "FAILED SENDING REQUEST. REPORT THIS BUG. " + e.getMessage()).subscribe(); - } - - private enum AddAction { - ERR_ALREADY_FRIENDS, - ERR_ALREADY_REQUESTED, - ERR_CAN_NOT_ADD_SELF, - SEND_REQUEST, - ACCEPT_REQUEST - } -} diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/playermanager/PlayerManagerImpl.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/playermanager/PlayerManagerImpl.java new file mode 100644 index 0000000..0238f2f --- /dev/null +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/playermanager/PlayerManagerImpl.java @@ -0,0 +1,23 @@ +package de.kentoj.scrow.bukkit.playermanager; + +import com.leakyabstractions.result.api.Result; +import de.kentoj.scrow.bukkit.PlayerManager; +import de.kentoj.scrowlib.messaging.MessageBroker; +import lombok.RequiredArgsConstructor; +import org.bson.Document; + +import java.util.UUID; + +@RequiredArgsConstructor +public class PlayerManagerImpl implements PlayerManager { + + private final MessageBroker messageBroker; + + @Override + public Result sendPlayer(UUID playerId, String instanceHandle) { + return messageBroker.request("players.send", new Document() + .append("playerId", playerId) + .append("handle", instanceHandle)) + .mapSuccess(__ -> null); + } +} diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java index 4ddbac2..c3513f9 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java @@ -1,26 +1,29 @@ package de.kentoj.scrow.bukkit.server; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.scrow.bukkit.ScrowAPI; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; +import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -public class LobbyCommand implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class LobbyCommand implements CommandExecutor { @Getter - private final CommandNode rootNode; + private final CommandNode rootNode; public LobbyCommand() { - var cmds = ScrowAPI.getCommandApi(); - this.rootNode = cmds.createNode("lobby", "l", "hub"); + this.rootNode = CommandNode.node("lobby", "l", "hub"); this.rootNode.setExecutor(this); } @Override - public void execute(BukkitCommandContext ctx) { + public CompletableFuture> execute(CommandContext ctx) { Bukkit.dispatchCommand(ctx.getSender(), "play lobby"); + return CompletableFuture.completedFuture(Results.success(null)); } } diff --git a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java index 87f4171..ec2482c 100644 --- a/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java +++ b/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java @@ -1,43 +1,44 @@ package de.kentoj.scrow.bukkit.server; -import de.kentoj.kencommandapi.BukkitCommandContext; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.kencommandapi.api.types.StringArgumentType; +import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.scrow.bukkit.ScrowAPI; -import de.kentoj.scrowlib.servermanager.ServerInstance; import lombok.Getter; import org.bukkit.command.CommandSender; -import reactor.core.publisher.Mono; +import org.bukkit.entity.Player; -public class PlayCommand implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class PlayCommand implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument gamemodeArg; + private final CommandNode rootNode; + private final CommandArgument gamemodeArg; public PlayCommand() { - this.rootNode = ScrowAPI.getCommandApi().createNode("play", "queue"); - this.gamemodeArg = ScrowAPI.getCommandApi().createArgument("gamemode", new StringArgumentType<>()); + this.rootNode = CommandNode.node("play", "queue"); + this.gamemodeArg = CommandArgument.arg("gamemode", new StringArgumentType<>()); this.rootNode.addArgument(this.gamemodeArg); this.rootNode.setExecutor(this); } @Override - public void execute(BukkitCommandContext ctx) { + public CompletableFuture> execute(CommandContext ctx) { var gamemode = ctx.getArg(gamemodeArg); - ScrowAPI.getServerManager().getRunningServers() - .filter(inst -> inst.getTemplateName().equals(gamemode)) - .filter(inst -> inst.getState().equals("UP")) - .next() - .switchIfEmpty(Mono.error(new IllegalStateException("No instance available"))) - .map(ServerInstance::getHandle) - .flatMap(handle -> - ScrowAPI.getServerManager().sendPlayer(ctx.getPlayerSender().getUniqueId(), handle) - ) - .subscribe(null, ex -> { - ctx.getPlayerSender().sendMessage("failed sending you to an instance of " + gamemode + ": " + ex.getMessage()); - }); + var player = (Player) ctx.getSender(); + return CompletableFuture.supplyAsync(() -> ScrowAPI.getInstanceManager().getInstances() + .flatMapSuccess(instances -> { + var instance = instances.stream() + .filter(inst -> inst.getTemplate().equals(gamemode)) + .findFirst() + .orElse(null); + if (instance == null) return Results.failure("No instance available"); + return ScrowAPI.getPlayerManager().sendPlayer(player.getUniqueId(), instance.getHandle()); + })); } } diff --git a/core-lib/build.gradle.kts b/core-lib/build.gradle.kts index 2952630..9aed3e7 100644 --- a/core-lib/build.gradle.kts +++ b/core-lib/build.gradle.kts @@ -21,6 +21,9 @@ dependencies { // https://mvnrepository.com/artifact/org.mongodb/bson implementation("org.mongodb:bson:5.8.0") api("org.mongodb:bson:5.8.0") + + // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core + api("de.kentoj.scrow:kencommandapi-core:0.21") } publishing { diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/convention/ScrowMessageStyle.java b/core-lib/src/main/java/de/kentoj/scrowlib/convention/ScrowMessageStyle.java new file mode 100644 index 0000000..31a5261 --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/convention/ScrowMessageStyle.java @@ -0,0 +1,57 @@ +package de.kentoj.scrowlib.convention; + +import de.kentoj.kencommandapi.api.platform.MessageStyle; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; + +import java.util.List; + +public class ScrowMessageStyle implements MessageStyle { + + @Getter + private final Component prefix; + @Getter + private final String prefixText; + @Getter + private final TextColor prefixColor; + + public ScrowMessageStyle(String prefixText, TextColor prefixColor) { + this.prefixText = prefixText; + this.prefixColor = prefixColor; + this.prefix = Component.text(prefixText) + .color(prefixColor) + .decorate(TextDecoration.BOLD); + } + + @Override + public Component ok(Component msg) { + return prefix.append(Component.text(" ☆ ").color(NamedTextColor.GRAY)) + .append(msg.colorIfAbsent(NamedTextColor.GRAY)); + } + + public Component list(Component header, List list) { + var res = ok(header); + for (Component component : list) { + res = res.appendNewline() + .append(Component.text(" - ").color(NamedTextColor.GRAY)) + .append(component.colorIfAbsent(NamedTextColor.GRAY)); + } + return res; + } + + @Override + public Component err(Component msg) { + return ok(msg.color(NamedTextColor.RED)); + } + + @Override + public Component exception(Component msg) { + return ok(Component.text("ERROR: ") + .color(NamedTextColor.DARK_RED) + .append(msg) + .decorate(TextDecoration.BOLD)); + } +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/MessageBroker.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/MessageBroker.java new file mode 100644 index 0000000..72a8458 --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/MessageBroker.java @@ -0,0 +1,46 @@ +package de.kentoj.scrowlib.messaging; + +import com.leakyabstractions.result.api.Result; +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Slf4j +public class MessageBroker { + + private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + private final Connection nats; + protected final Dispatcher dispatcher; + + public MessageBroker(Connection nats) { + this.nats = nats; + this.dispatcher = nats.createDispatcher(); + + } + + public void publish(String subject, Result result) { + nats.publish(subject, ResultRepository.toBytes(result)); + } + + public void handle(String subject, RequestHandler handler) { + dispatcher.subscribe(subject, msg -> { + if (msg.getReplyTo() == null) { + log.warn("request in {} has replyTo unset; ignoring", subject); + return; + } + handler.handle(DocumentRepository.fromMessage(msg)) + .thenAccept(result -> + executor.submit(() -> publish(msg.getReplyTo(), result)) + ); + }); + } + + public Result request(String subject, Document document) { + var response = nats.request(subject, DocumentRepository.toBytes(document)).join(); + return ResultRepository.fromDocument(DocumentRepository.fromMessage(response)); + } +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/NatsRepository.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/NatsRepository.java deleted file mode 100644 index 8e8ba0b..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/NatsRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.kentoj.scrowlib.messaging; - -import de.kentoj.scrowlib.messaging.respose.SafeResult; -import io.nats.client.Connection; -import io.nats.client.Dispatcher; -import io.nats.client.Message; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class NatsRepository { - - private final Connection nats; - - private final Dispatcher dispatcher; - - public NatsRepository(Connection nats) { - this.nats = nats; - this.dispatcher = nats.createDispatcher(); - } - - public Mono<@NotNull Void> publishSafeResult(String subject, SafeResult safeResult) { - return this.publish(subject, safeResult.toDocument()); - } - - public Mono<@NotNull Void> publish(String subject, Document document) { - return publishRaw(subject, DocumentRepository.toBytes(document)); - } - - public Mono<@NotNull Void> publishRaw(String subject, byte[] data) { - return Mono.fromRunnable(() -> nats.publish(subject, data)); - } - - public Flux<@NotNull SubscriptionContext> subscribe(String subject) { - return Flux.create(sink -> { - dispatcher.subscribe(subject, msg -> { - try { - sink.next(new SubscriptionContext(msg)); - } catch (Throwable e) { - sink.error(e); - } - }); - }); - } - - public Mono<@NotNull Message> requestRaw(String subject, Document document) { - return Mono.fromFuture(nats.request(subject, DocumentRepository.toBytes(document))); - } - - public Mono<@NotNull Message> requestRaw(String subject, byte[] data) { - return Mono.fromFuture(nats.request(subject, data)); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/RequestHandler.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/RequestHandler.java new file mode 100644 index 0000000..2efef74 --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/RequestHandler.java @@ -0,0 +1,16 @@ +package de.kentoj.scrowlib.messaging; + +import com.leakyabstractions.result.api.Result; +import org.bson.Document; + +import java.util.concurrent.CompletionStage; + +@FunctionalInterface +public interface RequestHandler { + /** + * Processes a request and replies + * + * @return result to send back to the requesting client. + */ + CompletionStage> handle(Document request); +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/ResultRepository.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/ResultRepository.java new file mode 100644 index 0000000..3faa12f --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/ResultRepository.java @@ -0,0 +1,26 @@ +package de.kentoj.scrowlib.messaging; + +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.bson.Document; + + +@NoArgsConstructor(access = AccessLevel.NONE) +public class ResultRepository { + + public static byte[] toBytes(Result result) { + var doc = result.hasSuccess() + ? new Document("data", result.getSuccess().orElseThrow()) + : new Document("error", result.getFailure().orElseThrow()); + return DocumentRepository.toBytes(doc); + } + + public static Result fromDocument(Document doc) { + var error = doc.getString("error"); + return error != null + ? Results.failure(error) + : Results.success(doc.get("data", Document.class)); + } +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/SubscriptionContext.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/SubscriptionContext.java deleted file mode 100644 index 7d00f05..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/SubscriptionContext.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.kentoj.scrowlib.messaging; - -import io.nats.client.Message; -import lombok.AllArgsConstructor; -import lombok.Value; -import org.bson.Document; - -@Value -@AllArgsConstructor -public class SubscriptionContext { - Message msg; - Document document; - - public SubscriptionContext(Message msg) { - this(msg, DocumentRepository.fromMessage(msg)); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeErrorResult.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeErrorResult.java deleted file mode 100644 index 2de9027..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeErrorResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.kentoj.scrowlib.messaging.respose; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.bson.Document; - -@Getter -@AllArgsConstructor(access = AccessLevel.PACKAGE) -public final class SafeErrorResult extends SafeResult { - private final String type; - private final String message; - - public SafeErrorResult(Throwable throwable) { - this.type = throwable.getClass().getCanonicalName(); - this.message = throwable.getMessage(); - } - - @Override - public Document unwrap() { - throw this.toException(); - } - - public RuntimeException toException() { - return new RuntimeException(message); - } - - @Override - public Document toDocument() { - return new Document("error", new Document() - .append("type", this.type) - .append("message", this.getMessage())); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeResult.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeResult.java deleted file mode 100644 index 75a7504..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeResult.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.kentoj.scrowlib.messaging.respose; - -import de.kentoj.scrowlib.messaging.DocumentRepository; -import io.nats.client.Message; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public abstract sealed class SafeResult permits SafeErrorResult, SafeSuccessResult { - - /** - * @return the SafeResult as a Document for transit - */ - public abstract Document toDocument(); - - /** - * @return the data stored in the SafeResult - * @throws RuntimeException if the SafeResult represents an error - */ - public abstract Document unwrap(); - - public boolean isError() { - return this instanceof SafeErrorResult; - } - - public boolean isSuccess() { - return this instanceof SafeSuccessResult; - } - - public SafeErrorResult asError() { - return (SafeErrorResult) this; - } - - public SafeSuccessResult asSuccess() { - return (SafeSuccessResult) this; - } - - public static Document unwrap(Message message) { - return fromMessage(message).unwrap(); - } - - public static SafeResult wrap(Document document) { - return new SafeSuccessResult(document); - } - - public static SafeResult wrap(Throwable throwable) { - return new SafeErrorResult(throwable); - } - - public static SafeResult fromMessage(Message message) { - return fromDocument(DocumentRepository.fromMessage(message)); - } - - public static SafeResult fromDocument(Document document) { - if (document.containsKey("error")) { - var doc = document.get("error", Document.class); - return new SafeErrorResult( - doc.getString("type"), - doc.getString("message") - ); - } else if (document.containsKey("data")) { - return new SafeSuccessResult(document.get("data", Document.class)); - } - throw new IllegalArgumentException("document does not conform to SafeDocument format"); - } - - public static Mono<@NotNull SafeResult> mono(Mono<@NotNull Document> mono) { - return mono.map(SafeResult::wrap) - .onErrorResume(err -> Mono.just(SafeResult.wrap(err))); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeSuccessResult.java b/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeSuccessResult.java deleted file mode 100644 index 69242d1..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeSuccessResult.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.kentoj.scrowlib.messaging.respose; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.bson.Document; - -@Getter -@AllArgsConstructor(access = AccessLevel.PACKAGE) -public final class SafeSuccessResult extends SafeResult { - private final Document data; - - @Override - public Document unwrap() { - return this.getData(); - } - - @Override - public Document toDocument() { - return new Document("data", this.getData()); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManager.java b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManager.java new file mode 100644 index 0000000..22f930f --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManager.java @@ -0,0 +1,23 @@ +package de.kentoj.scrowlib.servermanager; + +import com.leakyabstractions.result.api.Result; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public interface InstanceManager { + + /** + * @return Mono with handle of deployed server + */ + Result deployInstance(String template); + + Result destroyInstance(String handle); + + /** + * @return Flux with handles of running servers + */ + Result, String> getInstances(); + + Result<@Nullable ServerInstance, String> getInstance(String handle); +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManagerImpl.java b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManagerImpl.java new file mode 100644 index 0000000..9dfc538 --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManagerImpl.java @@ -0,0 +1,45 @@ +package de.kentoj.scrowlib.servermanager; + +import com.leakyabstractions.result.api.Result; +import de.kentoj.scrowlib.messaging.MessageBroker; +import io.nats.client.Connection; +import org.bson.Document; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public class InstanceManagerImpl implements InstanceManager { + + private final MessageBroker messageBroker; + + public InstanceManagerImpl(Connection natsCon) { + messageBroker = new MessageBroker(natsCon); + } + + @Override + public Result deployInstance(String template) { + var result = messageBroker.request("instance.deploy", new Document("template", template)); + return result.mapSuccess(ServerInstance::fromDocument); + } + + @Override + public Result destroyInstance(String handle) { + var result = messageBroker.request("instance.destroy", new Document("handle", handle)); + return result.mapSuccess(__ -> null); + } + + @Override + public Result, String> getInstances() { + var result = messageBroker.request("instance.list", new Document()); + return result.mapSuccess(doc -> doc.getList("instances", Document.class) + .stream() + .map(ServerInstance::fromDocument) + .toList()); + } + + @Override + public Result<@Nullable ServerInstance, String> getInstance(String handle) { + var result = messageBroker.request("instance.get", new Document("handle", handle)); + return result.mapSuccess(ServerInstance::fromDocument); + } +} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java index 0750e1b..853226d 100644 --- a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java +++ b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java @@ -2,25 +2,27 @@ package de.kentoj.scrowlib.servermanager; import com.google.common.base.Preconditions; import lombok.Value; +import org.bson.Document; @Value public class ServerInstance { - String templateName; String handle; + String template; int port; - String state; - public ServerInstance(String templateName, String handle, int port, String state) { - Preconditions.checkNotNull(templateName); + public ServerInstance(String handle, String template, int port) { + Preconditions.checkNotNull(template); Preconditions.checkNotNull(handle); - Preconditions.checkNotNull(state); - this.templateName = templateName; + this.template = template; this.handle = handle; this.port = port; - this.state = state; } - public ServerInstance(String templateName, String handle, int port) { - this(templateName, handle, port, "STARTING"); + static ServerInstance fromDocument(Document document) { + return new ServerInstance( + document.getString("handle"), + document.getString("template"), + document.getInteger("port") + ); } } diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManager.java b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManager.java deleted file mode 100644 index f092bc4..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManager.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.kentoj.scrowlib.servermanager; - -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.UUID; - -public interface ServerManager { - - /** - * @return Mono with handle of deployed server - */ - Mono<@NotNull ServerInstance> deployServer(String template); - - /** - * Attempts a clean shutdown, if that doesn't succeed, kills the server. - */ - Mono<@NotNull Void> destroyServer(String handle); - - /** - * @return Flux with handles of running servers - */ - Flux<@NotNull ServerInstance> getRunningServers(); - - Mono<@NotNull ServerInstance> getInfo(String handle); - - Mono<@NotNull Void> setState(String handle, String state); - - Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle); -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManagerImpl.java b/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManagerImpl.java deleted file mode 100644 index 7d71452..0000000 --- a/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManagerImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package de.kentoj.scrowlib.servermanager; - -import com.google.common.base.Preconditions; -import de.kentoj.scrowlib.messaging.NatsRepository; -import de.kentoj.scrowlib.messaging.respose.SafeResult; -import io.nats.client.Connection; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.UUID; - -public class ServerManagerImpl implements ServerManager { - - private final NatsRepository natsRepo; - - public ServerManagerImpl(Connection nats) { - this.natsRepo = new NatsRepository(nats); - } - - @Override - public Mono<@NotNull ServerInstance> deployServer(String template) { - return natsRepo.requestRaw("crown.deploy-server", new Document("template", template)) - .map(SafeResult::unwrap) - .map(doc -> new ServerInstance( - template, - doc.getString("handle"), - doc.getInteger("port") - )); - } - - @Override - public Flux<@NotNull ServerInstance> getRunningServers() { - return natsRepo.requestRaw("crown.list-servers", new Document()) - .map(SafeResult::unwrap) - .map(doc -> - doc.getList("instances", Document.class) - ) - .flatMapIterable(l -> l) - .map(doc -> new ServerInstance( - doc.getString("templateName"), - doc.getString("handle"), - doc.getInteger("port"), - doc.getString("state") - )); - } - - @Override - public Mono<@NotNull Void> destroyServer(String handle) { - return natsRepo.requestRaw("crown.destroy-server", new Document("handle", handle)) - .map(SafeResult::unwrap) - .then(); - } - - @Override - public Mono<@NotNull ServerInstance> getInfo(String handle) { - return natsRepo.requestRaw("crown.get-info", new Document("handle", handle)) - .map(SafeResult::unwrap) - .map(doc -> new ServerInstance( - doc.getString("templateName"), - handle, - doc.getInteger("port"), - doc.getString("state") - )); - } - - @Override - public Mono<@NotNull Void> setState(String handle, String state) { - Preconditions.checkArgument(!state.contains(" "), "state may not contain spaces"); - var payload = new Document() - .append("handle", handle) - .append("state", state); - return natsRepo.requestRaw("crown.set-state", payload) - .map(SafeResult::unwrap) - .then(); - } - - @Override - public Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle) { - var payload = new Document() - .append("playerId", playerId.toString()) - .append("handle", serverHandle); - return natsRepo.requestRaw("proxy.send-player", payload) - .map(SafeResult::unwrap) - .then(); - } -} diff --git a/core-lib/src/main/java/de/kentoj/scrowlib/utils/EnvUtils.java b/core-lib/src/main/java/de/kentoj/scrowlib/utils/EnvUtils.java new file mode 100644 index 0000000..2a6570b --- /dev/null +++ b/core-lib/src/main/java/de/kentoj/scrowlib/utils/EnvUtils.java @@ -0,0 +1,13 @@ +package de.kentoj.scrowlib.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.NONE) +public class EnvUtils { + public static String envOrThrow(String name) { + var env = System.getenv(name); + if (env == null) throw new IllegalStateException("environment variable " + name + " not set"); + return env; + } +} diff --git a/core-velocity-api/build.gradle.kts b/core-velocity-api/build.gradle.kts index b1c24bb..2911306 100644 --- a/core-velocity-api/build.gradle.kts +++ b/core-velocity-api/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.java-library") + id("de.kentoj.scrow.reactor") } group = "de.kentoj.scrow" @@ -23,9 +24,9 @@ dependencies { api("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT") // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core - api("de.kentoj.scrow:kencommandapi-core:0.8") + api("de.kentoj.scrow:kencommandapi-core:0.21") // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-velocity - api("de.kentoj.scrow:kencommandapi-velocity:0.8") + api("de.kentoj.scrow:kencommandapi-velocity:0.21") } publishing { diff --git a/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java b/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java index b93f81d..f682c83 100644 --- a/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java +++ b/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java @@ -1,8 +1,8 @@ package de.kentoj.scrow.velocity; -import de.kentoj.kencommandapi.VelocityKenCommandApi; +import de.kentoj.kencommandapi.CommandAPI; import de.kentoj.scrowlib.connection.DatabaseConnectionFactory; -import de.kentoj.scrowlib.servermanager.ServerManager; +import de.kentoj.scrowlib.servermanager.InstanceManager; import io.nats.client.Connection; import lombok.AccessLevel; import lombok.Getter; @@ -16,14 +16,14 @@ public class ScrowAPI { @Getter private static DatabaseConnectionFactory dbConFactory; @Getter - private static VelocityKenCommandApi commandApi; + private static CommandAPI commandApi; @Getter private static Connection nats; @Getter - private static ServerManager serverManager; + private static InstanceManager instanceManager; @ApiStatus.Internal - public static void setCommandApi(VelocityKenCommandApi commandApi) { + public static void setCommandApi(CommandAPI commandApi) { ScrowAPI.commandApi = commandApi; } @@ -33,8 +33,8 @@ public class ScrowAPI { } @ApiStatus.Internal - public static void setServerManager(ServerManager serverManager) { - ScrowAPI.serverManager = serverManager; + public static void setInstanceManager(InstanceManager instanceManager) { + ScrowAPI.instanceManager = instanceManager; } @ApiStatus.Internal diff --git a/core-velocity-impl/build.gradle.kts b/core-velocity-impl/build.gradle.kts index 463b32a..1821ec9 100644 --- a/core-velocity-impl/build.gradle.kts +++ b/core-velocity-impl/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.database") + id("de.kentoj.scrow.reactor") } group = "de.kentoj.scrow" @@ -23,8 +24,6 @@ dependencies { // https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine implementation("com.github.ben-manes.caffeine:caffeine:3.2.4") - - implementation("de.kentoj.scrow:kencommandapi-velocity") } val generateBuildInfo by tasks.registering { diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java index 74c5ec3..5f1c66b 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java @@ -33,7 +33,7 @@ public class CoreVelocityPlugin { cmds.register(new OnlineCommand(server).getRootNode()); } - var registerWatchdog = new ServerRegisterWatchdog(ScrowAPI.getNats(), ScrowAPI.getServerManager(), server); + var registerWatchdog = new ServerRegisterWatchdog(ScrowAPI.getNats(), ScrowAPI.getInstanceManager(), server); var sendPlayerWatchdog = new SendPlayerWatchdog(ScrowAPI.getNats(), server); registerWatchdog.listen(); sendPlayerWatchdog.listen(); diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java index a33d453..f0a4934 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java @@ -1,10 +1,10 @@ package de.kentoj.scrow.corevelocity; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityKenCommandApi; +import de.kentoj.kencommandapi.CommandAPI; import de.kentoj.scrow.velocity.ScrowAPI; import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl; -import de.kentoj.scrowlib.servermanager.ServerManagerImpl; +import de.kentoj.scrowlib.utils.EnvUtils; import io.nats.client.Nats; import io.nats.client.Options; import io.r2dbc.spi.ConnectionFactories; @@ -19,7 +19,7 @@ public class ScrowAPISurface { public static void initScrowAPI(ProxyServer server, boolean enableDatabase) { try { - String natsHost = System.getenv("HOST_NATS"); + String natsHost = EnvUtils.envOrThrow("HOST_NATS"); var options = Options.builder() .server(natsHost) .pedantic() @@ -30,13 +30,13 @@ public class ScrowAPISurface { } if (enableDatabase) { - ConnectionFactory factory = ConnectionFactories.get(System.getenv("HOST_POSTGRES")); + ConnectionFactory factory = ConnectionFactories.get(EnvUtils.envOrThrow("HOST_POSTGRES")); ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory)); } - ScrowAPI.setServerManager(new ServerManagerImpl(ScrowAPI.getNats())); + ScrowAPI.setInstanceManager(new ServerManagerImpl(ScrowAPI.getNats())); - ScrowAPI.setCommandApi(new VelocityKenCommandApi(server)); + ScrowAPI.setCommandApi(new CommandAPI(server)); } public static void destroyScrowAPI() { diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java index 2e3ddba..6a34d18 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java @@ -2,34 +2,38 @@ package de.kentoj.scrow.corevelocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityCommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.scrow.velocity.ScrowAPI; +import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import lombok.Getter; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; +import java.util.concurrent.CompletableFuture; + @RequiredArgsConstructor -public class OnlineCommand implements CommandExecutor { +public class OnlineCommand implements CommandExecutor { @Getter - private final CommandNode rootNode; + private final CommandNode rootNode; private final ProxyServer server; public OnlineCommand(ProxyServer server) { this.server = server; - rootNode = ScrowAPI.getCommandApi().createNode("online"); + rootNode = CommandNode.node("online"); rootNode.setExecutor(this); } @Override - public void execute(VelocityCommandContext ctx) { + public CompletableFuture> execute(CommandContext ctx) { int cnt = server.getPlayerCount(); var msg = cnt != 1 ? "There are currently " + cnt + " players online" : "There is currently 1 player online"; - ctx.getPlayerSender().sendMessage(Component.text(msg)); + ctx.getSender().sendMessage(Component.text(msg)); + return Result.success().toFuture(); } } diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java index 35f9ae2..3812ed4 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java @@ -3,46 +3,50 @@ package de.kentoj.scrow.corevelocity.privmsg; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.kencommandapi.api.types.StringArgumentType; +import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import de.kentoj.kencommandapi.type.PlayerArgumentType; import de.kentoj.scrow.velocity.ScrowAPI; import lombok.Getter; -public class MsgCommand implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class MsgCommand implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument targetArg; - private final CommandArgument msgArg; + private final CommandNode rootNode; + private final CommandArgument targetArg; + private final CommandArgument msgArg; private final LastTargetCache cache; public MsgCommand(ProxyServer server, LastTargetCache cache) { this.cache = cache; - var cmds = ScrowAPI.getCommandApi(); - targetArg = cmds.createArgument("target", new PlayerArgumentType(server)); - msgArg = cmds.createArgument("msg", new StringArgumentType<>(true)); - rootNode = cmds.createNode("msg", "w", "privmsg"); + targetArg = CommandArgument.arg("target", new PlayerArgumentType(server)); + msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true)); + rootNode = CommandNode.node("msg", "w", "privmsg"); rootNode.setExecutor(this); rootNode.addArgument(targetArg); rootNode.addArgument(msgArg); } @Override - public void execute(VelocityCommandContext ctx) { + public CompletableFuture> execute(CommandContext ctx) { var target = ctx.getArg(targetArg); var msg = ctx.getArg(msgArg); + var sender = (Player)ctx.getSender(); - var isMessagingSelf = target.getUniqueId().equals(ctx.getPlayerSender().getUniqueId()); - if (isMessagingSelf) throw new CommandException("You can't message yourself."); + var isMessagingSelf = target.getUniqueId().equals(sender.getUniqueId()); + if (isMessagingSelf) return Result.error("You can't message yourself").toFuture(); - cache.setLastTarget(ctx.getPlayerSender().getUniqueId(), target.getUniqueId()); - PrivmsgHelper.handle(ctx.getPlayerSender(), target, msg); + cache.setLastTarget(sender.getUniqueId(), target.getUniqueId()); + PrivmsgHelper.handle(sender, target, msg); + return Result.success().toFuture(); } } diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java index a0bd3cd..5aa38a8 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java @@ -1,21 +1,24 @@ package de.kentoj.scrow.corevelocity.privmsg; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityCommandContext; +import de.kentoj.kencommandapi.api.node.CommandNode; import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import de.kentoj.kencommandapi.api.types.StringArgumentType; -import de.kentoj.scrow.velocity.ScrowAPI; +import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.api.Results; import lombok.Getter; -public class ReplyCommand implements CommandExecutor { +import java.util.concurrent.CompletableFuture; + +public class ReplyCommand implements CommandExecutor { @Getter - private final CommandNode rootNode; - private final CommandArgument msgArg; + private final CommandNode rootNode; + private final CommandArgument msgArg; private final LastTargetCache cache; private final ProxyServer server; @@ -24,21 +27,22 @@ public class ReplyCommand implements CommandExecutor(true)); - rootNode = cmds.createNode("reply", "r"); + msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true)); + rootNode = CommandNode.node("reply", "r"); rootNode.setExecutor(this); rootNode.addArgument(msgArg); } @Override - public void execute(VelocityCommandContext ctx) { - var targetId = cache.getLastTarget(ctx.getPlayerSender().getUniqueId()); + public CompletableFuture> execute(CommandContext ctx) { + var sender = (Player)ctx.getSender(); + var targetId = cache.getLastTarget(sender.getUniqueId()); var target = server.getPlayer(targetId).orElse(null); - if (target == null) throw new CommandException("The player you last messaged is no longer online"); + if (target == null) return Result.error("The player you last messaged is no longer online").toFuture(); var msg = ctx.getArg(msgArg); - cache.setLastTarget(ctx.getPlayerSender().getUniqueId(), target.getUniqueId()); - PrivmsgHelper.handle(ctx.getPlayerSender(), target, msg); + cache.setLastTarget(sender.getUniqueId(), target.getUniqueId()); + PrivmsgHelper.handle(sender, target, msg); + return Result.success().toFuture(); } } diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java index e85601c..0ed69cc 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java @@ -1,43 +1,47 @@ package de.kentoj.scrow.corevelocity.server; +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.scrowlib.messaging.NatsRepository; -import de.kentoj.scrowlib.messaging.respose.SafeResult; +import de.kentoj.scrowlib.messaging.MessageBroker; import io.nats.client.Connection; +import lombok.extern.slf4j.Slf4j; import org.bson.Document; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +@Slf4j public class SendPlayerWatchdog { - private final NatsRepository natsRepo; + private final MessageBroker natsRepo; private final ProxyServer server; public SendPlayerWatchdog(Connection nats, ProxyServer server) { - this.natsRepo = new NatsRepository(nats); + this.natsRepo = new MessageBroker(nats); this.server = server; } public void listen() { - natsRepo.subscribe("proxy.send-player") - .flatMap(ctx -> - SafeResult.mono(handleSendPlayer(ctx.getDocument())) - ) - .subscribe(); + natsRepo.handle("players.send", doc -> sendPlayer( + doc.get("playerId", UUID.class), + doc.getString("handle") + )); } - private Mono<@NotNull Document> handleSendPlayer(Document doc) { - return Mono.fromCallable(() -> { - var player = server.getPlayer(UUID.fromString(doc.getString("playerId"))).orElseThrow(); - var targetServer = server.getServer(doc.getString("handle")).orElseThrow(); - return player.createConnectionRequest(targetServer).connect(); - }) - .flatMap(Mono::fromFuture) - .flatMap(res -> res.isSuccessful() - ? Mono.just(new Document()) - : Mono.error(new RuntimeException(res.getStatus().name().toLowerCase().replace('_', ' '))) - ); + private CompletionStage> sendPlayer(UUID playerId, String handle) { + return CompletableFuture.supplyAsync(() -> { + var player = server.getPlayer(playerId).orElseThrow(); + var targetServer = server.getServer(handle).orElseThrow(); + try { + var res = player.createConnectionRequest(targetServer).connect().get(); + return res.isSuccessful() ? Results.success(new Document()) : Results.failure(res.getStatus().name()); + } catch (InterruptedException | ExecutionException e) { + log.error("failed to send player to {}", handle, e); + return Results.failure(e.getMessage()); + } + }); } } diff --git a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java index 7f5af50..d8f2cda 100644 --- a/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java +++ b/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java @@ -2,10 +2,9 @@ package de.kentoj.scrow.corevelocity.server; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.ServerInfo; -import de.kentoj.scrowlib.messaging.NatsRepository; -import de.kentoj.scrowlib.messaging.SubscriptionContext; +import de.kentoj.scrowlib.messaging.MessageBroker; import de.kentoj.scrowlib.messaging.respose.SafeResult; -import de.kentoj.scrowlib.servermanager.ServerManager; +import de.kentoj.scrowlib.servermanager.InstanceManager; import io.nats.client.Connection; import lombok.extern.java.Log; import org.bson.Document; @@ -17,13 +16,13 @@ import java.net.InetSocketAddress; @Log public class ServerRegisterWatchdog { - private final NatsRepository natsRepo; - private final ServerManager serverManager; + private final MessageBroker natsRepo; + private final InstanceManager instanceManager; private final ProxyServer server; - public ServerRegisterWatchdog(Connection nats, ServerManager serverManager, ProxyServer server) { - this.natsRepo = new NatsRepository(nats); - this.serverManager = serverManager; + public ServerRegisterWatchdog(Connection nats, InstanceManager instanceManager, ProxyServer server) { + this.natsRepo = new MessageBroker(nats); + this.instanceManager = instanceManager; this.server = server; } @@ -42,11 +41,11 @@ public class ServerRegisterWatchdog { private @NotNull Mono<@NotNull Void> handleStateChange(SubscriptionContext ctx, String state, String handle) { return switch (state) { - case "UP" -> serverManager.getInfo(handle) + case "UP" -> instanceManager.getInfo(handle) .doOnNext(instance -> server.registerServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())))) .then(); - case "DOWN" -> serverManager.getInfo(handle) + case "DOWN" -> instanceManager.getInfo(handle) .doOnNext(instance -> server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())))) .then();