currently refactoring a lot

This commit is contained in:
kento2 2026-07-04 01:03:46 +02:00
parent 701a5ff620
commit 7fe7409a15
49 changed files with 856 additions and 767 deletions

View file

@ -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"))

View file

@ -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

View file

@ -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 ?

View file

@ -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<CommandSender, BukkitCommandContext> rootNode;
private final CommandNode<CommandSender> 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());
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
import java.util.concurrent.CompletableFuture;
public class CrownDeployServerLiteral implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> templateArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, String> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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();
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
public class CrownDestroyServerLiteral implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> handleArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, String> 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<CommandSender,
}
@Override
public void execute(BukkitCommandContext ctx) {
public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> 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();
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
import java.util.concurrent.CompletableFuture;
public class CrownListServersLiteral implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandNode<CommandSender> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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();
}
}

View file

@ -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<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg;
private final CommandArgument<CommandSender, BukkitCommandContext, Integer> amountArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, OfflinePlayer> playerArg;
private final CommandArgument<CommandSender, Integer> 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<CommandSender> setLiteral = CommandNode.node("set");
rootNode.addLiteral(setLiteral);
setLiteral.addArgument(playerArg);
setLiteral.addArgument(amountArg);
setLiteral.setExecutor(this::setCoins);
}
{
var getLiteral = ScrowAPI.getCommandApi().createNode("get");
CommandNode<CommandSender> getLiteral = CommandNode.node("get");
rootNode.addLiteral(getLiteral);
getLiteral.addArgument(playerArg);
getLiteral.setExecutor(this::getCoins);
}
}
private void getCoins(BukkitCommandContext ctx) {
private CompletableFuture<Result<Void,String>> getCoins(CommandContext<CommandSender> 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<Result<Void,String>> setCoins(CommandContext<CommandSender> 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();
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
@RequiredArgsConstructor
public class FriendAddLiteral implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, OfflinePlayer> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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
}
}

View file

@ -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<CommandSender, BukkitCommandContext> rootNode;
private final RootCommandNode<CommandSender> 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());
}
}

View file

@ -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<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandNode<CommandSender> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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 <player>");
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) {

View file

@ -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<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, OfflinePlayer> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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();
}
}

View file

@ -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<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandNode<CommandSender> 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<Result<Void,String>> execute(CommandContext<CommandSender> 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();
}
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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<Void, String> sendPlayer(UUID playerId, String instanceHandle) {
return messageBroker.request("players.send", new Document()
.append("playerId", playerId)
.append("handle", instanceHandle))
.mapSuccess(__ -> null);
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
import java.util.concurrent.CompletableFuture;
public class LobbyCommand implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandNode<CommandSender> 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<Result<Void, String>> execute(CommandContext<CommandSender> ctx) {
Bukkit.dispatchCommand(ctx.getSender(), "play lobby");
return CompletableFuture.completedFuture(Results.success(null));
}
}

View file

@ -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<CommandSender, BukkitCommandContext> {
import java.util.concurrent.CompletableFuture;
public class PlayCommand implements CommandExecutor<CommandSender> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> gamemodeArg;
private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, String> 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<Result<Void, String>> execute(CommandContext<CommandSender> 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());
}));
}
}