feat: NickCore v1.0.0 - enterprise nickname plugin for Paper/Folia/Velocity
This commit is contained in:
22
nickcore-velocity/build.gradle.kts
Normal file
22
nickcore-velocity/build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val velocityApiVersion: String by project
|
||||
|
||||
implementation(project(":nickcore-common"))
|
||||
|
||||
compileOnly("com.velocitypowered:velocity-api:$velocityApiVersion")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:$velocityApiVersion")
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveClassifier.set("")
|
||||
archiveBaseName.set("NickCore-Velocity")
|
||||
minimize()
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn(tasks.shadowJar)
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.nickcore.velocity;
|
||||
|
||||
import com.nickcore.common.dto.NickSyncMessage;
|
||||
import com.nickcore.common.model.NickProfile;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* NickCore Velocity proxy plugin.
|
||||
* Maintains a cross-server nick profile cache and routes sync messages.
|
||||
*/
|
||||
@Plugin(id = "nickcore", name = "NickCore", version = "1.0.0",
|
||||
authors = {"NickCore Team"}, description = "Enterprise nickname system - Velocity module")
|
||||
public final class NickCoreVelocity {
|
||||
|
||||
private static final MinecraftChannelIdentifier CHANNEL =
|
||||
MinecraftChannelIdentifier.from(NickSyncMessage.CHANNEL);
|
||||
|
||||
private final ProxyServer proxy;
|
||||
private final Logger logger;
|
||||
|
||||
/** Cross-server nick profile cache. */
|
||||
private final ConcurrentHashMap<UUID, NickProfile> proxyCache = new ConcurrentHashMap<>(512);
|
||||
|
||||
@Inject
|
||||
public NickCoreVelocity(ProxyServer proxy, Logger logger) {
|
||||
this.proxy = proxy;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInit(ProxyInitializeEvent event) {
|
||||
proxy.getChannelRegistrar().register(CHANNEL);
|
||||
logger.info("NickCore Velocity module initialized");
|
||||
logger.info("Sync channel registered: " + NickSyncMessage.CHANNEL);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyShutdown(ProxyShutdownEvent event) {
|
||||
proxyCache.clear();
|
||||
proxy.getChannelRegistrar().unregister(CHANNEL);
|
||||
logger.info("NickCore Velocity module disabled");
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(ServerConnectedEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
NickProfile cached = proxyCache.get(player.getUniqueId());
|
||||
|
||||
if (cached != null && cached.nicked()) {
|
||||
// Forward profile to the new server
|
||||
NickSyncMessage msg = NickSyncMessage.fullSync(cached, "velocity-proxy");
|
||||
event.getServer().sendPluginMessage(CHANNEL, msg.encode());
|
||||
logger.info("Forwarded nick profile for " + player.getUsername() +
|
||||
" to " + event.getServer().getServerInfo().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
proxyCache.remove(uuid);
|
||||
|
||||
// Broadcast cache invalidation to all servers
|
||||
NickSyncMessage invalidate = NickSyncMessage.cacheInvalidate(uuid, "velocity-proxy");
|
||||
broadcastToAllServers(invalidate);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginMessage(PluginMessageEvent event) {
|
||||
if (!CHANNEL.equals(event.getIdentifier())) return;
|
||||
|
||||
// Consume the message so it doesn't get forwarded raw
|
||||
event.setResult(PluginMessageEvent.ForwardResult.handled());
|
||||
|
||||
try {
|
||||
NickSyncMessage message = NickSyncMessage.decode(event.getData());
|
||||
|
||||
// Update proxy cache
|
||||
switch (message.action()) {
|
||||
case FULL_SYNC -> {
|
||||
NickProfile profile = new NickProfile(
|
||||
message.playerUuid(), message.nickname(), message.rankId(),
|
||||
message.skinId(), message.skinValue(), message.skinSignature(),
|
||||
message.nickname() != null, null, null
|
||||
);
|
||||
proxyCache.put(message.playerUuid(), profile);
|
||||
}
|
||||
case NICK_UPDATE -> proxyCache.computeIfPresent(message.playerUuid(),
|
||||
(uuid, p) -> p.withNickname(message.nickname()));
|
||||
case RANK_UPDATE -> proxyCache.computeIfPresent(message.playerUuid(),
|
||||
(uuid, p) -> p.withRank(message.rankId()));
|
||||
case RESET -> proxyCache.computeIfPresent(message.playerUuid(),
|
||||
(uuid, p) -> p.reset());
|
||||
case CACHE_INVALIDATE -> proxyCache.remove(message.playerUuid());
|
||||
default -> { /* ignore */ }
|
||||
}
|
||||
|
||||
// Forward to all OTHER servers
|
||||
broadcastToOtherServers(message, event);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to process sync message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastToAllServers(NickSyncMessage message) {
|
||||
byte[] data = message.encode();
|
||||
for (var server : proxy.getAllServers()) {
|
||||
if (!server.getPlayersConnected().isEmpty()) {
|
||||
server.sendPluginMessage(CHANNEL, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastToOtherServers(NickSyncMessage message, PluginMessageEvent sourceEvent) {
|
||||
byte[] data = message.encode();
|
||||
ServerConnection sourceConn = sourceEvent.getSource() instanceof ServerConnection sc ? sc : null;
|
||||
|
||||
for (var server : proxy.getAllServers()) {
|
||||
// Skip the source server
|
||||
if (sourceConn != null &&
|
||||
server.getServerInfo().equals(sourceConn.getServerInfo())) continue;
|
||||
|
||||
if (!server.getPlayersConnected().isEmpty()) {
|
||||
server.sendPluginMessage(CHANNEL, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proxy-level cache for external access.
|
||||
*/
|
||||
public Map<UUID, NickProfile> getProxyCache() {
|
||||
return Map.copyOf(proxyCache);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user