This commit is contained in:
kento2 2026-08-05 21:40:52 +02:00
parent c60f19cafc
commit 2ea2eb5d1e
114 changed files with 671 additions and 609 deletions

34
core-velocity/BUILD Normal file
View file

@ -0,0 +1,34 @@
load("@rules_jvm_external//:defs.bzl", "artifact")
load("@rules_java//java:java_library.bzl", "java_library")
load("@rules_java//java:java_binary.bzl", "java_binary")
java_library(
name = "api",
srcs = glob(["api/main/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
"//core-lib:core",
artifact("com.velocitypowered:velocity-api"),
artifact("org.projectlombok:lombok"),
artifact("de.kentoj.scrow:kencommandapi-velocity"),
],
exports = [
"//core-lib:core",
artifact("de.kentoj.scrow:kencommandapi-velocity"),
artifact("org.mongodb:bson"),
],
plugins = [ "//third_party:lombok_plugin" ],
)
java_binary(
name = "plugin",
srcs = glob(["plugin/main/**/*.java"]),
main_class = "none",
resources = glob(["plugin/main/resources/**"], allow_empty = True),
deps = [
":api",
artifact("com.velocitypowered:velocity-api"),
artifact("io.nats:jnats"),
],
plugins = [ "//third_party:lombok_plugin" ],
)

View file

@ -0,0 +1,71 @@
package de.kentoj.scrow.velocity;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrowlib.instancemanager.InstanceManager;
import de.kentoj.scrowlib.messaging.MessageBroker;
import de.kentoj.scrowlib.player.NetworkPlayer;
import de.kentoj.scrowlib.player.NetworkPlayerFactory;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.jdbi.v3.core.Jdbi;
import org.jetbrains.annotations.ApiStatus;
import java.util.UUID;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ScrowAPI {
@Getter
private static Jdbi jdbi;
@Getter
private static CommandAPI<CommandSource> commands;
@Getter
private static CommandAPI<Player> playerCommands;
@Getter
private static MessageBroker messageBroker;
@Getter
private static InstanceManager instanceManager;
private static NetworkPlayerFactory playerFactory;
public static NetworkPlayer getPlayer(Player player) {
return getPlayer(player.getUniqueId());
}
public static NetworkPlayer getPlayer(UUID uuid) {
return playerFactory.create(uuid);
}
@ApiStatus.Internal
public static void setMessageBroker(MessageBroker messageBroker) {
ScrowAPI.messageBroker = messageBroker;
}
@ApiStatus.Internal
public static void setInstanceManager(InstanceManager instanceManager) {
ScrowAPI.instanceManager = instanceManager;
}
@ApiStatus.Internal
public static void setJdbi(Jdbi jdbi) {
ScrowAPI.jdbi = jdbi;
}
@ApiStatus.Internal
public static void setPlayerFactory(NetworkPlayerFactory playerFactory) {
ScrowAPI.playerFactory = playerFactory;
}
@ApiStatus.Internal
public static void setPlayerCommands(CommandAPI<Player> playerCommands) {
ScrowAPI.playerCommands = playerCommands;
}
@ApiStatus.Internal
public static void setCommands(CommandAPI<CommandSource> commands) {
ScrowAPI.commands = commands;
}
}

View file

@ -0,0 +1,53 @@
package de.kentoj.scrow.corevelocity;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.scrow.corevelocity.misc.OnlineCommand;
import de.kentoj.scrow.corevelocity.network.FriendNotifications;
import de.kentoj.scrow.corevelocity.network.SendPlayerWatchdog;
import de.kentoj.scrow.corevelocity.network.ServerRegisterWatchdog;
import de.kentoj.scrow.corevelocity.privmsg.LastTargetCache;
import de.kentoj.scrow.corevelocity.privmsg.MsgCommand;
import de.kentoj.scrow.corevelocity.privmsg.PrivMsgHandler;
import de.kentoj.scrow.corevelocity.privmsg.ReplyCommand;
import de.kentoj.scrow.generated.BuildInfo;
import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.format.NamedTextColor;
@Slf4j
@Plugin(
id = "corevelocity",
name = "CoreVelocity",
authors = {"gradlehater57"},
version = BuildInfo.VERSION // may not resolve until building for the first time
)
public final class CoreVelocityPlugin {
@Inject
private ProxyServer server;
@Subscribe
private void onInit(ProxyInitializeEvent __) {
ScrowAPISurface.initScrowAPI(server);
var cmds = ScrowAPI.getCommandApi();
var lastTargetCache = new LastTargetCache();
var style = new ScrowMessageStyle("MSG", NamedTextColor.LIGHT_PURPLE);
var privmsgHelper = new PrivMsgHandler(style);
cmds.register(new ReplyCommand(server, lastTargetCache, privmsgHelper, style).getRootLiteral());
cmds.register(new MsgCommand(server, lastTargetCache, privmsgHelper, style).getRootLiteral());
cmds.register(new OnlineCommand(server).getRootLiteral());
var registerWatchdog = new ServerRegisterWatchdog(server);
var sendPlayerWatchdog = new SendPlayerWatchdog(server);
registerWatchdog.listen();
sendPlayerWatchdog.listen();
server.getEventManager().register(this, new FriendNotifications());
}
}

View file

@ -0,0 +1,63 @@
package de.kentoj.scrow.corevelocity;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.instancemanager.InstanceManagerImpl;
import de.kentoj.scrowlib.messaging.InMemoyMessageBroker;
import de.kentoj.scrowlib.messaging.NatsMessageBroker;
import de.kentoj.scrowlib.player.NetworkPlayerImpl;
import io.nats.client.Nats;
import io.nats.client.Options;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.core.Jdbi;
import java.io.IOException;
import java.util.Properties;
@Slf4j
@NoArgsConstructor(access = AccessLevel.NONE)
public class ScrowAPISurface {
public static void initScrowAPI(ProxyServer server) {
var natsHost = System.getenv("HOST_NATS");
if (natsHost != null) {
try {
var options = Options.builder()
.server(natsHost)
.pedantic()
.build();
ScrowAPI.setMessageBroker(new NatsMessageBroker(Nats.connect(options)));
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
} else {
log.error("HOST_NATS not set -> Using in-memory dummy message broker");
ScrowAPI.setMessageBroker(new InMemoyMessageBroker());
}
var hostPostgres = System.getenv().get("HOST_POSTGRES");
if (hostPostgres != null) {
try {
Class.forName("org.postgresql.Driver", true, ScrowAPISurface.class.getClassLoader());
var props = new Properties();
props.setProperty("user", "postgres");
ScrowAPI.setJdbi(Jdbi.create(hostPostgres, props));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
log.error("HOST_POSTGRES not set -> Using in-memory database");
}
ScrowAPI.setInstanceManager(new InstanceManagerImpl(ScrowAPI.getMessageBroker()));
ScrowAPI.setCommandApi(new CommandAPI(server));
ScrowAPI.setPlayerFactory(playerId -> new NetworkPlayerImpl(ScrowAPI.getMessageBroker(), playerId));
}
public static void destroyScrowAPI() {
}
}

View file

@ -0,0 +1,38 @@
package de.kentoj.scrow.corevelocity.misc;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.SyncCommandExecutor;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class OnlineCommand implements SyncCommandExecutor<CommandSource> {
@Getter
private final RootLiteral<CommandSource> rootLiteral;
private final ScrowMessageStyle style = ScrowMessageStyle.GENERIC;
private final ProxyServer server;
public OnlineCommand(ProxyServer server) {
this.server = server;
rootLiteral = Literal.rootLiteral(ScrowMessageStyle.GENERIC, "online");
rootLiteral.setExecutor(this);
}
@Override
public Result<Object, String> executeSync(CommandContext<CommandSource> ctx) {
int cnt = server.getPlayerCount();
var msg = cnt != 1
? "There are currently " + cnt + " players online."
: "There is currently 1 player online.";
ctx.getSender().sendMessage(style.ok(msg));
return Results.success(new Object());
}
}

View file

@ -0,0 +1,23 @@
package de.kentoj.scrow.corevelocity.network;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.messaging.MessageBroker;
import org.bson.Document;
public class FriendNotifications {
private final MessageBroker messageBroker = ScrowAPI.getMessageBroker();
@Subscribe
private void onConnect(PostLoginEvent ev) {
messageBroker.publish("event.player.connect", new Document("playerId", ev.getPlayer().getUniqueId()));
}
@Subscribe
private void onDisconnect(DisconnectEvent ev) {
messageBroker.publish("event.player.disconnect", new Document("playerId", ev.getPlayer().getUniqueId()));
}
}

View file

@ -0,0 +1,41 @@
package de.kentoj.scrow.corevelocity.network;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.messaging.MessageBroker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j
@RequiredArgsConstructor
public class SendPlayerWatchdog {
private final ExecutorService EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
private final MessageBroker messageBroker = ScrowAPI.getMessageBroker();
private final ProxyServer server;
public void listen() {
messageBroker.handle("player.send", doc -> sendPlayer(
doc.get("playerId", UUID.class),
doc.getString("handle")
));
}
private Future<Document> sendPlayer(UUID playerId, String handle) {
return EXECUTOR.submit(() -> {
var player = server.getPlayer(playerId).orElseThrow();
var targetServer = server.getServer(handle).orElseThrow();
var res = player.createConnectionRequest(targetServer).connect().get();
if (!res.isSuccessful())
throw new RuntimeException(res.getStatus().name());
return new Document();
});
}
}

View file

@ -0,0 +1,34 @@
package de.kentoj.scrow.corevelocity.network;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.messaging.MessageBroker;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import java.net.InetSocketAddress;
@Log
@RequiredArgsConstructor
public class ServerRegisterWatchdog {
private final MessageBroker messageBroker = ScrowAPI.getMessageBroker();
private final ProxyServer server;
public void listen() {
messageBroker.subscribe("event.instance.up", doc -> {
var handle = doc.getString("handle");
var port = doc.getInteger("port");
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(port)));
log.info("registered server " + handle);
});
messageBroker.subscribe("event.instance.down", doc -> {
var handle = doc.getString("handle");
var port = doc.getInteger("port");
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(port)));
log.info("unregistered server " + handle);
});
}
}

View file

@ -0,0 +1,24 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class LastTargetCache {
private final Cache<@NotNull UUID, @NotNull UUID> cache = CacheBuilder.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build();
public @Nullable UUID getLastTarget(UUID uuid) {
return cache.getIfPresent(uuid);
}
public void setLastTarget(UUID uuid, UUID target) {
cache.put(uuid, target);
}
}

View file

@ -0,0 +1,54 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.SyncCommandExecutor;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import de.kentoj.kencommandapi.type.PlayerArgumentType;
import lombok.Getter;
public class MsgCommand implements SyncCommandExecutor<CommandSource> {
@Getter
private final RootLiteral<CommandSource> rootLiteral;
private final CommandArgument<CommandSource, Player> targetArg;
private final CommandArgument<CommandSource, String> msgArg;
private final LastTargetCache cache;
private final PrivMsgHandler privMsgHandler;
public MsgCommand(ProxyServer server, LastTargetCache cache, PrivMsgHandler helper, MessageStyle style) {
this.cache = cache;
this.privMsgHandler = helper;
targetArg = CommandArgument.arg("target", new PlayerArgumentType(server));
msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true));
rootLiteral = Literal.rootLiteral(style, "msg", "w", "privmsg");
rootLiteral.setExecutor(this);
rootLiteral.addArgument(targetArg);
rootLiteral.addArgument(msgArg);
}
@Override
public Result<Object, String> executeSync(CommandContext<CommandSource> ctx) {
var target = ctx.getArg(targetArg);
var msg = ctx.getArg(msgArg);
var sender = (Player) ctx.getSender();
var isMessagingSelf = target.getUniqueId().equals(sender.getUniqueId());
if (isMessagingSelf) return Results.failure("You can't message yourself.");
cache.setLastTarget(sender.getUniqueId(), target.getUniqueId());
privMsgHandler.handle(sender, target, msg);
return Results.success(new Object());
}
}

View file

@ -0,0 +1,19 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.proxy.Player;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import static net.kyori.adventure.text.Component.text;
@RequiredArgsConstructor
public class PrivMsgHandler {
private final ScrowMessageStyle style;
public void handle(Player player, Player target, String msg) {
target.sendMessage(style.ok(text(target.getUsername() + " -> You: ").append(Component.text(msg))));
player.sendMessage(style.ok(text("You -> " + target.getUsername() + ": ").append(Component.text(msg))));
}
}

View file

@ -0,0 +1,52 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.SyncCommandExecutor;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import lombok.Getter;
public class ReplyCommand implements SyncCommandExecutor<CommandSource> {
@Getter
private final RootLiteral<CommandSource> rootLiteral;
private final CommandArgument<CommandSource, String> msgArg;
private final LastTargetCache cache;
private final ProxyServer server;
private final PrivMsgHandler privMsgHandler;
public ReplyCommand(ProxyServer server, LastTargetCache cache, PrivMsgHandler privMsgHandler, MessageStyle style) {
this.cache = cache;
this.server = server;
this.privMsgHandler = privMsgHandler;
msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true));
rootLiteral = Literal.rootLiteral(style, "reply", "r");
rootLiteral.setExecutor(this);
rootLiteral.addArgument(msgArg);
}
@Override
public Result<Object, String> executeSync(CommandContext<CommandSource> ctx) {
var sender = (Player) ctx.getSender();
var targetId = cache.getLastTarget(sender.getUniqueId());
if (targetId == null)
return Results.failure("You didn't messaged anyone recently. Use /msg first");
var target = server.getPlayer(targetId).orElse(null);
var msg = ctx.getArg(msgArg);
if (target == null)
return Results.failure("The player you last messaged is no longer online.");
privMsgHandler.handle(sender, target, msg);
return Results.success(new Object());
}
}