going to make new module

This commit is contained in:
kento2 2026-06-02 22:45:40 +02:00
parent bdbd079a14
commit e85228e717
12 changed files with 16 additions and 30 deletions

View file

@ -0,0 +1,29 @@
plugins {
id("java")
id("com.gradleup.shadow") version "9.2.0"
}
group = "de.kentoj.scrow"
version = "0.1"
repositories {
mavenCentral()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
}
dependencies {
// https://docs.papermc.io/velocity/dev/creating-your-first-plugin/
annotationProcessor("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
compileOnly("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
// https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine
implementation("com.github.ben-manes.caffeine:caffeine:3.2.4")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-velocity
implementation("de.kentoj.scrow:kencommandapi-velocity")
implementation(project(":core-lib"))
}

View file

@ -0,0 +1,64 @@
package de.kentoj.scrow.corevelocity;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.scrow.corevelocity.command.OnlineCommand;
import de.kentoj.scrow.corevelocity.privmsg.LastTargetCache;
import de.kentoj.scrow.corevelocity.privmsg.MsgCommand;
import de.kentoj.scrow.corevelocity.privmsg.ReplyCommand;
import de.kentoj.scrowlib.servermanager.ServerManager;
import de.kentoj.scrowlib.servermanager.ServerManagerImpl;
import io.nats.client.Connection;
import io.nats.client.Nats;
import io.nats.client.Options;
import lombok.extern.java.Log;
import java.io.IOException;
@Plugin(
id = "corevelocity",
name = "CoreVelocity",
version = "0.1"
)
@Log
public class CoreVelocity {
private final ProxyServer server;
private final Connection nats;
private final ServerManager serverManager;
private final ServerRegisterWatchdog registerWatchdog;
private final SendPlayerWatchdog sendPlayerWatchdog;
@Inject
public CoreVelocity(ProxyServer server) {
this.server = server;
var cmds = server.getCommandManager();
{
cmds.register(OnlineCommand.commandMeta(cmds), new OnlineCommand(server));
}
{
var lastTargetCache = new LastTargetCache();
cmds.register(MsgCommand.commandMeta(cmds), new MsgCommand(lastTargetCache, server));
cmds.register(ReplyCommand.commandMeta(cmds), new ReplyCommand(lastTargetCache, server));
}
try {
var natsHost = System.getenv("HOST_NATS");
nats = Nats.connect(Options.builder()
.pedantic()
.server(natsHost)
.build());
serverManager = new ServerManagerImpl(nats);
registerWatchdog = new ServerRegisterWatchdog(nats, serverManager, server);
sendPlayerWatchdog = new SendPlayerWatchdog(nats, server);
log.info("listening on " + natsHost + " for server registrations");
registerWatchdog.listen();
sendPlayerWatchdog.listen();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,49 @@
package de.kentoj.scrow.corevelocity;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.kentoj.scrowlib.messaging.DocumentRepository;
import de.kentoj.scrowlib.messaging.NatsRepository;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import de.kentoj.scrowlib.messaging.respose.SafeSuccessResult;
import io.nats.client.Connection;
import org.bson.Document;
import org.checkerframework.checker.optional.qual.Present;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import java.util.UUID;
public class SendPlayerWatchdog {
private final NatsRepository natsRepo;
private final ProxyServer server;
public SendPlayerWatchdog(Connection nats, ProxyServer server) {
this.natsRepo = new NatsRepository(nats);
this.server = server;
}
public void listen() {
natsRepo.subscribe("proxy.send-player")
.flatMap(msg ->
handleSendPlayer(DocumentRepository.fromMessage(msg))
.flatMap(result -> natsRepo.publishSafeResult(msg.getReplyTo(), SafeResult.wrap(result)))
.onErrorResume(err -> natsRepo.publishSafeResult(msg.getReplyTo(), SafeResult.wrap(err)))
)
.subscribe();
}
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('_', ' ')))
);
}
}

View file

@ -0,0 +1,56 @@
package de.kentoj.scrow.corevelocity;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.kentoj.scrowlib.messaging.DocumentRepository;
import de.kentoj.scrowlib.messaging.NatsRepository;
import de.kentoj.scrowlib.servermanager.ServerManager;
import io.nats.client.Connection;
import lombok.extern.java.Log;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
@Log
public class ServerRegisterWatchdog {
private final NatsRepository natsRepo;
private final ServerManager serverManager;
private final ProxyServer server;
public ServerRegisterWatchdog(Connection nats, ServerManager serverManager, ProxyServer server) {
this.natsRepo = new NatsRepository(nats);
this.serverManager = serverManager;
this.server = server;
}
public void listen() {
natsRepo.subscribe("crown.set-state")
.map(DocumentRepository::fromMessage)
.concatMap(doc -> {
var state = doc.getString("state");
var handle = doc.getString("handle");
return handleStateChange(state, handle);
})
.subscribe(null, e -> log.throwing(getClass().getName(), "listen", e));
}
private @NotNull Mono<@NotNull Void> handleStateChange(String state, String handle) {
return switch (state) {
case "UP" -> serverManager.getInfo(handle)
.doOnNext(instance -> {
log.info("registering " + handle);
server.registerServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())));
})
.then();
case "DOWN" -> serverManager.getInfo(handle)
.doOnNext(instance -> {
log.info("unregistering " + handle);
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())));
})
.then();
default -> Mono.empty();
};
}
}

View file

@ -0,0 +1,27 @@
package de.kentoj.scrow.corevelocity.command;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
@RequiredArgsConstructor
public class OnlineCommand implements SimpleCommand {
private final ProxyServer server;
@Override
public void execute(Invocation invocation) {
int cnt = server.getPlayerCount();
var msg = cnt != 1
? "There are currently " + cnt + " players online"
: "There is currently 1 player online";
invocation.source().sendMessage(Component.text(msg));
}
public static CommandMeta commandMeta(CommandManager cmds) {
return cmds.metaBuilder("online").build();
}
}

View file

@ -0,0 +1,24 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
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, UUID> cache = Caffeine.newBuilder()
.expireAfterWrite(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,59 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Arrays;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MsgCommand implements SimpleCommand {
private final LastTargetCache cache;
private final ProxyServer server;
@Override
public void execute(Invocation invocation) {
if (!(invocation.source() instanceof Player player)) return;
var targetName = invocation.arguments()[0];
if (invocation.arguments().length < 2) {
player.sendMessage(Component.text("usage: ./msg <name> <msg>"));
return;
NamedTextColor
}
var target = server.getPlayer(targetName).orElse(null);
if (target == null) {
player.sendMessage(Component.text("The player you last messaged is now offline."));
return;
}
if (target.getUniqueId().equals(player.getUniqueId())) {
player.sendMessage(Component.text("You can't message yourself."));
return;
}
var msg = Arrays.stream(invocation.arguments())
.skip(1)
.collect(Collectors.joining(" "));
cache.setLastTarget(player.getUniqueId(), target.getUniqueId());
PrivmsgHelper.handle(player, target, msg);
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("corevelocity.cmd.msg");
}
public static CommandMeta commandMeta(CommandManager cmds) {
return cmds.metaBuilder("msg").aliases("w").build();
}
}

View file

@ -0,0 +1,15 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.proxy.Player;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
@NoArgsConstructor(access = AccessLevel.NONE)
public class PrivmsgHelper {
public static void handle(Player player, Player target, String msg) {
target.sendMessage(Component.text("[PRIVMSG] " + player.getUsername() + " -> You: " + msg));
player.sendMessage(Component.text("[PRIVMSG] " + "you -> " + player.getUsername() + ": " + msg));
}
}

View file

@ -0,0 +1,44 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
@RequiredArgsConstructor
public class ReplyCommand implements RawCommand {
private final LastTargetCache cache;
private final ProxyServer server;
@Override
public void execute(Invocation invocation) {
if (!(invocation.source() instanceof Player player)) return;
var targetId = cache.getLastTarget(player.getUniqueId());
if (targetId == null) {
player.sendMessage(Component.text("You haven't msg'd anyone recently. Use '/msg <name>' first."));
return;
}
var target = server.getPlayer(targetId).orElse(null);
if (target == null) {
player.sendMessage(Component.text("The player you last messaged is now offline."));
return;
}
cache.setLastTarget(player.getUniqueId(), target.getUniqueId());
PrivmsgHelper.handle(player, target, invocation.arguments());
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("corevelocity.cmd.msg");
}
public static CommandMeta commandMeta(CommandManager cmds) {
return cmds.metaBuilder("reply").aliases("r").build();
}
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.proxy.ProxyServer;
public class ReplyKenCommand {
public ReplyKenCommand(ProxyServer server) {
}
}