diff --git a/gradle.properties b/gradle.properties index 1d78eeb..a64735b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,7 +34,7 @@ mod_name=PlayerSync # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPL-3.0 license # The mod version. See https://semver.org/ -mod_version=2.1.0 +mod_version=2.1.2 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/vip/fubuki/playersync/CommandInit.java b/src/main/java/vip/fubuki/playersync/CommandInit.java new file mode 100644 index 0000000..5ca2cd7 --- /dev/null +++ b/src/main/java/vip/fubuki/playersync/CommandInit.java @@ -0,0 +1,28 @@ +package vip.fubuki.playersync; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import vip.fubuki.playersync.sync.chat.ChatSyncClient; + +@Mod.EventBusSubscriber() +public class CommandInit { + + @SubscribeEvent + public static void registerCommand(RegisterCommandsEvent event){ + CommandDispatcher dispatcher=event.getDispatcher(); + dispatcher.register(Commands.literal("playersync") + .requires(cs->cs.hasPermission(2)) + .then(Commands.literal("reconnect") + .executes(context -> { + new ChatSyncClient().run(); +// context.getSource().sendSuccess(()->MutableComponent.create(new TranslatableContents("playersync.command.reconnect")),true); + return 0; + } + )) + ); + } +} diff --git a/src/main/java/vip/fubuki/playersync/sync/ChatSync.java b/src/main/java/vip/fubuki/playersync/sync/ChatSync.java index 4d085b6..02b8661 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ChatSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/ChatSync.java @@ -1,140 +1,38 @@ package vip.fubuki.playersync.sync; -import net.minecraft.network.chat.Component; -import net.minecraft.server.players.PlayerList; -import net.neoforged.bus.api.SubscribeEvent; +import com.mojang.logging.LogUtils; import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.slf4j.Logger; import vip.fubuki.playersync.config.JdbcConfig; +import vip.fubuki.playersync.sync.chat.ChatSyncClient; +import vip.fubuki.playersync.sync.chat.ChatSyncServer; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.Objects; -import java.util.Scanner; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.slf4j.Logger; - -import com.mojang.logging.LogUtils; public class ChatSync { - private static final Logger LOGGER = LogUtils.getLogger(); - - static PlayerList playerList; - - static ServerSocket serverSocket; - static Socket clientSocket; - static Set SocketList = ConcurrentHashMap.newKeySet(); - static ExecutorService executorService = Executors.newCachedThreadPool(); + public static final Logger LOGGER = LogUtils.getLogger(); public static void register(){ if(JdbcConfig.IS_CHAT_SERVER.get()) { - LOGGER.info("Launching chat server thread."); - new Thread(ChatSync::ServerSocket).start(); - } - ClientSocket(); - NeoForge.EVENT_BUS.register(ChatSync.class); - } - - - private static void ServerSocket() { - try { LOGGER.info("Trying to setup chat server at port " + JdbcConfig.CHAT_SERVER_PORT.get()); - serverSocket = new ServerSocket(JdbcConfig.CHAT_SERVER_PORT.get()); - while (true) { - Socket newSocket = serverSocket.accept(); - SocketList.add(newSocket); - executorService.submit(() -> handleClient(newSocket)); - } - } catch (IOException e) { - LOGGER.error("Unable to start chat server"); - e.printStackTrace(); - } finally { - try { - serverSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void handleClient(Socket socket) { - try (InputStream inputStream = socket.getInputStream()) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - String message = new String(buffer, 0, bytesRead); - broadcastMessage(socket, message); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - SocketList.remove(socket); - try { - socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void broadcastMessage(Socket sender, String message) { - for (Socket socket : SocketList) { - if (!socket.equals(sender)) { + new Thread(()->{ + ChatSyncServer chatSyncServer = new ChatSyncServer(); try { - OutputStream outputStream = socket.getOutputStream(); - outputStream.write(message.getBytes()); + chatSyncServer.run(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Unable to start chat server", e); } - } + }).start(); } - } - private static void ClientSocket() { - try { + new Thread(()->{ LOGGER.info("Trying to connect to chat server " + JdbcConfig.CHAT_SERVER_IP.get() + ":" + JdbcConfig.CHAT_SERVER_PORT.get()); - clientSocket = new Socket(JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_PORT.get()); - Scanner scanner = new Scanner(clientSocket.getInputStream()); - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - Component textComponents = Component.nullToEmpty(line); - playerList.broadcastSystemMessage(textComponents,true); - } - } catch (IOException e) { - e.printStackTrace(); - reconnectClient(); - } - } - - private static void reconnectClient() { - LOGGER.warn("TODO: implement reconnectClient()"); - //TODO - } - - @SubscribeEvent - public static void onPlayerChat(net.neoforged.neoforge.event.ServerChatEvent event) throws IOException { - String message= event.getUsername()+":"+event.getMessage(); - OutputStream outputStream = clientSocket.getOutputStream(); - outputStream.write(message.getBytes()); - } - - @SubscribeEvent - public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){ - playerList= Objects.requireNonNull(event.getEntity().getServer()).getPlayerList(); - } - - @SubscribeEvent - public static void onPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event){ - playerList= Objects.requireNonNull(event.getEntity().getServer()).getPlayerList(); + ChatSyncClient chatSyncClient = new ChatSyncClient(); + chatSyncClient.run(); + }).start(); + NeoForge.EVENT_BUS.register(ChatSyncClient.class); } } diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 86e4816..8bef268 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -1,13 +1,9 @@ package vip.fubuki.playersync.sync; import com.mojang.brigadier.exceptions.CommandSyntaxException; - import net.minecraft.ChatFormatting; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.*; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; @@ -29,7 +25,6 @@ import net.neoforged.neoforge.event.OnDatapackSyncEvent; import net.neoforged.neoforge.event.TickEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStoppedEvent; -import net.minecraft.core.registries.BuiltInRegistries; import net.neoforged.neoforge.server.ServerLifecycleHooks; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.config.JdbcConfig; @@ -43,11 +38,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -184,10 +175,8 @@ public class VanillaSync { // Restore basic attributes serverPlayer.setHealth(rs2.getInt("health")); serverPlayer.getFoodData().setFoodLevel(rs2.getInt("food_level")); - serverPlayer.totalExperience = 0; - serverPlayer.experienceLevel = 0; - serverPlayer.experienceProgress = 0; - serverPlayer.giveExperiencePoints(rs2.getInt("xp")); + + setXpForPlayer(serverPlayer, rs2.getInt("xp")); serverPlayer.setScore(rs2.getInt("score")); // Restore left-hand item @@ -635,4 +624,52 @@ public class VanillaSync { } } } + + private static void setXpForPlayer(ServerPlayer serverPlayer, int databaseXp) { + // Don't use giveExperience() as it has several side-effects: + // triggers an event, sends network packets, increases the score, ... + serverPlayer.totalExperience = databaseXp; + serverPlayer.experienceLevel = 0; + serverPlayer.experienceProgress = 0; + + int xpForLevel; + + while (databaseXp >= (xpForLevel = serverPlayer.getXpNeededForNextLevel())) { + databaseXp -= xpForLevel; + serverPlayer.experienceLevel++; + } + + serverPlayer.experienceProgress = serverPlayer.experienceLevel > 0 + ? (float) databaseXp / serverPlayer.getXpNeededForNextLevel() + : 0f; + + PlayerSync.LOGGER.debug("Giving player " + + serverPlayer.experienceLevel + " levels and " + + serverPlayer.experienceProgress * 100 + "% experience progress, calculated from " + + serverPlayer.totalExperience + " XP."); + } + + private static int getTotalExperience(final Player player) { + int level = player.experienceLevel; + int totalXp = 0; + + // Calculate total XP for completed levels + if (level > 30) { + totalXp = (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220); + } else if (level > 15) { + totalXp = (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360); + } else { + totalXp = level * level + 6 * level; + } + + // Add partial level progress + totalXp += Math.round(player.getXpNeededForNextLevel() * player.experienceProgress); + + PlayerSync.LOGGER.debug("Experience calcuation for " + + player.experienceLevel + " levels and " + + player.experienceProgress * 100 + "% experience progress yields " + + totalXp + " XP."); + + return totalXp; + } } diff --git a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java new file mode 100644 index 0000000..d8de645 --- /dev/null +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java @@ -0,0 +1,66 @@ +package vip.fubuki.playersync.sync.chat; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.players.PlayerList; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import vip.fubuki.playersync.PlayerSync; +import vip.fubuki.playersync.config.JdbcConfig; +import vip.fubuki.playersync.sync.ChatSync; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.Objects; + +public class ChatSyncClient { + static PlayerList playerList; + static Socket clientSocket; + static PrintWriter out; + + public void run() { + try { + clientSocket = new Socket(JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_PORT.get()); + out = new PrintWriter(clientSocket.getOutputStream(),true); + + BufferedReader in = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream())); + + String serverMessage; + while ((serverMessage = in.readLine()) != null) { + PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage); + Component textComponents = Component.nullToEmpty(serverMessage); + playerList.broadcastSystemMessage(textComponents,false); + } + } catch (IOException e) { + e.printStackTrace(); + reconnectClient(); + } + } + + private void reconnectClient() { + ChatSync.LOGGER.warn("TODO: implement reconnectClient()"); + //TODO + } + + @SubscribeEvent + public static void onPlayerChat(ServerChatEvent event) { + String message= "<"+event.getUsername()+"> "+event.getMessage().getString(); + if (out != null) { + out.println(message); + } + } + + @SubscribeEvent + public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){ + playerList = Objects.requireNonNull(event.getEntity().getServer()).getPlayerList(); + } + + @SubscribeEvent + public static void onPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event){ + playerList = Objects.requireNonNull(event.getEntity().getServer()).getPlayerList(); + } +} diff --git a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java new file mode 100644 index 0000000..a9ecf65 --- /dev/null +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java @@ -0,0 +1,63 @@ +package vip.fubuki.playersync.sync.chat; + +import vip.fubuki.playersync.config.JdbcConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ChatSyncServer { + static ServerSocket serverSocket; + static final Set SocketList = ConcurrentHashMap.newKeySet(); + static final ExecutorService executorService = Executors.newCachedThreadPool(); + + public void run() throws IOException { + serverSocket = new ServerSocket(JdbcConfig.CHAT_SERVER_PORT.get()); + while (!Thread.currentThread().isInterrupted()) { + Socket newSocket = serverSocket.accept(); + SocketList.add(newSocket); + executorService.submit(() -> handleClient(newSocket)); + } + serverSocket.close(); + } + + private void handleClient(Socket socket) { + try (InputStream inputStream = socket.getInputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + String message = new String(buffer, 0, bytesRead); + broadcastMessage(socket, message); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + SocketList.remove(socket); + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void broadcastMessage(Socket sender, String message) { + for (Socket socket : SocketList) { + if (!socket.equals(sender)) { + try { + OutputStream outputStream = socket.getOutputStream(); + outputStream.write(message.getBytes()); + outputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/src/main/resources/assets/playersync/lang/en_us.json b/src/main/resources/assets/playersync/lang/en_us.json index fceecfd..4b03f8e 100644 --- a/src/main/resources/assets/playersync/lang/en_us.json +++ b/src/main/resources/assets/playersync/lang/en_us.json @@ -1,5 +1,5 @@ { - "playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.","playersync.placeholder_titel_override": "Item Voucher", + "playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.", "playersync.item_placeholder_title": "Item Voucher", "playersync.already_online": "You can't join more than one synchronization server at the same time." } diff --git a/src/main/resources/assets/playersync/lang/zh_cn.json b/src/main/resources/assets/playersync/lang/zh_cn.json index a19647d..f206090 100644 --- a/src/main/resources/assets/playersync/lang/zh_cn.json +++ b/src/main/resources/assets/playersync/lang/zh_cn.json @@ -1,3 +1,5 @@ { + "playersync.item_placeholder_description": "物品在此服务器未知。这可能是一个模组物品,或是不同版本的原版物品。\n这张券将会在加入可识别此物品的服务器后自动替换为对应物品。", + "playersync.item_placeholder_title": "物品券", "playersync.already_online": "你不能同时加入多个同步的服务器。" -} \ No newline at end of file +}