diff --git a/Citizens-2.0.41-b4138.jar b/Citizens-2.0.41-b4138.jar deleted file mode 100644 index 29ab357..0000000 Binary files a/Citizens-2.0.41-b4138.jar and /dev/null differ diff --git a/Freesia-Backend/build.gradle.kts b/Freesia-Backend/build.gradle.kts index fb3d0f4..d9cfa6b 100644 --- a/Freesia-Backend/build.gradle.kts +++ b/Freesia-Backend/build.gradle.kts @@ -5,9 +5,6 @@ repositories { dependencies { compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") compileOnly("io.netty:netty-all:4.1.118.Final") - compileOnly("net.citizensnpcs:citizens-main:2.0.41-SNAPSHOT") { - exclude(group = "*", module = "*") - } } tasks.build { diff --git a/Freesia-Backend/src/main/java/com/nguyendevs/freesia/backend/FreesiaBackend.java b/Freesia-Backend/src/main/java/com/nguyendevs/freesia/backend/FreesiaBackend.java index 219fe53..b671943 100644 --- a/Freesia-Backend/src/main/java/com/nguyendevs/freesia/backend/FreesiaBackend.java +++ b/Freesia-Backend/src/main/java/com/nguyendevs/freesia/backend/FreesiaBackend.java @@ -24,11 +24,10 @@ public void onEnable() { Bukkit.getMessenger().registerIncomingPluginChannel(this, VirtualPlayerManager.CHANNEL_NAME, this.virtualPlayerManager); Bukkit.getMessenger().registerOutgoingPluginChannel(this, VirtualPlayerManager.CHANNEL_NAME); - Bukkit.getPluginManager().registerEvents(this.trackerProcessor, this); - Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', - "&5[&dFreesia&5] &aFreesia Backend plugin enabled successfully!")); + Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', + "&5[&dFreesia-Backend&5] &aFreesia Backend plugin enabled successfully!")); } public VirtualPlayerManager getVirtualPlayerManager() { @@ -38,6 +37,6 @@ public VirtualPlayerManager getVirtualPlayerManager() { @Override public void onDisable() { Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', - "&5[&dFreesia&5] &cFreesia Backend plugin disabled!")); + "&5[&dFreesia-Backend&5] &cFreesia Backend plugin disabled!")); } } diff --git a/Freesia-Backend/src/main/resources/plugin.yml b/Freesia-Backend/src/main/resources/plugin.yml index eb28a59..f04d6e9 100644 --- a/Freesia-Backend/src/main/resources/plugin.yml +++ b/Freesia-Backend/src/main/resources/plugin.yml @@ -3,4 +3,3 @@ version: '1.0.3-YSM-2.6.4' main: com.nguyendevs.freesia.backend.FreesiaBackend api-version: '1.16' folia-supported: true -softdepend: [Citizens] diff --git a/Freesia-NPC/build.gradle.kts b/Freesia-Citizens/build.gradle.kts similarity index 90% rename from Freesia-NPC/build.gradle.kts rename to Freesia-Citizens/build.gradle.kts index cf700fd..f98d431 100644 --- a/Freesia-NPC/build.gradle.kts +++ b/Freesia-Citizens/build.gradle.kts @@ -25,5 +25,5 @@ java { tasks.shadowJar { archiveClassifier.set("") - archiveFileName.set("Freesia-NPC-${project.version}.jar") + archiveFileName.set("Freesia-Citizens-${project.version}.jar") } diff --git a/Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/FreesiaNPCPlugin.java b/Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/FreesiaCitizensPlugin.java similarity index 63% rename from Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/FreesiaNPCPlugin.java rename to Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/FreesiaCitizensPlugin.java index 5d23a4d..58a7d59 100644 --- a/Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/FreesiaNPCPlugin.java +++ b/Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/FreesiaCitizensPlugin.java @@ -1,14 +1,14 @@ -package com.nguyendevs.freesia.npc; +package com.nguyendevs.freesia.citizens; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; -public class FreesiaNPCPlugin extends JavaPlugin { +public class FreesiaCitizensPlugin extends JavaPlugin { public static final String CHANNEL_NAME = "freesia:npc"; - public static FreesiaNPCPlugin INSTANCE; + public static FreesiaCitizensPlugin INSTANCE; @Override public void onEnable() { @@ -19,14 +19,17 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new TrackerListener(), this); Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', - "&5[&dFreesia-NPC&5] &a Enabled successfully. Hooked Citizens for proxy model injection!")); + "&6[&eFreesia-Citizens&6] &aFreesia Citizens plugin enabled successfully.")); + Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', + "&6[&eFreesia-Citizens&6] &aHooked Citizens for proxy model injection!")); } @Override public void onDisable() { Bukkit.getMessenger().unregisterIncomingPluginChannel(this, CHANNEL_NAME); Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, CHANNEL_NAME); - getLogger().info(ChatColor.RED + "[Freesia-NPC] Disabled!"); + Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', + "&6[&eFreesia-Citizens&6] &aFreesia Citizens plugin disabled!")); } public void sendProxyPayload(Player player, byte[] payload) { diff --git a/Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/TrackerListener.java b/Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/TrackerListener.java new file mode 100644 index 0000000..3ca4918 --- /dev/null +++ b/Freesia-Citizens/src/main/java/com/nguyendevs/freesia/citizens/TrackerListener.java @@ -0,0 +1,114 @@ +package com.nguyendevs.freesia.citizens; + +import io.papermc.paper.event.player.PlayerTrackEntityEvent; +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.npc.NPC; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.util.Collection; + +public class TrackerListener implements Listener, PluginMessageListener { + + private static final byte OP_TRACK_SYNC = 0; + private static final byte OP_REQ_LIST = 1; + private static final byte OP_RES_LIST = 2; + private static final byte OP_UNTRACK_SYNC = 3; + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + sendNpcList(event.getPlayer()); + } + + @EventHandler + public void onUntrackEntity(io.papermc.paper.event.player.PlayerUntrackEntityEvent event) { + final Entity beingWatched = event.getEntity(); + final Player watcher = event.getPlayer(); + + if (beingWatched.hasMetadata("NPC")) { + NPC npc = CitizensAPI.getNPCRegistry().getNPC(beingWatched); + if (npc != null) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(OP_UNTRACK_SYNC); + dos.writeLong(watcher.getUniqueId().getMostSignificantBits()); + dos.writeLong(watcher.getUniqueId().getLeastSignificantBits()); + dos.writeInt(npc.getId()); + dos.flush(); + + FreesiaCitizensPlugin.INSTANCE.sendProxyPayload(watcher, bos.toByteArray()); + } catch (Exception e) { + FreesiaCitizensPlugin.INSTANCE.getLogger().warning("Failed to send untrack sync: " + e.getMessage()); + } + } + } + } + + @EventHandler + public void onTrackEntity(PlayerTrackEntityEvent event) { + final Entity beingWatched = event.getEntity(); + final Player watcher = event.getPlayer(); + + if (beingWatched.hasMetadata("NPC")) { + NPC npc = CitizensAPI.getNPCRegistry().getNPC(beingWatched); + if (npc != null) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(OP_TRACK_SYNC); + dos.writeLong(watcher.getUniqueId().getMostSignificantBits()); + dos.writeLong(watcher.getUniqueId().getLeastSignificantBits()); + dos.writeInt(npc.getId()); + dos.writeInt(beingWatched.getEntityId()); + dos.flush(); + + FreesiaCitizensPlugin.INSTANCE.sendProxyPayload(watcher, bos.toByteArray()); + } catch (Exception e) { + FreesiaCitizensPlugin.INSTANCE.getLogger().warning("Failed to send track sync: " + e.getMessage()); + } + } + } + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message) { + if (!channel.equals(FreesiaCitizensPlugin.CHANNEL_NAME)) return; + + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) { + byte opcode = in.readByte(); + if (opcode == OP_REQ_LIST) { + sendNpcList(player); + } + } catch (Exception e) { + FreesiaCitizensPlugin.INSTANCE.getLogger().warning("Failed to parse plugin messaging: " + e.getMessage()); + } + } + + private void sendNpcList(Player player) { + try { + Collection npcs = (Collection) CitizensAPI.getNPCRegistry().sorted(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(OP_RES_LIST); + dos.writeInt(npcs.size()); + for (NPC npc : npcs) { + dos.writeInt(npc.getId()); + dos.writeUTF(npc.getName()); + } + dos.flush(); + FreesiaCitizensPlugin.INSTANCE.sendProxyPayload(player, bos.toByteArray()); + } catch (Exception e) { + FreesiaCitizensPlugin.INSTANCE.getLogger().warning("Failed to send NPC list: " + e.getMessage()); + } + } +} diff --git a/Freesia-NPC/src/main/resources/plugin.yml b/Freesia-Citizens/src/main/resources/plugin.yml similarity index 69% rename from Freesia-NPC/src/main/resources/plugin.yml rename to Freesia-Citizens/src/main/resources/plugin.yml index 017e8ec..76d45d6 100644 --- a/Freesia-NPC/src/main/resources/plugin.yml +++ b/Freesia-Citizens/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -name: Freesia-NPC +name: Freesia-Citizens version: 1.0.0 -main: com.nguyendevs.freesia.npc.FreesiaNPCPlugin +main: com.nguyendevs.freesia.citizens.FreesiaCitizensPlugin api-version: '1.20' depend: [Citizens] description: Freesia expansion for mapping Citizens NPCs to YesSteveModel environments via Velocity/Waterfall arrays. diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/BuiltinMessageRegitres.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/BuiltinMessageRegitres.java index 4facde0..9b2a2ef 100644 --- a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/BuiltinMessageRegitres.java +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/BuiltinMessageRegitres.java @@ -7,6 +7,7 @@ import com.nguyendevs.freesia.common.communicating.message.w2m.W2MPlayerDataGetRequestMessage; import com.nguyendevs.freesia.common.communicating.message.w2m.W2MUpdatePlayerDataRequestMessage; import com.nguyendevs.freesia.common.communicating.message.w2m.W2MWorkerInfoMessage; +import com.nguyendevs.freesia.common.communicating.message.w2m.W2MDispatchCommandRequestMessage; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,6 +26,7 @@ public class BuiltinMessageRegitres { registerMessage(W2MCommandResultMessage.class, W2MCommandResultMessage::new); registerMessage(M2WDispatchCommandMessage.class, M2WDispatchCommandMessage::new); registerMessage(W2MWorkerInfoMessage.class, W2MWorkerInfoMessage::new); + registerMessage(W2MDispatchCommandRequestMessage.class, W2MDispatchCommandRequestMessage::new); } public static void registerMessage(Class> clazz, Supplier> creator) { diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/handler/NettyServerChannelHandlerLayer.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/handler/NettyServerChannelHandlerLayer.java index 13d5bc0..25022ae 100644 --- a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/handler/NettyServerChannelHandlerLayer.java +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/handler/NettyServerChannelHandlerLayer.java @@ -72,5 +72,7 @@ public void sendMessage(IMessage packet) { public abstract void onCommandDispatchResult(int traceId, @Nullable String result); public abstract void updateWorkerInfo(UUID workerUUID, String workerName); + + public abstract void onCommandFromWorker(String command); } diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/message/w2m/W2MDispatchCommandRequestMessage.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/message/w2m/W2MDispatchCommandRequestMessage.java new file mode 100644 index 0000000..293bf5d --- /dev/null +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/communicating/message/w2m/W2MDispatchCommandRequestMessage.java @@ -0,0 +1,34 @@ +package com.nguyendevs.freesia.common.communicating.message.w2m; + +import io.netty.buffer.ByteBuf; +import com.nguyendevs.freesia.common.communicating.handler.NettyServerChannelHandlerLayer; +import com.nguyendevs.freesia.common.communicating.message.IMessage; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; + +public class W2MDispatchCommandRequestMessage implements IMessage { + private String command; + + public W2MDispatchCommandRequestMessage() { + } + + public W2MDispatchCommandRequestMessage(String command) { + this.command = command; + } + + @Override + public void writeMessageData(@NotNull ByteBuf buffer) { + buffer.writeCharSequence(this.command, StandardCharsets.UTF_8); + } + + @Override + public void readMessageData(@NotNull ByteBuf buffer) { + this.command = buffer.readCharSequence(buffer.readableBytes(), StandardCharsets.UTF_8).toString(); + } + + @Override + public void process(@NotNull NettyServerChannelHandlerLayer handler) { + handler.onCommandFromWorker(this.command); + } +} diff --git a/Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/TrackerListener.java b/Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/TrackerListener.java deleted file mode 100644 index af9dfa5..0000000 --- a/Freesia-NPC/src/main/java/com/nguyendevs/freesia/npc/TrackerListener.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.nguyendevs.freesia.npc; - -import io.papermc.paper.event.player.PlayerTrackEntityEvent; -import net.citizensnpcs.api.CitizensAPI; -import net.citizensnpcs.api.npc.NPC; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.plugin.messaging.PluginMessageListener; -import org.jetbrains.annotations.NotNull; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.util.Collection; - -public class TrackerListener implements Listener, PluginMessageListener { - - private static final byte OP_TRACK_SYNC = 0; - private static final byte OP_REQ_LIST = 1; - private static final byte OP_RES_LIST = 2; - - @EventHandler - public void onTrackEntity(PlayerTrackEntityEvent event) { - final Entity beingWatched = event.getEntity(); - final Player watcher = event.getPlayer(); - - if (beingWatched.hasMetadata("NPC")) { - NPC npc = CitizensAPI.getNPCRegistry().getNPC(beingWatched); - if (npc != null) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(OP_TRACK_SYNC); - dos.writeLong(watcher.getUniqueId().getMostSignificantBits()); - dos.writeLong(watcher.getUniqueId().getLeastSignificantBits()); - dos.writeInt(npc.getId()); - dos.writeInt(beingWatched.getEntityId()); - dos.flush(); - - FreesiaNPCPlugin.INSTANCE.sendProxyPayload(watcher, bos.toByteArray()); - } catch (Exception e) { - FreesiaNPCPlugin.INSTANCE.getLogger().warning("Failed to send track sync: " + e.getMessage()); - } - } - } - } - - @Override - public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message) { - if (!channel.equals(FreesiaNPCPlugin.CHANNEL_NAME)) return; - - try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) { - byte opcode = in.readByte(); - if (opcode == OP_REQ_LIST) { - Collection npcs = (Collection) CitizensAPI.getNPCRegistry().sorted(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(OP_RES_LIST); - dos.writeInt(npcs.size()); - for (NPC npc : npcs) { - dos.writeInt(npc.getId()); - dos.writeUTF(npc.getName()); - } - dos.flush(); - FreesiaNPCPlugin.INSTANCE.sendProxyPayload(player, bos.toByteArray()); - } - } catch (Exception e) { - FreesiaNPCPlugin.INSTANCE.getLogger().warning("Failed to parse plugin messaging: " + e.getMessage()); - } - } -} diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java index f7c49ef..bd2b321 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java @@ -58,7 +58,7 @@ public class Freesia implements PacketListener { public static YsmClientKickingDetector kickChecker; public static YsmMapperPayloadManager mapperManager; public static NettySocketServer masterServer; - public static com.nguyendevs.freesia.velocity.network.misc.NpcMessageReceiver npcMessageReceiver; + public static com.nguyendevs.freesia.velocity.network.misc.CitizensMessageReceiver citizensMessageReceiver; @Inject private Logger logger; @@ -92,7 +92,7 @@ public void onProxyStart(ProxyInitializeEvent event) { LOGGER = this.logger; PROXY_SERVER = this.proxyServer; - EntryPoint.initLogger(this.logger); // Common module + EntryPoint.initLogger(this.logger); printLogo(); @@ -115,8 +115,8 @@ public void onProxyStart(ProxyInitializeEvent event) { virtualPlayerManager.init(); - npcMessageReceiver = new com.nguyendevs.freesia.velocity.network.misc.NpcMessageReceiver(); - this.proxyServer.getEventManager().register(this, npcMessageReceiver); + citizensMessageReceiver = new com.nguyendevs.freesia.velocity.network.misc.CitizensMessageReceiver(); + this.proxyServer.getEventManager().register(this, citizensMessageReceiver); io.netty.handler.ssl.SslContext sslContext = null; try { diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaConstants.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaConstants.java index 8f0efbd..d7a175b 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaConstants.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaConstants.java @@ -24,7 +24,8 @@ public static final class FileConstants { public static final class PermissionConstants { public static final String LIST_PLAYER_COMMAND = "freesia.commands.listysmplayers", DISPATCH_WORKER_COMMAND = "freesia.commands.dworkerc", - SET_SKIN_COMMAND = "freesia.commands.setskin"; + SET_SKIN_COMMAND = "freesia.commands.setskin", + RELOAD_COMMAND = "freesia.commands.reload"; } public static final class LanguageConstants { @@ -40,7 +41,18 @@ public static final class LanguageConstants { HANDSHAKE_TIMED_OUT = "freesia.mod_handshake_time_outed", WORKER_TERMINATED_CONNECTION = "freesia.backend.disconnected", - WORKER_NOT_CONNECTED = "freesia.backend.not_connected"; + WORKER_NOT_CONNECTED = "freesia.backend.not_connected", + + COMMAND_NO_PERMISSION = "freesia.command.no_permission", + COMMAND_PROVIDE_SERVER_NAME = "freesia.command.provide_server_name", + COMMAND_RELOAD_SUCCESS = "freesia.command.reload_success", + COMMAND_RELOAD_FAIL = "freesia.command.reload_fail", + COMMAND_CITIZENS_ID_INTEGER = "freesia.command.citizens_id_integer", + COMMAND_USAGE_HEADER = "freesia.command.usage.header", + COMMAND_USAGE_LISTPLAYERS = "freesia.command.usage.listplayers", + COMMAND_USAGE_DWORKERC = "freesia.command.usage.dworkerc", + COMMAND_USAGE_SETSKIN = "freesia.command.usage.setskin", + COMMAND_USAGE_RELOAD = "freesia.command.usage.reload"; } diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/command/FreesiaCommand.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/command/FreesiaCommand.java index b5eab42..f2b1db7 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/command/FreesiaCommand.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/command/FreesiaCommand.java @@ -100,17 +100,27 @@ public static void register() { })))) .then(BrigadierCommand.literalArgumentBuilder("setskin") - .requires(source -> source.hasPermission(FreesiaConstants.PermissionConstants.SET_SKIN_COMMAND)) + .requires(source -> { + if (!source.hasPermission(FreesiaConstants.PermissionConstants.SET_SKIN_COMMAND)) return false; + if (!(source instanceof Player player)) return true; // Console always sees it + return player.getCurrentServer().map(s -> Freesia.citizensMessageReceiver.isSupported(s.getServerInfo().getName())).orElse(false); + }) .then(BrigadierCommand.requiredArgumentBuilder("npcId", IntegerArgumentType.integer(0)) .suggests((ctx, builder) -> { - for (Map.Entry entry : Freesia.npcMessageReceiver.getCachedNpcNames().entrySet()) { - builder.suggest(String.valueOf(entry.getKey())); + String serverName = null; + if (ctx.getSource() instanceof Player player) { + serverName = player.getCurrentServer().map(s -> s.getServerInfo().getName()).orElse(null); + } + if (serverName != null) { + for (Map.Entry entry : Freesia.citizensMessageReceiver.getCachedNpcNames(serverName).entrySet()) { + builder.suggest(String.valueOf(entry.getKey())); + } } return builder.buildFuture(); }) .then(BrigadierCommand.requiredArgumentBuilder("modelId", StringArgumentType.word()) .suggests((ctx, builder) -> { - for (String model : Freesia.mapperManager.getNpcModelBinaryCache().keySet()) { + for (String model : Freesia.mapperManager.getcitizensModelBinaryCache().keySet()) { if (model.toLowerCase().endsWith(".ysm")) { builder.suggest(model.substring(0, model.length() - 4)); } else { @@ -124,15 +134,77 @@ public static void register() { final int npcId = IntegerArgumentType.getInteger(context, "npcId"); final String modelId = StringArgumentType.getString(context, "modelId"); - Freesia.mapperManager.npcPersistenceManager.saveAssignment(npcId, java.util.UUID.randomUUID(), modelId); + String serverName = null; + if (source instanceof Player player) { + serverName = player.getCurrentServer().map(s -> s.getServerInfo().getName()).orElse(null); + } + + if (serverName == null) { + source.sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_PROVIDE_SERVER_NAME, List.of(), List.of())); + return -1; + } + + Freesia.mapperManager.citizensPersistenceManager.saveAssignment(serverName, npcId, modelId); + Freesia.mapperManager.broadcastCitizensSkinUpdate(serverName, npcId); source.sendMessage(Freesia.languageManager.i18n( FreesiaConstants.LanguageConstants.SETSKIN_SUCCESS, - List.of("npc_id", "model_id"), + List.of("Citizens_id", "model_id"), List.of(String.valueOf(npcId), modelId))); return Command.SINGLE_SUCCESS; - })))) - + }) + .then(BrigadierCommand.requiredArgumentBuilder("serverId", StringArgumentType.word()) + .suggests((ctx, builder) -> { + for (com.velocitypowered.api.proxy.server.RegisteredServer server : Freesia.PROXY_SERVER.getAllServers()) { + builder.suggest(server.getServerInfo().getName()); + } + return builder.buildFuture(); + }) + .executes(context -> { + final CommandSource source = context.getSource(); + final int npcId = IntegerArgumentType.getInteger(context, "npcId"); + final String modelId = StringArgumentType.getString(context, "modelId"); + final String serverName = StringArgumentType.getString(context, "serverId"); + + Freesia.mapperManager.citizensPersistenceManager.saveAssignment(serverName, npcId, modelId); + Freesia.mapperManager.broadcastCitizensSkinUpdate(serverName, npcId); + + source.sendMessage(Freesia.languageManager.i18n( + FreesiaConstants.LanguageConstants.SETSKIN_SUCCESS, + List.of("Citizens_id", "model_id"), + List.of(String.valueOf(npcId), modelId))); + return Command.SINGLE_SUCCESS; + }))))) + .then(BrigadierCommand.literalArgumentBuilder("reload") + .requires(source -> source.hasPermission(FreesiaConstants.PermissionConstants.RELOAD_COMMAND)) + .executes(context -> { + final CommandSource source = context.getSource(); + try { + com.nguyendevs.freesia.velocity.FreesiaConfig.init(); + com.nguyendevs.freesia.velocity.FreesiaSecurityConfig.init(); + Freesia.languageManager.loadLanguageFile(com.nguyendevs.freesia.velocity.FreesiaConfig.languageName); + source.sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_RELOAD_SUCCESS, List.of(), List.of())); + } catch (Exception e) { + source.sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_RELOAD_FAIL, + List.of("error"), List.of(e.getMessage() != null ? e.getMessage() : "unknown"))); + Freesia.LOGGER.error("Failed to reload configurations!", e); + } + return Command.SINGLE_SUCCESS; + })) + .executes(context -> { + context.getSource().sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_HEADER, List.of(), List.of())); + context.getSource().sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_LISTPLAYERS, List.of(), List.of())); + context.getSource().sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_DWORKERC, List.of(), List.of())); + if (context.getSource() instanceof Player player) { + player.getCurrentServer().ifPresent(s -> { + if (Freesia.citizensMessageReceiver.isSupported(s.getServerInfo().getName())) { + context.getSource().sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_SETSKIN, List.of(), List.of())); + } + }); + } + context.getSource().sendMessage(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_RELOAD, List.of(), List.of())); + return Command.SINGLE_SUCCESS; + }) .build(); return new BrigadierCommand(root); diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/backend/MasterServerMessageHandler.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/backend/MasterServerMessageHandler.java index 18344fe..a19375e 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/backend/MasterServerMessageHandler.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/backend/MasterServerMessageHandler.java @@ -162,4 +162,9 @@ public void updateWorkerInfo(UUID workerUUID, String workerName) { Freesia.PROXY_SERVER.getEventManager().fire(new WorkerConnectedEvent(workerUUID, workerName)); } + + @Override + public void onCommandFromWorker(String command) { + Freesia.PROXY_SERVER.getCommandManager().executeAsync(Freesia.PROXY_SERVER.getConsoleCommandSource(), command); + } } diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/FreesiaPlayerTracker.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/FreesiaPlayerTracker.java index 1903a14..335c224 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/FreesiaPlayerTracker.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/FreesiaPlayerTracker.java @@ -139,7 +139,7 @@ public CompletableFuture> getCanSee(@NotNull UUID target) { final Consumer> expiredTask = this.pendingCanSeeTasks.remove(callbackId); if (expiredTask != null) { try { - expiredTask.accept(Collections.emptySet()); // Return empty set to unblock the caller + expiredTask.accept(Collections.emptySet()); } catch (Exception e) { Freesia.LOGGER.error("Failed to complete expired tracker callback", e); } diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/impl/StandardNbtRemapperImpl.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/impl/StandardNbtRemapperImpl.java index ba02a34..2d6862f 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/impl/StandardNbtRemapperImpl.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/mc/impl/StandardNbtRemapperImpl.java @@ -17,7 +17,7 @@ public class StandardNbtRemapperImpl implements NbtRemapper { @Override public boolean shouldRemap(int pid) { - return pid < FreesiaConstants.MCProtocolConstants.PROTOCOL_NUM_V1202; //1.20.2 + return pid < FreesiaConstants.MCProtocolConstants.PROTOCOL_NUM_V1202; } @Override @@ -25,7 +25,7 @@ public byte[] remapToMasterVer(NBTCompound nbt) throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(bos); - this.serializer.serializeTag(dos, nbt, true); //1.20.2 + this.serializer.serializeTag(dos, nbt, true); dos.flush(); return bos.toByteArray(); @@ -44,7 +44,7 @@ public byte[] remapToWorkerVer(NBTCompound nbt) throws IOException { @Override public NBTCompound readBound(FriendlyByteBuf data) throws IOException { - return (NBTCompound) this.serializer.deserializeTag(NBTLimiter.forBuffer(data, Integer.MAX_VALUE), new ByteBufInputStream(data), false); //1.21 + return (NBTCompound) this.serializer.deserializeTag(NBTLimiter.forBuffer(data, Integer.MAX_VALUE), new ByteBufInputStream(data), false); } } diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensMessageReceiver.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensMessageReceiver.java new file mode 100644 index 0000000..8f7bc82 --- /dev/null +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensMessageReceiver.java @@ -0,0 +1,73 @@ +package com.nguyendevs.freesia.velocity.network.misc; + +import com.nguyendevs.freesia.velocity.Freesia; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.util.HashMap; +import java.util.Map; + +public class CitizensMessageReceiver { + + public static final MinecraftChannelIdentifier CHANNEL = MinecraftChannelIdentifier.create("freesia", "npc"); + private final Map> serverNpcCaches = new java.util.concurrent.ConcurrentHashMap<>(); + + public CitizensMessageReceiver() { + Freesia.PROXY_SERVER.getChannelRegistrar().register(CHANNEL); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!event.getIdentifier().equals(CHANNEL)) return; + + if (!(event.getTarget() instanceof Player watcher)) return; + if (!(event.getSource() instanceof ServerConnection serverConn)) return; + + String serverName = serverConn.getServerInfo().getName(); + + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) { + byte opcode = in.readByte(); + + if (opcode == 0) { + in.readLong(); in.readLong(); + int npcId = in.readInt(); + int entityId = in.readInt(); + + Freesia.mapperManager.handleCitizensTrackSync(serverName, watcher, npcId, entityId); + } else if (opcode == 3) { + in.readLong(); in.readLong(); + int npcId = in.readInt(); + + Freesia.mapperManager.handleCitizensUntrackSync(serverName, watcher, npcId); + } else if (opcode == 2) { + int count = in.readInt(); + Map npcNames = new java.util.HashMap<>(); + for (int i = 0; i < count; i++) { + npcNames.put(in.readInt(), in.readUTF()); + } + serverNpcCaches.put(serverName, npcNames); + } + + // Mark server as supported if any opcode received + if (opcode == 0 || opcode == 2 || opcode == 3) { + if (!serverNpcCaches.containsKey(serverName)) { + serverNpcCaches.put(serverName, new java.util.HashMap<>()); + } + } + } catch (Exception e) { + Freesia.LOGGER.warn("[Citizens] Error reading proxy payload: " + e.getMessage()); + } + } + + public Map getCachedNpcNames(String serverName) { + return serverNpcCaches.getOrDefault(serverName, java.util.Collections.emptyMap()); + } + + public boolean isSupported(String serverName) { + return serverNpcCaches.containsKey(serverName); + } +} diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensPersistenceManager.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensPersistenceManager.java new file mode 100644 index 0000000..607d555 --- /dev/null +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/CitizensPersistenceManager.java @@ -0,0 +1,96 @@ +package com.nguyendevs.freesia.velocity.network.misc; + +import com.nguyendevs.freesia.velocity.Freesia; +import com.nguyendevs.freesia.velocity.FreesiaConstants; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CitizensPersistenceManager { + + private static final File ASSIGNMENTS_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "citizens_assignments.dat"); + private static final File MODEL_CACHE_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "citizens_model_cache.dat"); + + private final Map> byServerId = new ConcurrentHashMap<>(); + + public record CitizensEntry(int npcId, String modelId) {} + + public void load() { + byServerId.clear(); + if (!ASSIGNMENTS_FILE.exists()) return; + try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(ASSIGNMENTS_FILE)))) { + final int serverCount = in.readInt(); + for (int i = 0; i < serverCount; i++) { + String serverId = in.readUTF(); + int npcCount = in.readInt(); + Map serverMap = new ConcurrentHashMap<>(); + for (int j = 0; j < npcCount; j++) { + int npcId = in.readInt(); + String modelId = in.readUTF(); + serverMap.put(npcId, new CitizensEntry(npcId, modelId)); + } + byServerId.put(serverId, serverMap); + } + Freesia.LOGGER.info("[Citizens] Loaded assignments for {} servers", byServerId.size()); + } catch (Exception e) { + Freesia.LOGGER.warn("[Citizens] Failed to load citizens_assignments.dat: {}", e.getMessage()); + } + } + + public void saveAssignment(String serverId, int npcId, String modelId) { + byServerId.computeIfAbsent(serverId, k -> new ConcurrentHashMap<>()).put(npcId, new CitizensEntry(npcId, modelId)); + flush(); + } + + public Map> getServerIdAssignments() { return byServerId; } + + public Map loadModelBinaryCache() { + final Map cache = new HashMap<>(); + if (!MODEL_CACHE_FILE.exists()) return cache; + try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(MODEL_CACHE_FILE)))) { + final int count = in.readInt(); + for (int i = 0; i < count; i++) { + final String model = in.readUTF(); + final int len = in.readInt(); + final byte[] data = new byte[len]; + in.readFully(data); + cache.put(model, data); + } + Freesia.LOGGER.info("[Citizens] Loaded {} model binary entries", cache.size()); + } catch (Exception e) { + Freesia.LOGGER.warn("[Citizens] Failed to load citizens_model_cache.dat: {}", e.getMessage()); + } + return cache; + } + + public void saveModelBinaryCache(Map cache) { + try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(MODEL_CACHE_FILE)))) { + out.writeInt(cache.size()); + for (Map.Entry e : cache.entrySet()) { + out.writeUTF(e.getKey()); + out.writeInt(e.getValue().length); + out.write(e.getValue()); + } + } catch (Exception e) { + Freesia.LOGGER.warn("[Citizens] Failed to save citizens_model_cache.dat: {}", e.getMessage()); + } + } + + private void flush() { + try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(ASSIGNMENTS_FILE)))) { + out.writeInt(byServerId.size()); + for (Map.Entry> serverEntry : byServerId.entrySet()) { + out.writeUTF(serverEntry.getKey()); + out.writeInt(serverEntry.getValue().size()); + for (CitizensEntry e : serverEntry.getValue().values()) { + out.writeInt(e.npcId()); + out.writeUTF(e.modelId()); + } + } + } catch (Exception e) { + Freesia.LOGGER.warn("[Citizens] Failed to save citizens_assignments.dat: {}", e.getMessage()); + } + } +} diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcMessageReceiver.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcMessageReceiver.java deleted file mode 100644 index 8d42543..0000000 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcMessageReceiver.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.nguyendevs.freesia.velocity.network.misc; - -import com.nguyendevs.freesia.velocity.Freesia; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.util.HashMap; -import java.util.Map; - -public class NpcMessageReceiver { - - public static final MinecraftChannelIdentifier CHANNEL = MinecraftChannelIdentifier.create("freesia", "npc"); - private final Map cachedNpcNames = new HashMap<>(); - - public NpcMessageReceiver() { - Freesia.PROXY_SERVER.getChannelRegistrar().register(CHANNEL); - } - - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!event.getIdentifier().equals(CHANNEL)) return; - - if (!(event.getTarget() instanceof Player watcher)) return; - - try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) { - byte opcode = in.readByte(); - - if (opcode == 0) { // OP_TRACK_SYNC - in.readLong(); in.readLong(); // discard UUID, we only care about player which we already have - int npcId = in.readInt(); - int entityId = in.readInt(); - - Freesia.mapperManager.handleNpcTrackSync(watcher, npcId, entityId); - } else if (opcode == 2) { // OP_RES_LIST - int count = in.readInt(); - cachedNpcNames.clear(); - for (int i = 0; i < count; i++) { - cachedNpcNames.put(in.readInt(), in.readUTF()); - } - } - } catch (Exception e) { - Freesia.LOGGER.warn("[NPC] Error reading proxy payload: " + e.getMessage()); - } - } - - public Map getCachedNpcNames() { - return cachedNpcNames; - } -} diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcPersistenceManager.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcPersistenceManager.java deleted file mode 100644 index 60ca589..0000000 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/NpcPersistenceManager.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.nguyendevs.freesia.velocity.network.misc; - -import com.nguyendevs.freesia.velocity.Freesia; -import com.nguyendevs.freesia.velocity.FreesiaConstants; - -import java.io.*; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class NpcPersistenceManager { - - private static final File ASSIGNMENTS_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "npc_assignments.dat"); - private static final File MODEL_CACHE_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "npc_model_cache.dat"); - - private final Map byId = new ConcurrentHashMap<>(); - - public record NpcEntry(int npcId, String modelId) {} - - public void load() { - byId.clear(); - if (!ASSIGNMENTS_FILE.exists()) return; - try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(ASSIGNMENTS_FILE)))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final int npcId = in.readInt(); - in.readLong(); in.readLong(); - final String model = in.readUTF(); - byId.put(npcId, new NpcEntry(npcId, model)); - } - Freesia.LOGGER.info("[NPC] Loaded {} NPC assignments from disk", byId.size()); - } catch (Exception e) { - Freesia.LOGGER.warn("[NPC] Failed to load npc_assignments.dat: {}", e.getMessage()); - } - } - - public void saveAssignment(int npcId, java.util.UUID ignored, String modelId) { - byId.put(npcId, new NpcEntry(npcId, modelId)); - flush(); - } - - public Map getIdAssignments() { return byId; } - - public Map loadModelBinaryCache() { - final Map cache = new HashMap<>(); - if (!MODEL_CACHE_FILE.exists()) return cache; - try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(MODEL_CACHE_FILE)))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final String model = in.readUTF(); - final int len = in.readInt(); - final byte[] data = new byte[len]; - in.readFully(data); - cache.put(model, data); - } - Freesia.LOGGER.info("[NPC] Loaded {} model binary entries from disk", cache.size()); - } catch (Exception e) { - Freesia.LOGGER.warn("[NPC] Failed to load npc_model_cache.dat: {}", e.getMessage()); - } - return cache; - } - - public void saveModelBinaryCache(Map cache) { - try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(MODEL_CACHE_FILE)))) { - out.writeInt(cache.size()); - for (Map.Entry e : cache.entrySet()) { - out.writeUTF(e.getKey()); - out.writeInt(e.getValue().length); - out.write(e.getValue()); - } - } catch (Exception e) { - Freesia.LOGGER.warn("[NPC] Failed to save npc_model_cache.dat: {}", e.getMessage()); - } - } - - private void flush() { - try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(ASSIGNMENTS_FILE)))) { - out.writeInt(byId.size()); - for (NpcEntry e : byId.values()) { - out.writeInt(e.npcId()); - out.writeLong(0); - out.writeLong(0); - out.writeUTF(e.modelId()); - } - } catch (Exception e) { - Freesia.LOGGER.warn("[NPC] Failed to save npc_assignments.dat: {}", e.getMessage()); - } - } -} diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/VirtualPlayerManager.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/VirtualPlayerManager.java index 14ca025..f0e3dfc 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/VirtualPlayerManager.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/misc/VirtualPlayerManager.java @@ -30,7 +30,6 @@ public void init() { Freesia.PROXY_SERVER.getChannelRegistrar().register(CITIZENS_SETSKIN_CHANNEL); Freesia.PROXY_SERVER.getChannelRegistrar().register(CITIZENS_UUID_RESP_CHANNEL); Freesia.PROXY_SERVER.getEventManager().register(Freesia.INSTANCE, this); - // Model binary cache + NPC preload already done inside YsmMapperPayloadManager constructor } @Subscribe @@ -44,7 +43,7 @@ public EventTask onPluginMessage(@NotNull PluginMessageEvent event) { if (channelId.equals(CITIZENS_UUID_RESP_CHANNEL.getId())) { event.setResult(PluginMessageEvent.ForwardResult.handled()); - handleCitizensUuidResponse(event.getData()); + handleCitizensUuidResponse(((ServerConnection) event.getSource()).getServerInfo().getName(), event.getData()); return; } @@ -94,7 +93,6 @@ public EventTask onPluginMessage(@NotNull PluginMessageEvent event) { }); } - // Virtual NPC tracker update — now includes entityId and optionally npcId (if Citizens) case 3 -> { final UUID virtualEntityUUID = packetData.readUUID(); final int npcEntityId = packetData.readVarInt(); @@ -141,22 +139,21 @@ public EventTask onPluginMessage(@NotNull PluginMessageEvent event) { }); } - private void handleCitizensUuidResponse(byte[] data) { + private void handleCitizensUuidResponse(String serverName, byte[] data) { final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); final int npcId = buf.readVarInt(); final UUID npcUUID = buf.readUUID(); final int npcEntityId = buf.readVarInt(); final String modelId = buf.readUtf(); - // Persist assignment (npcId + npcUUID + modelId) so proxy survives restarts - Freesia.mapperManager.npcPersistenceManager.saveAssignment(npcId, npcUUID, modelId); + Freesia.mapperManager.citizensPersistenceManager.saveAssignment(serverName, npcId, modelId); Freesia.mapperManager.addVirtualPlayer(npcUUID, npcEntityId).whenComplete((addResult, addEx) -> { if (addEx != null) { Freesia.LOGGER.error("[Citizens] Failed to register NPC {} as virtual player", npcUUID, addEx); } - final byte[] cachedBinary = Freesia.mapperManager.getCachedNpcModelBinary(modelId); + final byte[] cachedBinary = Freesia.mapperManager.getCachedCitizensModelBinary(modelId); if (cachedBinary == null) { Freesia.LOGGER.warn("[Citizens] No cached binary for model '{}'. A real player using this model must be online first, then run /freesia setskin again.", modelId); return; diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/MapperSessionProcessor.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/MapperSessionProcessor.java index 0785ff1..698c793 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/MapperSessionProcessor.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/MapperSessionProcessor.java @@ -34,9 +34,9 @@ public class MapperSessionProcessor implements SessionListener { private final MultiThreadedQueue pendingYsmPacketsInbound = new MultiThreadedQueue<>(); private final MultiThreadedQueue pendingTrackerUpdatesTo = new MultiThreadedQueue<>(); private final MultiThreadedQueue pendingYsmPacketsOutbound = new MultiThreadedQueue<>(); - private final MultiThreadedQueue pendingNpcTrackerUpdates = new MultiThreadedQueue<>(); + private final MultiThreadedQueue pendingCitizensTrackerUpdates = new MultiThreadedQueue<>(); - public record NpcTrackerUpdate(int entityId, byte[] binary) {} + public record CitizensTrackerUpdate(int entityId, byte[] binary) {} private volatile Session session; private boolean kickMasterWhenDisconnect = true; @@ -61,8 +61,8 @@ protected boolean queueTrackerUpdate(UUID target) { return this.pendingTrackerUpdatesTo.offer(target); } - protected boolean queueNpcTrackerUpdate(int entityId, byte[] binary) { - return this.pendingNpcTrackerUpdates.offer(new NpcTrackerUpdate(entityId, binary)); + protected boolean queueCitizensTrackerUpdate(int entityId, byte[] binary) { + return this.pendingCitizensTrackerUpdates.offer(new CitizensTrackerUpdate(entityId, binary)); } protected void retireTrackerCallbacks() { @@ -79,8 +79,8 @@ protected void retireTrackerCallbacks() { this.packetProxy.sendEntityStateTo(targetPlayer); } - NpcTrackerUpdate npcUpdate; - while ((npcUpdate = this.pendingNpcTrackerUpdates.pollOrBlockAdds()) != null) { + CitizensTrackerUpdate npcUpdate; + while ((npcUpdate = this.pendingCitizensTrackerUpdates.pollOrBlockAdds()) != null) { this.mapperPayloadManager.sendEntityStateToRaw(this.bindPlayer.getUniqueId(), npcUpdate.entityId(), YsmState.ofBinary(npcUpdate.binary())); } } @@ -153,7 +153,6 @@ protected void onBackendReady() { @Override public void packetReceived(Session session, Packet packet) { if (packet instanceof ClientboundLoginPacket loginPacket) { - // Notify entity update to notify the tracker update of the player Freesia.mapperManager.updateWorkerPlayerEntityId(this.bindPlayer, loginPacket.getEntityId()); byte[] pendingData; diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java index 1295f74..6a751e5 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java @@ -62,7 +62,7 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { final FriendlyByteBuf probe = new FriendlyByteBuf(io.netty.buffer.Unpooled.wrappedBuffer(binaryData)); final String modelPath = probe.readUtf(); if (!modelPath.isEmpty()) { - Freesia.mapperManager.cacheNpcModelBinary(modelPath, binaryData); + Freesia.mapperManager.cacheCitizensModelBinary(modelPath, binaryData); } } catch (Exception ignored) {} } else { @@ -101,8 +101,6 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { canSwitchModel); if (this.hasHandshaked) { - // Synthesize C2S Handshake Request to inform the new backend worker about the - // client version FriendlyByteBuf c2sBuf = new FriendlyByteBuf(Unpooled.buffer()); c2sBuf.writeByte(YsmProtocolMetaFile .getC2SPacketId(FreesiaConstants.YsmProtocolMetaConstants.Serverbound.HAND_SHAKE_REQUEST)); @@ -211,8 +209,8 @@ public ProxyComputeResult processC2S(Key key, ByteBuf copiedPacketData) { } final String clientYsmVersion = mcBuffer.readUtf(); - this.ysmVersion = clientYsmVersion; // Store the version - this.hasHandshaked = true; // Mark as handshaked + this.ysmVersion = clientYsmVersion; + this.hasHandshaked = true; Freesia.LOGGER.info("Player {} is connected to the backend with ysm version {}", this.player.getUsername(), clientYsmVersion); Freesia.mapperManager.onClientYsmHandshakePacketReply(this.player); diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/YsmMapperPayloadManager.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/YsmMapperPayloadManager.java index d0ab9a2..4bfa5e3 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/YsmMapperPayloadManager.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/network/ysm/YsmMapperPayloadManager.java @@ -49,14 +49,14 @@ public class YsmMapperPayloadManager { private final Set ysmInstalledPlayers = Sets.newConcurrentHashSet(); private final Map> worker2PlayerEntityIdCache = Maps.newConcurrentMap(); - private final Map npcModelBinaryCache = Maps.newConcurrentMap(); - private final Map> playerTrackedNpcs = Maps.newConcurrentMap(); + private final Map citizensModelBinaryCache = Maps.newConcurrentMap(); + private final Map>> playerTrackedCitizens = Maps.newConcurrentMap(); - public final com.nguyendevs.freesia.velocity.network.misc.NpcPersistenceManager npcPersistenceManager - = new com.nguyendevs.freesia.velocity.network.misc.NpcPersistenceManager(); + public final com.nguyendevs.freesia.velocity.network.misc.CitizensPersistenceManager citizensPersistenceManager + = new com.nguyendevs.freesia.velocity.network.misc.CitizensPersistenceManager(); - public Map getNpcModelBinaryCache() { - return npcModelBinaryCache; + public Map getcitizensModelBinaryCache() { + return citizensModelBinaryCache; } public YsmMapperPayloadManager(Function packetProxyCreator, @@ -64,8 +64,8 @@ public YsmMapperPayloadManager(Function packetProxyCreat this.packetProxyCreator = packetProxyCreator; this.packetProxyCreatorVirtual = packetProxyCreatorVirtual; this.backend2Players.put(FreesiaConfig.workerMSessionAddress, 1); - this.npcModelBinaryCache.putAll(this.npcPersistenceManager.loadModelBinaryCache()); - this.npcPersistenceManager.load(); + this.citizensModelBinaryCache.putAll(this.citizensPersistenceManager.loadModelBinaryCache()); + this.citizensPersistenceManager.load(); } @Nullable @@ -179,40 +179,98 @@ public CompletableFuture setVirtualPlayerEntityStateBinary(UUID playerU return CompletableFuture.completedFuture(true); } - public void cacheNpcModelBinary(String modelPath, byte[] binary) { + public void cacheCitizensModelBinary(String modelPath, byte[] binary) { final String key = modelPath.toLowerCase(); - if (!npcModelBinaryCache.containsKey(key)) { - npcModelBinaryCache.put(key, binary); + if (!citizensModelBinaryCache.containsKey(key)) { + citizensModelBinaryCache.put(key, binary); Freesia.LOGGER.info("[YSM] Cached model binary for '{}' ({} bytes)", modelPath, binary.length); - npcPersistenceManager.saveModelBinaryCache(npcModelBinaryCache); + citizensPersistenceManager.saveModelBinaryCache(citizensModelBinaryCache); } } - public byte[] getCachedNpcModelBinary(String modelId) { - byte[] result = npcModelBinaryCache.get(modelId); + public byte[] getCachedCitizensModelBinary(String modelId) { + byte[] result = citizensModelBinaryCache.get(modelId); if (result != null) return result; - result = npcModelBinaryCache.get(modelId.toLowerCase()); + result = citizensModelBinaryCache.get(modelId.toLowerCase()); if (result != null) return result; - result = npcModelBinaryCache.get(modelId + ".ysm"); + result = citizensModelBinaryCache.get(modelId + ".ysm"); if (result != null) return result; - return npcModelBinaryCache.get(modelId.toLowerCase() + ".ysm"); + return citizensModelBinaryCache.get(modelId.toLowerCase() + ".ysm"); + } + + public void handleCitizensTrackSync(String serverName, Player watcher, int npcId, int entityId) { + this.playerTrackedCitizens.computeIfAbsent(watcher, k -> Maps.newConcurrentMap()) + .computeIfAbsent(serverName, k -> Maps.newConcurrentMap()) + .put(npcId, entityId); + + Map serverAssignments = + citizensPersistenceManager.getServerIdAssignments().get(serverName); + + if (serverAssignments != null) { + com.nguyendevs.freesia.velocity.network.misc.CitizensPersistenceManager.CitizensEntry entry = serverAssignments.get(npcId); + if (entry != null) { + byte[] binary = getCachedCitizensModelBinary(entry.modelId()); + if (binary != null) { + final MapperSessionProcessor mapperSession = this.mapperSessions.get(watcher); + if (mapperSession != null) { + if (this.isPlayerInstalledYsm(watcher)) { + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, YsmState.ofBinary(binary)); + } else { + mapperSession.queueCitizensTrackerUpdate(entityId, binary); + } + } + } + } + } } - public void handleNpcTrackSync(Player watcher, int npcId, int entityId) { - com.nguyendevs.freesia.velocity.network.misc.NpcPersistenceManager.NpcEntry entry = npcPersistenceManager.getIdAssignments().get(npcId); - if (entry != null) { - byte[] binary = getCachedNpcModelBinary(entry.modelId()); - if (binary != null) { + public void handleCitizensUntrackSync(String serverName, Player watcher, int npcId) { + final Map> playerMap = this.playerTrackedCitizens.get(watcher); + if (playerMap != null) { + Map serverMap = playerMap.get(serverName); + if (serverMap != null) { + serverMap.remove(npcId); + if (serverMap.isEmpty()) { + playerMap.remove(serverName); + } + } + if (playerMap.isEmpty()) { + this.playerTrackedCitizens.remove(watcher); + } + } + } + + public void broadcastCitizensSkinUpdate(String serverName, int npcId) { + Map serverAssignments = + citizensPersistenceManager.getServerIdAssignments().get(serverName); + if (serverAssignments == null) return; + + com.nguyendevs.freesia.velocity.network.misc.CitizensPersistenceManager.CitizensEntry entry = serverAssignments.get(npcId); + if (entry == null) return; + + byte[] binary = getCachedCitizensModelBinary(entry.modelId()); + if (binary == null) return; + + final YsmState state = YsmState.ofBinary(binary); + + for (Map.Entry>> playerEntry : this.playerTrackedCitizens.entrySet()) { + final Player watcher = playerEntry.getKey(); + Map serverTracked = playerEntry.getValue().get(serverName); + if (serverTracked == null) continue; + + final Integer entityId = serverTracked.get(npcId); + if (entityId != null) { final MapperSessionProcessor mapperSession = this.mapperSessions.get(watcher); if (mapperSession != null) { if (this.isPlayerInstalledYsm(watcher)) { - this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, YsmState.ofBinary(binary)); + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, state); + Freesia.PROXY_SERVER.getScheduler().buildTask(Freesia.INSTANCE, () -> { + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, state); + }).delay(java.time.Duration.ofMillis(100)).schedule(); } else { - mapperSession.queueNpcTrackerUpdate(entityId, binary); + mapperSession.queueCitizensTrackerUpdate(entityId, binary); } } - } else { - Freesia.LOGGER.warn("[NPC] Binary missing for model '{}' on NPC ID {}", entry.modelId(), npcId); } } } @@ -347,6 +405,7 @@ public boolean isPlayerInstalledYsm(UUID target) { public void onPlayerDisconnect(@NotNull Player player) { this.ysmInstalledPlayers.remove(player.getUniqueId()); + this.playerTrackedCitizens.remove(player); final MapperSessionProcessor mapperSession = this.mapperSessions.remove(player); @@ -569,7 +628,6 @@ public void onVirtualPlayerTrackerUpdate(UUID owner, Player watcher) { } if (this.isPlayerInstalledYsm(watcher)) { - // Queue it properly through the mapper session so we don't send to unprepared clients if (!mapperSession.queueTrackerUpdate(owner)) { virtualProxy.sendEntityStateTo(watcher); } diff --git a/Freesia-Velocity/src/main/resources/lang/en_US.lang b/Freesia-Velocity/src/main/resources/lang/en_US.lang index 2445afc..aaa0dd1 100644 --- a/Freesia-Velocity/src/main/resources/lang/en_US.lang +++ b/Freesia-Velocity/src/main/resources/lang/en_US.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm backend has been disconnected, reason: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> There is not a valid connection to ysm backend! If you still meet this issue. please report to the administrators -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Cannot found specified worker -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Handshake time outed, please ensure you have installed the corrected version of ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Sent setskin request for NPC with model . Check backend logs for result. -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> No online players to relay the command to Backend. -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens is not enabled on the Backend server. \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm backend has been disconnected, reason: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> There is not a valid connection to ysm backend! If you still meet this issue. please report to the administrators +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Cannot found specified worker +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Handshake time outed, please ensure you have installed the corrected version of ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Sent setskin request for Citizens with model . Check backend logs for result. +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> No online players to relay the command to Backend. +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens is not enabled on the Backend server. +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>No permission. +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Please provide a server name. +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Proxy configurations reloaded successfully! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Failed to reload configurations: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> must be an integer. +freesia.command.usage.header=<#f9e2af>Freesia Command Help: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- List all Ysm players +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- Dispatch command to worker +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- Set Citizens skin +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- Reload configurations diff --git a/Freesia-Velocity/src/main/resources/lang/vi_VN.lang b/Freesia-Velocity/src/main/resources/lang/vi_VN.lang index 98ec4cb..54d5ee0 100644 --- a/Freesia-Velocity/src/main/resources/lang/vi_VN.lang +++ b/Freesia-Velocity/src/main/resources/lang/vi_VN.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Backend Ysm đã ngắt kết nối, lý do: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có kết nối hợp lệ tới backend Ysm! Nếu bạn vẫn gặp sự cố này, vui lòng báo cáo cho quản trị viên -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không tìm thấy worker được chỉ định -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Bắt tay kết nối đã hết thời gian chờ, vui lòng đảm bảo bạn đã cài đúng phiên bản Ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Danh sách người chơi Ysm: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Đã gửi yêu cầu setskin cho NPC với model . Kiểm tra log backend để xem kết quả. -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có người chơi nào online để chuyển tiếp lệnh tới Backend. -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens chưa được bật trên server Backend. \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Backend Ysm đã ngắt kết nối, lý do: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có kết nối hợp lệ tới backend Ysm! Nếu bạn vẫn gặp sự cố này, vui lòng báo cáo cho quản trị viên +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không tìm thấy worker được chỉ định +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Bắt tay kết nối đã hết thời gian chờ, vui lòng đảm bảo bạn đã cài đúng phiên bản Ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Danh sách người chơi Ysm: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Đã gửi yêu cầu setskin cho Citizens với model . Kiểm tra log backend để xem kết quả. +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có người chơi nào online để chuyển tiếp lệnh tới Backend. +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens chưa được bật trên server Backend. +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Bạn không có quyền thực hiện việc này. +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Vui lòng cung cấp tên máy chủ. +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Đã tải lại cấu hình Proxy thành công! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Tải lại cấu hình Proxy thất bại: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> phải là một số nguyên. +freesia.command.usage.header=<#f9e2af>Hướng dẫn lệnh Freesia: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- Liệt kê người chơi Ysm +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- Gửi lệnh tới worker +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- Đặt skin cho Citizens +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- Tải lại cấu hình diff --git a/Freesia-Velocity/src/main/resources/lang/zh_CN.lang b/Freesia-Velocity/src/main/resources/lang/zh_CN.lang index 5b30857..ab47523 100644 --- a/Freesia-Velocity/src/main/resources/lang/zh_CN.lang +++ b/Freesia-Velocity/src/main/resources/lang/zh_CN.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm后端worker断开了连接,原因: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 你没有连接到Ysm后端worker.如果反复遇到该问题,请汇报管理员! -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 找不到该worker! -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm握手超时,请确认您安装了正确版本的Ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> 已发送NPC 的setskin请求,模型为 。请查看后端日志确认结果。 -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 没有在线玩家可用于转发命令到后端。 -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens未在后端服务器上启用。 \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm后端worker断开了连接,原因: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 你没有连接到Ysm后端worker.如果反复遇到该问题,请汇报管理员! +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 找不到该worker! +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm握手超时,请确认您安装了正确版本的Ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> 已发送Citizens 的setskin请求,模型为 。请查看后端日志确认结果。 +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 没有在线玩家可用于转发命令到后端。 +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens未在后端服务器上启用。 +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>没有权限。 +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>请提供服务器名称。 +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> 代理配置重载成功! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 重载配置失败: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> 必须是整数。 +freesia.command.usage.header=<#f9e2af>Freesia 指令帮助: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- 列出所有 Ysm 玩家 +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- 向 worker 发送指令 +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- 设置 Citizens 皮肤 +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- 重载配置 diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java index a5b2137..e07b3a8 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java @@ -51,7 +51,7 @@ public class Freesia extends Plugin implements PacketListener, Listener { public static YsmClientKickingDetector kickChecker; public static YsmMapperPayloadManager mapperManager; public static NettySocketServer masterServer; - public static com.nguyendevs.freesia.waterfall.network.misc.NpcMessageReceiver npcMessageReceiver; + public static com.nguyendevs.freesia.waterfall.network.misc.CitizensMessageReceiver citizensMessageReceiver; private static void printLogo() { String RESET = "\u001B[0m"; @@ -109,8 +109,8 @@ public void onEnable() { virtualPlayerManager.init(); - npcMessageReceiver = new com.nguyendevs.freesia.waterfall.network.misc.NpcMessageReceiver(); - getProxy().getPluginManager().registerListener(this, npcMessageReceiver); + citizensMessageReceiver = new com.nguyendevs.freesia.waterfall.network.misc.CitizensMessageReceiver(); + PROXY_SERVER.getPluginManager().registerListener(this, citizensMessageReceiver); io.netty.handler.ssl.SslContext sslContext = null; try { diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaConstants.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaConstants.java index e622f95..25af7c7 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaConstants.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaConstants.java @@ -23,7 +23,8 @@ public static final class FileConstants { public static final class PermissionConstants { public static final String LIST_PLAYER_COMMAND = "freesia.commands.listysmplayers", DISPATCH_WORKER_COMMAND = "freesia.commands.dworkerc", - SET_SKIN_COMMAND = "freesia.commands.setskin"; + SET_SKIN_COMMAND = "freesia.commands.setskin", + RELOAD_COMMAND = "freesia.commands.reload"; } public static final class LanguageConstants { @@ -39,7 +40,18 @@ public static final class LanguageConstants { HANDSHAKE_TIMED_OUT = "freesia.mod_handshake_time_outed", WORKER_TERMINATED_CONNECTION = "freesia.backend.disconnected", - WORKER_NOT_CONNECTED = "freesia.backend.not_connected"; + WORKER_NOT_CONNECTED = "freesia.backend.not_connected", + + COMMAND_NO_PERMISSION = "freesia.command.no_permission", + COMMAND_PROVIDE_SERVER_NAME = "freesia.command.provide_server_name", + COMMAND_RELOAD_SUCCESS = "freesia.command.reload_success", + COMMAND_RELOAD_FAIL = "freesia.command.reload_fail", + COMMAND_CITIZENS_ID_INTEGER = "freesia.command.citizens_id_integer", + COMMAND_USAGE_HEADER = "freesia.command.usage.header", + COMMAND_USAGE_LISTPLAYERS = "freesia.command.usage.listplayers", + COMMAND_USAGE_DWORKERC = "freesia.command.usage.dworkerc", + COMMAND_USAGE_SETSKIN = "freesia.command.usage.setskin", + COMMAND_USAGE_RELOAD = "freesia.command.usage.reload"; } diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/command/FreesiaCommand.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/command/FreesiaCommand.java index c331a5f..6a22deb 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/command/FreesiaCommand.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/command/FreesiaCommand.java @@ -14,7 +14,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.UUID; +import java.util.Map; import java.util.stream.Collectors; public class FreesiaCommand extends Command implements TabExecutor { @@ -37,14 +37,21 @@ public void execute(CommandSender sender, String[] args) { switch (args[0].toLowerCase()) { case "listplayers" -> handleListPlayers(sender); case "dworkerc" -> handleDispatchWorker(sender, args); - case "setskin" -> handleSetSkin(sender, args); + case "setskin" -> { + if (isCitizensSupported(sender)) { + handleSetSkin(sender, args); + } else { + sendUsage(sender); + } + } + case "reload" -> handleReload(sender); default -> sendUsage(sender); } } private void handleListPlayers(CommandSender sender) { if (!sender.hasPermission(FreesiaConstants.PermissionConstants.LIST_PLAYER_COMMAND)) { - sender.sendMessage(TextComponent.fromLegacyText("§cNo permission.")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_NO_PERMISSION, List.of(), List.of())); return; } @@ -68,12 +75,12 @@ private void handleListPlayers(CommandSender sender) { private void handleDispatchWorker(CommandSender sender, String[] args) { if (!sender.hasPermission(FreesiaConstants.PermissionConstants.DISPATCH_WORKER_COMMAND)) { - sender.sendMessage(TextComponent.fromLegacyText("§cNo permission.")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_NO_PERMISSION, List.of(), List.of())); return; } if (args.length < 3) { - sender.sendMessage(TextComponent.fromLegacyText("Usage: /freesia dworkerc ")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_DWORKERC, List.of(), List.of())); return; } @@ -93,9 +100,7 @@ private void handleDispatchWorker(CommandSender sender, String[] args) { } if (targetWorker == null) { - sender.sendMessage(TextComponent.fromLegacyText(LegacyComponentSerializer.legacySection().serialize( - Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_NOT_FOUND, - List.of(), List.of())))); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_NOT_FOUND, List.of(), List.of())); return; } @@ -103,20 +108,19 @@ private void handleDispatchWorker(CommandSender sender, String[] args) { if (feedback == null) { return; } - sender.sendMessage(TextComponent.fromLegacyText(LegacyComponentSerializer.legacySection().serialize( - Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_COMMAND_FEEDBACK, - List.of("worker_name", "feedback"), List.of(workerName, feedback.toString()))))); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_COMMAND_FEEDBACK, + List.of("worker_name", "feedback"), List.of(workerName, feedback.toString()))); }); } private void handleSetSkin(CommandSender sender, String[] args) { if (!sender.hasPermission(FreesiaConstants.PermissionConstants.SET_SKIN_COMMAND)) { - sender.sendMessage(TextComponent.fromLegacyText("§cNo permission.")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_NO_PERMISSION, List.of(), List.of())); return; } if (args.length < 3) { - sender.sendMessage(TextComponent.fromLegacyText("Usage: /freesia setskin ")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_SETSKIN, List.of(), List.of())); return; } @@ -124,23 +128,73 @@ private void handleSetSkin(CommandSender sender, String[] args) { try { npcId = Integer.parseInt(args[1]); } catch (NumberFormatException e) { - sender.sendMessage(TextComponent.fromLegacyText("§c must be an integer.")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_CITIZENS_ID_INTEGER, + List.of("Citizens_id"), List.of(args[1]))); return; } final String modelId = args[2]; + String serverName = null; + + if (args.length >= 4) { + serverName = args[3]; + } else if (sender instanceof ProxiedPlayer player) { + serverName = player.getServer() != null ? player.getServer().getInfo().getName() : null; + } + + if (serverName == null) { + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_PROVIDE_SERVER_NAME, List.of(), List.of())); + return; + } + + if (!isCitizensSupported(sender)) { + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_NOT_CONNECTED, List.of(), List.of())); + return; + } - Freesia.mapperManager.npcPersistenceManager.saveAssignment(npcId, UUID.randomUUID(), modelId); + Freesia.mapperManager.citizensPersistenceManager.saveAssignment(serverName, npcId, modelId); + Freesia.mapperManager.broadcastCitizensSkinUpdate(serverName, npcId); - sender.sendMessage(TextComponent.fromLegacyText(LegacyComponentSerializer.legacySection().serialize( - Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.SETSKIN_SUCCESS, - List.of("npc_id", "model_id"), List.of(String.valueOf(npcId), modelId))))); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.SETSKIN_SUCCESS, + List.of("Citizens_id", "model_id"), List.of(String.valueOf(npcId), modelId))); + } + + private void handleReload(CommandSender sender) { + if (!sender.hasPermission(FreesiaConstants.PermissionConstants.RELOAD_COMMAND)) { + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_NO_PERMISSION, List.of(), List.of())); + return; + } + + try { + com.nguyendevs.freesia.waterfall.FreesiaConfig.init(); + com.nguyendevs.freesia.waterfall.FreesiaSecurityConfig.init(); + Freesia.languageManager.loadLanguageFile(com.nguyendevs.freesia.waterfall.FreesiaConfig.languageName); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_RELOAD_SUCCESS, List.of(), List.of())); + } catch (Exception e) { + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_RELOAD_FAIL, + List.of("error"), List.of(e.getMessage() != null ? e.getMessage() : "unknown"))); + Freesia.LOGGER.severe("Failed to reload configurations: " + e.getMessage()); + } } private void sendUsage(CommandSender sender) { - sender.sendMessage(TextComponent.fromLegacyText("§6/freesia §elistplayers")); - sender.sendMessage(TextComponent.fromLegacyText("§6/freesia §edworkerc §7 ")); - sender.sendMessage(TextComponent.fromLegacyText("§6/freesia §esetskin §7 ")); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_HEADER, List.of(), List.of())); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_LISTPLAYERS, List.of(), List.of())); + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_DWORKERC, List.of(), List.of())); + if (isCitizensSupported(sender)) { + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_SETSKIN, List.of(), List.of())); + } + sendMessage(sender, Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.COMMAND_USAGE_RELOAD, List.of(), List.of())); + } + + private boolean isCitizensSupported(CommandSender sender) { + if (!(sender instanceof ProxiedPlayer player)) return true; + String serverName = player.getServer() != null ? player.getServer().getInfo().getName() : null; + return serverName != null && Freesia.citizensMessageReceiver.isSupported(serverName); + } + + private void sendMessage(CommandSender sender, Component component) { + sender.sendMessage(TextComponent.fromLegacyText(LegacyComponentSerializer.legacySection().serialize(component))); } @Override @@ -148,7 +202,14 @@ public Iterable onTabComplete(CommandSender sender, String[] args) { final List completions = new ArrayList<>(); if (args.length == 1) { - for (String sub : List.of("listplayers", "dworkerc", "setskin")) { + List subs = new ArrayList<>(); + subs.add("listplayers"); + subs.add("dworkerc"); + subs.add("reload"); + if (isCitizensSupported(sender)) { + subs.add("setskin"); + } + for (String sub : subs) { if (sub.startsWith(args[0].toLowerCase())) { completions.add(sub); } @@ -163,18 +224,21 @@ public Iterable onTabComplete(CommandSender sender, String[] args) { completions.add(conn.getWorkerName()); } } - } else if (args[0].equalsIgnoreCase("setskin")) { - Freesia.npcMessageReceiver.getCachedNpcNames().forEach((id, name) -> { - String sid = String.valueOf(id); - if (sid.startsWith(args[1])) { - completions.add(sid); - } - }); + } else if (args[0].equalsIgnoreCase("setskin") && isCitizensSupported(sender)) { + String serverName = sender instanceof ProxiedPlayer player && player.getServer() != null ? player.getServer().getInfo().getName() : null; + if (serverName != null) { + Freesia.citizensMessageReceiver.getCachedNpcNames(serverName).forEach((id, name) -> { + String sid = String.valueOf(id); + if (sid.startsWith(args[1])) { + completions.add(sid); + } + }); + } } } - if (args.length == 3 && args[0].equalsIgnoreCase("setskin")) { - Freesia.mapperManager.getNpcModelBinaryCache().keySet().forEach(model -> { + if (args.length == 3 && args[0].equalsIgnoreCase("setskin") && isCitizensSupported(sender)) { + Freesia.mapperManager.getcitizensModelBinaryCache().keySet().forEach(model -> { String suggestion; if (model.toLowerCase().endsWith(".ysm")) { suggestion = model.substring(0, model.length() - 4); @@ -187,6 +251,14 @@ public Iterable onTabComplete(CommandSender sender, String[] args) { }); } + if (args.length == 4 && args[0].equalsIgnoreCase("setskin")) { + Freesia.PROXY_SERVER.getServers().values().forEach(server -> { + if (server.getName().toLowerCase().startsWith(args[3].toLowerCase())) { + completions.add(server.getName()); + } + }); + } + return completions; } } diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/backend/MasterServerMessageHandler.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/backend/MasterServerMessageHandler.java index 2868af5..350181a 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/backend/MasterServerMessageHandler.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/backend/MasterServerMessageHandler.java @@ -164,4 +164,9 @@ public void updateWorkerInfo(UUID workerUUID, String workerName) { Freesia.PROXY_SERVER.getPluginManager().callEvent(new WorkerConnectedEvent(workerUUID, workerName)); } + + @Override + public void onCommandFromWorker(String command) { + Freesia.PROXY_SERVER.getPluginManager().dispatchCommand(Freesia.PROXY_SERVER.getConsole(), command); + } } diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/mc/FreesiaPlayerTracker.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/mc/FreesiaPlayerTracker.java index 26f052f..e93a2fb 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/mc/FreesiaPlayerTracker.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/mc/FreesiaPlayerTracker.java @@ -126,7 +126,7 @@ public CompletableFuture> getCanSee(@NotNull UUID target) { targetPlayer.getServer().sendData(SYNC_CHANNEL_KEY, callbackRequest.getBytes()); } else { cancelCallbackAdd[0] = true; - callback.complete(null); // Maybe at the early stage + callback.complete(null); } } else { cancelCallbackAdd[0] = true; diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensMessageReceiver.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensMessageReceiver.java new file mode 100644 index 0000000..a59fffc --- /dev/null +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensMessageReceiver.java @@ -0,0 +1,73 @@ +package com.nguyendevs.freesia.waterfall.network.misc; + +import com.nguyendevs.freesia.waterfall.Freesia; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.util.HashMap; +import java.util.Map; + +public class CitizensMessageReceiver implements Listener { + + public static final String CHANNEL = "freesia:npc"; + private final Map> serverNpcCaches = new java.util.concurrent.ConcurrentHashMap<>(); + + public CitizensMessageReceiver() { + Freesia.PROXY_SERVER.registerChannel(CHANNEL); + } + + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + if (!event.getTag().equals(CHANNEL)) return; + + if (!(event.getReceiver() instanceof ProxiedPlayer watcher)) return; + if (!(event.getSender() instanceof Server server)) return; + + String serverName = server.getInfo().getName(); + + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) { + byte opcode = in.readByte(); + + if (opcode == 0) { + in.readLong(); in.readLong(); + int npcId = in.readInt(); + int entityId = in.readInt(); + + Freesia.mapperManager.handleCitizensTrackSync(serverName, watcher, npcId, entityId); + } else if (opcode == 3) { + in.readLong(); in.readLong(); + int npcId = in.readInt(); + + Freesia.mapperManager.handleCitizensUntrackSync(serverName, watcher, npcId); + } else if (opcode == 2) { + int count = in.readInt(); + Map npcNames = new java.util.HashMap<>(); + for (int i = 0; i < count; i++) { + npcNames.put(in.readInt(), in.readUTF()); + } + serverNpcCaches.put(serverName, npcNames); + } + + // Mark server as supported if any opcode received + if (opcode == 0 || opcode == 2 || opcode == 3) { + if (!serverNpcCaches.containsKey(serverName)) { + serverNpcCaches.put(serverName, new java.util.HashMap<>()); + } + } + } catch (Exception e) { + Freesia.LOGGER.warning("[Citizens] Error reading proxy payload: " + e.getMessage()); + } + } + + public Map getCachedNpcNames(String serverName) { + return serverNpcCaches.getOrDefault(serverName, java.util.Collections.emptyMap()); + } + + public boolean isSupported(String serverName) { + return serverNpcCaches.containsKey(serverName); + } +} diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensPersistenceManager.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensPersistenceManager.java new file mode 100644 index 0000000..dca1f04 --- /dev/null +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/CitizensPersistenceManager.java @@ -0,0 +1,96 @@ +package com.nguyendevs.freesia.waterfall.network.misc; + +import com.nguyendevs.freesia.waterfall.Freesia; +import com.nguyendevs.freesia.waterfall.FreesiaConstants; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CitizensPersistenceManager { + + private static final File ASSIGNMENTS_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "citizens_assignments.dat"); + private static final File MODEL_CACHE_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "citizens_model_cache.dat"); + + private final Map> byServerId = new ConcurrentHashMap<>(); + + public record CitizensEntry(int npcId, String modelId) {} + + public void load() { + byServerId.clear(); + if (!ASSIGNMENTS_FILE.exists()) return; + try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(ASSIGNMENTS_FILE)))) { + final int serverCount = in.readInt(); + for (int i = 0; i < serverCount; i++) { + String serverId = in.readUTF(); + int npcCount = in.readInt(); + Map serverMap = new ConcurrentHashMap<>(); + for (int j = 0; j < npcCount; j++) { + int npcId = in.readInt(); + String modelId = in.readUTF(); + serverMap.put(npcId, new CitizensEntry(npcId, modelId)); + } + byServerId.put(serverId, serverMap); + } + Freesia.LOGGER.info("[Citizens] Loaded assignments for " + byServerId.size() + " servers"); + } catch (Exception e) { + Freesia.LOGGER.warning("[Citizens] Failed to load citizens_assignments.dat: " + e.getMessage()); + } + } + + public void saveAssignment(String serverId, int npcId, String modelId) { + byServerId.computeIfAbsent(serverId, k -> new ConcurrentHashMap<>()).put(npcId, new CitizensEntry(npcId, modelId)); + flush(); + } + + public Map> getServerIdAssignments() { return byServerId; } + + public Map loadModelBinaryCache() { + final Map cache = new HashMap<>(); + if (!MODEL_CACHE_FILE.exists()) return cache; + try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(MODEL_CACHE_FILE)))) { + final int count = in.readInt(); + for (int i = 0; i < count; i++) { + final String model = in.readUTF(); + final int len = in.readInt(); + final byte[] data = new byte[len]; + in.readFully(data); + cache.put(model, data); + } + Freesia.LOGGER.info("[Citizens] Loaded " + cache.size() + " model binary entries"); + } catch (Exception e) { + Freesia.LOGGER.warning("[Citizens] Failed to load citizens_model_cache.dat: " + e.getMessage()); + } + return cache; + } + + public void saveModelBinaryCache(Map cache) { + try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(MODEL_CACHE_FILE)))) { + out.writeInt(cache.size()); + for (Map.Entry e : cache.entrySet()) { + out.writeUTF(e.getKey()); + out.writeInt(e.getValue().length); + out.write(e.getValue()); + } + } catch (Exception e) { + Freesia.LOGGER.warning("[Citizens] Failed to save citizens_model_cache.dat: " + e.getMessage()); + } + } + + private void flush() { + try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(ASSIGNMENTS_FILE)))) { + out.writeInt(byServerId.size()); + for (Map.Entry> serverEntry : byServerId.entrySet()) { + out.writeUTF(serverEntry.getKey()); + out.writeInt(serverEntry.getValue().size()); + for (CitizensEntry e : serverEntry.getValue().values()) { + out.writeInt(e.npcId()); + out.writeUTF(e.modelId()); + } + } + } catch (Exception e) { + Freesia.LOGGER.warning("[Citizens] Failed to save citizens_assignments.dat: " + e.getMessage()); + } + } +} diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcMessageReceiver.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcMessageReceiver.java deleted file mode 100644 index d9b9473..0000000 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcMessageReceiver.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.nguyendevs.freesia.waterfall.network.misc; - -import com.nguyendevs.freesia.waterfall.Freesia; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PluginMessageEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.util.HashMap; -import java.util.Map; - -public class NpcMessageReceiver implements Listener { - - public static final String CHANNEL = "freesia:npc"; - private final Map cachedNpcNames = new HashMap<>(); - - public NpcMessageReceiver() { - Freesia.PROXY_SERVER.registerChannel(CHANNEL); - } - - @EventHandler - public void onPluginMessage(PluginMessageEvent event) { - if (!event.getTag().equals(CHANNEL)) return; - - if (!(event.getReceiver() instanceof ProxiedPlayer watcher)) return; - - try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) { - byte opcode = in.readByte(); - - if (opcode == 0) { // OP_TRACK_SYNC - in.readLong(); in.readLong(); // discard UUID - int npcId = in.readInt(); - int entityId = in.readInt(); - - Freesia.mapperManager.handleNpcTrackSync(watcher, npcId, entityId); - } else if (opcode == 2) { // OP_RES_LIST - int count = in.readInt(); - cachedNpcNames.clear(); - for (int i = 0; i < count; i++) { - cachedNpcNames.put(in.readInt(), in.readUTF()); - } - } - } catch (Exception e) { - Freesia.LOGGER.warning("[NPC] Error reading proxy payload: " + e.getMessage()); - } - } - - public Map getCachedNpcNames() { - return cachedNpcNames; - } -} diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcPersistenceManager.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcPersistenceManager.java deleted file mode 100644 index 97021ee..0000000 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/NpcPersistenceManager.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.nguyendevs.freesia.waterfall.network.misc; - -import com.nguyendevs.freesia.waterfall.Freesia; -import com.nguyendevs.freesia.waterfall.FreesiaConstants; - -import java.io.*; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class NpcPersistenceManager { - - private static final File ASSIGNMENTS_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "npc_assignments.dat"); - private static final File MODEL_CACHE_FILE = new File(FreesiaConstants.FileConstants.PLUGIN_DIR, "npc_model_cache.dat"); - - private final Map byId = new ConcurrentHashMap<>(); - - public record NpcEntry(int npcId, String modelId) {} - - public void load() { - byId.clear(); - if (!ASSIGNMENTS_FILE.exists()) return; - try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(ASSIGNMENTS_FILE)))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final int npcId = in.readInt(); - in.readLong(); in.readLong(); - final String model = in.readUTF(); - byId.put(npcId, new NpcEntry(npcId, model)); - } - Freesia.LOGGER.info("[NPC] Loaded " + byId.size() + " NPC assignments from disk"); - } catch (Exception e) { - Freesia.LOGGER.warning("[NPC] Failed to load npc_assignments.dat: " + e.getMessage()); - } - } - - public void saveAssignment(int npcId, java.util.UUID ignored, String modelId) { - byId.put(npcId, new NpcEntry(npcId, modelId)); - flush(); - } - - public Map getIdAssignments() { return byId; } - - public Map loadModelBinaryCache() { - final Map cache = new HashMap<>(); - if (!MODEL_CACHE_FILE.exists()) return cache; - try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(MODEL_CACHE_FILE)))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final String model = in.readUTF(); - final int len = in.readInt(); - final byte[] data = new byte[len]; - in.readFully(data); - cache.put(model, data); - } - Freesia.LOGGER.info("[NPC] Loaded " + cache.size() + " model binary entries from disk"); - } catch (Exception e) { - Freesia.LOGGER.warning("[NPC] Failed to load npc_model_cache.dat: " + e.getMessage()); - } - return cache; - } - - public void saveModelBinaryCache(Map cache) { - try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(MODEL_CACHE_FILE)))) { - out.writeInt(cache.size()); - for (Map.Entry e : cache.entrySet()) { - out.writeUTF(e.getKey()); - out.writeInt(e.getValue().length); - out.write(e.getValue()); - } - } catch (Exception e) { - Freesia.LOGGER.warning("[NPC] Failed to save npc_model_cache.dat: " + e.getMessage()); - } - } - - private void flush() { - try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(ASSIGNMENTS_FILE)))) { - out.writeInt(byId.size()); - for (NpcEntry e : byId.values()) { - out.writeInt(e.npcId()); - out.writeLong(0); - out.writeLong(0); - out.writeUTF(e.modelId()); - } - } catch (Exception e) { - Freesia.LOGGER.warning("[NPC] Failed to save npc_assignments.dat: " + e.getMessage()); - } - } -} diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/VirtualPlayerManager.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/VirtualPlayerManager.java index 71eda0f..66d4b1e 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/VirtualPlayerManager.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/misc/VirtualPlayerManager.java @@ -32,7 +32,6 @@ public void init() { Freesia.PROXY_SERVER.registerChannel(CITIZENS_SETSKIN_CHANNEL); Freesia.PROXY_SERVER.registerChannel(CITIZENS_UUID_RESP_CHANNEL); Freesia.PROXY_SERVER.getPluginManager().registerListener(Freesia.INSTANCE, this); - // Persistence load & preload logic is already in YsmMapperPayloadManager constructor } @EventHandler @@ -46,7 +45,7 @@ public void onPluginMessage(@NotNull PluginMessageEvent event) { if (tag.equals(CITIZENS_UUID_RESP_CHANNEL)) { event.setCancelled(true); - handleCitizensUuidResponse(event.getData()); + handleCitizensUuidResponse(((Server) event.getSender()).getInfo().getName(), event.getData()); return; } @@ -95,7 +94,6 @@ public void onPluginMessage(@NotNull PluginMessageEvent event) { }); } - // Virtual NPC tracker update — now includes entityId and optionally npcId (if Citizens) case 3 -> { final UUID virtualEntityUUID = packetData.readUUID(); final int npcEntityId = packetData.readVarInt(); @@ -144,22 +142,21 @@ public void onPluginMessage(@NotNull PluginMessageEvent event) { }); } - private void handleCitizensUuidResponse(byte[] data) { + private void handleCitizensUuidResponse(String serverName, byte[] data) { final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); final int npcId = buf.readVarInt(); final UUID npcUUID = buf.readUUID(); final int npcEntityId = buf.readVarInt(); final String modelId = buf.readUtf(); - // Persist assignment (npcId + npcUUID + modelId) so proxy survives restarts - Freesia.mapperManager.npcPersistenceManager.saveAssignment(npcId, npcUUID, modelId); + Freesia.mapperManager.citizensPersistenceManager.saveAssignment(serverName, npcId, modelId); Freesia.mapperManager.addVirtualPlayer(npcUUID, npcEntityId).whenComplete((addResult, addEx) -> { if (addEx != null) { Freesia.LOGGER.warning("[Citizens] Failed to register NPC " + npcUUID + " as virtual player: " + addEx.getMessage()); } - final byte[] cachedBinary = Freesia.mapperManager.getCachedNpcModelBinary(modelId); + final byte[] cachedBinary = Freesia.mapperManager.getCachedCitizensModelBinary(modelId); if (cachedBinary == null) { Freesia.LOGGER.warning("[Citizens] No cached binary for model '" + modelId + "'. A real player using this model must be online first, then run /freesia setskin again."); return; diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/MapperSessionProcessor.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/MapperSessionProcessor.java index e975647..983ddef 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/MapperSessionProcessor.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/MapperSessionProcessor.java @@ -33,11 +33,10 @@ public class MapperSessionProcessor implements SessionListener { private final MultiThreadedQueue pendingYsmPacketsInbound = new MultiThreadedQueue<>(); private final MultiThreadedQueue pendingTrackerUpdatesTo = new MultiThreadedQueue<>(); private final MultiThreadedQueue pendingYsmPacketsOutbound = new MultiThreadedQueue<>(); - private final MultiThreadedQueue pendingNpcTrackerUpdates = new MultiThreadedQueue<>(); + private final MultiThreadedQueue pendingCitizensTrackerUpdates = new MultiThreadedQueue<>(); - public record NpcTrackerUpdate(int entityId, byte[] binary) {} + public record CitizensTrackerUpdate(int entityId, byte[] binary) {} - // Controlled by the VarHandles following private volatile Session session; private boolean kickMasterWhenDisconnect = true; private boolean destroyed = false; @@ -61,8 +60,8 @@ protected boolean queueTrackerUpdate(UUID target) { return this.pendingTrackerUpdatesTo.offer(target); } - protected boolean queueNpcTrackerUpdate(int entityId, byte[] binary) { - return this.pendingNpcTrackerUpdates.offer(new NpcTrackerUpdate(entityId, binary)); + protected boolean queueCitizensTrackerUpdate(int entityId, byte[] binary) { + return this.pendingCitizensTrackerUpdates.offer(new CitizensTrackerUpdate(entityId, binary)); } protected void retireTrackerCallbacks() { @@ -77,8 +76,8 @@ protected void retireTrackerCallbacks() { this.packetProxy.sendEntityStateTo(player); } - NpcTrackerUpdate npcUpdate; - while ((npcUpdate = this.pendingNpcTrackerUpdates.pollOrBlockAdds()) != null) { + CitizensTrackerUpdate npcUpdate; + while ((npcUpdate = this.pendingCitizensTrackerUpdates.pollOrBlockAdds()) != null) { this.mapperPayloadManager.sendEntityStateToRaw(this.bindPlayer.getUniqueId(), npcUpdate.entityId(), YsmState.ofBinary(npcUpdate.binary())); } } @@ -289,9 +288,8 @@ public void destroyAndAwaitDisconnected() { } protected void waitForDisconnected() { - // We will set the session to null after finishing all disconnect logics while (SESSION_HANDLE.getVolatile(this) != null) { - Thread.onSpinWait(); // Spin wait instead of block waiting + Thread.onSpinWait(); } } diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/RealPlayerYsmPacketProxyImpl.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/RealPlayerYsmPacketProxyImpl.java index b5f7341..4d6aba5 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/RealPlayerYsmPacketProxyImpl.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/RealPlayerYsmPacketProxyImpl.java @@ -37,9 +37,8 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { .getS2CPacketId(FreesiaConstants.YsmProtocolMetaConstants.Clientbound.ENTITY_DATA_UPDATE)) { final int workerEntityId = mcBuffer.readVarInt(); - if (!this.isEntityStateOfSelf(workerEntityId)) { // Check if the packet is current player and drop to - // prevent incorrect broadcasting - return ProxyComputeResult.ofDrop(); // Do not process the entity state if it is not ours + if (!this.isEntityStateOfSelf(workerEntityId)) { + return ProxyComputeResult.ofDrop(); } try { @@ -60,7 +59,7 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { final FriendlyByteBuf probe = new FriendlyByteBuf(io.netty.buffer.Unpooled.wrappedBuffer(binaryData)); final String modelPath = probe.readUtf(); if (!modelPath.isEmpty()) { - Freesia.mapperManager.cacheNpcModelBinary(modelPath, binaryData); + Freesia.mapperManager.cacheCitizensModelBinary(modelPath, binaryData); } } catch (Exception ignored) {} } else { @@ -72,13 +71,13 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { final YsmState to = result.getEntityState(); - this.acquireWriteReference(); // Acquire write reference + this.acquireWriteReference(); LAST_YSM_ENTITY_DATA_HANDLE.setVolatile(this, to); - this.releaseWriteReference(); // Release write reference + this.releaseWriteReference(); - this.notifyFullTrackerUpdates(); // Notify updates + this.notifyFullTrackerUpdates(); } catch (Exception e) { Freesia.LOGGER.log(java.util.logging.Level.SEVERE, "Failed to process entity state update packet", e); } @@ -98,8 +97,6 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { + canSwitchModel); if (this.hasHandshaked) { - // Synthesize C2S Handshake Request to inform the new backend worker about the - // client version FriendlyByteBuf c2sBuf = new FriendlyByteBuf(Unpooled.buffer()); c2sBuf.writeByte(YsmProtocolMetaFile .getC2SPacketId(FreesiaConstants.YsmProtocolMetaConstants.Serverbound.HAND_SHAKE_REQUEST)); @@ -129,8 +126,6 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { final InetSocketAddress remoteAddress = this.handler == null ? null : this.handler.getRemoteAddress(); final Map collectedPaddingWorkerEntityId = remoteAddress == null ? Map.of() : Freesia.mapperManager .collectRealProxy2WorkerEntityId(remoteAddress); - - // remap the entity id int idx = 0; for (int singleWorkerEntityId : entityIds) { final Integer targetProxyId = collectedPaddingWorkerEntityId @@ -140,11 +135,9 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { continue; } - entityIdsRemapped[idx] = targetProxyId; // we are on backend side + entityIdsRemapped[idx] = targetProxyId; idx++; } - - // re-send packet as it's much cheaper than modify this.executeMolang(entityIdsRemapped, expression); return ProxyComputeResult.ofDrop(); } @@ -211,8 +204,8 @@ public ProxyComputeResult processC2S(Key key, ByteBuf copiedPacketData) { } final String clientYsmVersion = mcBuffer.readUtf(); - this.ysmVersion = clientYsmVersion; // Store the version - this.hasHandshaked = true; // Mark as handshaked + this.ysmVersion = clientYsmVersion; + this.hasHandshaked = true; Freesia.LOGGER.info("Player " + this.player.getName() + " is connected to the backend with ysm version " + clientYsmVersion); Freesia.mapperManager.onClientYsmHandshakePacketReply(this.player); @@ -260,7 +253,6 @@ public ProxyComputeResult processC2S(Key key, ByteBuf copiedPacketData) { if (entityId == -1) { mappedEntityId = -1; } else { - // Reverse lookup: find the worker ID belonging to the real entity ID final InetSocketAddress remoteAddress = this.handler == null ? null : this.handler.getRemoteAddress(); final Map workerToProxy = remoteAddress == null ? Map.of() : Freesia.mapperManager .collectRealProxy2WorkerEntityId(remoteAddress); diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmMapperPayloadManager.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmMapperPayloadManager.java index 434268a..db85a0e 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmMapperPayloadManager.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmMapperPayloadManager.java @@ -31,46 +31,40 @@ import java.util.function.Function; public class YsmMapperPayloadManager { - // Ysm channel key name public static final Key YSM_CHANNEL_KEY_ADVENTURE = Key .key(YsmProtocolMetaFile.getYsmChannelNamespace() + ":" + YsmProtocolMetaFile.getYsmChannelPath()); public static final String YSM_CHANNEL_KEY_VELOCITY = YsmProtocolMetaFile.getYsmChannelNamespace() + ":" + YsmProtocolMetaFile.getYsmChannelPath(); - // Used for virtual players like NPCs private final Map virtualProxies = Maps.newHashMap(); private final Function packetProxyCreatorVirtual; - // Player to worker mappers connections private final Map mapperSessions = Maps.newConcurrentMap(); - // Real player proxy factory + private final Function packetProxyCreator; - // Backend connect infos private final ReadWriteLock backendIpsAccessLock = new ReentrantReadWriteLock(false); private final Map backend2Players = Maps.newLinkedHashMap(); - // The players who installed ysm(Used for packet sending reduction) private final Set ysmInstalledPlayers = Sets.newConcurrentHashSet(); private final Map> worker2PlayerEntityIdCache = Maps.newConcurrentMap(); - private final Map npcModelBinaryCache = Maps.newConcurrentMap(); - public final com.nguyendevs.freesia.waterfall.network.misc.NpcPersistenceManager npcPersistenceManager - = new com.nguyendevs.freesia.waterfall.network.misc.NpcPersistenceManager(); + private final Map citizensModelBinaryCache = Maps.newConcurrentMap(); + private final Map>> playerTrackedCitizens = Maps.newConcurrentMap(); + public final com.nguyendevs.freesia.waterfall.network.misc.CitizensPersistenceManager citizensPersistenceManager + = new com.nguyendevs.freesia.waterfall.network.misc.CitizensPersistenceManager(); - public Map getNpcModelBinaryCache() { - return npcModelBinaryCache; + public Map getcitizensModelBinaryCache() { + return citizensModelBinaryCache; } public YsmMapperPayloadManager(Function packetProxyCreator, Function packetProxyCreatorVirtual) { this.packetProxyCreator = packetProxyCreator; this.packetProxyCreatorVirtual = packetProxyCreatorVirtual; - this.backend2Players.put(FreesiaConfig.workerMSessionAddress, 1); // TODO Load balance - // Load persisted model binary cache - this.npcModelBinaryCache.putAll(this.npcPersistenceManager.loadModelBinaryCache()); - // Load persisted NPC assignments. Keyed purely by npcId now. - this.npcPersistenceManager.load(); + this.backend2Players.put(FreesiaConfig.workerMSessionAddress, 1); + this.citizensModelBinaryCache.putAll(this.citizensPersistenceManager.loadModelBinaryCache()); + this.citizensPersistenceManager.load(); } @Nullable @@ -138,14 +132,12 @@ public CompletableFuture setVirtualPlayerEntityState(UUID playerUUID, N final YsmState entityState = virtualProxy.getCurrentEntityState(); - // Probably be reset if (entityState == null || entityState.isBinary()) { return CompletableFuture.completedFuture(true); } final NBTCompound entityData = entityState.getNbt(); - // Async io final CompletableFuture callback = new CompletableFuture<>(); Freesia.PROXY_SERVER.getScheduler().runAsync(Freesia.INSTANCE, () -> { @@ -190,43 +182,98 @@ public CompletableFuture setVirtualPlayerEntityStateBinary(UUID playerU return CompletableFuture.completedFuture(true); } - public void cacheNpcModelBinary(String modelPath, byte[] binary) { + public void cacheCitizensModelBinary(String modelPath, byte[] binary) { final String key = modelPath.toLowerCase(); - if (!npcModelBinaryCache.containsKey(key)) { - npcModelBinaryCache.put(key, binary); + if (!citizensModelBinaryCache.containsKey(key)) { + citizensModelBinaryCache.put(key, binary); Freesia.LOGGER.info("[YSM] Cached model binary for '" + modelPath + "' (" + binary.length + " bytes)"); - npcPersistenceManager.saveModelBinaryCache(npcModelBinaryCache); + citizensPersistenceManager.saveModelBinaryCache(citizensModelBinaryCache); } } - public byte[] getCachedNpcModelBinary(String modelId) { - byte[] result = npcModelBinaryCache.get(modelId); + public byte[] getCachedCitizensModelBinary(String modelId) { + byte[] result = citizensModelBinaryCache.get(modelId); if (result != null) return result; - result = npcModelBinaryCache.get(modelId.toLowerCase()); + result = citizensModelBinaryCache.get(modelId.toLowerCase()); if (result != null) return result; - result = npcModelBinaryCache.get(modelId + ".ysm"); + result = citizensModelBinaryCache.get(modelId + ".ysm"); if (result != null) return result; - return npcModelBinaryCache.get(modelId.toLowerCase() + ".ysm"); + return citizensModelBinaryCache.get(modelId.toLowerCase() + ".ysm"); + } + + public void handleCitizensTrackSync(String serverName, ProxiedPlayer watcher, int npcId, int entityId) { + this.playerTrackedCitizens.computeIfAbsent(watcher, k -> Maps.newConcurrentMap()) + .computeIfAbsent(serverName, k -> Maps.newConcurrentMap()) + .put(npcId, entityId); + + Map serverAssignments = + citizensPersistenceManager.getServerIdAssignments().get(serverName); + + if (serverAssignments != null) { + com.nguyendevs.freesia.waterfall.network.misc.CitizensPersistenceManager.CitizensEntry entry = serverAssignments.get(npcId); + if (entry != null) { + byte[] binary = getCachedCitizensModelBinary(entry.modelId()); + if (binary != null) { + final MapperSessionProcessor mapperSession = this.mapperSessions.get(watcher); + if (mapperSession != null) { + if (this.isPlayerInstalledYsm(watcher)) { + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, YsmState.ofBinary(binary)); + } else { + mapperSession.queueCitizensTrackerUpdate(entityId, binary); + } + } + } + } + } } - /** - * Invariant track sync using npcId to apply cached models directly to entityId. - */ - public void handleNpcTrackSync(ProxiedPlayer watcher, int npcId, int entityId) { - com.nguyendevs.freesia.waterfall.network.misc.NpcPersistenceManager.NpcEntry entry = npcPersistenceManager.getIdAssignments().get(npcId); - if (entry != null) { - byte[] binary = getCachedNpcModelBinary(entry.modelId()); - if (binary != null) { + public void handleCitizensUntrackSync(String serverName, ProxiedPlayer watcher, int npcId) { + final Map> playerMap = this.playerTrackedCitizens.get(watcher); + if (playerMap != null) { + Map serverMap = playerMap.get(serverName); + if (serverMap != null) { + serverMap.remove(npcId); + if (serverMap.isEmpty()) { + playerMap.remove(serverName); + } + } + if (playerMap.isEmpty()) { + this.playerTrackedCitizens.remove(watcher); + } + } + } + + public void broadcastCitizensSkinUpdate(String serverName, int npcId) { + Map serverAssignments = + citizensPersistenceManager.getServerIdAssignments().get(serverName); + if (serverAssignments == null) return; + + com.nguyendevs.freesia.waterfall.network.misc.CitizensPersistenceManager.CitizensEntry entry = serverAssignments.get(npcId); + if (entry == null) return; + + byte[] binary = getCachedCitizensModelBinary(entry.modelId()); + if (binary == null) return; + + final YsmState state = YsmState.ofBinary(binary); + + for (Map.Entry>> playerEntry : this.playerTrackedCitizens.entrySet()) { + final ProxiedPlayer watcher = playerEntry.getKey(); + Map serverTracked = playerEntry.getValue().get(serverName); + if (serverTracked == null) continue; + + final Integer entityId = serverTracked.get(npcId); + if (entityId != null) { final MapperSessionProcessor mapperSession = this.mapperSessions.get(watcher); if (mapperSession != null) { if (this.isPlayerInstalledYsm(watcher)) { - this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, YsmState.ofBinary(binary)); + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, state); + Freesia.PROXY_SERVER.getScheduler().schedule(Freesia.INSTANCE, () -> { + this.sendEntityStateToRaw(watcher.getUniqueId(), entityId, state); + }, 100, java.util.concurrent.TimeUnit.MILLISECONDS); } else { - mapperSession.queueNpcTrackerUpdate(entityId, binary); + mapperSession.queueCitizensTrackerUpdate(entityId, binary); } } - } else { - Freesia.LOGGER.warning("[NPC] Binary missing for model '" + entry.modelId() + "' on NPC ID " + npcId); } } } @@ -361,8 +408,9 @@ public boolean isPlayerInstalledYsm(UUID target) { public void onPlayerDisconnect(@NotNull ProxiedPlayer player) { this.ysmInstalledPlayers.remove(player.getUniqueId()); + this.playerTrackedCitizens.remove(player); - final MapperSessionProcessor mapperSession = this.mapperSessions.remove(player); + final MapperSessionProcessor mapperSession = this.mapperSessions.get(player); if (mapperSession != null) { this.disconnectMapperWithoutKickingMaster(mapperSession); @@ -386,39 +434,11 @@ public void onPlayerDisconnect(@NotNull ProxiedPlayer player) { protected void onWorkerSessionDisconnect(@NotNull MapperSessionProcessor mapperSession, boolean kickMaster, @Nullable Component reason) { - // Kick the master it binds if (kickMaster) mapperSession.getBindPlayer().disconnect(Freesia.languageManager.i18n( FreesiaConstants.LanguageConstants.WORKER_TERMINATED_CONNECTION, List.of("reason"), - List.of(reason == null ? Component.text("DISCONNECTED MANUAL") : reason)).toString()); // In Bungee - // you pass - // TextComponent - // or String. - // Wait, we - // should use - // basecomponent - // if using - // KYORI - // directly, - // but - // toString - // for now. - // Actually, - // Kyori - // Bungee - // adapter - // exists but - // let's use - // BungeeCord - // string or - // base - // components. - // We'll fix - // i18n - // later. - - // Remove from list + List.of(reason == null ? Component.text("DISCONNECTED MANUAL") : reason)).toString()); this.mapperSessions.remove(mapperSession.getBindPlayer()); } @@ -431,8 +451,6 @@ public void onPluginMessageIn(@NotNull ProxiedPlayer player, @NotNull String cha Freesia.LOGGER.info("[DEBUG] YSM Packet from " + player.getName() + " on " + channel + " (len=" + packetData.length + "): " + debugHex.toString()); } - - // Check if it is the message of ysm if (!channel.equals(YSM_CHANNEL_KEY_VELOCITY)) { return; } @@ -440,7 +458,6 @@ public void onPluginMessageIn(@NotNull ProxiedPlayer player, @NotNull String cha final MapperSessionProcessor mapperSession = this.mapperSessions.get(player); if (mapperSession == null) { - // Actually it shouldn't be and never be happened Freesia.LOGGER.warning("Mapper session not found or ready for player " + player.getName()); return; } @@ -452,7 +469,6 @@ public void onBackendReady(ProxiedPlayer player) { final MapperSessionProcessor mapperSession = this.mapperSessions.get(player); if (mapperSession == null) { - // race condition: already disconnected return; } @@ -534,12 +550,10 @@ public boolean hasMapperSession(ProxiedPlayer player) { public boolean disconnectAlreadyConnected(ProxiedPlayer player) { final MapperSessionProcessor current = this.mapperSessions.get(player); - // Not exists or created if (current == null) { return false; } - // Will do remove in the callback this.disconnectMapperWithoutKickingMaster(current); return true; } @@ -575,7 +589,6 @@ public void initMapperPacketProcessor(@NotNull ProxiedPlayer player) { } public void createMapperSession(@NotNull ProxiedPlayer player, @NotNull InetSocketAddress backend) { - // Instance new session final TcpClientSession mapperSession = new TcpClientSession( backend.getHostName(), backend.getPort(), @@ -585,22 +598,18 @@ public void createMapperSession(@NotNull ProxiedPlayer player, @NotNull InetSock player.getName()), null)); - // Our packet processor for packet forwarding final MapperSessionProcessor packetProcessor = this.mapperSessions.get(player); if (packetProcessor == null) { - // Should be created in ServerPreConnectEvent throw new IllegalStateException("Mapper session not found or ready for player " + player.getName()); } packetProcessor.setSession(mapperSession); mapperSession.addListener(packetProcessor); - // Default as Minecraft client mapperSession.setFlag(BuiltinFlags.READ_TIMEOUT, 30_000); mapperSession.setFlag(BuiltinFlags.WRITE_TIMEOUT, 30_000); - // Do connect mapperSession.connect(true, false); } @@ -618,7 +627,6 @@ public void onVirtualPlayerTrackerUpdate(UUID owner, ProxiedPlayer watcher) { } if (this.isPlayerInstalledYsm(watcher)) { - // Queue it properly through the mapper session so we don't send to unprepared clients if (!mapperSession.queueTrackerUpdate(owner)) { virtualProxy.sendEntityStateTo(watcher); } @@ -627,22 +635,11 @@ public void onVirtualPlayerTrackerUpdate(UUID owner, ProxiedPlayer watcher) { public void onRealPlayerTrackerUpdate(ProxiedPlayer beingWatched, ProxiedPlayer watcher) { final MapperSessionProcessor mapperSession = this.mapperSessions.get(beingWatched); - - // The mapper was created earlier than the player's connection turned in-game - // state - // so as the result, we could simply pass it down directly if (mapperSession == null) { - // Should not be happened - // We use random player as the payload of custom payload of freesia tracker, so - // there is a possibility - // that race condition would happen between the disconnect logic and tracker - // update logic - // throw new IllegalStateException("???"); return; } - if (this.isPlayerInstalledYsm(watcher)) { // Skip players who don't install ysm - // Check if ready + if (this.isPlayerInstalledYsm(watcher)) { if (!mapperSession.queueTrackerUpdate(watcher.getUniqueId())) { mapperSession.getPacketProxy().sendEntityStateTo(watcher); } diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmPacketProxy.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmPacketProxy.java index 9f24d4e..cd275e9 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmPacketProxy.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/network/ysm/YsmPacketProxy.java @@ -8,7 +8,6 @@ public interface YsmPacketProxy { default void setParentHandler(MapperSessionProcessor processor) { - // No-op by default } ProxyComputeResult processS2C(Key channelKey, ByteBuf copiedPacketData); diff --git a/Freesia-Waterfall/src/main/resources/lang/en_US.lang b/Freesia-Waterfall/src/main/resources/lang/en_US.lang index 2445afc..6d74824 100644 --- a/Freesia-Waterfall/src/main/resources/lang/en_US.lang +++ b/Freesia-Waterfall/src/main/resources/lang/en_US.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm backend has been disconnected, reason: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> There is not a valid connection to ysm backend! If you still meet this issue. please report to the administrators -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Cannot found specified worker -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Handshake time outed, please ensure you have installed the corrected version of ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Sent setskin request for NPC with model . Check backend logs for result. -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> No online players to relay the command to Backend. -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens is not enabled on the Backend server. \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm backend has been disconnected, reason: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> There is not a valid connection to ysm backend! If you still meet this issue. please report to the administrators +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Cannot found specified worker +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Handshake time outed, please ensure you have installed the corrected version of ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Sent setskin request for Citizens with model . Check backend logs for result. +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> No online players to relay the command to Backend. +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens is not enabled on the Backend server. +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>No permission. +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Please provide a server name. +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#a6e3a1>Proxy configurations reloaded successfully! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Failed to reload configurations: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> must be an integer. +freesia.command.usage.header=<#f9e2af>Freesia Command Help: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- List all Ysm players +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- Dispatch command to worker +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- Set Citizens skin +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- Reload configurations diff --git a/Freesia-Waterfall/src/main/resources/lang/vi_VN.lang b/Freesia-Waterfall/src/main/resources/lang/vi_VN.lang index 98ec4cb..8e714d8 100644 --- a/Freesia-Waterfall/src/main/resources/lang/vi_VN.lang +++ b/Freesia-Waterfall/src/main/resources/lang/vi_VN.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Backend Ysm đã ngắt kết nối, lý do: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có kết nối hợp lệ tới backend Ysm! Nếu bạn vẫn gặp sự cố này, vui lòng báo cáo cho quản trị viên -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không tìm thấy worker được chỉ định -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Bắt tay kết nối đã hết thời gian chờ, vui lòng đảm bảo bạn đã cài đúng phiên bản Ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Danh sách người chơi Ysm: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Đã gửi yêu cầu setskin cho NPC với model . Kiểm tra log backend để xem kết quả. -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có người chơi nào online để chuyển tiếp lệnh tới Backend. -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens chưa được bật trên server Backend. \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Backend Ysm đã ngắt kết nối, lý do: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có kết nối hợp lệ tới backend Ysm! Nếu bạn vẫn gặp sự cố này, vui lòng báo cáo cho quản trị viên +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không tìm thấy worker được chỉ định +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Bắt tay kết nối đã hết thời gian chờ, vui lòng đảm bảo bạn đã cài đúng phiên bản Ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Danh sách người chơi Ysm: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> Đã gửi yêu cầu setskin cho Citizens với model . Kiểm tra log backend để xem kết quả. +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Không có người chơi nào online để chuyển tiếp lệnh tới Backend. +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens chưa được bật trên server Backend. +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Bạn không có quyền thực hiện việc này. +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Vui lòng cung cấp tên máy chủ. +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#a6e3a1>Đã tải lại cấu hình Proxy thành công! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>Tải lại cấu hình Proxy thất bại: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> phải là một số nguyên. +freesia.command.usage.header=<#f9e2af>Hướng dẫn lệnh Freesia: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- Liệt kê người chơi Ysm +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- Gửi lệnh tới worker +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- Đặt skin cho Citizens +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- Tải lại cấu hình diff --git a/Freesia-Waterfall/src/main/resources/lang/zh_CN.lang b/Freesia-Waterfall/src/main/resources/lang/zh_CN.lang index 5b30857..0c98319 100644 --- a/Freesia-Waterfall/src/main/resources/lang/zh_CN.lang +++ b/Freesia-Waterfall/src/main/resources/lang/zh_CN.lang @@ -1,10 +1,20 @@ -freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm后端worker断开了连接,原因: -freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 你没有连接到Ysm后端worker.如果反复遇到该问题,请汇报管理员! -freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 找不到该worker! -freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] -freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm握手超时,请确认您安装了正确版本的Ysm -freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: -freesia.list_player_command_body=<#cdd6f4> -freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> 已发送NPC 的setskin请求,模型为 。请查看后端日志确认结果。 -freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 没有在线玩家可用于转发命令到后端。 -freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens未在后端服务器上启用。 \ No newline at end of file +freesia.backend.disconnected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm后端worker断开了连接,原因: +freesia.backend.not_connected=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 你没有连接到Ysm后端worker.如果反复遇到该问题,请汇报管理员! +freesia.worker_command.worker_not_found=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 找不到该worker! +freesia.worker_command.command_feedback=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] [<#cdd6f4>] +freesia.mod_handshake_time_outed=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Ysm握手超时,请确认您安装了正确版本的Ysm +freesia.list_player_command_header=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] Ysm Player List: +freesia.list_player_command_body=<#cdd6f4> +freesia.setskin.success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#a6e3a1> 已发送Citizens 的setskin请求,模型为 。请查看后端日志确认结果。 +freesia.setskin.no_players=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> 没有在线玩家可用于转发命令到后端。 +freesia.setskin.citizens_disabled=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>]<#f38ba8> Citizens未在后端服务器上启用。 +freesia.command.no_permission=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>没有权限。 +freesia.command.provide_server_name=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>请提供服务器名称。 +freesia.command.reload_success=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#a6e3a1>代理配置重载成功! +freesia.command.reload_fail=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8>重载配置失败: +freesia.command.citizens_id_integer=<#cdd6f4>[<#89dceb>Freesia<#cdd6f4>] <#f38ba8> 必须是整数。 +freesia.command.usage.header=<#f9e2af>Freesia 指令帮助: +freesia.command.usage.listplayers=<#fab387>/freesia listplayers <#cdd6f4>- 列出所有 Ysm 玩家 +freesia.command.usage.dworkerc=<#fab387>/freesia dworkerc <#cdd6f4>- 向 worker 发送指令 +freesia.command.usage.setskin=<#fab387>/freesia setskin [serverId] <#cdd6f4>- 设置 Citizens 皮肤 +freesia.command.usage.reload=<#fab387>/freesia reload <#cdd6f4>- 重载配置 diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java index cbba589..693ef19 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java @@ -7,6 +7,10 @@ import com.nguyendevs.freesia.common.communicating.NettySocketClient; import com.nguyendevs.freesia.worker.impl.WorkerMessageHandlerImpl; import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import com.mojang.brigadier.arguments.StringArgumentType; +import net.minecraft.commands.Commands; +import com.nguyendevs.freesia.common.communicating.message.w2m.W2MDispatchCommandRequestMessage; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.MinecraftServer; @@ -62,6 +66,18 @@ protected boolean shouldDoNextReconnect() { }; connectToBackend(); + + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register(Commands.literal("freesia") + .then(Commands.argument("args", StringArgumentType.greedyString()) + .executes(context -> { + String command = StringArgumentType.getString(context, "args"); + if (clientInstance != null) { + clientInstance.sendToMaster(new W2MDispatchCommandRequestMessage("freesia " + command)); + } + return 1; + }))); + }); } } diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ConnectionMixin.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ConnectionMixin.java index 9b31931..a65a84d 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ConnectionMixin.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ConnectionMixin.java @@ -32,10 +32,6 @@ public abstract class ConnectionMixin { @Shadow private Channel channel; - /** - * @author MrHua269 - * @reason Force disable packet compressors - */ @Overwrite public void setupCompression(int i, boolean bl) { if (i >= 0) { diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/MinecraftServerMixin.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/MinecraftServerMixin.java index 1c9675b..5dc2928 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/MinecraftServerMixin.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/MinecraftServerMixin.java @@ -45,10 +45,6 @@ public void onExecutingChunkSystemTasks(@NotNull CallbackInfoReturnable cir.setReturnValue(false); } - /** - * @author MrHua269 - * @reason Kill the ticking - */ @Redirect(method = "tickChildren", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;tick(Ljava/util/function/BooleanSupplier;)V")) public void tickLevelHook(ServerLevel instance, BooleanSupplier booleanSupplier) { this.shouldPollTask = true; diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerGamePacketListenerImplMixin.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerGamePacketListenerImplMixin.java index 942aee7..f9cdde0 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerGamePacketListenerImplMixin.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerGamePacketListenerImplMixin.java @@ -11,10 +11,6 @@ public abstract class ServerGamePacketListenerImplMixin { @Shadow private int tickCount; - /** - * @author MrHua269 - * @reason Only keep basic logics - */ @Overwrite public void tick() { ++this.tickCount; diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerLoginPacketListenerImplMixin.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerLoginPacketListenerImplMixin.java index f785bde..f88bf9f 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerLoginPacketListenerImplMixin.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/mixin/ServerLoginPacketListenerImplMixin.java @@ -21,10 +21,6 @@ public abstract class ServerLoginPacketListenerImplMixin { @Shadow abstract void startClientVerification(GameProfile gameProfile); - /** - * @author MrHua269 - * @reason Kill UUID checks and preload player data - */ @Overwrite public void handleHello(@NotNull ServerboundHelloPacket serverboundHelloPacket) { this.requestedUsername = serverboundHelloPacket.name(); diff --git a/settings.gradle b/settings.gradle index 5a8059b..f2a6d81 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,4 +19,4 @@ include("Freesia-Waterfall") include("Freesia-Backend") include("Freesia-Common") include("Freesia-Worker") -include("Freesia-NPC") \ No newline at end of file +include("Freesia-Citizens") \ No newline at end of file