From 0c62d360d98a9f1ddf0435fb3777ce45479805ac Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Thu, 12 Mar 2026 23:56:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B7=A8=E7=89=88=E6=9C=AC=E5=8C=96?= =?UTF-8?q?=EF=BC=8C=E7=AC=AC=E4=B8=89=E9=83=A8=E5=88=86=EF=BC=8C=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/top/r3944realms/lib39/Lib39.java | 43 + .../base/command/Lib39CommandHelpManager.java | 36 + .../lib39/base/command/Lib39HelpCommand.java | 404 ++++++ .../datagen/provider/Lib39RecipeProvider.java | 36 + .../base/datagen/value/Lib39LangKey.java | 874 ++++++++++++ .../core/command/ICommandHelpManager.java | 547 +++++++ .../lib39/core/command/IHelpCommand.java | 131 ++ .../command/SimpleCommandHelpManager.java | 87 ++ .../lib39/core/command/SimpleHelpCommand.java | 36 + .../lib39/core/command/model/CommandNode.java | 1266 +++++++++++++++++ .../lib39/core/command/model/CommandPath.java | 127 ++ .../lib39/core/command/model/Parameter.java | 78 + .../lib39/core/compat/CompatManager.java | 157 ++ .../lib39/core/compat/ICompat.java | 77 + .../lib39/core/lang/ClassEncryptor.java | 92 ++ .../lib39/core/lang/EncryptedClassLoader.java | 227 +++ .../lib39/core/registry/LocaleRegistry.java | 78 + .../lib39/core/sync/CachedSyncManager.java | 74 + .../r3944realms/lib39/core/sync/IEntity.java | 13 + .../lib39/core/sync/INBTSerializable.java | 9 + .../lib39/core/sync/ISyncData.java | 50 + .../lib39/core/sync/ISyncManager.java | 129 ++ .../r3944realms/lib39/core/sync/IUpdate.java | 6 + .../lib39/core/sync/NBTEntitySyncData.java | 58 + .../lib39/core/sync/SyncData2Manager.java | 460 ++++++ .../lib39/example/Lib39Example.java | 29 + .../example/client/screen/ForgeScreen.java | 128 ++ .../content/data/AbstractedTestSyncData.java | 240 ++++ .../content/item/AbstractFabricItem.java | 539 +++++++ .../content/item/AbstractNeoForgeItem.java | 282 ++++ .../lib39/example/content/item/ForgeItem.java | 40 + .../example/core/register/ExLib39Items.java | 24 + .../lib39/mixin/minecraft/ScreenAccessor.java | 14 + .../platform/services/IHelpCommandHook.java | 11 + .../platform/services/IPlatformHelper.java | 2 + common/src/main/resources/lib39.mixins.json | 1 + .../top/r3944realms/lib39/Lib39Fabric.java | 5 +- .../r3944realms/lib39/Lib39FabricClient.java | 35 +- .../lib39/api/callback/ActionResult.java | 21 + .../MinecraftSetUpServiceCallback.java | 11 +- .../callback/RegisterCommandHelpCallback.java | 120 ++ .../callback/SyncManagerRegisterCallback.java | 158 ++ .../callback/client/ClientWorldCallback.java | 28 + .../lib39/client/ItemRenderRegister.java | 14 + .../lib39/client/ShaderRegister.java | 26 + .../lib39/core/CreativeTabAdder.java | 48 - .../lib39/core/event/CommonEventHandler.java | 173 +++ .../lib39/core/network/NetworkHandler.java | 19 + .../SyncNBTLookupDataEntityS2CPacket.java | 73 + .../lib39/core/sync/IFabricUpdate.java | 25 + .../core/sync/SyncData2LookupManager.java | 96 ++ .../lib39/example/FabricLib39Examples.java | 5 + .../example/content/data/TestSyncData.java | 359 +++++ .../example/content/item/FabricItem.java | 77 + .../example/content/item/NeoForgeItem.java | 17 + .../core/network/ClientDataPacket.java | 64 + .../core/network/ExNetworkHandler.java | 15 + .../MixinDedicateServer.java | 2 +- .../{init => callback}/MixinMinecraft.java | 21 +- .../callback/client/MixinClientLevel.java | 30 + .../lib39/platform/FabricHelpCommandHook.java | 17 + .../lib39/platform/FabricPlatformHelper.java | 6 + .../util/FabricBlockRegistryBuilder.java | 4 +- ...ms.lib39.platform.services.IPlatformHelper | 2 +- .../main/resources/lib39.fabric.mixins.json | 5 +- .../api/event/RegisterCommandHelpEvent.java | 100 ++ .../api/event/SyncManagerRegisterEvent.java | 125 ++ .../base/datagen/Lib39BaseDataGenEvent.java | 85 ++ .../datagen/provider/Lib39BlockLootTable.java | 18 + .../provider/Lib39BlockModelProvider.java | 44 + .../provider/Lib39BlockStatesProvider.java | 74 + .../provider/Lib39ItemModelProvider.java | 123 ++ .../Lib39SoundDefinitionsProvider.java | 45 + .../lib39/content/item/ForgeDollItem.java | 24 + .../lib39/core/compat/ForgeCompatManager.java | 260 ++++ .../lib39/core/compat/IForgeCompat.java | 62 + .../lib39/core/event/CommonEventHandler.java | 16 +- .../lib39/core/network/NetworkHandler.java | 72 + .../SyncNBTCapDataEntityS2CPacket.java | 93 ++ .../lib39/core/register/ForgeLib39Items.java | 3 +- .../lib39/core/sync/IForgeUpdate.java | 10 + .../lib39/core/sync/SyncData2CapManager.java | 85 ++ .../provider/subprovider/BlockLootTables.java | 66 +- .../lib39/example/ForgeLib39Example.java | 5 + .../lib39/example/compat/Lib39Compat.java | 58 + .../example/compat/Lib39CompatManager.java | 19 + .../content/data/ExCapabilityHandler.java | 40 + .../content/data/TestSyncCapProvider.java | 49 + .../example/content/data/TestSyncData.java | 357 +++++ .../example/content/item/FabricItem.java | 61 + .../example/content/item/NeoForgeItem.java | 32 + .../core/event/ExClientEventHandler.java | 16 + .../core/event/ExCommonEventHandler.java | 121 ++ .../core/event/ExServerEventHandler.java | 16 + .../core/network/ClientDataPacket.java | 70 + .../core/network/ExNetworkHandler.java | 37 + .../example/core/register/ExLib39Items.java | 61 + .../lib39/platform/ForgeHelpCommandHook.java | 19 + .../lib39/platform/ForgePlatformHelper.java | 6 + .../capability/AbstractedTestSyncData.java | 2 +- .../capability/ExCapabilityHandler.java | 2 +- .../capability/TestSyncCapProvider.java | 2 +- .../content/capability/TestSyncData.java | 2 +- .../example/content/item/FabricItem.java | 6 +- .../example/content/item/NeoForgeItem.java | 6 +- .../core/event/ExCommonEventHandler.java | 6 +- .../core/network/ClientDataPacket.java | 2 +- 107 files changed, 10032 insertions(+), 149 deletions(-) create mode 100644 common/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java create mode 100644 common/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39RecipeProvider.java create mode 100644 common/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/IHelpCommand.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/compat/CompatManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/compat/ICompat.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/CachedSyncManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/IEntity.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/INBTSerializable.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/IUpdate.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/NBTEntitySyncData.java create mode 100644 common/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/Lib39Example.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/content/data/AbstractedTestSyncData.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractFabricItem.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractNeoForgeItem.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java create mode 100644 common/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java create mode 100644 common/src/main/java/top/r3944realms/lib39/mixin/minecraft/ScreenAccessor.java create mode 100644 common/src/main/java/top/r3944realms/lib39/platform/services/IHelpCommandHook.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/api/callback/ActionResult.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/api/callback/RegisterCommandHelpCallback.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/api/callback/SyncManagerRegisterCallback.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/api/callback/client/ClientWorldCallback.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/client/ItemRenderRegister.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/client/ShaderRegister.java delete mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/CreativeTabAdder.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTLookupDataEntityS2CPacket.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/sync/IFabricUpdate.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/core/sync/SyncData2LookupManager.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/FabricLib39Examples.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java rename fabric/src/main/java/top/r3944realms/lib39/mixin/{init => callback}/MixinDedicateServer.java (98%) rename fabric/src/main/java/top/r3944realms/lib39/mixin/{init => callback}/MixinMinecraft.java (75%) create mode 100644 fabric/src/main/java/top/r3944realms/lib39/mixin/callback/client/MixinClientLevel.java create mode 100644 fabric/src/main/java/top/r3944realms/lib39/platform/FabricHelpCommandHook.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockLootTable.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39SoundDefinitionsProvider.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/content/item/ForgeDollItem.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/compat/ForgeCompatManager.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/compat/IForgeCompat.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTCapDataEntityS2CPacket.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/sync/IForgeUpdate.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/core/sync/SyncData2CapManager.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/ForgeLib39Example.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39Compat.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39CompatManager.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/content/data/ExCapabilityHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncCapProvider.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/event/ExClientEventHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/event/ExServerEventHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java create mode 100644 forge/src/main/java/top/r3944realms/lib39/platform/ForgeHelpCommandHook.java diff --git a/common/src/main/java/top/r3944realms/lib39/Lib39.java b/common/src/main/java/top/r3944realms/lib39/Lib39.java index 88c6d17..ad158dd 100644 --- a/common/src/main/java/top/r3944realms/lib39/Lib39.java +++ b/common/src/main/java/top/r3944realms/lib39/Lib39.java @@ -17,6 +17,10 @@ public class Lib39 { public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "lib39.enable_examples"; public static void initialize() { Lib39.LOGGER.info("[Lib39-Common] Lib39-Common start initialization."); + if (shouldRegisterExamples()) { + LOGGER.info("[Lib39-Common] Registering Examples"); + registerExamples(); + } Lib39.LOGGER.info("[Lib39-Common] Finished Lib39-Common!."); } /** @@ -56,4 +60,43 @@ public class Lib39 { public static boolean isClientEnvironment() { return Services.PLATFORM.isClientEnvironment(); } + + /** + * Should register examples boolean. + * + * @return the boolean + */ + public static boolean shouldRegisterExamples() { + return !Services.PLATFORM.isDevelopmentEnvironment() || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY); + } + + + /** + * Register examples. + */ + static void registerExamples() { + LOGGER.info("[Lib39] Starting example demonstrations"); + try { + // 创建示例实例并演示功能 + Lib39Example example = new Lib39Example(); + example.demonstrateFeature(); + + LOGGER.info("[Lib39] Example demonstrations completed successfully"); + } catch (Exception e) { + LOGGER.error("[Lib39] Failed to demonstrate examples", e); + } + } +} + /** + * The type Mod info. + */ + public static class ModInfo { + /** + * The constant VERSION. + */ + public static final String VERSION; + static { + VERSION = Services.PLATFORM.getModVersion(); + } + } } \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java b/common/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java new file mode 100644 index 0000000..bc43ef6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.base.command; + +import net.minecraft.resources.ResourceLocation; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.command.SimpleCommandHelpManager; + +/** + * The type Lib 39 command help manager. + */ +public class Lib39CommandHelpManager extends SimpleCommandHelpManager { + /** + * The constant INSTANCE. + */ + public static volatile Lib39CommandHelpManager INSTANCE = new Lib39CommandHelpManager(); + /** + * The Id. + */ + ResourceLocation ID = Lib39.rl("command_helper"); + + /** + * Instantiates a new Lib 39 command help manager. + */ + public Lib39CommandHelpManager() { + initialize(); + } + + @Override + public ResourceLocation getID() { + return ID; + } + + @Override + public String getHeadKey() { + return "lib39"; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java b/common/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java new file mode 100644 index 0000000..e0aa2b3 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java @@ -0,0 +1,404 @@ +package top.r3944realms.lib39.base.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.command.ICommandHelpManager; +import top.r3944realms.lib39.core.command.SimpleHelpCommand; + +import java.util.Map; + +/** + * The type Lib 39 help command. + */ +public class Lib39HelpCommand extends SimpleHelpCommand { + + /** + * Instantiates a new Lib 39 help command. + * + * @param event the event + */ + public Lib39HelpCommand(CommandDispatcher dispatcher, CommandBuildContext context) { + super(dispatcher, context); + if(Lib39.shouldRegisterExamples()) { + // 在這裡註冊測試命令 + registerTestCommands(dispatcher); + } + } + + @Override + public ICommandHelpManager getCommandHelpManager() { + return Lib39CommandHelpManager.INSTANCE; + } + + /** + * 註冊測試命令 + */ + private void registerTestCommands(@NotNull CommandDispatcher dispatcher) { + // 註冊幫助系統本身 + dispatcher.register( + getRoot() + .then(Commands.literal("test") + .executes(this::executeTest) + .then(Commands.argument("param", StringArgumentType.string()) + .executes(this::executeTestWithParam)) + ) + .then(Commands.literal("demo") + .executes(this::executeDemo) + ) + ); + + // 註冊其他測試命令 + registerTestCommandTree(dispatcher); + + // 在幫助系統中註冊這些命令 + registerCommandsInHelpSystem(); + } + + /** + * 註冊測試命令樹 + */ + private void registerTestCommandTree(@NotNull CommandDispatcher dispatcher) { + // 基本命令 + dispatcher.register( + Commands.literal("lib39") + .then(Commands.literal("greet") + .executes(this::executeGreet) + .then(Commands.argument("player", EntityArgument.player()) + .executes(this::executeGreetPlayer)) + ) + .then(Commands.literal("calculate") + .then(Commands.argument("a", IntegerArgumentType.integer()) + .then(Commands.argument("b", IntegerArgumentType.integer()) + .executes(this::executeCalculate))) + ) + .then(Commands.literal("teleport") + .requires(source -> source.hasPermission(2)) // 需要OP權限 + .then(Commands.argument("target", EntityArgument.player()) + .executes(this::executeTeleport)) + ) + .then(Commands.literal("info") + .executes(this::executeInfo) + ) + ); + + // 嵌套命令示例 + dispatcher.register( + Commands.literal("lib39") + .then(Commands.literal("team") + .then(Commands.literal("create") + .then(Commands.argument("teamName", StringArgumentType.string()) + .executes(this::executeTeamCreate)) + ) + .then(Commands.literal("join") + .then(Commands.argument("teamName", StringArgumentType.string()) + .executes(this::executeTeamJoin)) + ) + .then(Commands.literal("leave") + .executes(this::executeTeamLeave)) + ) + .then(Commands.literal("game") + .then(Commands.literal("start") + .then(Commands.argument("map", StringArgumentType.string()) + .executes(this::executeGameStart)) + ) + .then(Commands.literal("stop") + .executes(this::executeGameStop)) + .then(Commands.literal("pause") + .executes(this::executeGamePause)) + .then(Commands.literal("resume") + .executes(this::executeGameResume)) + ) + ); + } + + /** + * 在幫助系統中註冊命令 + */ + private void registerCommandsInHelpSystem() { + ICommandHelpManager helpManager = getCommandHelpManager(); + + // 使用Builder模式註冊完整的命令樹 + helpManager.registerCommands(builder -> { + builder.root("lib39", "commands.lib39.root") + .expanded(true) // 根節點默認展開 + + // 問候命令 - 添加多個子命令 + .branch("greet", "commands.lib39.greet.basic", greetBuilder -> { + greetBuilder.expanded(false); // 默認摺疊 + greetBuilder.leaf("hello", "commands.lib39.greet.hello"); + greetBuilder.leaf("morning", "commands.lib39.greet.morning"); + greetBuilder.leaf("evening", "commands.lib39.greet.evening"); + greetBuilder.push("player", "commands.lib39.greet.player") + .required("player") + .pop(); + }) + + // 計算命令 + .push("calculate", "commands.lib39.calculate") + .required("a") + .required("b") + .pop() + + // 傳送命令 + .push("teleport", "commands.lib39.teleport") + .required("target") + .pop() + + // 信息命令 + .leaf("info", "commands.lib39.info") + + // 隊伍系統 - 添加多個子命令 + .branch("team", "commands.lib39.team", teamBuilder -> { + teamBuilder.expanded(false); // 默認摺疊 + teamBuilder.leaf("create", "commands.lib39.team.create") + .required("teamName"); + teamBuilder.leaf("join", "commands.lib39.team.join") + .required("teamName"); + teamBuilder.leaf("leave", "commands.lib39.team.leave"); + teamBuilder.leaf("list", "commands.lib39.team.list"); + teamBuilder.leaf("info", "commands.lib39.team.info"); + }) + + // 遊戲系統 - 添加多個子命令 + .branch("game", "commands.lib39.game", gameBuilder -> { + gameBuilder.expanded(false); // 默認摺疊 + gameBuilder.leaf("start", "commands.lib39.game.start") + .required("map"); + gameBuilder.leaf("stop", "commands.lib39.game.stop"); + gameBuilder.leaf("pause", "commands.lib39.game.pause"); + gameBuilder.leaf("resume", "commands.lib39.game.resume"); + gameBuilder.leaf("status", "commands.lib39.game.status"); + }) + + // 設置命令 + .leavesT(Map.of( + "settings", "commands.lib39.settings", + "config", "commands.lib39.config", + "reload", "commands.lib39.reload", + "debug", "commands.lib39.debug", + "demo", "commands.lib39.demo", + "test", "commands.lib39.test" + )); + }); + } + + // ==================== 命令執行方法 ==================== + + private int executeTest(CommandContext context) { + CommandSourceStack source = context.getSource(); + source.sendSuccess(() -> + Component.translatable("commands.lib39.test.success") + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + return Command.SINGLE_SUCCESS; + } + + private int executeTestWithParam(CommandContext context) { + String param = StringArgumentType.getString(context, "param"); + CommandSourceStack source = context.getSource(); + source.sendSuccess(() -> + Component.translatable("commands.lib39.test.with_param", param) + .withStyle(net.minecraft.ChatFormatting.AQUA), + false + ); + return Command.SINGLE_SUCCESS; + } + + private int executeDemo(CommandContext context) { + CommandSourceStack source = context.getSource(); + source.sendSuccess(() -> + Component.translatable("commands.lib39.demo.message") + .withStyle(net.minecraft.ChatFormatting.GOLD), + false + ); + return Command.SINGLE_SUCCESS; + } + + private int executeGreet(CommandContext context) { + CommandSourceStack source = context.getSource(); + source.sendSuccess(() -> + Component.translatable("commands.lib39.greet.default") + .withStyle(net.minecraft.ChatFormatting.YELLOW), + false + ); + return Command.SINGLE_SUCCESS; + } + + private int executeGreetPlayer(CommandContext context) throws CommandSyntaxException { + ServerPlayer player = EntityArgument.getPlayer(context, "player"); + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.greet.player", player.getDisplayName()) + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + player.sendSystemMessage( + Component.translatable("commands.lib39.greet.received", source.getDisplayName()) + .withStyle(net.minecraft.ChatFormatting.AQUA) + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeCalculate(CommandContext context) { + int a = IntegerArgumentType.getInteger(context, "a"); + int b = IntegerArgumentType.getInteger(context, "b"); + int sum = a + b; + + CommandSourceStack source = context.getSource(); + source.sendSuccess(() -> + Component.translatable("commands.lib39.calculate.result", a, b, sum) + .withStyle(net.minecraft.ChatFormatting.LIGHT_PURPLE), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeTeleport(CommandContext context) throws CommandSyntaxException { + ServerPlayer target = EntityArgument.getPlayer(context, "target"); + CommandSourceStack source = context.getSource(); + + if (source.getEntity() instanceof ServerPlayer player) { + player.teleportTo( + target.serverLevel(), + target.getX(), + target.getY(), + target.getZ(), + target.getYRot(), + target.getXRot() + ); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.teleport.success", target.getDisplayName()) + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + } + + return Command.SINGLE_SUCCESS; + } + + private int executeInfo(CommandContext context) { + CommandSourceStack source = context.getSource(); + ResourceLocation dimension = source.getLevel().dimension().location(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.info.message") + .append("\n") + .append(Component.translatable("commands.lib39.info.dimension", dimension)) + .append("\n") + .append(Component.translatable("commands.lib39.info.position", + String.format("%.1f", source.getPosition().x()), + String.format("%.1f", source.getPosition().y()), + String.format("%.1f", source.getPosition().z()))) + .withStyle(net.minecraft.ChatFormatting.AQUA), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeTeamCreate(CommandContext context) { + String teamName = StringArgumentType.getString(context, "teamName"); + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.team.create.success", teamName) + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeTeamJoin(CommandContext context) { + String teamName = StringArgumentType.getString(context, "teamName"); + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.team.join.success", teamName) + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeTeamLeave(CommandContext context) { + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.team.leave.success") + .withStyle(net.minecraft.ChatFormatting.YELLOW), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeGameStart(CommandContext context) { + String map = StringArgumentType.getString(context, "map"); + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.game.start.success", map) + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeGameStop(CommandContext context) { + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.game.stop.success") + .withStyle(net.minecraft.ChatFormatting.RED), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeGamePause(CommandContext context) { + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.game.pause.success") + .withStyle(net.minecraft.ChatFormatting.YELLOW), + false + ); + + return Command.SINGLE_SUCCESS; + } + + private int executeGameResume(CommandContext context) { + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.game.resume.success") + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + return Command.SINGLE_SUCCESS; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39RecipeProvider.java b/common/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39RecipeProvider.java new file mode 100644 index 0000000..fa11437 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39RecipeProvider.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.base.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.item.Items; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.register.Lib39Items; + +import java.util.function.Consumer; + +/** + * The type Lib 39 recipe provider. + */ +public class Lib39RecipeProvider extends RecipeProvider { + /** + * Instantiates a new Lib 39 recipe provider. + * + * @param output the output + */ + public Lib39RecipeProvider(PackOutput output) { + super(output); + } + + @Override + protected void buildRecipes(@NotNull Consumer consumer) { + ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Lib39Items.DOLL.get()) + .requires(ItemTags.WOOL) + .requires(Items.ARMOR_STAND) + .unlockedBy("has_armor_stand",has(Items.ARMOR_STAND)) + .save(consumer); + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java b/common/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java new file mode 100644 index 0000000..f30c2e0 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java @@ -0,0 +1,874 @@ +package top.r3944realms.lib39.base.datagen.value; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.register.Lib39Blocks; +import top.r3944realms.lib39.core.register.Lib39Items; +import top.r3944realms.lib39.core.register.Lib39SoundEvents; +import top.r3944realms.lib39.datagen.value.ILangKeyValueCollection; +import top.r3944realms.lib39.datagen.value.LangKeyValue; +import top.r3944realms.lib39.datagen.value.ModPartEnum; +import top.r3944realms.lib39.example.core.register.ExLib39Items; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The enum Lib 39 lang key. + */ +public enum Lib39LangKey implements ILangKeyValueCollection { + /** + * Instance lib 39 lang key. + */ + INSTANCE; + Lib39LangKey() { + initLangKeyValues(); + } + + /** + * The type Message. + */ + public static final class Message { + private static final Set items = new HashSet<>(); + /** + * The constant HELP_HEADER. + */ + public static final LangKeyValue HELP_HEADER = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.header", ModPartEnum.MESSAGE, + "===== %s =====", + "===== %s 命令帮助 =====", + "===== %s 命令幫助 =====", + "〇〇 %s 〇〇", // 文言文:用〇〇表示分隔線 + true + ) + ); + + /** + * The constant HELP_CLICK_EXPAND. + */ + public static final LangKeyValue HELP_CLICK_EXPAND = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.click_expand", ModPartEnum.MESSAGE, + "Click to expand", + "點擊展開", + "點擊展開", + "點展", + true + ) + ); + + /** + * The constant HELP_PAGE_INFO. + */ + public static final LangKeyValue HELP_PAGE_INFO = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.page_info", ModPartEnum.MESSAGE, + "Page %d of %d", + "第 %d 页,共 %d 页", + "第 %d 頁,共 %d 頁", + "第 %d 卷,凡 %d 卷", // 文言文:用「卷」表示頁 + true + ) + ); + + /** + * The constant HELP_NO_ENTRIES. + */ + public static final LangKeyValue HELP_NO_ENTRIES = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.no_entries", ModPartEnum.MESSAGE, + "No help entries available", + "暂无帮助条目", + "暫無幫助條目", + "尚無助之目錄", // 文言文:尚無幫助的目錄 + true + ) + ); + + /** + * The constant HELP_TOGGLE_FAILED. + */ + public static final LangKeyValue HELP_TOGGLE_FAILED = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.toggle_failed", ModPartEnum.MESSAGE, + "Toggle Failed: No Hash Cached", + "切换失败: 无缓存Hash", + "切換失敗: 無緩存Hash", + "變更未果: 無貯存之哈希", // 文言文:變更沒有成功,沒有貯存的哈希 + true + ) + ); + + /** + * The constant HELP_COMMAND_NOT_FOUND. + */ + public static final LangKeyValue HELP_COMMAND_NOT_FOUND = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.command_not_found", ModPartEnum.MESSAGE, + "Command not found: %s", + "命令不存在: %s", + "指令不存在: %s", + "令未見之: %s", // 文言文:命令沒有見到 + true + ) + ); + /** + * The constant DOLL_SOUND. + */ + public static final LangKeyValue DOLL_SOUND = addAndRet( + LangKeyValue.ofKey( + Lib39SoundEvents.getSubTitleTranslateKey("duck_toy"), ModPartEnum.SOUND, + "Duck Doll Sound", + "玩偶声音", + "玩偶聲音", + "偶音", + true + ) + ); + + + /** + * The constant HELP_SUBCOMMANDS_TITLE. + */ + public static final LangKeyValue HELP_SUBCOMMANDS_TITLE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.subcommands_title", ModPartEnum.MESSAGE, + "Subcommands:", + "子命令:", + "子指令:", + "子令:", // 文言文:子命令 + true + ) + ); + + /** + * The constant HELP_NODE_EXPAND. + */ + public static final LangKeyValue HELP_NODE_EXPAND = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.node.expand", ModPartEnum.MESSAGE, + "%d subcommands collapsed", + "%d 个子命令已折叠", + "%d 個子指令已折疊", + "%d 子令已收", // 文言文:子命令已經收起 + true + ) + ); + + /** + * The constant HELP_NODE_TOGGLE_EXPAND. + */ + public static final LangKeyValue HELP_NODE_TOGGLE_EXPAND = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.node.toggle.expand", ModPartEnum.MESSAGE, + "Expand", + "展开", + "展開", + "展", // 文言文:展開 + true + ) + ); + + /** + * The constant HELP_NODE_TOGGLE_COLLAPSE. + */ + public static final LangKeyValue HELP_NODE_TOGGLE_COLLAPSE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.node.toggle.collapse", ModPartEnum.MESSAGE, + "Collapse", + "折叠", + "折疊", + "收", // 文言文:收起 + true + ) + ); + + /** + * The constant BASIC_HELP. + */ + public static final LangKeyValue BASIC_HELP = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.basic.help", ModPartEnum.MESSAGE, + "Show Help Info", + "显示帮助信息", + "顯示幫助信息", + "示助之訊", // 文言文:顯示幫助的訊息 + true + ) + ); + + /** + * The constant HELP_HOVER_COPY_TIP. + */ + public static final LangKeyValue HELP_HOVER_COPY_TIP = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.help.hover.copy", ModPartEnum.MESSAGE, + "Copy to clipboard", + "点击复制", + "點擊複製", + "點之複刻", // 文言文:點之複刻 + true + ) + ); + private static LangKeyValue addAndRet(LangKeyValue item) { + items.add(item); + return item; + } + + /** + * Gets items. + * + * @return the items + */ + public static Set getItems() { + return items; + } + } + + /** + * The Lang key values. + */ + final List langKeyValues = new ArrayList<>(); + + + /** + * Init lang key values. + */ + public void initLangKeyValues() { + Message.getItems().forEach(this::addLang); + LangKeyValue dollName = LangKeyValue.ofSupplier( + Lib39Items.DOLL, ModPartEnum.ITEM, + "Doll", "人偶", "人偶", "偶", false + ); + addLang(dollName); + addLang(LangKeyValue.copyOf( + Lib39Blocks.DOLL, ModPartEnum.BLOCK, dollName + )); + addLang(LangKeyValue.ofKey( + "tooltip.lib39.content.doll.hover.1", ModPartEnum.DESCRIPTION, + "§eSkinOwner §7:§a %s ", "§e皮肤所有者§7:§a%s", "§e皮膚所有者§7:§a%s", "§e膚主§7:§a%s" + )); + addLang(LangKeyValue.ofKey( + "tooltip.lib39.content.doll.hover.2", ModPartEnum.DESCRIPTION, + "§7Rename with a player name in an anvil to change skin", + "§7在铁砧上可通过重命名对应玩家名来改变皮肤", "§7在鐵砧上可通過重命名對應玩家名來改變皮膚", "§7鐵砧之上,更名以易膚" + )); + addLang(LangKeyValue.ofKey( + "invalid.player_name.too_long", ModPartEnum.DESCRIPTION, + "§c§lPlayer 's Name is too long than 16 characters.", + "§c§l玩家名称过长(最多16个字符)", "§c§l玩家名稱過長(最多16個字符)", "§c§l玩家名過長,限十六字" + )); + if (Lib39.shouldRegisterExamples()) { + addLang(LangKeyValue.ofSupplier( + ExLib39Items.FABRIC, ModPartEnum.ITEM, + "Fabric", "织布", "織布", "織", true + )); + addLang(LangKeyValue.ofSupplier( + ExLib39Items.NEOFORGE, ModPartEnum.ITEM, + "NeoForge", "小狐狸", "狐狸", "狸", true + )); + addLang(LangKeyValue.ofSupplier( + ExLib39Items.FORGE, ModPartEnum.ITEM, + "Forge", "铁砧", "铁砧", "砧", true + )); + TestMessage.getItems().forEach(this::addLang); + } + } + + + /** + * Add lang. + * + * @param keyValue the key value + */ + public void addLang(LangKeyValue keyValue) { + langKeyValues.add(keyValue); + } + + /** + * Clear. + */ + public void clear() { + langKeyValues.clear(); + } + + + @Contract(pure = true) + @Override + public @Unmodifiable List getValues() { + return List.copyOf(langKeyValues); + } + + /** + * The type Test message. + */ + @SuppressWarnings("unused") + public static final class TestMessage { + private static final Set items = new HashSet<>(); + + // ===== lib39 測試命令翻譯 ===== + + /** + * The constant LIB39_ROOT. + */ +// 根命令 + public static final LangKeyValue LIB39_ROOT = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.root", ModPartEnum.MESSAGE, + "Lib39 Command System", + "Lib39 命令系統", + "Lib39 指令系統", + "Lib39 令系", + true + ) + ); + + /** + * The constant LIB39_TEST. + */ +// 測試命令 + public static final LangKeyValue LIB39_TEST = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.test", ModPartEnum.MESSAGE, + "Test command", + "測試命令", + "測試指令", + "試令", + true + ) + ); + + /** + * The constant LIB39_TEST_SUCCESS. + */ + public static final LangKeyValue LIB39_TEST_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.test.success", ModPartEnum.MESSAGE, + "Test command executed successfully!", + "測試命令執行成功!", + "測試指令執行成功!", + "試令行成!", + true + ) + ); + + /** + * The constant LIB39_TEST_WITH_PARAM. + */ + public static final LangKeyValue LIB39_TEST_WITH_PARAM = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.test.with_param", ModPartEnum.MESSAGE, + "Test command with parameter: %s", + "帶參數的測試命令:%s", + "帶參數的測試指令:%s", + "帶參試令:%s", + true + ) + ); + + /** + * The constant LIB39_DEMO. + */ + public static final LangKeyValue LIB39_DEMO = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.demo", ModPartEnum.MESSAGE, + "Demo command", + "演示命令", + "演示指令", + "演令", + true + ) + ); + + /** + * The constant LIB39_DEMO_MESSAGE. + */ + public static final LangKeyValue LIB39_DEMO_MESSAGE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.demo.message", ModPartEnum.MESSAGE, + "This is a demo command showing Lib39 features!", + "這是一個展示 Lib39 功能的演示命令!", + "這是一個展示 Lib39 功能的演示指令!", + "此乃展 Lib39 能之演令!", + true + ) + ); + + /** + * The constant LIB39_GREET_BASIC. + */ +// 問候命令 + public static final LangKeyValue LIB39_GREET_BASIC = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.greet.basic", ModPartEnum.MESSAGE, + "Greet everyone", + "向大家問好", + "向大家問好", + "問眾安", + true + ) + ); + + /** + * The constant LIB39_GREET_DEFAULT. + */ + public static final LangKeyValue LIB39_GREET_DEFAULT = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.greet.default", ModPartEnum.MESSAGE, + "Hello everyone from Lib39!", + "來自 Lib39 的問候!", + "來自 Lib39 的問候!", + "自 Lib39 問安!", + true + ) + ); + + /** + * The constant LIB39_GREET_PLAYER. + */ + public static final LangKeyValue LIB39_GREET_PLAYER = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.greet.player", ModPartEnum.MESSAGE, + "Greet specific player", + "問候特定玩家", + "問候特定玩家", + "問特者安", + true + ) + ); + + /** + * The constant LIB39_GREET_RECEIVED. + */ + public static final LangKeyValue LIB39_GREET_RECEIVED = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.greet.received", ModPartEnum.MESSAGE, + "%s greeted you!", + "%s 向你問好!", + "%s 向你問好!", + "%s 問汝安!", + true + ) + ); + + /** + * The constant LIB39_CALCULATE. + */ +// 計算命令 + public static final LangKeyValue LIB39_CALCULATE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.calculate", ModPartEnum.MESSAGE, + "Calculate sum of two numbers", + "計算兩個數字的和", + "計算兩個數字的和", + "算二數和", + true + ) + ); + + /** + * The constant LIB39_CALCULATE_RESULT. + */ + public static final LangKeyValue LIB39_CALCULATE_RESULT = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.calculate.result", ModPartEnum.MESSAGE, + "%d + %d = %d", + "%d + %d = %d", + "%d + %d = %d", + "%d 加 %d 等 %d", + true + ) + ); + + /** + * The constant LIB39_TELEPORT. + */ +// 傳送命令 + public static final LangKeyValue LIB39_TELEPORT = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.teleport", ModPartEnum.MESSAGE, + "Teleport to player (OP only)", + "傳送到玩家(僅OP)", + "傳送到玩家(僅OP)", + "送至者(唯管)", + true + ) + ); + + /** + * The constant LIB39_TELEPORT_SUCCESS. + */ + public static final LangKeyValue LIB39_TELEPORT_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.teleport.success", ModPartEnum.MESSAGE, + "Teleported to %s", + "已傳送至 %s", + "已傳送至 %s", + "已送至 %s", + true + ) + ); + + /** + * The constant LIB39_INFO. + */ +// 信息命令 + public static final LangKeyValue LIB39_INFO = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.info", ModPartEnum.MESSAGE, + "Show player information", + "顯示玩家信息", + "顯示玩家資訊", + "示者訊", + true + ) + ); + + /** + * The constant LIB39_INFO_MESSAGE. + */ + public static final LangKeyValue LIB39_INFO_MESSAGE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.info.message", ModPartEnum.MESSAGE, + "=== Player Information ===", + "=== 玩家信息 ===", + "=== 玩家資訊 ===", + "〇〇 者訊 〇〇", + true + ) + ); + + /** + * The constant LIB39_INFO_DIMENSION. + */ + public static final LangKeyValue LIB39_INFO_DIMENSION = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.info.dimension", ModPartEnum.MESSAGE, + "Dimension: %s", + "維度:%s", + "維度:%s", + "界:%s", + true + ) + ); + + /** + * The constant LIB39_INFO_POSITION. + */ + public static final LangKeyValue LIB39_INFO_POSITION = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.info.position", ModPartEnum.MESSAGE, + "Position: X=%.1f, Y=%.1f, Z=%.1f", + "位置:X=%.1f, Y=%.1f, Z=%.1f", + "位置:X=%.1f, Y=%.1f, Z=%.1f", + "位:X=%.1f, Y=%.1f, Z=%.1f", + true + ) + ); + + /** + * The constant LIB39_TEAM. + */ +// 隊伍系統 + public static final LangKeyValue LIB39_TEAM = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team", ModPartEnum.MESSAGE, + "Team management", + "隊伍管理", + "隊伍管理", + "隊管", + true + ) + ); + + /** + * The constant LIB39_TEAM_CREATE. + */ + public static final LangKeyValue LIB39_TEAM_CREATE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.create", ModPartEnum.MESSAGE, + "Create a new team", + "創建新隊伍", + "創建新隊伍", + "創新隊", + true + ) + ); + + /** + * The constant LIB39_TEAM_CREATE_SUCCESS. + */ + public static final LangKeyValue LIB39_TEAM_CREATE_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.create.success", ModPartEnum.MESSAGE, + "Team '%s' created successfully!", + "隊伍 '%s' 創建成功!", + "隊伍 '%s' 創建成功!", + "隊 '%s' 創新成!", + true + ) + ); + + /** + * The constant LIB39_TEAM_JOIN. + */ + public static final LangKeyValue LIB39_TEAM_JOIN = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.join", ModPartEnum.MESSAGE, + "Join a team", + "加入隊伍", + "加入隊伍", + "入隊", + true + ) + ); + + /** + * The constant LIB39_TEAM_JOIN_SUCCESS. + */ + public static final LangKeyValue LIB39_TEAM_JOIN_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.join.success", ModPartEnum.MESSAGE, + "Joined team '%s'", + "已加入隊伍 '%s'", + "已加入隊伍 '%s'", + "已入隊 '%s'", + true + ) + ); + + /** + * The constant LIB39_TEAM_LEAVE. + */ + public static final LangKeyValue LIB39_TEAM_LEAVE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.leave", ModPartEnum.MESSAGE, + "Leave current team", + "離開當前隊伍", + "離開當前隊伍", + "離現隊", + true + ) + ); + + /** + * The constant LIB39_TEAM_LEAVE_SUCCESS. + */ + public static final LangKeyValue LIB39_TEAM_LEAVE_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.team.leave.success", ModPartEnum.MESSAGE, + "Left the team", + "已離開隊伍", + "已離開隊伍", + "已離隊", + true + ) + ); + + /** + * The constant LIB39_GAME. + */ +// 遊戲系統 + public static final LangKeyValue LIB39_GAME = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game", ModPartEnum.MESSAGE, + "Game control", + "遊戲控制", + "遊戲控制", + "戲控", + true + ) + ); + + /** + * The constant LIB39_GAME_START. + */ + public static final LangKeyValue LIB39_GAME_START = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.start", ModPartEnum.MESSAGE, + "Start a game", + "開始遊戲", + "開始遊戲", + "啟戲", + true + ) + ); + + /** + * The constant LIB39_GAME_START_SUCCESS. + */ + public static final LangKeyValue LIB39_GAME_START_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.start.success", ModPartEnum.MESSAGE, + "Game '%s' started!", + "遊戲 '%s' 已開始!", + "遊戲 '%s' 已開始!", + "戲 '%s' 已啟!", + true + ) + ); + + /** + * The constant LIB39_GAME_STOP. + */ + public static final LangKeyValue LIB39_GAME_STOP = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.stop", ModPartEnum.MESSAGE, + "Stop current game", + "停止當前遊戲", + "停止當前遊戲", + "止現戲", + true + ) + ); + + /** + * The constant LIB39_GAME_STOP_SUCCESS. + */ + public static final LangKeyValue LIB39_GAME_STOP_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.stop.success", ModPartEnum.MESSAGE, + "Game stopped!", + "遊戲已停止!", + "遊戲已停止!", + "戲已止!", + true + ) + ); + + /** + * The constant LIB39_GAME_PAUSE. + */ + public static final LangKeyValue LIB39_GAME_PAUSE = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.pause", ModPartEnum.MESSAGE, + "Pause current game", + "暫停當前遊戲", + "暫停當前遊戲", + "暫現戲", + true + ) + ); + + /** + * The constant LIB39_GAME_PAUSE_SUCCESS. + */ + public static final LangKeyValue LIB39_GAME_PAUSE_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.pause.success", ModPartEnum.MESSAGE, + "Game paused!", + "遊戲已暫停!", + "遊戲已暫停!", + "戲已暫!", + true + ) + ); + + /** + * The constant LIB39_GAME_RESUME. + */ + public static final LangKeyValue LIB39_GAME_RESUME = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.resume", ModPartEnum.MESSAGE, + "Resume paused game", + "恢復暫停的遊戲", + "恢復暫停的遊戲", + "復暫戲", + true + ) + ); + + /** + * The constant LIB39_GAME_RESUME_SUCCESS. + */ + public static final LangKeyValue LIB39_GAME_RESUME_SUCCESS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.game.resume.success", ModPartEnum.MESSAGE, + "Game resumed!", + "遊戲已恢復!", + "遊戲已恢復!", + "戲已復!", + true + ) + ); + + /** + * The constant LIB39_SETTINGS. + */ +// 設置命令 + public static final LangKeyValue LIB39_SETTINGS = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.settings", ModPartEnum.MESSAGE, + "Show settings", + "顯示設置", + "顯示設定", + "示置", + true + ) + ); + + /** + * The constant LIB39_CONFIG. + */ + public static final LangKeyValue LIB39_CONFIG = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.config", ModPartEnum.MESSAGE, + "Show configuration", + "顯示配置", + "顯示設定", + "示配", + true + ) + ); + + /** + * The constant LIB39_RELOAD. + */ + public static final LangKeyValue LIB39_RELOAD = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.reload", ModPartEnum.MESSAGE, + "Reload configuration", + "重新加載配置", + "重新載入設定", + "重載配", + true + ) + ); + + /** + * The constant LIB39_DEBUG. + */ + public static final LangKeyValue LIB39_DEBUG = addAndRet( + LangKeyValue.ofKey( + "commands.lib39.debug", ModPartEnum.MESSAGE, + "Debug information", + "調試信息", + "除錯資訊", + "調訊", + true + ) + ); + + // ===== 添加缺失的導入 ===== + private static final java.util.function.Consumer addConsumer = items::add; + + private static LangKeyValue addAndRet(LangKeyValue item) { + items.add(item); + return item; + } + + /** + * Gets items. + * + * @return the items + */ + public static Set getItems() { + return items; + } + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java b/common/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java new file mode 100644 index 0000000..11a267f --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java @@ -0,0 +1,547 @@ +package top.r3944realms.lib39.core.command; + +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.*; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.base.datagen.value.Lib39LangKey; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.CommandPath; +import top.r3944realms.lib39.core.command.model.Parameter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * The interface Command help manager. + */ +public interface ICommandHelpManager { + /** + * The constant NEWLINE. + */ + String NEWLINE = "\n"; + + /** + * Gets id. + * + * @return the id + */ + ResourceLocation getID(); + + /** + * Gets head key. + * + * @return the head key + */ + String getHeadKey(); + + /** + * Gets command head. + * + * @return the command head + */ + default String getCommandHead() { + return getID().getNamespace(); + } + + /** + * Init command node. + * + * @return the command node + */ + default CommandNode init() { + CommandNode root; + root = new CommandNode(this, getCommandHead(), Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), true); + root.addChild(new CommandNode(this, "help", Component.translatable(Lib39LangKey.Message.BASIC_HELP.getKey()))); + return root; + } + + /** + * Gets cache. + * + * @return the cache + */ + Map getCache(); + + /** + * Gets root node. + * + * @return the root node + */ + CommandNode getRootNode(); + + /** + * Register command help. + * + * @param commandNode the command node + * @param description the description + */ + default void registerCommandHelp(@NotNull CommandNode commandNode, MutableComponent description) { + registerCommandHelp(commandNode.getFullPath(), description); + } + + /** + * Register command help. + * + * @param commandNode the command node + * @param descriptionKey the description key + */ + default void registerCommandHelp(@NotNull CommandNode commandNode, String descriptionKey) { + registerCommandHelp(commandNode.getFullPath(), Component.translatable(descriptionKey)); + } + + /** + * Register command help. + * + * @param commandPath the command path + */ + default void registerCommandHelp(@NotNull CommandPath commandPath) { + registerCommandHelp(commandPath.fullPath(), Component.literal("")); + } + + /** + * Register command parameters. + * + * @param commandPath the command path + * @param parameters the parameters + */ + default void registerCommandParameters(@NotNull CommandPath commandPath, @NotNull Parameter.Builder parameters) { + registerCommandParameters(commandPath.fullPath(), parameters.build()); + } + + private void registerCommandHelp(@NotNull String commandPath, MutableComponent description) { + String[] pathParts = commandPath.split(" "); + CommandNode currentNode = getRootNode(); + + int startIndex = pathParts[0].equals(getRootNode().getName()) ? 1 : 0; + + for (int i = startIndex; i < pathParts.length; i++) { + String part = pathParts[i]; + CommandNode child = currentNode.getChild(part); + + if (child == null) { + MutableComponent nodeDescription = (i == pathParts.length - 1) ? description : Component.literal(""); + child = new CommandNode(this, part, nodeDescription); + currentNode.addChild(child); + } + + currentNode = child; + } + } + + /** + * 註冊命令幫助節點 + * + * @param builder the builder + */ + default void registerCommand(@NotNull CommandNode.Builder builder) { + CommandNode newRoot = builder.build(); + mergeTree(getRootNode(), newRoot); + } + + /** + * Merge tree. + * + * @param target the target + * @param source the source + */ + void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source); + + /** + * 使用Builder模式註冊命令樹 + * + * @param builder the builder + */ + default void registerCommandTree(@NotNull CommandNode.Builder builder) { + registerCommand(builder); + } + + /** + * 使用Builder模式快速註冊命令 + * + * @param configurator the configurator + */ + default void registerCommands(@NotNull Consumer configurator) { + CommandNode.Builder builder = CommandNode.Builder.of(this); + configurator.accept(builder); + registerCommand(builder); + } + + /** + * 根据路径查找节点 + * + * @param path the path + * @return the optional + */ + default Optional findNode(CommandPath path) { + CommandNode currentNode = getRootNode(); + String[] segments = path.segments(); + + // 跳过根节点(如果路径包含) + int startIndex = segments[0].equals(getRootNode().getName()) ? 1 : 0; + + for (int i = startIndex; i < segments.length; i++) { + currentNode = currentNode.getChild(segments[i]); + if (currentNode == null) { + return Optional.empty(); + } + } + + return Optional.of(currentNode); + } + + /** + * 检查命令是否存在 + * + * @param path the path + * @return the boolean + */ + default boolean hasCommand(CommandPath path) { + return findNode(path).isPresent(); + } + + private void registerCommandHelp(String commandPath, String descriptionKey) { + registerCommandHelp(commandPath, Component.translatable(descriptionKey)); + } + + private void registerCommandHelp(String commandPath) { + registerCommandHelp(commandPath, Component.literal("")); + } + + /** + * 注册命令参数(支持单个参数可选标记,使用*前缀表示必选参数) + * + * @param commandPath 命令路径,如 "fpsm tacz dummy" + * @param parameters 参数列表,如 "*requiredParam", "optionalParam" + */ + private void registerCommandParameters(@NotNull String commandPath, Parameter... parameters) { + String[] pathParts = commandPath.split(" "); + CommandNode currentNode = getRootNode(); + + // 遍历命令路径,找到目标节点 + int startIndex = pathParts[0].equals(getRootNode().getName()) ? 1 : 0; + + for (int i = startIndex; i < pathParts.length; i++) { + String part = pathParts[i]; + CommandNode child = currentNode.getChild(part); + + if (child == null) { + // 如果节点不存在,创建空描述节点 + child = new CommandNode(this, part, Component.literal("")); + currentNode.addChild(child); + } + + currentNode = child; + } + + // 添加参数,处理可选标记 + for (Parameter param : parameters) { + currentNode.addParameter(param.name(), param.required()); + } + } + + /** + * 动态添加子指令到指定命令路径 + * + * @param commandPath 命令路径,如 "fpsm map modify" + * @param childName 子指令名称 + * @param description 子指令描述 + * @return 是否添加成功 boolean + */ + default boolean addChildCommand(@NotNull String commandPath, String childName, MutableComponent description) { + String[] pathParts = commandPath.split(" "); + CommandNode currentNode = getRootNode(); + + // 遍历命令路径,找到目标父节点 + for (String part : pathParts) { + if (!part.equals(currentNode.getName())) { + CommandNode child = currentNode.getChild(part); + if (child == null) { + // 路径不存在,创建中间节点 + child = new CommandNode(this, part, Component.literal("")); + currentNode.addChild(child); + } + currentNode = child; + } + } + + // 添加子指令 + CommandNode childNode = new CommandNode(this, childName, description); + currentNode.addChild(childNode); + return true; + } + + /** + * 构建单个命令节点的显示格式 + * + * @param node 当前命令节点 + * @param indent 当前缩进 + * @param isRoot 是否为根节点 + * @return 格式化后的命令节点组件 + */ + private @NotNull MutableComponent buildCommandLine(CommandNode node, String indent, boolean isRoot, @Nullable String currentFullPath) { + if (isRoot) { + // 根节点特殊处理 + String rootCommand = "/" + node.getName(); + return Component.literal(rootCommand) + .withStyle(ChatFormatting.AQUA) + .withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + rootCommand + " " + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey()) + )) + ); + } else { + // 构建完整命令路径 + String fullCommand = (currentFullPath != null && !currentFullPath.isEmpty()) + ? currentFullPath + " " + node.getName() + : "/" + getRootNode().getName() + " " + node.getFullPath(); + + // 构建建议的命令(带参数占位符) + String suggestedCommand = buildSuggestedCommand(node, fullCommand); + + // 非根节点:显示命令和描述 + MutableComponent prefix = Component.literal(indent + "└─ ").withStyle(ChatFormatting.GRAY); + + // 命令名称(可点击) + MutableComponent commandName = Component.literal(node.getName()) + .withStyle(ChatFormatting.DARK_AQUA) + .withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + suggestedCommand + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey(), suggestedCommand) + )) + ); + + MutableComponent displayLine = prefix.append(commandName); + + // 添加参数显示(只显示,不添加额外空格) + if (!node.getParameters().isEmpty()) { + displayLine.append(Component.literal(" ")); // 命令名和参数之间的空格 + + for (int i = 0; i < node.getParameters().size(); i++) { + Parameter param = node.getParameters().get(i); + if (param.required()) { + displayLine.append(Component.literal("<").withStyle(ChatFormatting.GRAY)) + .append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE)) + .append(Component.literal(">").withStyle(ChatFormatting.GRAY)); + } else { + displayLine.append(Component.literal("[").withStyle(ChatFormatting.GRAY)) + .append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE)) + .append(Component.literal("]").withStyle(ChatFormatting.GRAY)); + } + + // 参数之间添加空格(除了最后一个) + if (i < node.getParameters().size() - 1) { + displayLine.append(Component.literal(" ")); + } + } + } + + // 添加分隔符和描述 + displayLine.append(Component.literal(" - ").withStyle(ChatFormatting.DARK_GRAY)) + .append(node.getDescription().copy().withStyle(ChatFormatting.GRAY)); + + // 如果有子节点,添加展开/折叠按钮 + boolean shouldShowToggle = node.hasChildren() && !node.isLeaf(); + + if (shouldShowToggle) { + String toggleKey = node.isExpanded() + ? Lib39LangKey.Message.HELP_NODE_TOGGLE_COLLAPSE.getKey() + : Lib39LangKey.Message.HELP_NODE_TOGGLE_EXPAND.getKey(); + + MutableComponent toggleButton = Component.literal(" [") + .withStyle(ChatFormatting.GRAY) + .append(Component.translatable(toggleKey).withStyle(ChatFormatting.YELLOW)) + .append(Component.literal("]").withStyle(ChatFormatting.GRAY)); + + // 为按钮添加点击事件 + toggleButton.withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.RUN_COMMAND, + "/" + getCommandHead() + " help toggle " + node.hashCode() + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey()) + .withStyle(ChatFormatting.GRAY) + )) + ); + + displayLine.append(toggleButton); + } + + return displayLine; + } + } + + /** + * 构建建议的命令(包含参数占位符) + */ + private @NotNull String buildSuggestedCommand(@NotNull CommandNode node, @NotNull String baseCommand) { + StringBuilder sb = new StringBuilder(baseCommand); + + // 如果有参数,添加参数占位符 + if (!node.getParameters().isEmpty()) { + for (Parameter param : node.getParameters()) { + sb.append(" "); + if (param.required()) { + sb.append("<").append(param.name()).append(">"); + } else { + sb.append("[").append(param.name()).append("]"); + } + } + } + + // 如果是叶子节点且没有参数,添加空格以便继续输入 + if (node.isLeaf() && node.getParameters().isEmpty()) { + sb.append(" "); + } + + return sb.toString(); + } + + /** + * 檢查節點是否應該顯示摺疊信息 + */ + private boolean shouldShowCollapsedInfo(@NotNull CommandNode node) { + return node.hasChildren() && !node.isExpanded() && !node.getChildren().isEmpty(); + } + + /** + * 獲取有效的子命令數量(過濾掉空描述的命令) + */ + private long getValidChildCount(@NotNull CommandNode node) { + return node.getChildren().stream() + .filter(child -> !child.getDescription().getString().isEmpty()) + .count(); + } + + /** + * 遞歸構建命令樹 + */ + private void buildCommandTreeString(@NotNull CommandNode node, + @NotNull String indent, + @Nullable String currentFullPath, + @NotNull List result, + CommandSourceStack commandSourceStack) { + boolean isRoot = indent.isEmpty(); + if (node.testPermission(commandSourceStack)) { + MutableComponent commandLine = buildCommandLine(node, indent, isRoot, currentFullPath); + result.add(commandLine.append(Component.literal(NEWLINE))); + + // 遞歸處理子節點 + String childIndent = indent + "| "; + if (node.isExpanded()) { + String newFullPath = (currentFullPath != null && !currentFullPath.isEmpty()) + ? currentFullPath + " " + node.getName() + : "/" + node.getName(); + + for (CommandNode child : node.getChildren()) { + // 只顯示有描述的子命令 + if (!child.getDescription().getString().isEmpty() && node.testPermission(commandSourceStack)) { + buildCommandTreeString(child, childIndent, newFullPath, result, commandSourceStack); + } + } + } else if (shouldShowCollapsedInfo(node)) { + long childCount = getValidChildCount(node); + if (childCount > 0) { + MutableComponent collapsedInfo = Component.literal(indent + "| " + "└─ ") + .withStyle(ChatFormatting.GRAY) + .append(Component.translatable( + Lib39LangKey.Message.HELP_NODE_EXPAND.getKey(), + childCount + ).withStyle(ChatFormatting.GRAY)); + + collapsedInfo.withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.RUN_COMMAND, + "/" + getCommandHead() + " help toggle " + node.hashCode() + )) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey()))) + ); + + result.add(collapsedInfo.append(Component.literal(NEWLINE))); + } + } + } + } + + /** + * 获取命令树的字符串表示 + * + * @return 命令树列表 command tree + */ + default List getCommandTree(CommandSourceStack commandSourceStack) { + List result = new ArrayList<>(); + buildCommandTreeString(getRootNode(), "", "", result, commandSourceStack); + return result; + } + + /** + * 切换指定节点的展开/闭合状态 + * + * @param hashCode 节点哈希值 + * @return 是否成功切换 boolean + */ + default boolean toggleNodeExpanded(int hashCode) { + CommandNode currentNode = getCache().getOrDefault(hashCode, null); + if (currentNode == null || currentNode.getChildren().isEmpty()) { + return false; + } + currentNode.toggleExpanded(); + return true; + } + + /** + * 构建帮助消息 + * + * @param header 帮助头部 + * @param entries 帮助条目列表 + * @return the mutable component + */ + default MutableComponent buildHelpMessage(@NotNull Component header, @NotNull List entries) { + MutableComponent helpMessage = Component.empty(); + // 添加头部 + helpMessage.append(header.copy()).append(NEWLINE); + + // 添加分隔线 + helpMessage.append(Component.literal("\n")); + + + // 添加当前页的帮助内容 + if (entries.isEmpty()) { + helpMessage.append(Component.translatable(Lib39LangKey.Message.HELP_NO_ENTRIES.getKey())).append(NEWLINE); + } else { + for (MutableComponent entry : entries) { + helpMessage.append(entry); + } + } + + return helpMessage; + } + + /** + * Build command tree help mutable component. + * + * @return the mutable component + */ + default MutableComponent buildCommandTreeHelp(CommandSourceStack commandSourceStack) { + List commandTree = getCommandTree(commandSourceStack); + return buildHelpMessage(Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), commandTree); + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/IHelpCommand.java b/common/src/main/java/top/r3944realms/lib39/core/command/IHelpCommand.java new file mode 100644 index 0000000..14133e6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/IHelpCommand.java @@ -0,0 +1,131 @@ +package top.r3944realms.lib39.core.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.base.datagen.value.Lib39LangKey; +import top.r3944realms.lib39.platform.Services; + +import javax.annotation.Nullable; + +/** + * The interface Help command. + */ +public interface IHelpCommand { + /** + * Should show toggle failed boolean. + * + * @return the boolean + */ + default boolean shouldShowToggleFailed() { + return false; + } + + /** + * Gets help head. + * + * @return the help head + */ + @Nullable + default LiteralArgumentBuilder getHelpHead() { + return null; + } + + /** + * Gets command help manager. + * + * @return the command help manager + */ + ICommandHelpManager getCommandHelpManager(); + + /** + * Build command literal argument builder. + * + * @param dispatcher the dispatcher + * @param context the context + * @return the literal argument builder + */ + default LiteralArgumentBuilder buildCommand(CommandDispatcher dispatcher, CommandBuildContext context) { + LiteralArgumentBuilder head = getHelpHead(); + if (head == null) { + head = LiteralArgumentBuilder.literal(getCommandHelpManager().getID().getNamespace()); + } + LiteralArgumentBuilder tree = head.requires(this::requestPermission) + .then(Commands.literal("help").executes(this::handleHelp) + .then(Commands.literal("toggle") + .then(Commands.argument("hash", IntegerArgumentType.integer()).executes(this::handleHelpToggle)) + )); + Services.PLATFORM.getHelpCommandHook().onRegister(tree, getCommandHelpManager(), context); + dispatcher.register(head); + return head; + } + + /** + * Request permission boolean. + * + * @param context the context + * @return the boolean + */ + default boolean requestPermission(CommandSourceStack context) { + return true; + } + + /** + * Handle help int. + * + * @param context the context + * @return the int + */ + default int handleHelp(@NotNull CommandContext context) { + ICommandHelpManager commandHelpManager = getCommandHelpManager(); + MutableComponent helpMessage = commandHelpManager.buildCommandTreeHelp(context.getSource()); + sendSuccess(context.getSource(), helpMessage); + return 1; + } + + /** + * Handle help toggle int. + * + * @param context the context + * @return the int + */ + default int handleHelpToggle(@NotNull CommandContext context) { + int hash = IntegerArgumentType.getInteger(context, "hash"); + ICommandHelpManager commandHelpManager = getCommandHelpManager(); + boolean success = commandHelpManager.toggleNodeExpanded(hash); + if (success) { + MutableComponent helpMessage = Component.literal("\n".repeat(2)).append(commandHelpManager.buildCommandTreeHelp(context.getSource())); + sendSuccess(context.getSource(), helpMessage); + } else if (shouldShowToggleFailed()) { + sendFailure(context.getSource(), Component.translatable(Lib39LangKey.Message.HELP_TOGGLE_FAILED.getKey())); + } + return 1; + } + + /** + * Send success. + * + * @param source the source + * @param key the key + */ + static void sendSuccess(@NotNull CommandSourceStack source, Component key) { + source.sendSuccess(() -> key, true); + } + + /** + * Send failure. + * + * @param source the source + * @param key the key + */ + static void sendFailure(@NotNull CommandSourceStack source, Component key) { + source.sendFailure(key); + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java b/common/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java new file mode 100644 index 0000000..bd0fd05 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java @@ -0,0 +1,87 @@ +package top.r3944realms.lib39.core.command; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.Parameter; + +import java.util.HashMap; +import java.util.Map; + +/** + * The type Simple command help manager. + */ +public abstract class SimpleCommandHelpManager implements ICommandHelpManager { + private CommandNode root; + private final Map nodeCache = new HashMap<>(); + + /** + * Instantiates a new Simple command help manager. + */ + public SimpleCommandHelpManager() { + // + } + + /** + * 延遲初始化根節點 + */ + public void initialize() { + if (root == null) { + // 現在子類的字段已經初始化完成 + ResourceLocation id = getID(); + if (id == null) { + throw new IllegalStateException("getID() must return non-null"); + } + this.root = init(); + } + } + + @Override + public final @NotNull String getCommandHead() { + return getID().getNamespace(); + } + + @Override + public final Map getCache() { + return nodeCache; + } + @Override + public void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source) { + // 合併參數 + for (Parameter param : source.getParameters()) { + if (!target.getParameters().contains(param)) { + target.addParameter(param.name(), param.required()); + } + } + + // 合併子節點 + for (CommandNode sourceChild : source.getChildren()) { + CommandNode targetChild = target.getChild(sourceChild.getName()); + if (targetChild == null) { + target.addChild(sourceChild.deepCopy()); + } else { + mergeTree(targetChild, sourceChild); + } + } + } + + /** + * 獲取根節點(如果未初始化則初始化) + */ + @Override + public final CommandNode getRootNode() { + if (root == null) { + initialize(); + } + return root; + } + + /** + * 檢查是否已初始化 + * + * @return the boolean + */ + public boolean isInitialized() { + return root != null; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java b/common/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java new file mode 100644 index 0000000..dd0badc --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.core.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; + +/** + * The type Simple help command. + */ +public abstract class SimpleHelpCommand implements IHelpCommand { + /** + * The Root. + */ + protected final LiteralArgumentBuilder root; + + /** + * Instantiates a new Simple help command. + * + * @param dispatcher the dispatcher + * @param context the context + */ + public SimpleHelpCommand(CommandDispatcher dispatcher, + CommandBuildContext context) { + root = buildCommand(dispatcher, context); + } + + /** + * Gets root. + * + * @return the root + */ + public LiteralArgumentBuilder getRoot() { + return root; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java b/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java new file mode 100644 index 0000000..1caf181 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java @@ -0,0 +1,1266 @@ +package top.r3944realms.lib39.core.command.model; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.core.command.ICommandHelpManager; + +import java.util.*; +import java.util.function.Predicate; + +/** + * The type Command node. + */ +public class CommandNode { + private boolean isRoot = false; + private int hashCache = 0; + private boolean hashValid = false; + private final ICommandHelpManager helpManager; + private final String name; + private final Predicate testPermission; + private static final Predicate TRUE = i -> true; + private final MutableComponent description; + private final Map children; + + // 父節點引用,用於構建完整路徑 + @Nullable + private CommandNode parent; + + // 参数列表,用于存储命令参数 + private final List parameters = new ArrayList<>(); + + // 展开/闭合状态 + private boolean expanded = true; + + public boolean testPermission(CommandSourceStack source) { + return testPermission.test(source); + } + /** + * Instantiates a new Command node. + * + * @param helpManager the help manager + * @param name the name + * @param description the description + * @param isRoot the is root + */ + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot) { + this(helpManager, name, description, isRoot, TRUE); + } + + /** + * Instantiates a new Command node. + * + * @param helpManager the help manager + * @param name the name + * @param description the description + */ + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description) { + this(helpManager, name, description, TRUE); + } + + /** + * Instantiates a new Command node. + * + * @param helpManager the help manager + * @param name the name + * @param description the description + * @param isRoot the is root + * @param testPermission test has permission + */ + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot, Predicate testPermission) { + this.name = name; + this.helpManager = helpManager; + this.description = description; + this.children = new TreeMap<>(); + this.isRoot = isRoot; + this.testPermission = testPermission; + invalidateHash(); + } + + /** + * Instantiates a new Command node. + * + * @param helpManager the help manager + * @param name the name + * @param description the description + * @param testPermission test has permission + */ + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, Predicate testPermission) { + this.name = name; + this.helpManager = helpManager; + this.description = description; + this.children = new TreeMap<>(); + this.testPermission = testPermission; + invalidateHash(); + } + + /** + * Add child. + * + * @param child the child + */ + public void addChild(CommandNode child) { + children.put(child.name, child); + child.parent = this; // 設置父節點引用 + + // 如果不是根節點,確保展開狀態正確 + if (!isRoot) { + this.expanded = false; + } + + invalidateHash(); + } + + /** + * Gets child. + * + * @param name the name + * @return the child + */ + public CommandNode getChild(String name) { + return children.get(name); + } + + /** + * Gets parent. + * + * @return the parent + */ + @Nullable + public CommandNode getParent() { + return parent; + } + + /** + * Has parent boolean. + * + * @return the boolean + */ + public boolean hasParent() { + return parent != null; + } + + + /** + * Gets name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets description. + * + * @return the description + */ + public MutableComponent getDescription() { + return description; + } + + /** + * Gets children. + * + * @return the children + */ + public Collection getChildren() { + return children.values(); + } + + /** + * Add parameter. + * + * @param parameter the parameter + * @param required the required + */ +// 添加参数 + public void addParameter(String parameter, boolean required) { + parameters.add(new Parameter(parameter, required)); + invalidateHash(); + } + + /** + * Gets parameters. + * + * @return the parameters + */ +// 获取参数列表 + public List getParameters() { + return parameters; + } + + /** + * Is expanded boolean. + * + * @return the boolean + */ +// 获取展开状态 + public boolean isExpanded() { + return expanded; + } + + /** + * Sets expanded. + * + * @param expanded the expanded + */ +// 设置展开状态 + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + /** + * Toggle expanded. + */ +// 切换展开状态 + public void toggleExpanded() { + this.expanded = !this.expanded; + } + + @Override + public int hashCode() { + return computeHash(); + } + + /** + * Compute hash int. + * + * @return the int + */ +// 计算节点的Hash值 + public int computeHash() { + if (!hashValid) { + if (hashCache != 0) { + helpManager.getCache().remove(hashCache); + } + int result = 31; + result = 31 * result + name.hashCode(); + result = 31 * result + description.getString().hashCode(); + + // 计算参数的Hash + for (Parameter param : parameters) { + result = 31 * result + param.name().hashCode(); + result = 31 * result + (param.required() ? 1 : 0); + } + + // 计算子节点的Hash + for (CommandNode child : children.values()) { + result = 31 * result + child.computeHash(); + } + + if (!isRoot && children.size() > 1) { + expanded = false; + } + + hashCache = result; + helpManager.getCache().put(result, this); + hashValid = true; + } + return hashCache; + } + + // 使Hash缓存失效 + private void invalidateHash() { + hashValid = false; + computeHash(); + } + + /** + * 递归构建完整路径 + * + * @return the full path + */ + public String getFullPath() { + if (parent == null) { + return name; + } + return parent.getFullPath() + " " + name; + } + + /** + * 獲取完整的命令(以 / 開頭) + * + * @return the full command + */ + public String getFullCommand() { + if (isRoot) { + return "/" + name; + } + return "/" + getFullPath(); + } + + /** + * 檢查是否為葉子節點(沒有子節點) + * + * @return the boolean + */ + public boolean isLeaf() { + return children.isEmpty(); + } + + /** + * 獲取所有葉子節點(可執行的命令) + * + * @return the all leaf nodes + */ + public List getAllLeafNodes() { + List leaves = new ArrayList<>(); + collectLeafNodes(this, leaves); + return leaves; + } + + private void collectLeafNodes(@NotNull CommandNode node, List leaves) { + if (node.isLeaf()) { + leaves.add(node); + } else { + for (CommandNode child : node.getChildren()) { + collectLeafNodes(child, leaves); + } + } + } + + /** + * 獲取節點路徑(從根節點到當前節點的路徑列表) + * + * @return the path segments + */ + public List getPathSegments() { + List segments = new ArrayList<>(); + collectPathSegments(this, segments); + Collections.reverse(segments); // 從根到當前 + return segments; + } + + private void collectPathSegments(@NotNull CommandNode node, @NotNull List segments) { + segments.add(node.name); + if (node.parent != null) { + collectPathSegments(node.parent, segments); + } + } + + /** + * 比較兩個節點是否在同一路徑上 + * + * @param other the other + * @return the boolean + */ + public boolean isAncestorOf(CommandNode other) { + CommandNode current = other; + while (current != null) { + if (current == this) { + return true; + } + current = current.parent; + } + return false; + } + + /** + * 獲取最近的共同祖先 + * + * @param other the other + * @return the common ancestor + */ + @Nullable + public CommandNode getCommonAncestor(CommandNode other) { + Set ancestors = new HashSet<>(); + CommandNode current = this; + + // 收集當前節點的所有祖先 + while (current != null) { + ancestors.add(current); + current = current.parent; + } + + // 從另一個節點向上查找 + current = other; + while (current != null) { + if (ancestors.contains(current)) { + return current; + } + current = current.parent; + } + + return null; + } + + /** + * 複製節點(不包括子節點) + * + * @return the command node + */ + public CommandNode shallowCopy() { + CommandNode copy = new CommandNode(helpManager, name, description.copy(), isRoot); + for (Parameter param : parameters) { + copy.addParameter(param.name(), param.required()); + } + copy.expanded = expanded; + return copy; + } + + /** + * 深複製節點(包括所有子節點) + * + * @return the command node + */ + public CommandNode deepCopy() { + return deepCopy(this, null); + } + + /** + * 深複製節點(遞歸複製所有子節點) + */ + private @NotNull CommandNode deepCopy(@NotNull CommandNode original, @Nullable CommandNode newParent) { + // 複製當前節點 + CommandNode copy = new CommandNode(helpManager, original.name, original.description.copy(), original.isRoot); + copy.parent = newParent; + copy.expanded = original.expanded; + + // 複製參數 + for (Parameter param : original.parameters) { + copy.addParameter(param.name(), param.required()); + } + + // 遞歸複製子節點 + for (CommandNode child : original.children.values()) { + CommandNode childCopy = deepCopy(child, copy); + copy.children.put(childCopy.name, childCopy); + } + + return copy; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommandNode that = (CommandNode) o; + return hashCache == that.hashCache && + Objects.equals(name, that.name) && + Objects.equals(helpManager, that.helpManager); + } + + @Override + public String toString() { + return String.format("CommandNode{name='%s', path='%s', children=%d, params=%d}", + name, getFullPath(), children.size(), parameters.size()); + } + + /** + * The type Builder. + */ + public static class Builder { + private final ICommandHelpManager helpManager; + private final Deque nodeStack = new ArrayDeque<>(); + private CommandNode root; + private boolean built = false; + + private Builder(@NotNull final ICommandHelpManager helpManager) { + this.helpManager = helpManager; + } + + /** + * Of builder. + * + * @param helpManager the help manager + * @return the builder + */ + @Contract(value = "_ -> new", pure = true) + public static @NotNull Builder of(@NotNull final ICommandHelpManager helpManager) { + return new Builder(helpManager); + } + + /** + * 添加根節點 + * + * @param name the name + * @param description the description + * @return the builder + */ + public Builder root(@NotNull String name, @NotNull MutableComponent description) { + if (root != null) { + throw new IllegalStateException("Root node already set"); + } + root = new CommandNode(helpManager, name, description, true); + nodeStack.push(root); + return this; + } + + /** + * 添加根節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @return the builder + */ + public Builder root(@NotNull String name, @NotNull String translationKey) { + return root(name, Component.translatable(translationKey)); + } + + /** + * 添加根節點 + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @return the builder + */ + public Builder root(@NotNull String name, @NotNull MutableComponent description, Predicate testPermission) { + if (root != null) { + throw new IllegalStateException("Root node already set"); + } + root = new CommandNode(helpManager, name, description, true, testPermission); + nodeStack.push(root); + return this; + } + + /** + * 添加根節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param testPermission the test permission + * @return the builder + */ + public Builder root(@NotNull String name, @NotNull String translationKey, Predicate testPermission) { + return root(name, Component.translatable(translationKey), testPermission); + } + + + /** + * 進入子節點(向下移動) + * + * @param name the name + * @param description the description + * @return the builder + */ + public Builder push(@NotNull String name, @NotNull MutableComponent description) { + checkBuilt(); + if (nodeStack.isEmpty()) { + throw new IllegalStateException("No parent node available. Call root() first."); + } + + CommandNode parent = nodeStack.peek(); + CommandNode child = new CommandNode(helpManager, name, description); + + if (!child.isLeaf()) { + child.setExpanded(false); + } + + parent.addChild(child); + nodeStack.push(child); + return this; + } + + /** + * 進入子節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @return the builder + */ + public Builder push(@NotNull String name, @NotNull String translationKey) { + return push(name, Component.translatable(translationKey)); + } + + /** + * 進入子節點並添加參數 + * + * @param name the name + * @param description the description + * @param parameters the parameters + * @return the builder + */ + public Builder pushWithParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull Parameter... parameters) { + push(name, description); + current().addParameters(parameters); + return this; + } + + /** + * 進入子節點並添加必填參數 + * + * @param name the name + * @param description the description + * @param paramNames the param names + * @return the builder + */ + public Builder pushWithRequiredParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull String @NotNull ... paramNames) { + push(name, description); + for (String paramName : paramNames) { + current().addParameter(paramName, true); + } + return this; + } + + /** + * 進入子節點並添加可選參數 + * + * @param name the name + * @param description the description + * @param paramNames the param names + * @return the builder + */ + public Builder pushWithOptionalParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull String @NotNull ... paramNames) { + push(name, description); + for (String paramName : paramNames) { + current().addParameter(paramName, false); + } + return this; + } + + /** + * 進入子節點(向下移動) + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @return the builder + */ + public Builder push(@NotNull String name, @NotNull MutableComponent description, Predicate testPermission) { + checkBuilt(); + if (nodeStack.isEmpty()) { + throw new IllegalStateException("No parent node available. Call root() first."); + } + + CommandNode parent = nodeStack.peek(); + CommandNode child = new CommandNode(helpManager, name, description, testPermission); + + if (!child.isLeaf()) { + child.setExpanded(false); + } + + parent.addChild(child); + nodeStack.push(child); + return this; + } + + /** + * 進入子節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param testPermission the test permission + * @return the builder + */ + public Builder push(@NotNull String name, @NotNull String translationKey, Predicate testPermission) { + return push(name, Component.translatable(translationKey), testPermission); + } + + /** + * 進入子節點並添加參數 + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @param parameters the parameters + * @return the builder + */ + public Builder pushWithParams(@NotNull String name, @NotNull MutableComponent description, + Predicate testPermission, @NotNull Parameter... parameters) { + push(name, description, testPermission); + current().addParameters(parameters); + return this; + } + + /** + * 進入子節點並添加必填參數 + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @param paramNames the param names + * @return the builder + */ + public Builder pushWithRequiredParams(@NotNull String name, @NotNull MutableComponent description, + Predicate testPermission, @NotNull String @NotNull ... paramNames) { + push(name, description, testPermission); + for (String paramName : paramNames) { + current().addParameter(paramName, true); + } + return this; + } + + /** + * 進入子節點並添加可選參數 + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @param paramNames the param names + * @return the builder + */ + public Builder pushWithOptionalParams(@NotNull String name, @NotNull MutableComponent description, + Predicate testPermission, @NotNull String @NotNull ... paramNames) { + push(name, description, testPermission); + for (String paramName : paramNames) { + current().addParameter(paramName, false); + } + return this; + } + + /** + * 返回上一級節點(向上移動) + * + * @return the builder + */ + public Builder pop() { + checkBuilt(); + if (nodeStack.size() <= 1) { + throw new IllegalStateException("Cannot pop root node"); + } + nodeStack.pop(); + return this; + } + + /** + * 返回到根節點 + * + * @return the builder + */ + public Builder popToRoot() { + checkBuilt(); + while (nodeStack.size() > 1) { + nodeStack.pop(); + } + return this; + } + + /** + * 返回到指定深度的節點 + * + * @param depth the depth + * @return the builder + */ + public Builder popToDepth(int depth) { + checkBuilt(); + if (depth < 0 || depth >= nodeStack.size()) { + throw new IllegalArgumentException("Invalid depth: " + depth); + } + while (nodeStack.size() > depth + 1) { + nodeStack.pop(); + } + return this; + } + + /** + * 獲取當前節點 + * + * @return the command node + */ + public @NotNull CommandNode current() { + checkBuilt(); + if (nodeStack.isEmpty()) { + throw new IllegalStateException("No current node"); + } + return nodeStack.peek(); + } + + /** + * 獲取當前深度 + * + * @return the int + */ + public int depth() { + return nodeStack.size() - 1; + } + + /** + * 為當前節點添加參數 + * + * @param name the name + * @param required the required + * @return the builder + */ + public Builder param(@NotNull String name, boolean required) { + checkBuilt(); + current().addParameter(name, required); + return this; + } + + /** + * 為當前節點添加必填參數 + * + * @param name the name + * @return the builder + */ + public Builder required(@NotNull String name) { + return param(name, true); + } + + /** + * 為當前節點添加可選參數 + * + * @param name the name + * @return the builder + */ + public Builder optional(@NotNull String name) { + return param(name, false); + } + + /** + * 為當前節點添加多個參數 + * + * @param parameters the parameters + * @return the builder + */ + public Builder params(@NotNull Parameter... parameters) { + checkBuilt(); + current().addParameters(parameters); + return this; + } + + /** + * 設置當前節點的展開狀態 + * + * @param expanded the expanded + * @return the builder + */ + public Builder expanded(boolean expanded) { + checkBuilt(); + current().setExpanded(expanded); + // 如果設置為展開,確保父節點知道 + if (expanded && current().hasParent()) { + CommandNode parent = current().getParent(); + if (parent != null && !parent.isExpanded()) { + parent.setExpanded(true); + } + } + return this; + } + + /** + * 添加一個完整的命令分支(鏈式調用) + * + * @param name the name + * @param description the description + * @param configurator the configurator + * @return the builder + */ + public Builder branch(@NotNull String name, @NotNull MutableComponent description, + @NotNull BranchConfigurator configurator) { + push(name, description); + configurator.configure(this); + pop(); + return this; + } + + /** + * 添加一個完整的命令分支(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param configurator the configurator + * @return the builder + */ + public Builder branch(@NotNull String name, @NotNull String translationKey, + @NotNull BranchConfigurator configurator) { + return branch(name, Component.translatable(translationKey), configurator); + } + + /** + * 添加一個完整的命令分支(鏈式調用) + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @param configurator the configurator + * @return the builder + */ + public Builder branch(@NotNull String name, @NotNull MutableComponent description, + Predicate testPermission, @NotNull BranchConfigurator configurator) { + push(name, description, testPermission); + configurator.configure(this); + pop(); + return this; + } + + /** + * 添加一個完整的命令分支(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param testPermission the test permission + * @param configurator the configurator + * @return the builder + */ + public Builder branch(@NotNull String name, @NotNull String translationKey, + Predicate testPermission, @NotNull BranchConfigurator configurator) { + return branch(name, Component.translatable(translationKey), testPermission, configurator); + } + + /** + * 快速添加葉子節點(沒有子節點的節點) + * + * @param name the name + * @param description the description + * @param testPermission the test permission + * @param parameters the parameters + * @return the builder + */ + public Builder leaf(@NotNull String name, @NotNull MutableComponent description, + Predicate testPermission, @NotNull Parameter @NotNull ... parameters) { + push(name, description, testPermission); + if (parameters.length > 0) { + params(parameters); + } + pop(); + return this; + } + + /** + * 快速添加葉子節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param testPermission the test permission + * @param parameters the parameters + * @return the builder + */ + public Builder leaf(@NotNull String name, @NotNull String translationKey, + Predicate testPermission, @NotNull Parameter... parameters) { + return leaf(name, Component.translatable(translationKey), testPermission,parameters); + } + + /** + * 批量添加多個葉子節點 + * + * @param commands the commands + * @param testPermission the test permission + * @return the builder + */ + public Builder leaves(@NotNull Map commands, Predicate testPermission) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue(), testPermission); + } + return this; + } + + /** + * 批量添加多個葉子節點 + * + * @param commands the commands + * @param testPermission the test permission + * @return the builder + */ + public Builder leavesT(@NotNull Map commands, Predicate testPermission) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue(), testPermission); + } + return this; + } + + /** + * 批量添加多個帶參數的葉子節點 + * + * @param commands the commands + * @param testPermission the test permission + * @return the builder + */ + public Builder leavesWithParams(@NotNull Map commands, Predicate testPermission) { + for (Map.Entry entry : commands.entrySet()) { + LeafConfig config = entry.getValue(); + push(entry.getKey(), config.description(), testPermission); + if (config.parameters() != null && !config.parameters().isEmpty()) { + for (Parameter param : config.parameters()) { + param(param.name(), param.required()); + } + } + pop(); + } + return this; + } + + /** + * 快速添加葉子節點(沒有子節點的節點) + * + * @param name the name + * @param description the description + * @param parameters the parameters + * @return the builder + */ + public Builder leaf(@NotNull String name, @NotNull MutableComponent description, + @NotNull Parameter @NotNull ... parameters) { + push(name, description); + if (parameters.length > 0) { + params(parameters); + } + pop(); + return this; + } + + /** + * 快速添加葉子節點(使用翻譯鍵) + * + * @param name the name + * @param translationKey the translation key + * @param parameters the parameters + * @return the builder + */ + public Builder leaf(@NotNull String name, @NotNull String translationKey, + @NotNull Parameter... parameters) { + return leaf(name, Component.translatable(translationKey) ,parameters); + } + + /** + * 批量添加多個葉子節點 + * + * @param commands the commands + * @return the builder + */ + public Builder leaves(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * 批量添加多個葉子節點 + * + * @param commands the commands + * @return the builder + */ + public Builder leavesT(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * 批量添加多個帶參數的葉子節點 + * + * @param commands the commands + * @return the builder + */ + public Builder leavesWithParams(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + LeafConfig config = entry.getValue(); + push(entry.getKey(), config.description()); + if (config.parameters() != null && !config.parameters().isEmpty()) { + for (Parameter param : config.parameters()) { + param(param.name(), param.required()); + } + } + pop(); + } + return this; + } + + /** + * 構建命令樹 + * + * @return the command node + */ + public @NotNull CommandNode build() { + checkBuilt(); + if (root == null) { + throw new IllegalStateException("Root node not set"); + } + built = true; + return root; + } + + /** + * 重置構建器(重用) + * + * @return the builder + */ + public Builder reset() { + nodeStack.clear(); + root = null; + built = false; + return this; + } + + private void checkBuilt() { + if (built) { + throw new IllegalStateException("Builder has already been built. Call reset() to reuse."); + } + } + + /** + * 分支配置器接口(用於lambda表達式) + */ + @FunctionalInterface + public interface BranchConfigurator { + /** + * Configure. + * + * @param builder the builder + */ + void configure(@NotNull Builder builder); + } + + /** + * 葉子節點配置記錄 + */ + public record LeafConfig(@NotNull MutableComponent description, + @Nullable List parameters) { + + /** + * Of leaf config. + * + * @param description the description + * @return the leaf config + */ + @Contract("_ -> new") + public static @NotNull LeafConfig of(@NotNull MutableComponent description) { + return new LeafConfig(description, null); + } + + /** + * Of leaf config. + * + * @param description the description + * @param parameters the parameters + * @return the leaf config + */ + @Contract("_, _ -> new") + public static @NotNull LeafConfig of(@NotNull MutableComponent description, + @NotNull Parameter... parameters) { + return new LeafConfig(description, Arrays.asList(parameters)); + } + + /** + * Of leaf config. + * + * @param translationKey the translation key + * @return the leaf config + */ + @Contract("_ -> new") + public static @NotNull LeafConfig of(@NotNull String translationKey) { + return new LeafConfig(Component.translatable(translationKey), null); + } + + /** + * Of leaf config. + * + * @param translationKey the translation key + * @param parameters the parameters + * @return the leaf config + */ + @Contract("_, _ -> new") + public static @NotNull LeafConfig of(@NotNull String translationKey, + @NotNull Parameter... parameters) { + return new LeafConfig(Component.translatable(translationKey), Arrays.asList(parameters)); + } + } + } + + // ==================== 新增的輔助方法 ==================== + + /** + * 添加多個參數 + * + * @param parameters the parameters + */ + public void addParameters(@NotNull Parameter @NotNull ... parameters) { + for (Parameter param : parameters) { + addParameter(param.name(), param.required()); + } + } + + /** + * 檢查是否有子節點 + * + * @return the boolean + */ + public boolean hasChildren() { + return !children.isEmpty(); + } + + /** + * 檢查是否有參數 + * + * @return the boolean + */ + public boolean hasParameters() { + return !parameters.isEmpty(); + } + + /** + * 獲取節點深度(根節點為0) + * + * @return the depth + */ + public int getDepth() { + if (isRoot || parent == null) { + return 0; + } + return parent.getDepth() + 1; + } + + /** + * 查找子節點(支持遞歸查找) + * + * @param path the path + * @return the command node + */ + @Nullable + public CommandNode findChild(@NotNull String @NotNull ... path) { + if (path.length == 0) { + return this; + } + + CommandNode current = this; + for (String segment : path) { + current = current.getChild(segment); + if (current == null) { + return null; + } + } + + return current; + } + + /** + * 獲取完整的命令(用於點擊建議) + * + * @return the suggested command + */ + public String getSuggestedCommand() { + StringBuilder sb = new StringBuilder(getFullCommand()); + + // 如果有參數,添加參數佔位符 + for (Parameter param : parameters) { + sb.append(" "); + if (param.required()) { + sb.append("<").append(param.name()).append(">"); + } else { + sb.append("[").append(param.name()).append("]"); + } + } + + // 如果沒有子節點且沒有參數,添加空格以便繼續輸入 + if (children.isEmpty() && parameters.isEmpty()) { + sb.append(" "); + } + + return sb.toString(); + } + + /** + * 獲取根節點 + * + * @return the root + */ + public CommandNode getRoot() { + if (isRoot) { + return this; + } + if (parent == null) { + throw new IllegalStateException("Node has no parent but is not marked as root"); + } + return parent.getRoot(); + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java b/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java new file mode 100644 index 0000000..64f3248 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java @@ -0,0 +1,127 @@ +package top.r3944realms.lib39.core.command.model; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 命令路径构建器 - 提供编译时类型安全 + */ +public final class CommandPath { + private final List segments; + private final String fullPath; + + private CommandPath(List segments) { + this.segments = List.copyOf(segments); + this.fullPath = String.join(" ", segments); + } + + /** + * Of command path. + * + * @param segments the segments + * @return the command path + */ + @Contract("_ -> new") + public static @NotNull CommandPath of(String... segments) { + validateSegments(segments); + return new CommandPath(List.of(segments)); + } + + /** + * From string command path. + * + * @param path the path + * @return the command path + */ + @Contract("_ -> new") + public static @NotNull CommandPath fromString(@NotNull String path) { + if (path.charAt(0) == '/') { + path = path.substring(1); + } + return of(path.split(" ")); + } + + /** + * Then command path. + * + * @param subSegments the sub segments + * @return the command path + */ + @Contract("_ -> new") + public @NotNull CommandPath then(String... subSegments) { + validateSegments(subSegments); + List newSegments = new ArrayList<>(this.segments); + newSegments.addAll(List.of(subSegments)); + return new CommandPath(newSegments); + } + + /** + * Parent optional. + * + * @return the optional + */ + public Optional parent() { + if (segments.size() <= 1) { + return Optional.empty(); + } + return Optional.of(new CommandPath(segments.subList(0, segments.size() - 1))); + } + + /** + * Last segment string. + * + * @return the string + */ + public String lastSegment() { + return segments.isEmpty() ? "" : segments.get(segments.size() - 1); + } + + /** + * Segments string @ not null [ ]. + * + * @return the string @ not null [ ] + */ + public String @NotNull [] segments() { + return segments.toArray(new String[0]); + } + + /** + * Full path string. + * + * @return the string + */ + public String fullPath() { + return fullPath; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommandPath that = (CommandPath) o; + return Objects.equals(fullPath, that.fullPath); + } + + @Override + public int hashCode() { + return fullPath.hashCode(); + } + + @Override + public String toString() { + return fullPath; + } + + private static void validateSegments(String @NotNull [] segments) { + for (String segment : segments) { + if (segment == null || segment.isEmpty() || segment.contains(" ")) { + throw new IllegalArgumentException("Invalid command segment: " + segment); + } + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java b/common/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java new file mode 100644 index 0000000..f399d88 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java @@ -0,0 +1,78 @@ +package top.r3944realms.lib39.core.command.model; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type Parameter. + */ +public record Parameter(String name, boolean required) { + + /** + * 獲取參數類型標識 + * + * @return the type indicator + */ + @Contract(pure = true) + public @NotNull String getTypeIndicator() { + return required ? "required" : "optional"; + } + + @Override + public String toString() { + return String.format("Parameter{name='%s', required=%s}", name, required); + } + + /** + * The type Builder. + */ + public static class Builder { + private final List parameters = new ArrayList<>(); + + /** + * Required builder. + * + * @param name the name + * @return the builder + */ + public Builder required(String name) { + parameters.add(new Parameter(name, true)); + return this; + } + + /** + * Optional builder. + * + * @param name the name + * @return the builder + */ + public Builder optional(String name) { + parameters.add(new Parameter(name, false)); + return this; + } + + + /** + * Build parameter [ ]. + * + * @return the parameter [ ] + */ + public Parameter[] build() { + return parameters.toArray(new Parameter[0]); + } + + /** + * Builder parameter . builder. + * + * @return the parameter . builder + */ +// 链式调用的便利方法 + @Contract(" -> new") + public static @NotNull Parameter.Builder builder() { + return new Builder(); + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/compat/CompatManager.java b/common/src/main/java/top/r3944realms/lib39/core/compat/CompatManager.java new file mode 100644 index 0000000..4d6abb5 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/compat/CompatManager.java @@ -0,0 +1,157 @@ +package top.r3944realms.lib39.core.compat; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.r3944realms.lib39.Lib39; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * The type Compat manager. + */ +@SuppressWarnings("unused") +public abstract class CompatManager { + public ResourceLocation getId() { + return id; + } + protected final Logger logger; + protected final ResourceLocation id; + protected final Map compats = new HashMap<>(); + protected boolean initialized = false; + protected final List pendingTasks = new ArrayList<>(); + + public void initialize() { + initializeAllCompat(); + onLoadComplete(); + } + + public CompatManager(@NotNull ResourceLocation id) { + this.id = id; + this.logger = LoggerFactory.getLogger(id.toString()); + } + + /** + * Register compat. + * + * @param id the id + * @param compat the compat + */ + public void registerCompat(ResourceLocation id, ICompat compat) { + if (initialized) { + // 已初始化,直接注册 + doRegisterCompat(id, compat); + } else { + // 未初始化,缓存起来 + pendingTasks.add(() -> doRegisterCompat(id, compat)); + logger.debug("Cached compat registration for: {}", id); + } + } + + protected void doRegisterCompat(ResourceLocation id, ICompat compat) { + if (compats.containsKey(id)) { + logger.warn("Compat with id {} is already registered!", id); + return; + } + compats.put(id, compat); + logger.debug("Registered compat: {}", id); + } + + /** + * Register compat. + * + * @param namespace the namespace + * @param path the path + * @param compat the compat + */ + public void registerCompat(String namespace, String path, ICompat compat) { + registerCompat(Lib39.rl(namespace, path), compat); + } + + + // ===================== 初始化和管理 ===================== + + /** + * 初始化所有兼容模块并应用事件监听器 + */ + protected synchronized void initializeAllCompat() { + logger.info("Initializing {} compatibility modules", compats.size()); + + // 先处理所有缓存的注册 + pendingTasks.forEach(Runnable::run); + pendingTasks.clear(); + + // 初始化所有兼容模块 + for (Map.Entry entry : compats.entrySet()) { + if (!entry.getValue().isInitialized() && entry.getValue().isModLoaded()) { + try { + entry.getValue().initialize(); + entry.getValue().setInitialize(true); + logger.info("Initialized compat: {}", entry.getKey()); + } catch (Exception e) { + logger.error("Failed to initialize compat: {}", entry.getKey(), e); + } + } + } + initialized = true; + } + + /** + * Gets compat. + * + * @param id the id + * @return the compat + */ + public Optional getCompat(ResourceLocation id) { + return Optional.ofNullable(compats.get(id)); + } + + /** + * Has compat boolean. + * + * @param id the id + * @return the boolean + */ + public boolean hasCompat(ResourceLocation id) { + return compats.containsKey(id); + } + + /** + * Unregister compat. + * + * @param id the id + */ + public void unregisterCompat(ResourceLocation id) { + ICompat removed = compats.remove(id); + if (removed != null) { + logger.debug("Unregistered compat: {}", id); + } + } + + /** + * Gets loaded compats. + * + * @return the loaded compats + */ + public List getLoadedCompats() { + return compats.values().stream() + .filter(ICompat::isModLoaded) + .collect(Collectors.toList()); + } + + /** + * On load complete. + */ + public void onLoadComplete() { + logger.info("Calling onLoadComplete for {} compatibility modules", compats.size()); + for (Map.Entry entry : compats.entrySet()) { + try { + entry.getValue().onLoadComplete(); + } catch (Exception e) { + logger.error("Error in onLoadComplete for compat: {}", entry.getKey(), e); + } + } + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/core/compat/ICompat.java b/common/src/main/java/top/r3944realms/lib39/core/compat/ICompat.java new file mode 100644 index 0000000..89ed5d5 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/compat/ICompat.java @@ -0,0 +1,77 @@ +package top.r3944realms.lib39.core.compat; + +import net.minecraft.resources.ResourceLocation; + +import java.util.concurrent.Callable; + +/** + * The interface Compat. + */ +public interface ICompat { + void setInitialize(boolean initialize); + boolean isInitialized(); + /** + * Id resource location. + * + * @return the resource location + */ + ResourceLocation id(); + + /** + * Initialize. + */ + void initialize(); + + /** + * On load complete. + */ + default void onLoadComplete() {} + + /** + * Is mod loaded boolean. + * + * @return the boolean + */ + default boolean isModLoaded() { + return false; + } + + /** + * Call if present t. + * + * @param the type parameter + * @param callable the callable + * @return the t + * @throws Exception the exception + */ + default T callIfPresent(Callable callable) throws Exception { + if (isModLoaded()) return callable.call(); + else return null; + } + + /** + * Call if pesent t. + * + * @param the type parameter + * @param callable the callable + * @param elseCall the else call + * @return the t + * @throws Exception the exception + */ + default T callIfPresent(Callable callable, Callable elseCall) throws Exception { + if (isModLoaded()) return callable.call(); + else return elseCall.call(); + } + + /** + * Run if present boolean. + * + * @param runnable the runnable + * @return the boolean + * @throws Exception the exception + */ + default boolean runIfPresent(Runnable runnable) throws Exception { + if (isModLoaded()) runnable.run(); else return false; + return true; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java b/common/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java new file mode 100644 index 0000000..adce3f9 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java @@ -0,0 +1,92 @@ +package top.r3944realms.lib39.core.lang; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +/** + * The type Class encryptor. + */ +public class ClassEncryptor { + static { + System.loadLibrary("ClassEncrypt"); + } + + /** + * Encrypt class byte [ ]. + * + * @param classData the class data + * @param key the key + * @return the byte [ ] + */ + public native byte[] encryptClass(byte[] classData, String key); + + /** + * Decrypt class byte [ ]. + * + * @param encryptedData the encrypted data + * @param key the key + * @return the byte [ ] + */ + public native byte[] decryptClass(byte[] encryptedData, String key); + + /** + * Is encrypted file boolean. + * + * @param fileData the file data + * @return the boolean + */ + public native boolean isEncryptedFile(byte[] fileData); + + /** + * Encrypt class file. + * + * @param inputPath the input path + * @param outputPath the output path + * @param key the key + * @throws IOException the io exception + */ + public void encryptClassFile(String inputPath, String outputPath, String key) + throws IOException { + byte[] classData = Files.readAllBytes(Paths.get(inputPath)); + byte[] encryptedData = encryptClass(classData, key); + Files.write(Paths.get(outputPath), encryptedData); + System.out.println("Encrypted: " + inputPath + " -> " + outputPath); + } + + + /** + * Encrypt directory. + * + * @param inputDir the input dir + * @param outputDir the output dir + * @param key the key + * @throws IOException the io exception + */ + public void encryptDirectory(String inputDir, String outputDir, String key) + throws IOException { + try (Stream walk = Files.walk(Paths.get(inputDir))) { + walk + .filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".class")) + .forEach(p -> { + try { + String relativePath = inputDir.equals(p.getParent().toString()) + ? p.getFileName().toString() + : inputDir.equals(p.getParent().getParent().toString()) + ? p.getParent().getFileName() + "/" + p.getFileName() + : p.toString().substring(inputDir.length() + 1); + + Path outputPath = Paths.get(outputDir, relativePath); + Files.createDirectories(outputPath.getParent()); + + encryptClassFile(p.toString(), outputPath.toString(), key); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java b/common/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java new file mode 100644 index 0000000..3a9805b --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java @@ -0,0 +1,227 @@ +package top.r3944realms.lib39.core.lang; + +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +/** + * The type Encrypted class loader. + */ +public class EncryptedClassLoader extends ClassLoader { + static { + System.loadLibrary("ClassEncrypt"); + } + + private native byte[] decryptClass(byte[] encryptedData, String key); + + private final String encryptedClassPath; + private final String decryptionKey; + + // 缓存已加载的类字节码,避免重复加载 + private final Map classBytesCache = new HashMap<>(); + + /** + * Instantiates a new Encrypted class loader. + * + * @param encryptedClassPath the encrypted class path + * @param key the key + */ + public EncryptedClassLoader(String encryptedClassPath, String key) { + this.encryptedClassPath = encryptedClassPath; + this.decryptionKey = key; + } + + /** + * Instantiates a new Encrypted class loader. + * + * @param encryptedClassPath the encrypted class path + * @param key the key + * @param parent the parent + */ + public EncryptedClassLoader(String encryptedClassPath, String key, ClassLoader parent) { + super(parent); + this.encryptedClassPath = encryptedClassPath; + this.decryptionKey = key; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + try { + // 从缓存获取或加载类字节码 + byte[] classData; + synchronized (classBytesCache) { + classData = classBytesCache.get(name); + if (classData == null) { + // 1. 读取加密的class文件 + byte[] encryptedData = loadEncryptedClass(name); + + // 2. 使用JNI解密 + classData = decryptClass(encryptedData, decryptionKey); + + // 3. 验证解密后的数据是否是有效的class文件 + if (!isValidClass(classData)) { + throw new ClassNotFoundException("Invalid class data after decryption"); + } + + // 缓存类字节码 + classBytesCache.put(name, classData); + } + } + + // 4. 定义类 + return defineClass(name, classData, 0, classData.length); + + } catch (Exception e) { + throw new ClassNotFoundException("Failed to load encrypted class: " + name, e); + } + } + + private byte [] loadEncryptedClass(String className) throws IOException { + String path = className.replace('.', File.separatorChar) + ".class"; + Path fullPath = Paths.get(encryptedClassPath, path); + + if (!Files.exists(fullPath)) { + // 尝试寻找内部类 + int dollarIndex = className.lastIndexOf('$'); + if (dollarIndex != -1) { + String outerClass = className.substring(0, dollarIndex); + path = outerClass.replace('.', File.separatorChar) + + "$" + className.substring(dollarIndex + 1) + ".class"; + fullPath = Paths.get(encryptedClassPath, path); + } + } + + if (!Files.exists(fullPath)) { + // 尝试其他可能的文件扩展名 + fullPath = Paths.get(encryptedClassPath, className.replace('.', File.separatorChar) + ".enc"); + if (!Files.exists(fullPath)) { + throw new FileNotFoundException("Encrypted class not found: " + className); + } + } + + return Files.readAllBytes(fullPath); + } + + private boolean isValidClass(byte [] data) { + // Java class文件的魔数是0xCAFEBABE + return data.length >= 4 && + data[0] == (byte)0xCA && + data[1] == (byte)0xFE && + data[2] == (byte)0xBA && + data[3] == (byte)0xBE; + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + // 优先检查是否已加载 + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + + // Java核心类库使用父加载器 + if (name.startsWith("java.") || name.startsWith("javax.") || + name.startsWith("sun.") || name.startsWith("jdk.")) { + return super.loadClass(name, resolve); + } + + try { + // 尝试用自定义ClassLoader加载 + clazz = findClass(name); + } catch (ClassNotFoundException e) { + // 如果找不到,委托给父加载器 + clazz = super.loadClass(name, resolve); + } + + if (resolve) { + resolveClass(clazz); + } + + return clazz; + } + + /** + * Get class bytes byte [ ]. + * + * @param className the class name + * @return the byte [ ] + * @throws ClassNotFoundException the class not found exception + */ +// 添加获取类字节码的方法 + public byte[] getClassBytes(String className) throws ClassNotFoundException { + synchronized (classBytesCache) { + byte[] bytes = classBytesCache.get(className); + if (bytes == null) { + // 触发类加载以填充缓存 + loadClass(className); + bytes = classBytesCache.get(className); + } + return bytes != null ? bytes.clone() : null; // 返回副本 + } + } + + @Override + public InputStream getResourceAsStream(String name) { + try { + // 处理.class资源请求 + if (name.endsWith(".class")) { + String className = name.substring(0, name.length() - 6) + .replace('/', '.'); + byte[] classData = getClassBytes(className); + if (classData != null) { + return new ByteArrayInputStream(classData); + } + } + + // 处理其他资源文件 + Path resourcePath = Paths.get(encryptedClassPath, name); + if (Files.exists(resourcePath)) { + return Files.newInputStream(resourcePath); + } + + } catch (Exception e) { + // 忽略异常,返回null让父加载器处理 + } + + // 委托给父加载器 + return super.getResourceAsStream(name); + } + + @Override + public URL getResource(String name) { + try { + Path resourcePath = Paths.get(encryptedClassPath, name); + if (Files.exists(resourcePath)) { + return resourcePath.toUri().toURL(); + } + } catch (Exception e) { + // 忽略异常 + } + return super.getResource(name); + } + + /** + * Clear cache. + */ + public void clearCache() { + synchronized (classBytesCache) { + classBytesCache.clear(); + } + } + + /** + * Clear cache. + * + * @param className the class name + */ + public void clearCache(String className) { + synchronized (classBytesCache) { + classBytesCache.remove(className); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java b/common/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java new file mode 100644 index 0000000..6e02b3a --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java @@ -0,0 +1,78 @@ +package top.r3944realms.lib39.core.registry; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; +import top.r3944realms.lib39.datagen.value.ILocaleEntry; +import top.r3944realms.lib39.datagen.value.McLocale; + +import java.util.*; + +/** + * The type Locale registry. + */ +@SuppressWarnings("unused") +public class LocaleRegistry { + private static final Map REGISTRY = new LinkedHashMap<>(); + + // 初始化:注册所有枚举值 + static { + for (McLocale loc : McLocale.values()) { + register(loc); + } + } + + /** + * 注册(覆盖已有时直接返回旧值) @param entry the entry + * + * @param entry the entry + * @return the locale entry + */ + @SuppressWarnings("UnusedReturnValue") + public static ILocaleEntry register(ILocaleEntry entry) { + return REGISTRY.putIfAbsent(entry.mcCode().toLowerCase(), entry); + } + + /** + * 通过 Minecraft 代码查找 @param code the code + * + * @param code the code + * @return the locale entry + */ + public static ILocaleEntry fromMcCode(@NotNull String code) { + return REGISTRY.get(code.toLowerCase()); + } + + /** + * 列出所有 @return the collection + * + * @return the collection + */ + public static @NotNull @UnmodifiableView Collection allValues() { + return Collections.unmodifiableCollection(REGISTRY.values()); + } + + /** + * 动态注册一个扩展 Locale @param mcCode the mc code + * + * @param mcCode the mc code + * @param locale the locale + * @return the locale entry + */ + public static ILocaleEntry registerDynamic(@NotNull String mcCode, Locale locale) { + return REGISTRY.computeIfAbsent(mcCode.toLowerCase(), + k -> new ExtendedLocale(mcCode.toLowerCase(), locale)); + } + + /** + * 扩展类型 + */ + private record ExtendedLocale(String mcCode, Locale javaLocale) implements ILocaleEntry { + + @Contract(pure = true) + @Override + public @NotNull String toString() { + return "ExtendedLocale[" + mcCode + "]"; + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/CachedSyncManager.java b/common/src/main/java/top/r3944realms/lib39/core/sync/CachedSyncManager.java new file mode 100644 index 0000000..1c5d91b --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/CachedSyncManager.java @@ -0,0 +1,74 @@ +package top.r3944realms.lib39.core.sync; + +import java.util.Map; +import java.util.Set; + +/** + * The type Cached sync manager. + * + * @param the type parameter + * @param the type parameter + */ +@SuppressWarnings("unused") +public abstract class CachedSyncManager> implements ISyncManager { + + private volatile Set cachedSet; + private volatile int mapSize = -1; + + @Override + public Set getSyncSet() { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + + // 检查是否需要更新缓存 + if (cachedSet == null || mapSize != syncMap.size()) { + synchronized (this) { + if (cachedSet == null || mapSize != syncMap.size()) { + cachedSet = Set.copyOf(syncMap.values()); + mapSize = syncMap.size(); + } + } + } + return cachedSet; + } + + /** + * 当Map发生变化时调用此方法清除缓存 + */ + protected void invalidateCache() { + cachedSet = null; + mapSize = -1; + } + + @Override + public void track(K key, T instance) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + syncMap.put(key, instance); + invalidateCache(); + } + + @Override + public void untrack(K key, T instance) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + // 只有当key对应的value确实是instance时才移除,避免误删 + syncMap.remove(key, instance); + invalidateCache(); + } + + @Override + public void clear() { + Map syncMap = getSyncMap(); + if (syncMap != null) { + syncMap.clear(); + } + invalidateCache(); + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/IEntity.java b/common/src/main/java/top/r3944realms/lib39/core/sync/IEntity.java new file mode 100644 index 0000000..249b1af --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/IEntity.java @@ -0,0 +1,13 @@ +package top.r3944realms.lib39.core.sync; + +/** + * The interface Entity. + */ +public interface IEntity { + /** + * Entity id int. + * + * @return the int + */ + int entityId(); +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/INBTSerializable.java b/common/src/main/java/top/r3944realms/lib39/core/sync/INBTSerializable.java new file mode 100644 index 0000000..0cb9b4d --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/INBTSerializable.java @@ -0,0 +1,9 @@ +package top.r3944realms.lib39.core.sync; + +import net.minecraft.nbt.Tag; + +public interface INBTSerializable { + T serializeNBT(); + + void deserializeNBT(T var1); +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java b/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java new file mode 100644 index 0000000..30f82b5 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java @@ -0,0 +1,50 @@ +package top.r3944realms.lib39.core.sync; + +import net.minecraft.resources.ResourceLocation; + +/** + * The interface Sync data. + * + * @param the type parameter + */ +public interface ISyncData { + /** + * Id resource location. + * + * @return the resource location + */ + ResourceLocation id(); + + /** + * Is dirty boolean. + * + * @return the boolean + */ + boolean isDirty(); + + /** + * Sets dirty. + * + * @param dirty the dirty + */ + void setDirty(boolean dirty); + + /** + * Mark dirty. + */ + default void markDirty() { + setDirty(true); + } + + /** + * Copy from. + * + * @param src the src + */ + void copyFrom(T src); + + /** + * Check if dirty then update. + */ + void checkIfDirtyThenUpdate(); +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java b/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java new file mode 100644 index 0000000..661d22e --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java @@ -0,0 +1,129 @@ +package top.r3944realms.lib39.core.sync; + +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * The interface Sync manager. + * + * @param the type parameter + * @param the type parameter + */ +@SuppressWarnings("unused") +public interface ISyncManager> { + + /** + * 获取同步映射 + * + * @return the sync map + */ + Map getSyncMap(); + + /** + * 获取同步集合 + * + * @return the sync set + */ + default Set getSyncSet() { + Map syncMap = getSyncMap(); + return Set.copyOf(syncMap.values()); + } + + /** + * 跟踪实例 + * + * @param key the key + * @param instance the instance + */ + default void track(K key, T instance) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + syncMap.put(key, instance); + } + + /** + * 取消跟踪 + * + * @param key the key + * @param instance the instance + */ + default void untrack(K key, T instance) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + // 只有当key对应的value确实是instance时才移除,避免误删 + syncMap.remove(key, instance); + } + + /** + * 遍历操作 + * + * @param consumer the consumer + */ + default void foreach(Consumer consumer) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + syncMap.values().forEach(consumer); + } + + /** + * 批量操作 + * + * @param instances the instances + */ + default void trackAll(Map instances) { + Map syncMap = getSyncMap(); + if (syncMap == null) { + throw new IllegalStateException("SyncMap is not initialized"); + } + syncMap.putAll(instances); + } + + /** + * 获取大小 + * + * @return the int + */ + default int size() { + Map syncMap = getSyncMap(); + return syncMap != null ? syncMap.size() : 0; + } + + /** + * 检查是否包含key + * + * @param key the key + * @return the boolean + */ + default boolean containsKey(K key) { + Map syncMap = getSyncMap(); + return syncMap != null && syncMap.containsKey(key); + } + + /** + * 检查是否包含value + * + * @param value the value + * @return the boolean + */ + default boolean containsValue(T value) { + Map syncMap = getSyncMap(); + return syncMap != null && syncMap.containsValue(value); + } + + /** + * 清空所有数据 + */ + default void clear() { + Map syncMap = getSyncMap(); + if (syncMap != null) { + syncMap.clear(); + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/IUpdate.java b/common/src/main/java/top/r3944realms/lib39/core/sync/IUpdate.java new file mode 100644 index 0000000..97091a3 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/IUpdate.java @@ -0,0 +1,6 @@ +package top.r3944realms.lib39.core.sync; + +public interface IUpdate { + void update(); + NBTEntitySyncData getSyncData(); +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/NBTEntitySyncData.java b/common/src/main/java/top/r3944realms/lib39/core/sync/NBTEntitySyncData.java new file mode 100644 index 0000000..b1ab889 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/NBTEntitySyncData.java @@ -0,0 +1,58 @@ +package top.r3944realms.lib39.core.sync; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +public abstract class NBTEntitySyncData implements IEntity, ISyncData, INBTSerializable, IUpdate { + /** + * The Dirty. + */ + protected boolean dirty; + /** + * The Id. + */ + protected final ResourceLocation id; + + /** + * Instantiates a new Nbt sync data. + * + * @param id the id + */ + protected NBTEntitySyncData(ResourceLocation id) { + this.id = id; + } + + @Override + public ResourceLocation id() { + return id; + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + @Override + public void copyFrom(@NotNull NBTEntitySyncData src) { + this.dirty = src.isDirty(); + } + + @Override + public void checkIfDirtyThenUpdate() { + if (isDirty()) { + update(); + } + dirty = false; + } + + @Override + public NBTEntitySyncData getSyncData() { + return this; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java b/common/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java new file mode 100644 index 0000000..dcf92b7 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java @@ -0,0 +1,460 @@ +package top.r3944realms.lib39.core.sync; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * The type Sync data 2 manager. + */ +@SuppressWarnings({"unused", "DuplicatedCode"}) +public class SyncData2Manager { + protected final Map> typedEntries = Maps.newConcurrentMap(); + + /** + * 数据提供者接口 - 用于通过键获取数据 + * + * @param the type parameter + * @param the type parameter + */ + @FunctionalInterface + public interface DataProvider { + /** + * 通过键获取数据的 Optional + * + * @param key 键 + * @return 数据的 Optional + */ + Optional getData(K key); + } + + protected static class TypedSyncEntry> { + /** + * The Manager. + */ + final ISyncManager manager; + /** + * The Data provider. + */ + @Nullable + final DataProvider dataProvider; + /** + * The Allowed classes. + */ + final Set> allowedClasses; + + /** + * Instantiates a new Typed sync entry. + * + * @param manager the manager + * @param dataProvider the data provider + */ + public TypedSyncEntry(ISyncManager manager, @Nullable DataProvider dataProvider) { + this.manager = manager; + this.dataProvider = dataProvider; + this.allowedClasses = Sets.newConcurrentHashSet(); + } + } + + /** + * Register manager with data provider. + * + * @param the type parameter + * @param the type parameter + * @param key the key + * @param manager the manager + * @param dataProvider the data provider + */ + public > void registerManagerWithProvider( + ResourceLocation key, + ISyncManager manager, + DataProvider dataProvider + ) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + Objects.requireNonNull(dataProvider, "Data provider cannot be null"); + + typedEntries.put(key, new TypedSyncEntry<>(manager, dataProvider)); + } + + /** + * Register manager with function getter. + * + * @param the type parameter + * @param the type parameter + * @param key the key + * @param manager the manager + * @param getter the data getter function + */ + public > void registerManager( + ResourceLocation key, + ISyncManager manager, + Function> getter + ) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + Objects.requireNonNull(getter, "Data getter function cannot be null"); + + typedEntries.put(key, new TypedSyncEntry<>(manager, getter::apply)); + } + + /** + * 向后兼容的注册方法(只注册管理器,不注册数据提供者) + * + * @param key the key + * @param manager the manager + */ + @SuppressWarnings("unchecked") + public void registerManager(ResourceLocation key, ISyncManager> manager) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + + // 创建一个没有数据提供者的 TypedSyncEntry + typedEntries.put(key, new TypedSyncEntry<>( + (ISyncManager>) manager, + null + )); + } + + /** + * Gets manager. + * + * @param the type parameter + * @param the type parameter + * @param key the key + * @return the manager + */ + @SuppressWarnings("unchecked") + public > Optional> getManager(ResourceLocation key) { + TypedSyncEntry entry = typedEntries.get(key); + return entry != null ? Optional.of((ISyncManager) entry.manager) : Optional.empty(); + } + + /** + * Gets data provider. + * + * @param the type parameter + * @param key the key + * @return the data provider + */ + @SuppressWarnings("unchecked") + public > Optional> getDataProvider(ResourceLocation key) { + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null && entry.dataProvider != null) { + return Optional.of((DataProvider) entry.dataProvider); + } + return Optional.empty(); + } + + /** + * 获取实体数据 + * + * @param the type parameter + * @param key the key + * @param entity the entity + * @return the entity data + */ + @SuppressWarnings("unchecked") + public > Optional getEntityData(ResourceLocation key, Entity entity) { + return getDataProvider(key) + .flatMap(provider -> { + Optional> result = provider.getData(entity); + return (Optional) result; + }); + } + + /** + * Allow entity class. + * + * @param key the key + * @param classes the classes + */ + public final void allowEntityClass(ResourceLocation key, Class... classes) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(classes, "Classes array cannot be null"); + + if (classes.length == 0) { + return; + } + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + entry.allowedClasses.addAll(Arrays.asList(classes)); + } + } + + /** + * 移除允许的实体类 + * + * @param key the key + * @param classes the classes + */ + public final void disallowEntityClass(ResourceLocation key, Class... classes) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(classes, "Classes array cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null && classes.length > 0) { + Arrays.asList(classes).forEach(entry.allowedClasses::remove); + } + } + + /** + * 绑定数据提供者(用于分离注册的情况) + * + * @param the type parameter + * @param key the key + * @param dataProvider the data provider + */ + public > void bindDataProvider(ResourceLocation key, DataProvider dataProvider) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(dataProvider, "Data provider cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 更新现有条目的数据提供者 + updateDataProviderInEntry(key, entry, dataProvider); + } else { + throw new IllegalArgumentException("No manager found for " + key); + } + } + + /** + * 绑定简单的数据获取器 + * + * @param the type parameter + * @param key the key + * @param getter the data getter function + */ + public > void bindDataGetter(ResourceLocation key, @NotNull Function> getter) { + bindDataProvider(key, getter::apply); + } + + /** + * 解绑数据提供者 + * + * @param key the key + */ + public void unbindDataProvider(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 将数据提供者设置为null,但保留管理器和其他配置 + updateDataProviderInEntry(key, entry, null); + } + } + + /** + * 清除允许的实体类 + * + * @param key the key + */ + public void clearAllowedEntityClasses(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + entry.allowedClasses.clear(); + } + } + + /** + * Is entity class allowed boolean. + * + * @param key the key + * @param entityClass the entity class + * @return the boolean + */ + public boolean isEntityClassAllowed(ResourceLocation key, Class entityClass) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(entityClass, "Entity class cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + boolean isAllowed = false; + if (entry != null) { + for (Class allowedClass : entry.allowedClasses) { + if (entityClass.isAssignableFrom(allowedClass)) { + isAllowed = true; + break; + } + } + } + return entry != null && isAllowed ; + } + + /** + * Track entity for manager. + * + * @param entity the entity + * @param managerId the manager id + */ + @SuppressWarnings("unchecked") + public void trackEntityForManager(Entity entity, ResourceLocation managerId) { + TypedSyncEntry entry = (TypedSyncEntry) typedEntries.get(managerId); + if (entry != null) { + trackEntityWithTypedEntry(entity, entry); + } + } + + private > void trackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry entry) { + if (entry.dataProvider != null) { + entry.dataProvider.getData(entity) + .ifPresent(data -> entry.manager.track(entity.getUUID(), data)); + } + } + + /** + * Untrack entity for manager. + * + * @param entity the entity + * @param managerId the manager id + */ + @SuppressWarnings("unchecked") + public void untrackEntityForManager(Entity entity, ResourceLocation managerId) { + TypedSyncEntry entry = (TypedSyncEntry) typedEntries.get(managerId); + if (entry != null) { + untrackEntityWithTypedEntry(entity, entry); + } + } + + private > void untrackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry entry) { + if (entry.dataProvider != null) { + entry.dataProvider.getData(entity) + .ifPresent(data -> entry.manager.untrack(entity.getUUID(), data)); + } + } + + /** + * 从所有管理器中移除实体跟踪 + * + * @param entity the entity + */ + public void untrackEntityFromAllManagers(Entity entity) { + for (ResourceLocation id : getRegisteredKeys()) { + if (isEntityClassAllowed(id, entity.getClass())) { + untrackEntityForManager(entity, id); + } + } + } + + /** + * 批量从管理器中移除实体跟踪 + * + * @param entities the entities + * @param managerId the manager id + */ + public void untrackEntitiesForManager(@NotNull Iterable entities, ResourceLocation managerId) { + for (Entity entity : entities) { + untrackEntityForManager(entity, managerId); + } + } + + /** + * 从所有管理器中批量移除实体跟踪 + * + * @param entities the entities + */ + public void untrackEntitiesFromAllManagers(@NotNull Iterable entities) { + for (Entity entity : entities) { + untrackEntityFromAllManagers(entity); + } + } + + /** + * 强制清理管理器中的所有跟踪数据 + * + * @param managerId the manager id + */ + public void clearAllTrackedData(ResourceLocation managerId) { + TypedSyncEntry entry = typedEntries.get(managerId); + if (entry != null) { + clearTrackedDataForEntry(entry); + } + } + + private > void clearTrackedDataForEntry(@NotNull TypedSyncEntry entry) { + Set syncSet = entry.manager.getSyncSet(); + if (syncSet != null) { + syncSet.clear(); + } + } + + /** + * 清理所有管理器的跟踪数据 + */ + public void clearAllTrackedData() { + for (ResourceLocation id : getRegisteredKeys()) { + clearAllTrackedData(id); + } + } + + // 辅助方法:更新条目的数据提供者 + @SuppressWarnings("unchecked") + protected > void updateDataProviderInEntry( + ResourceLocation id, + TypedSyncEntry entry, + DataProvider newDataProvider + ) { + // 由于 DataProvider 是 final,我们需要创建一个新的 TypedSyncEntry + TypedSyncEntry newEntry = new TypedSyncEntry<>( + (ISyncManager) entry.manager, + newDataProvider + ); + newEntry.allowedClasses.addAll(entry.allowedClasses); + + typedEntries.put(id, newEntry); + } + + /** + * Gets registered keys. + * + * @return the registered keys + */ + public Set getRegisteredKeys() { + return Collections.unmodifiableSet(typedEntries.keySet()); + } + + /** + * For each. + * + * @param consumer the consumer + */ + public void forEach(BiConsumer> consumer) { + Objects.requireNonNull(consumer, "Consumer cannot be null"); + typedEntries.forEach((key, entry) -> consumer.accept(key, entry.manager)); + } + + /** + * Gets manager count. + * + * @return the manager count + */ + public int getManagerCount() { + return typedEntries.size(); + } + + /** + * Clear all. + */ + public void clearAll() { + typedEntries.clear(); + } + + /** + * 移除管理器(包括所有相关配置) + * + * @param key the key + */ + public void removeManager(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + typedEntries.remove(key); + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/example/Lib39Example.java b/common/src/main/java/top/r3944realms/lib39/example/Lib39Example.java new file mode 100644 index 0000000..5482f33 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/Lib39Example.java @@ -0,0 +1,29 @@ +package top.r3944realms.lib39.example; + +/** + * The type Lib 39 example. + */ +public class Lib39Example { + private static boolean registered = false; + + /** + * Instantiates a new Lib 39 example. + */ + public Lib39Example() { + if (!registered) { + init(); + registered = true; + } + } + public void init() { + + } + + + /** + * Demonstrate feature. + */ + public void demonstrateFeature() { + + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java b/common/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java new file mode 100644 index 0000000..602ffa6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java @@ -0,0 +1,128 @@ +package top.r3944realms.lib39.example.client.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2f; +import top.r3944realms.lib39.client.gui.component.WheelWidget; +import top.r3944realms.lib39.mixin.minecraft.ScreenAccessor; +import top.r3944realms.lib39.util.lang.FourConsumer; +import top.r3944realms.lib39.util.lang.Pair; + +import java.util.List; +import java.util.Objects; + +import static top.r3944realms.lib39.client.gui.component.WheelWidget.IGNORE_CURSOR_MOVE_LENGTH; + +/** + * The type Forge screen. + */ +public class ForgeScreen extends Screen { + private final LocalPlayer player = Objects.requireNonNull(Minecraft.getInstance().player); + private final InteractionHand hand; + private final int mode; + + /** + * The Wheel. + */ + public WheelWidget wheel; + + /** + * Instantiates a new Forge screen. + * + * @param hand the hand + * @param mode the mode + */ + public ForgeScreen(InteractionHand hand, int mode) { + super(Component.literal("Test")); + this.hand = hand; + this.mode = mode; + } + + @Override + protected void init() { + int leftPos = (this.width - 75) / 2; + int topPos = (this.height - 75) / 2; + ItemStack holding = player.getItemInHand(this.hand); + WheelWidget wheel = new WheelWidget( + leftPos, topPos, 75, 75, + 12.5f, 32.5f, 0.75f, + List.of( + Pair.of( + Component.literal("auto"), + renderItem(holding)), + Pair.of( + Component.literal("axe"), + renderItem(holding)), + Pair.of( + Component.literal("shovel"), + renderItem(holding)), + Pair.of( + Component.literal("hoe"), + renderItem(holding)), + Pair.of( + Component.literal("pickaxe"), + renderItem(holding)) + ) + ).setCurrentIndex(this.wheel != null ? this.wheel.getCurrentSectionIndex() : this.mode); + this.clearWidgets(); + this.wheel = this.addRenderableWidget(wheel); + } + + @Contract(pure = true) + private static @NotNull FourConsumer renderItem(ItemStack holding) { + return (graphics, pose, width, height) -> { + ItemStack stack = holding.copy(); + graphics.renderItem(stack, 2, 2, 9910597); + }; + } + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { + if (wheel != null && wheel.isClosingAnimationStarted()) return true; + float screenCenterX = this.width / 2f; + float screenCenterY = this.height / 2f; + Vector2f cursorVec2 = new Vector2f( + (float) mouseX - screenCenterX, + (float) mouseY - screenCenterY + ); + if (cursorVec2.length() < IGNORE_CURSOR_MOVE_LENGTH) { + return true; + } + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (wheel != null ) { + wheel.onClosing(); + } + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public void removed() { + super.removed(); + } + + + @Override + public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + for (Renderable renderable : ((ScreenAccessor) this).getrRenderables()) { + renderable.render(guiGraphics, mouseX, mouseY, partialTick); + } + } + + @Override + public boolean isPauseScreen() { + return false; + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/example/content/data/AbstractedTestSyncData.java b/common/src/main/java/top/r3944realms/lib39/example/content/data/AbstractedTestSyncData.java new file mode 100644 index 0000000..e80d583 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/content/data/AbstractedTestSyncData.java @@ -0,0 +1,240 @@ +package top.r3944realms.lib39.example.content.data; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.sync.NBTEntitySyncData; + +/** + * The type Abstracted test sync data. + */ +@SuppressWarnings("unused") +public abstract class AbstractedTestSyncData extends NBTEntitySyncData { + public final static String DEFAULT_TEST_STRING = "default_value"; + public final static int DEFAULT_TEST_INT = 42; + public final static boolean DEFAULT_TEST_BOOLEAN = true; + public final static double DEFAULT_TEST_DOUBLE = 3.14159; + public final static TestData DEFAULT_TEST_DATA = new TestData("default", 100, false); + /** + * Instantiates a new Nbt sync data. + * + * @param id the id + */ + protected AbstractedTestSyncData(ResourceLocation id) { + super(id); + } + + /** + * Gets test string. + * + * @return the test string + */ + public abstract String getTestString(); + + /** + * Sets test string. + * + * @param value the value + */ + public abstract void setTestString(String value); + + /** + * Gets test int. + * + * @return the test int + */ + public abstract int getTestInt(); + + /** + * Sets test int. + * + * @param value the value + */ + public abstract void setTestInt(int value); + + /** + * Is test boolean boolean. + * + * @return the boolean + */ + public abstract boolean isTestBoolean(); + + /** + * Sets test boolean. + * + * @param value the value + */ + public abstract void setTestBoolean(boolean value); + + /** + * Gets test double. + * + * @return the test double + */ + public abstract double getTestDouble(); + + /** + * Sets test double. + * + * @param value the value + */ + public abstract void setTestDouble(double value); + + /** + * Gets counter. + * + * @return the counter + */ + public abstract int getCounter(); + + /** + * Increment counter. + */ + public abstract void incrementCounter(); + + public abstract void clearCounter(); + + /** + * Gets last sync time. + * + * @return the last sync time + */ + public abstract long getLastSyncTime(); + + /** + * Update sync time. + */ + public abstract void updateSyncTime(); + + public abstract void clearSyncTime(); + /** + * Gets custom data. + * + * @return the custom data + */ + public abstract TestData getCustomData(); + + /** + * Sets custom data. + * + * @param data the data + */ + public abstract void setCustomData(TestData data); + + /** + * Validate data boolean. + * + * @return the boolean + */ + public abstract boolean validateData(); + + public void resetToDefaults() { + setTestString(DEFAULT_TEST_STRING); + setTestInt(DEFAULT_TEST_INT); + setTestBoolean(DEFAULT_TEST_BOOLEAN); + setTestDouble(DEFAULT_TEST_DOUBLE); + setCustomData(DEFAULT_TEST_DATA); + clearCounter(); + clearSyncTime(); + markDirty(); + } + + public void generateRandomData() { + setTestString("random_" + System.currentTimeMillis()); + setTestInt((int) (Math.random() * 1000)); + setTestBoolean(Math.random() > 0.5); + setTestDouble(Math.random() * 100.0); + setCustomData(new TestData( + "custom_" + getCounter(), + (int) (Math.random() * 500), + Math.random() > 0.5 + )); + updateSyncTime(); + incrementCounter(); + markDirty(); + } + + public abstract void toBytes(FriendlyByteBuf buf); + public abstract void fromBytes(@NotNull FriendlyByteBuf buf); + /** + * 测试数据对象 + */ + public static class TestData { + private String name; + private int value; + private boolean flag; + + /** + * Instantiates a new Test data. + */ + public TestData() {} + + /** + * Instantiates a new Test data. + * + * @param name the name + * @param value the value + * @param flag the flag + */ + public TestData(String name, int value, boolean flag) { + this.name = name; + this.value = value; + this.flag = flag; + } + + /** + * Gets name. + * + * @return the name + */ + public String getName() { return name; } + + /** + * Sets name. + * + * @param name the name + */ + public void setName(String name) { this.name = name; } + + /** + * Gets value. + * + * @return the value + */ + public int getValue() { return value; } + + /** + * Sets value. + * + * @param value the value + */ + public void setValue(int value) { this.value = value; } + + /** + * Is flag boolean. + * + * @return the boolean + */ + public boolean isFlag() { return flag; } + + /** + * Sets flag. + * + * @param flag the flag + */ + public void setFlag(boolean flag) { this.flag = flag; } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof TestData other)) return false; + return value == other.value && flag == other.flag && + java.util.Objects.equals(name, other.name); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(name, value, flag); + } + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractFabricItem.java b/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractFabricItem.java new file mode 100644 index 0000000..1477e83 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractFabricItem.java @@ -0,0 +1,539 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * 用于执行数据查询并检查同步状态的物品 + * Shift + 右键:客户端与服务器双端同时查询检查同步 + * 普通右键:单端查询目标生物数据 + */ +public abstract class AbstractFabricItem extends Item { + + /** + * Instantiates a new Fabric item. + * + * @param properties the properties + */ + public AbstractFabricItem(Properties properties) { + super(properties); + } + + @Override + public @NotNull InteractionResultHolder use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) { + ItemStack itemStack = player.getItemInHand(hand); + + if (level.isClientSide()) { + // 客户端逻辑 + if (player.isShiftKeyDown()) { + // Shift + 右键:双端检查 - 先获取客户端数据,然后发送到服务器 + handleClientDualCheck(player); + } else { + // 普通右键:客户端单端查询 + handleClientSideQuery(player); + } + } else { + // 服务器逻辑 + ServerPlayer serverPlayer = (ServerPlayer) player; + + if (player.isShiftKeyDown()) { + // 服务器端已经通过数据包处理双端检查,这里只发送开始消息 + player.sendSystemMessage(Component.literal("§b开始双端同步检查,请等待客户端数据...")); + } else { + // 服务器单端查询 + handleServerSingleEndQuery(serverPlayer); + } + + // 添加冷却时间 + player.getCooldowns().addCooldown(this, 20); // 1秒冷却 + } + + return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide()); + } + + /** + * 客户端处理双端检查 + */ + private void handleClientDualCheck(Player player) { + Entity targetEntity = getClientTargetedEntity(player); + + if (targetEntity instanceof LivingEntity livingTarget) { + // 在客户端获取本地数据 + AbstractedTestSyncData clientData = getLocalClientData(livingTarget); + + if (clientData != null) { + // 发送客户端数据到服务器 + sendClientDataToServer(clientData, livingTarget.getId()); + + // 客户端提示 + player.sendSystemMessage(Component.literal("§b已发送客户端数据到服务器,等待对比结果...")); + } else { + player.sendSystemMessage(Component.literal("§c无法获取客户端本地数据")); + } + } else { + if (targetEntity == null && player.isShiftKeyDown()) { + handlePlayerSelfData(player); + } else { + player.sendSystemMessage(Component.literal("§c请对准一个生物进行同步检查!")); + } + } + } + + /** + * 处理玩家自身数据的双端检查 + */ + private void handlePlayerSelfData(Player player) { + // 获取玩家自身的客户端数据 + AbstractedTestSyncData clientData = getLocalClientData(player); + + if (clientData != null) { + // 发送玩家自身数据到服务器 + sendClientDataToServer(clientData, player.getId()); + + // 客户端提示 + player.sendSystemMessage(Component.literal("§b已发送玩家自身客户端数据到服务器,等待对比结果...")); + } else { + player.sendSystemMessage(Component.literal("§c无法获取玩家自身客户端数据")); + } + } + /** + * 客户端单端查询 + */ + private void handleClientSideQuery(Player player) { + Entity targetEntity = getClientTargetedEntity(player); + + if (targetEntity instanceof LivingEntity livingTarget) { + AbstractedTestSyncData clientData = getLocalClientData(livingTarget); + + if (clientData != null) { + displayClientSideResults(player, livingTarget, clientData); + } else { + player.sendSystemMessage(Component.literal("§c无法查询客户端本地数据")); + } + } else { + player.sendSystemMessage(Component.literal("§c请对准一个生物使用!")); + } + } + + /** + * 服务器端处理单端查询 + */ + private void handleServerSingleEndQuery(ServerPlayer player) { + Entity targetEntity = getServerTargetedEntity(player); + + if (targetEntity instanceof LivingEntity livingTarget) { + player.sendSystemMessage(Component.literal( + String.format("§b开始查询 §e%s§b 的数据,3秒后显示结果...", livingTarget.getName().getString()) + )); + + // 启动异步数据查询 + startServerSingleEndQuery(player, livingTarget); + } else { + player.sendSystemMessage(Component.literal("§c请对准一个生物使用!")); + } + } + protected abstract AbstractedTestSyncData getData(Entity target); + /** + * 在客户端获取本地数据 + */ + protected AbstractedTestSyncData getLocalClientData(LivingEntity target) { + try { + return getData(target); + } catch (Exception e) { + Lib39.LOGGER.error("[FabricItem] 获取客户端数据失败", e); + } + return null; + } + + /** + * 发送客户端数据到服务器 + */ + protected abstract void sendClientDataToServer(AbstractedTestSyncData clientData, int targetEntityId); + + /** + * 启动服务器单端查询 + */ + private void startServerSingleEndQuery(ServerPlayer player, LivingEntity target) { + CompletableFuture.runAsync(() -> { + try { + // 等待 3 秒 + Thread.sleep(3000); + + // 在服务器线程中执行结果处理 + player.server.execute(() -> { + displayServerSingleEndResults(player, target); + }); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Lib39.LOGGER.error("[FabricItem] 数据查询被中断", e); + player.sendSystemMessage(Component.literal("§c数据查询被中断")); + } catch (Exception e) { + Lib39.LOGGER.error("[FabricItem] 数据查询出错", e); + player.server.execute(() -> + player.sendSystemMessage(Component.literal("§c数据查询出错: " + e.getMessage())) + ); + } + }); + } + + /** + * 显示服务器单端查询结果 + */ + private void displayServerSingleEndResults(ServerPlayer player, @NotNull LivingEntity target) { + Lib39.LOGGER.info("[FabricItem] 查询生物 {} 的数据", target.getName().getString()); + + // 获取目标生物的数据 + AbstractedTestSyncData abstractData = getTestSyncData(target); + + if (abstractData != null) { + // 显示详细数据 + displayServerDetailedData(player, target, abstractData); + } else { + player.sendSystemMessage(Component.literal( + String.format("§c生物 §e%s§c 没有测试数据或数据无效", target.getName().getString()) + )); + } + } + + /** + * 显示客户端查询结果 + */ + private void displayClientSideResults(Player player, LivingEntity target, AbstractedTestSyncData clientData) { + player.sendSystemMessage(Component.literal("§6=== 客户端数据查询结果 ===")); + player.sendSystemMessage(Component.literal("§7目标生物: §e" + target.getName().getString())); + player.sendSystemMessage(Component.literal("§7数据来源: §9客户端本地")); + player.sendSystemMessage(Component.literal("")); + + player.sendSystemMessage(Component.literal("§a基础数据:")); + player.sendSystemMessage(Component.literal("§7字符串: §f" + clientData.getTestString())); + player.sendSystemMessage(Component.literal("§7整数值: §f" + clientData.getTestInt())); + player.sendSystemMessage(Component.literal("§7布尔值: §f" + clientData.isTestBoolean())); + player.sendSystemMessage(Component.literal("§7双精度值: §f" + String.format("%.2f", clientData.getTestDouble()))); + player.sendSystemMessage(Component.literal("§7计数器: §f" + clientData.getCounter())); + + // 显示客户端特定信息 + player.sendSystemMessage(Component.literal("")); + player.sendSystemMessage(Component.literal("§e客户端状态:")); + player.sendSystemMessage(Component.literal("§7数据验证: " + (clientData.validateData() ? "§a通过" : "§c失败"))); + player.sendSystemMessage(Component.literal("§7同步状态: " + (clientData.isDirty() ? "§6待同步" : "§a已同步"))); + } + + /** + * 显示服务器详细数据(单端查询) + */ + private void displayServerDetailedData(ServerPlayer player, LivingEntity target, AbstractedTestSyncData testData) { + player.sendSystemMessage(Component.literal("§6=== 数据查询结果 ===")); + player.sendSystemMessage(Component.literal( + String.format("§7目标生物: §e%s", target.getName().getString()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7实体ID: §e%d", target.getId()) + )); + player.sendSystemMessage(Component.literal("")); + + // 显示基础数据 + player.sendSystemMessage(Component.literal("§a基础数据:")); + player.sendSystemMessage(Component.literal( + String.format("§7字符串: §f%s", testData.getTestString()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7整数值: §f%d", testData.getTestInt()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7布尔值: §f%s", testData.isTestBoolean()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7双精度值: §f%.2f", testData.getTestDouble()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7计数器: §f%d", testData.getCounter()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7最后同步: §f%dms前", System.currentTimeMillis() - testData.getLastSyncTime()) + )); + player.sendSystemMessage(Component.literal("")); + + // 显示自定义数据 + AbstractedTestSyncData.TestData customData = testData.getCustomData(); + player.sendSystemMessage(Component.literal("§a自定义数据:")); + player.sendSystemMessage(Component.literal( + String.format("§7名称: §f%s", customData.getName()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7数值: §f%d", customData.getValue()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7标志: §f%s", customData.isFlag()) + )); + player.sendSystemMessage(Component.literal("")); + + // 显示验证状态 + boolean isValid = testData.validateData(); + player.sendSystemMessage(Component.literal( + String.format("§7数据验证: %s", isValid ? "§a通过" : "§c失败") + )); + player.sendSystemMessage(Component.literal( + String.format("§7数据状态: %s", testData.isDirty() ? "§6未同步" : "§a已同步") + )); + } + + /** + * 显示双端比较结果 + */ + protected static void displayDualEndComparison(ServerPlayer player, LivingEntity target, AbstractedTestSyncData serverData, AbstractedTestSyncData clientData) { + player.sendSystemMessage(Component.literal("§6=== 客户端-服务器双端同步检查结果 ===")); + player.sendSystemMessage(Component.literal( + String.format("§7目标生物: §e%s", target.getName().getString()) + )); + player.sendSystemMessage(Component.literal("")); + + // 显示双端数据来源 + player.sendSystemMessage(Component.literal("§a数据来源:")); + player.sendSystemMessage(Component.literal("§7- §c服务器端§7: 实体ID " + serverData.entityId())); + player.sendSystemMessage(Component.literal("§7- §9客户端§7: 实体ID " + clientData.entityId())); + player.sendSystemMessage(Component.literal("")); + + // 比较各个字段 + boolean stringSynced = serverData.getTestString().equals(clientData.getTestString()); + boolean intSynced = serverData.getTestInt() == clientData.getTestInt(); + boolean booleanSynced = serverData.isTestBoolean() == clientData.isTestBoolean(); + boolean doubleSynced = Math.abs(serverData.getTestDouble() - clientData.getTestDouble()) < 0.001; + boolean counterSynced = serverData.getCounter() == clientData.getCounter(); + boolean customDataSynced = compareCustomData(serverData.getCustomData(), clientData.getCustomData()); + + // 显示字段同步状态 + player.sendSystemMessage(Component.literal("§a字段同步状态:")); + displayDualEndSyncStatus(player, "字符串", stringSynced, + serverData.getTestString(), clientData.getTestString()); + displayDualEndSyncStatus(player, "整数值", intSynced, + serverData.getTestInt(), clientData.getTestInt()); + displayDualEndSyncStatus(player, "布尔值", booleanSynced, + serverData.isTestBoolean(), clientData.isTestBoolean()); + displayDualEndSyncStatus(player, "双精度值", doubleSynced, + serverData.getTestDouble(), clientData.getTestDouble()); + displayDualEndSyncStatus(player, "计数器", counterSynced, + serverData.getCounter(), clientData.getCounter()); + displayDualEndSyncStatus(player, "自定义数据", customDataSynced, + serverData.getCustomData().toString(), clientData.getCustomData().toString()); + + player.sendSystemMessage(Component.literal("")); + + // 计算总体同步率 + int totalFields = 6; + int syncedFields = (stringSynced ? 1 : 0) + (intSynced ? 1 : 0) + + (booleanSynced ? 1 : 0) + (doubleSynced ? 1 : 0) + + (counterSynced ? 1 : 0) + (customDataSynced ? 1 : 0); + double syncRate = (double) syncedFields / totalFields * 100; + + // 显示总体同步状态 + player.sendSystemMessage(Component.literal("§a总体同步状态:")); + player.sendSystemMessage(Component.literal( + String.format("§7同步字段: §e%d§7/§e%d", syncedFields, totalFields) + )); + player.sendSystemMessage(Component.literal( + String.format("§7同步率: %s", getSyncRateColor(syncRate) + String.format("%.1f%%", syncRate)) + )); + player.sendSystemMessage(Component.literal( + String.format("§7同步状态: %s", getOverallSyncStatus(syncRate)) + )); + + // 显示数据状态差异 + player.sendSystemMessage(Component.literal("")); + player.sendSystemMessage(Component.literal("§a数据状态差异:")); + player.sendSystemMessage(Component.literal( + String.format("§7服务器脏数据状态: %s", serverData.isDirty() ? "§6脏" : "§a干净") + )); + player.sendSystemMessage(Component.literal( + String.format("§7客户端脏数据状态: %s", clientData.isDirty() ? "§6脏" : "§a干净") + )); + player.sendSystemMessage(Component.literal( + String.format("§7服务器验证状态: %s", serverData.validateData() ? "§a通过" : "§c失败") + )); + player.sendSystemMessage(Component.literal( + String.format("§7客户端验证状态: %s", clientData.validateData() ? "§a通过" : "§c失败") + )); + + // 显示同步建议 + player.sendSystemMessage(Component.literal("")); + player.sendSystemMessage(Component.literal("§e同步建议:")); + if (syncRate == 100) { + player.sendSystemMessage(Component.literal("§a✓ 数据完全同步,无需操作")); + } else if (syncRate >= 80) { + player.sendSystemMessage(Component.literal("§e⚠ 数据基本同步,建议观察")); + } else if (syncRate >= 50) { + player.sendSystemMessage(Component.literal("§6⚠ 数据部分不同步,建议检查网络")); + } else { + player.sendSystemMessage(Component.literal("§c✗ 数据严重不同步,建议重新同步")); + } + } + + /** + * 显示双端同步状态 + */ + private static void displayDualEndSyncStatus(ServerPlayer player, String fieldName, boolean synced, Object serverValue, Object clientValue) { + String status = synced ? "§a✓ 同步" : "§c✗ 不同步"; + if (synced) { + player.sendSystemMessage(Component.literal( + String.format("§7%s: %s §8(值: §7%s§8)", fieldName, status, serverValue) + )); + } else { + player.sendSystemMessage(Component.literal( + String.format("§7%s: %s", fieldName, status) + )); + player.sendSystemMessage(Component.literal( + String.format("§8 §c服务器: §7%s", serverValue) + )); + player.sendSystemMessage(Component.literal( + String.format("§8 §9客户端: §7%s", clientValue) + )); + } + } + + /** + * 比较自定义数据 + */ + private static boolean compareCustomData(AbstractedTestSyncData.TestData first, AbstractedTestSyncData.TestData second) { + return first.getName().equals(second.getName()) && + first.getValue() == second.getValue() && + first.isFlag() == second.isFlag(); + } + + /** + * 获取同步率颜色 + */ + private static String getSyncRateColor(double syncRate) { + if (syncRate >= 90) return "§a"; + if (syncRate >= 70) return "§e"; + if (syncRate >= 50) return "§6"; + return "§c"; + } + + /** + * 获取总体同步状态 + */ + private static String getOverallSyncStatus(double syncRate) { + if (syncRate == 100) return "§a完全同步"; + if (syncRate >= 90) return "§a优秀同步"; + if (syncRate >= 70) return "§e良好同步"; + if (syncRate >= 50) return "§6部分同步"; + return "§c同步较差"; + } + + /** + * 客户端获取准星目标实体 + */ + private Entity getClientTargetedEntity(Player player) { + double reachDistance = 20.0; + float partialTicks = 1.0f; // 服务器端通常用1.0 + + // 获取玩家的视线向量和位置 + Vec3 eyePosition = player.getEyePosition(partialTicks); + Vec3 lookVector = player.getViewVector(partialTicks); + Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance); + + // 先检测实体 + EntityHitResult entityHit = ProjectileUtil.getEntityHitResult( + player, + eyePosition, + endPosition, + player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0), + entity -> !entity.isSpectator() && entity.isPickable(), + reachDistance * reachDistance // 平方距离 + ); + + if (entityHit != null) { + return entityHit.getEntity(); + } + + return null; + } + + /** + * 服务器获取准星目标实体 + */ + private Entity getServerTargetedEntity(ServerPlayer player) { + double reachDistance = 20.0; + float partialTicks = 1.0f; // 服务器端通常用1.0 + + // 获取玩家的视线向量和位置 + Vec3 eyePosition = player.getEyePosition(partialTicks); + Vec3 lookVector = player.getViewVector(partialTicks); + Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance); + + // 先检测实体 + EntityHitResult entityHit = ProjectileUtil.getEntityHitResult( + player, + eyePosition, + endPosition, + player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0), + entity -> !entity.isSpectator() && entity.isPickable(), + reachDistance * reachDistance // 平方距离 + ); + + if (entityHit != null) { + return entityHit.getEntity(); + } + + return null; + } + + /** + * 获取测试同步数据 + */ + private AbstractedTestSyncData getTestSyncData(Entity entity) { + try { + return getData(entity); + } catch (Exception e) { + Lib39.LOGGER.debug("[FabricItem] 获取生物 {} 的 TestSyncData 失败: {}", + entity.getName().getString(), e.getMessage()); + return null; + } + } + + @Override + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, + @NotNull List tooltip, @NotNull TooltipFlag flag) { + super.appendHoverText(stack, level, tooltip, flag); + + tooltip.add(Component.literal("§7右键点击在 3 秒后执行")); + tooltip.add(Component.literal("§7§e准星瞄准生物§7的数据查询")); + tooltip.add(Component.literal("§7§oShift + 右键§7进行§e客户端-服务器双端同步检查§7")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§6查询延迟: §e3秒")); + tooltip.add(Component.literal("§6瞄准距离: §e20格")); + tooltip.add(Component.literal("§6冷却时间: §e1秒")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§a单端查询内容:")); + tooltip.add(Component.literal("§7- 基础数据字段")); + tooltip.add(Component.literal("§7- 自定义数据结构")); + tooltip.add(Component.literal("§7- 数据验证状态")); + tooltip.add(Component.literal("§7- 同步状态信息")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§e双端同步检查:")); + tooltip.add(Component.literal("§7- 客户端和服务器同时查询")); + tooltip.add(Component.literal("§7- 字段级同步状态对比")); + tooltip.add(Component.literal("§7- 总体同步率计算")); + tooltip.add(Component.literal("§7- 双端数据状态差异")); + tooltip.add(Component.literal("§7- 同步建议")); + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractNeoForgeItem.java b/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractNeoForgeItem.java new file mode 100644 index 0000000..aabc01d --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/content/item/AbstractNeoForgeItem.java @@ -0,0 +1,282 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; + +import java.util.List; +import java.util.Random; + +/** + * 用于对准星生物触发 TestSyncData 随机变换的物品 + * Shift + 右键:操作自己的数据 + * 普通右键:操作瞄准生物的数据 + */ +public abstract class AbstractNeoForgeItem extends Item { + private static final Random RANDOM = new Random(); + + /** + * Instantiates a new Neo forge item. + * + * @param properties the properties + */ + public AbstractNeoForgeItem(Properties properties) { + super(properties); + } + + @Override + public @NotNull InteractionResultHolder use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) { + ItemStack itemStack = player.getItemInHand(hand); + + if (!level.isClientSide()) { + ServerPlayer serverPlayer = (ServerPlayer) player; + + if (player.isShiftKeyDown()) { + // Shift + 右键:操作自己的数据 + handleSelfDataOperation(serverPlayer); + } else { + // 普通右键:操作瞄准生物的数据 + handleTargetDataOperation(serverPlayer); + } + + // 添加冷却时间 + player.getCooldowns().addCooldown(this, 20); // 1秒冷却 + } + + return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide()); + } + + /** + * 处理玩家自身数据操作 + */ + private void handleSelfDataOperation(ServerPlayer player) { + boolean success = triggerRandomTransformation(player); + + if (success) { + player.sendSystemMessage(Component.literal("§a已触发§e自身§a测试数据的随机变换!")); + Lib39.LOGGER.info("[NeoForgeItem] 玩家 {} 触发了自身数据变换", player.getName().getString()); + } else { + player.sendSystemMessage(Component.literal("§c无法触发自身数据变换")); + } + } + + /** + * 处理目标生物数据操作 + */ + private void handleTargetDataOperation(ServerPlayer player) { + // 获取玩家准星瞄准的生物 + Entity targetEntity = getTargetedEntity(player); + + if (targetEntity instanceof LivingEntity livingTarget) { + // 触发对准星生物的数据变换 + boolean success = triggerRandomTransformation(livingTarget); + + if (success) { + player.sendSystemMessage(Component.literal( + String.format("§a已触发 §e%s§a 的测试数据随机变换!", livingTarget.getName().getString()) + )); + Lib39.LOGGER.info("[NeoForgeItem] 玩家 {} 触发生物 {} 的数据变换", + player.getName().getString(), livingTarget.getName().getString()); + } else { + player.sendSystemMessage(Component.literal( + String.format("§c无法触发 §e%s§c 的数据变换", livingTarget.getName().getString()) + )); + } + } else { + // 没有瞄准生物 + player.sendSystemMessage(Component.literal("§c请对准一个生物使用!")); + } + } + + /** + * 获取玩家准星瞄准的实体 + */ + private Entity getTargetedEntity(ServerPlayer player) { + double reachDistance = 20.0; + float partialTicks = 1.0f; // 服务器端通常用1.0 + + // 获取玩家的视线向量和位置 + Vec3 eyePosition = player.getEyePosition(partialTicks); + Vec3 lookVector = player.getViewVector(partialTicks); + Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance); + + // 先检测实体 + EntityHitResult entityHit = ProjectileUtil.getEntityHitResult( + player, + eyePosition, + endPosition, + player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0), + entity -> !entity.isSpectator() && entity.isPickable(), + reachDistance * reachDistance // 平方距离 + ); + + if (entityHit != null) { + return entityHit.getEntity(); + } + + return null; + } + + /** + * 为实体触发随机数据变换 + */ + private boolean triggerRandomTransformation(LivingEntity entity) { + try { + AbstractedTestSyncData testData = getOrCreateTestSyncData(entity); + + + // 随机选择一种变换方式 + int transformationType = RANDOM.nextInt(6); // 增加更多变换类型 + + switch (transformationType) { + case 0 -> { + // 完全随机数据 + testData.generateRandomData(); + Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 生成完全随机数据", getEntityName(entity)); + } + case 1 -> { + // 只修改字符串和计数器 + testData.setTestString("transformed_" + System.currentTimeMillis()); + testData.incrementCounter(); + testData.updateSyncTime(); + Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改字符串和计数器", getEntityName(entity)); + } + case 2 -> { + // 修改数值数据 + testData.setTestInt(RANDOM.nextInt(1000)); + testData.setTestDouble(RANDOM.nextDouble() * 100.0); + testData.setTestBoolean(RANDOM.nextBoolean()); + testData.updateSyncTime(); + Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改数值数据", getEntityName(entity)); + } + case 3 -> { + // 修改自定义数据 + AbstractedTestSyncData.TestData newCustomData = new AbstractedTestSyncData.TestData( + "custom_" + RANDOM.nextInt(100), + RANDOM.nextInt(500), + RANDOM.nextBoolean() + ); + testData.setCustomData(newCustomData); + testData.incrementCounter(); + Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改自定义数据", getEntityName(entity)); + } + case 4 -> { + // 重置为默认值 + testData.resetToDefaults(); + Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 重置数据", getEntityName(entity)); + } + case 5 -> { + // 特殊变换:玩家专属数据 + if (entity instanceof Player) { + testData.setTestString("player_special_" + entity.getUUID().toString().substring(0, 8)); + testData.setTestInt(entity.getId() * 10); + testData.setTestDouble(entity.getHealth()); + testData.incrementCounter(); + testData.updateSyncTime(); + Lib39.LOGGER.debug("[NeoForgeItem] 为玩家 {} 设置专属数据", getEntityName(entity)); + } else { + // 非玩家生物使用普通变换 + testData.generateRandomData(); + } + } + } + + // 验证数据有效性 + if (!testData.validateData()) { + Lib39.LOGGER.warn("[NeoForgeItem] {} 的数据验证失败,重置为默认值", getEntityName(entity)); + testData.resetToDefaults(); + } + + // 显示数据预览(仅对玩家自己操作时显示) + if (entity instanceof Player) { + displayDataPreview((Player) entity, testData); + } + + return true; + + } catch (Exception e) { + Lib39.LOGGER.error("[NeoForgeItem] 为 {} 触发数据变换时出错: {}", + getEntityName(entity), e.getMessage()); + return false; + } + } + + /** + * 显示数据预览给玩家 + */ + private void displayDataPreview(Player player, AbstractedTestSyncData testData) { + player.sendSystemMessage(Component.literal("§6数据预览:")); + player.sendSystemMessage(Component.literal( + String.format("§7字符串: §f%s", testData.getTestString()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7计数器: §f%d", testData.getCounter()) + )); + player.sendSystemMessage(Component.literal( + String.format("§7验证状态: %s", testData.validateData() ? "§a通过" : "§c失败") + )); + } + + /** + * 获取实体名称(用于日志) + */ + private String getEntityName(LivingEntity entity) { + if (entity instanceof Player) { + return "玩家 " + entity.getName().getString(); + } else { + return "生物 " + entity.getName().getString(); + } + } + + protected abstract AbstractedTestSyncData getData(Entity entity); + + protected AbstractedTestSyncData getOrCreateTestSyncData(Entity entity) { + try { + return getData(entity); + } catch (Exception e) { + Lib39.LOGGER.error("[NeoForgeItem] 获取 {} 的 TestSyncData 失败: {}", + getEntityName((LivingEntity) entity), e.getMessage()); + return null; + } + } + + @Override + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, + @NotNull List tooltip, @NotNull TooltipFlag flag) { + super.appendHoverText(stack, level, tooltip, flag); + + tooltip.add(Component.literal("§7右键点击触发§e准星瞄准生物§7的")); + tooltip.add(Component.literal("§7测试数据随机变换")); + tooltip.add(Component.literal("§7§oShift + 右键§7操作§e自身§7数据")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§6冷却时间: §e1秒")); + tooltip.add(Component.literal("§6瞄准距离: §e20格")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§a变换类型:")); + tooltip.add(Component.literal("§7- 完全随机数据")); + tooltip.add(Component.literal("§7- 字符串+计数器")); + tooltip.add(Component.literal("§7- 数值数据")); + tooltip.add(Component.literal("§7- 自定义数据")); + tooltip.add(Component.literal("§7- 重置默认值")); + tooltip.add(Component.literal("§7- 玩家专属数据")); + tooltip.add(Component.literal("")); + tooltip.add(Component.literal("§e自身操作特性:")); + tooltip.add(Component.literal("§7- 显示数据预览")); + tooltip.add(Component.literal("§7- 玩家专属数据变换")); + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java b/common/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java new file mode 100644 index 0000000..5801d86 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java @@ -0,0 +1,40 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.example.client.screen.ForgeScreen; +import top.r3944realms.lib39.util.IClientOnly; + +/** + * The type Forge item. + */ +public class ForgeItem extends Item { + /** + * Instantiates a new Forge item. + * + * @param properties the properties + */ + public ForgeItem(Properties properties) { + super(properties); + } + + @Override + public @NotNull InteractionResultHolder use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand usedHand) { + if (level.isClientSide() && usedHand == InteractionHand.MAIN_HAND) { + ClientOpt.clientUse(usedHand); + } + return super.use(level, player, usedHand); + } + + static class ClientOpt implements IClientOnly { + private static void clientUse(@NotNull InteractionHand usedHand) { + IClientOnly.check(() -> Minecraft.getInstance().setScreen(new ForgeScreen(usedHand, 0))); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java b/common/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java new file mode 100644 index 0000000..6e2c84d --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java @@ -0,0 +1,24 @@ +package top.r3944realms.lib39.example.core.register; + +import net.minecraft.world.item.Item; + +import java.util.function.Supplier; + +/** + * The type Ex lib 39 items. + */ +public class ExLib39Items { + /** + * The constant SUPER_LEAD_ROPE. + */ + public static Supplier FABRIC; + /** + * The constant ETERNAL_POTATO. + */ + public static Supplier NEOFORGE; + /** + * The constant FORGE. + */ + public static Supplier FORGE; + +} diff --git a/common/src/main/java/top/r3944realms/lib39/mixin/minecraft/ScreenAccessor.java b/common/src/main/java/top/r3944realms/lib39/mixin/minecraft/ScreenAccessor.java new file mode 100644 index 0000000..89ac229 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/mixin/minecraft/ScreenAccessor.java @@ -0,0 +1,14 @@ +package top.r3944realms.lib39.mixin.minecraft; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.screens.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Accessor("renderables") + List getrRenderables(); +} diff --git a/common/src/main/java/top/r3944realms/lib39/platform/services/IHelpCommandHook.java b/common/src/main/java/top/r3944realms/lib39/platform/services/IHelpCommandHook.java new file mode 100644 index 0000000..1eca5f6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/platform/services/IHelpCommandHook.java @@ -0,0 +1,11 @@ +package top.r3944realms.lib39.platform.services; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import top.r3944realms.lib39.core.command.ICommandHelpManager; + +@FunctionalInterface +public interface IHelpCommandHook { + void onRegister(LiteralArgumentBuilder tree, ICommandHelpManager manager, CommandBuildContext context); +} diff --git a/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java b/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java index 7a25c86..933f542 100644 --- a/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java +++ b/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java @@ -47,4 +47,6 @@ public interface IPlatformHelper { String getModVersion(); IUtilHelper getUtilHelper(); + + IHelpCommandHook getHelpCommandHook(); } \ No newline at end of file diff --git a/common/src/main/resources/lib39.mixins.json b/common/src/main/resources/lib39.mixins.json index 6bcbbb4..b2c93d8 100644 --- a/common/src/main/resources/lib39.mixins.json +++ b/common/src/main/resources/lib39.mixins.json @@ -9,6 +9,7 @@ "minecraft.CreativeModeTabsAccessor" ], "client": [ + "minecraft.ScreenAccessor" ], "server": [ ], diff --git a/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java b/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java index 3028c80..46060f0 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java +++ b/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java @@ -1,7 +1,7 @@ package top.r3944realms.lib39; import net.fabricmc.api.ModInitializer; -import top.r3944realms.lib39.core.CreativeTabAdder; +import top.r3944realms.lib39.core.event.CommonEventHandler; import top.r3944realms.lib39.core.register.*; public class Lib39Fabric implements ModInitializer { @@ -14,8 +14,7 @@ public class Lib39Fabric implements ModInitializer { FabricLib39Items.init(); FabricLib39BlockEntities.init(); FabricLib39SoundEvents.init(); - CreativeTabAdder.init(); + CommonEventHandler.initCommon(); Lib39.LOGGER.info("[Lib39-Fabric] Finished Initializing."); - } } diff --git a/fabric/src/main/java/top/r3944realms/lib39/Lib39FabricClient.java b/fabric/src/main/java/top/r3944realms/lib39/Lib39FabricClient.java index 20d001b..04f9eba 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/Lib39FabricClient.java +++ b/fabric/src/main/java/top/r3944realms/lib39/Lib39FabricClient.java @@ -1,36 +1,17 @@ package top.r3944realms.lib39; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; -import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.resources.ResourceManager; -import org.jetbrains.annotations.NotNull; -import top.r3944realms.lib39.client.renderer.item.DollItemRenderer; -import top.r3944realms.lib39.client.shader.Lib39Shaders; -import top.r3944realms.lib39.core.register.Lib39Items; +import top.r3944realms.lib39.client.ItemRenderRegister; +import top.r3944realms.lib39.client.ShaderRegister; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.network.NetworkHandler; public class Lib39FabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() { - - @Override - public void onResourceManagerReload(@NotNull ResourceManager resourceManager) { - Lib39Shaders.registerShaders(resourceManager); - } - - @Override - public ResourceLocation getFabricId() { - return Lib39.rl("shaders"); - } - - }); - BuiltinItemRendererRegistry.INSTANCE.register( - Lib39Items.DOLL.get(), - DollItemRenderer.getInstance()::renderByItem - ); + ShaderRegister.register(); + ItemRenderRegister.register(); + NetworkHandler.registerClientReceivers(); + CommonEventHandler.initClient(); } } diff --git a/fabric/src/main/java/top/r3944realms/lib39/api/callback/ActionResult.java b/fabric/src/main/java/top/r3944realms/lib39/api/callback/ActionResult.java new file mode 100644 index 0000000..187c7f4 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/api/callback/ActionResult.java @@ -0,0 +1,21 @@ +package top.r3944realms.lib39.api.callback; + +/** + * 结果枚举 + */ +public enum ActionResult { + /** + * 继续处理 + */ + PASS, + + /** + * 取消 + */ + CANCEL, + + /** + * 允许加入但停止后续处理 + */ + SUCCESS +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/api/callback/MinecraftSetUpServiceCallback.java b/fabric/src/main/java/top/r3944realms/lib39/api/callback/MinecraftSetUpServiceCallback.java index d1af907..1a9f87f 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/api/callback/MinecraftSetUpServiceCallback.java +++ b/fabric/src/main/java/top/r3944realms/lib39/api/callback/MinecraftSetUpServiceCallback.java @@ -19,11 +19,12 @@ public interface MinecraftSetUpServiceCallback { MinecraftSetUpServiceCallback.class, (listeners) -> ((services, mainThreadExecutor) -> { for (MinecraftSetUpServiceCallback listener : listeners) { - if (listener.load(services, mainThreadExecutor)) { - return true; + ActionResult result = listener.load(services, mainThreadExecutor); + if (result != ActionResult.PASS) { + return result; } } - return true; + return ActionResult.PASS; }) ); @@ -32,7 +33,7 @@ public interface MinecraftSetUpServiceCallback { * * @param services the services * @param mainThreadExecutor the main thread executor - * @return 是否取消事件 boolean + * @return 结果枚举 */ - boolean load(Services services, Executor mainThreadExecutor); + ActionResult load(Services services, Executor mainThreadExecutor); } diff --git a/fabric/src/main/java/top/r3944realms/lib39/api/callback/RegisterCommandHelpCallback.java b/fabric/src/main/java/top/r3944realms/lib39/api/callback/RegisterCommandHelpCallback.java new file mode 100644 index 0000000..8eb7336 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/api/callback/RegisterCommandHelpCallback.java @@ -0,0 +1,120 @@ +package top.r3944realms.lib39.api.callback; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import top.r3944realms.lib39.core.command.ICommandHelpManager; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.CommandPath; +import top.r3944realms.lib39.core.command.model.Parameter; + +@FunctionalInterface +public interface RegisterCommandHelpCallback { + ActionResult register(RegisterCommandHelpCallback.Registrar registrar); + /** + * 创建 Event 实例 + */ + Event EVENT = EventFactory.createArrayBacked( + RegisterCommandHelpCallback.class, + (listeners) -> (registrar) -> { + for (RegisterCommandHelpCallback listener : listeners) { + ActionResult result = listener.register(registrar); + if (result != ActionResult.PASS) { + return result; + } + } + return ActionResult.PASS; + } + ); + interface Registrar { + /** + * Gets id. + * + * @return the id + */ + ResourceLocation getID(); + + /** + * Add child. + * + * @param child the child + */ + void addChild(LiteralArgumentBuilder child); + + /** + * Get context command build context. + * + * @return the command build context + */ + CommandBuildContext getContext(); + + /** + * Get tree literal argument builder. + * + * @return the literal argument builder + */ + LiteralArgumentBuilder getTree(); + + /** + * 注册命令帮助信息 + * + * @param CommandNode 命令节点 + * @param description 命令描述 + */ + void registerHelp(CommandNode CommandNode, MutableComponent description); + /** + * 注册命令帮助信息 + * + * @param CommandNode 命令节点 + * @param descriptionKey 命令描述的语言键 + */ + void registerHelp(CommandNode CommandNode, String descriptionKey); + /** + * 注册命令参数 + * + * @param commandPath 命令节点 + * @param parametersBuilder 参数列表构造器 + */ + void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder); + } + record CommandHelpRegistrar(LiteralArgumentBuilder builder, ICommandHelpManager helpManager, CommandBuildContext context) implements Registrar { + @Override + public ResourceLocation getID() { + return helpManager.getID(); + } + + @Override + public void addChild(LiteralArgumentBuilder child) { + this.builder.then(child); + } + + @Override + public CommandBuildContext getContext(){ + return this.context; + } + + @Override + public LiteralArgumentBuilder getTree(){ + return this.builder; + } + + @Override + public void registerHelp(CommandNode CommandNode, MutableComponent description) { + this.helpManager.registerCommandHelp(CommandNode, description); + } + + @Override + public void registerHelp(CommandNode CommandNode, String descriptionKey) { + this.helpManager.registerCommandHelp(CommandNode, descriptionKey); + } + + @Override + public void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder) { + this.helpManager.registerCommandParameters(commandPath, parametersBuilder); + } + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/api/callback/SyncManagerRegisterCallback.java b/fabric/src/main/java/top/r3944realms/lib39/api/callback/SyncManagerRegisterCallback.java new file mode 100644 index 0000000..9e34e6a --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/api/callback/SyncManagerRegisterCallback.java @@ -0,0 +1,158 @@ +package top.r3944realms.lib39.api.callback; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.lookup.v1.entity.EntityApiLookup; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.ISyncManager; +import top.r3944realms.lib39.core.sync.SyncData2LookupManager; + + +/** + * Fabric 的同步管理器注册回调 + */ +@FunctionalInterface +public interface SyncManagerRegisterCallback { + + /** + * 注册同步管理器的回调方法 + * + * @param registrar 注册器实例 + */ + ActionResult registerSyncManagers(Registrar registrar); + + /** + * 创建 Event 实例 + */ + Event EVENT = EventFactory.createArrayBacked( + SyncManagerRegisterCallback.class, + (listeners) -> (registrar) -> { + for (SyncManagerRegisterCallback listener : listeners) { + ActionResult result = listener.registerSyncManagers(registrar); + if (result != ActionResult.PASS) { + return result; + } + } + return ActionResult.PASS; + } + ); + + /** + * 注册器接口 - 提供注册同步管理器的方法 + */ + interface Registrar { + + /** + * 获取 Fabric 同步管理器实例 + */ + SyncData2LookupManager manager(); + + /** + * 注册同步管理器 + * + * @param the type parameter + * @param id the id + * @param syncManager the sync manager + * @param dataClass the data class + */ + > void register( + ResourceLocation id, + ISyncManager syncManager, + Class dataClass + ); + + /** + * 允许实体类 + * + * @param id the id + * @param entityClasses the entity classes + */ + void allowEntityClass(ResourceLocation id, Class... entityClasses); + + /** + * 移除允许的实体类 + * + * @param id the id + * @param entityClasses the entity classes + */ + void disallowEntityClass(ResourceLocation id, Class... entityClasses); + + /** + * 绑定 EntityApiLookup(用于分离注册的情况) + * + * @param the type parameter + * @param id 必须先注册安全同步管理器,再绑定 Lookup,否则会抛出 {@link IllegalStateException} + * @param apiLookup the EntityApiLookup + */ + > void bindApiLookup( + ResourceLocation id, + net.fabricmc.fabric.api.lookup.v1.entity.EntityApiLookup apiLookup + ); + + /** + * 解绑 EntityApiLookup + * + * @param id the id + */ + void unbindApiLookup(ResourceLocation id); + + /** + * 完整的类型安全注册 + * + * @param the type parameter + * @param id the id + * @param syncManager the sync manager + * @param dataClass the data class + * @param allowedEntityClasses the allowed entity classes + */ + default > void registerComplete( + ResourceLocation id, + ISyncManager syncManager, + Class dataClass, + Class... allowedEntityClasses + ) { + register(id, syncManager, dataClass); + if (allowedEntityClasses.length > 0) { + allowEntityClass(id, allowedEntityClasses); + } + } + } + record SyncManagerRegistrar( + SyncData2LookupManager manager) implements SyncManagerRegisterCallback.Registrar { + + @Override + public > void register( + @NotNull ResourceLocation id, + @NotNull ISyncManager syncManager, + @NotNull Class dataClass + ) { + manager.registerManager(id, syncManager, dataClass); + } + + @Override + public void allowEntityClass(@NotNull ResourceLocation id, @NotNull Class... entityClasses) { + manager.allowEntityClass(id, entityClasses); + } + + @Override + public void disallowEntityClass(@NotNull ResourceLocation id, @NotNull Class... entityClasses) { + manager.disallowEntityClass(id, entityClasses); + } + + @Override + public > void bindApiLookup( + @NotNull ResourceLocation id, + @NotNull EntityApiLookup apiLookup + ) { + manager.bindApiLookup(id, apiLookup); + } + + @Override + public void unbindApiLookup(@NotNull ResourceLocation id) { + manager.unbindApiLookup(id); + } + } +} \ No newline at end of file diff --git a/fabric/src/main/java/top/r3944realms/lib39/api/callback/client/ClientWorldCallback.java b/fabric/src/main/java/top/r3944realms/lib39/api/callback/client/ClientWorldCallback.java new file mode 100644 index 0000000..57d06ce --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/api/callback/client/ClientWorldCallback.java @@ -0,0 +1,28 @@ +package top.r3944realms.lib39.api.callback.client; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.client.multiplayer.ClientLevel; + +public interface ClientWorldCallback { + Event LOAD = EventFactory.createArrayBacked(ClientWorldCallback.Load.class, (callbacks) -> (world) -> { + for (Load callback : callbacks) { + callback.onWorldLoad(world); + } + }); + Event UNLOAD = EventFactory.createArrayBacked(ClientWorldCallback.Unload.class, (callbacks) -> (world) -> { + for (Unload callback : callbacks) { + callback.onWorldUnload(world); + } + }); + + @FunctionalInterface + interface Unload { + void onWorldUnload(ClientLevel clientLevel); + } + + @FunctionalInterface + interface Load { + void onWorldLoad(ClientLevel clientLevel); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/client/ItemRenderRegister.java b/fabric/src/main/java/top/r3944realms/lib39/client/ItemRenderRegister.java new file mode 100644 index 0000000..579c9ac --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/client/ItemRenderRegister.java @@ -0,0 +1,14 @@ +package top.r3944realms.lib39.client; + +import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; +import top.r3944realms.lib39.client.renderer.item.DollItemRenderer; +import top.r3944realms.lib39.core.register.Lib39Items; + +public class ItemRenderRegister { + public static void register() { + BuiltinItemRendererRegistry.INSTANCE.register( + Lib39Items.DOLL.get(), + DollItemRenderer.getInstance()::renderByItem + ); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/client/ShaderRegister.java b/fabric/src/main/java/top/r3944realms/lib39/client/ShaderRegister.java new file mode 100644 index 0000000..6fa1d1e --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/client/ShaderRegister.java @@ -0,0 +1,26 @@ +package top.r3944realms.lib39.client; + +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.ResourceManager; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.client.shader.Lib39Shaders; + +public class ShaderRegister { + public static void register() { + ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() { + @Override + public void onResourceManagerReload(@NotNull ResourceManager resourceManager) { + Lib39Shaders.registerShaders(resourceManager); + } + + @Override + public ResourceLocation getFabricId() { + return Lib39.rl("shaders"); + } + }); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/CreativeTabAdder.java b/fabric/src/main/java/top/r3944realms/lib39/core/CreativeTabAdder.java deleted file mode 100644 index d0731c2..0000000 --- a/fabric/src/main/java/top/r3944realms/lib39/core/CreativeTabAdder.java +++ /dev/null @@ -1,48 +0,0 @@ -package top.r3944realms.lib39.core; - -import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; -import net.minecraft.resources.ResourceKey; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.level.block.Block; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -public class CreativeTabAdder { - private static final Map, ResourceKey[]> itemAddMap = new ConcurrentHashMap<>(); - private static final Map, List>> tabToItemsMap = new ConcurrentHashMap<>(); - /** - * Add item to tabs. - * - * @param item the item - * @param tabs the tabs - */ - @SafeVarargs - public static void addItemToTabs(Supplier item, ResourceKey... tabs) { - itemAddMap.put(item, tabs); - - // 更新反向映射 - for (ResourceKey tab : tabs) { - tabToItemsMap.computeIfAbsent(tab, k -> new ArrayList<>()).add(item); - } - } - - public static void init() { - for (Map.Entry, List>> resourceKeyListEntry : tabToItemsMap.entrySet()) { - ItemGroupEvents.modifyEntriesEvent(resourceKeyListEntry.getKey()).register(content -> resourceKeyListEntry.getValue().forEach(i -> content.accept(i.get()))); - } - } - - /** - * Gets item add map. - * - * @return the item add map - */ - public static Map, ResourceKey[]> getItemAddMap() { - return itemAddMap; - } - -} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java b/fabric/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java new file mode 100644 index 0000000..ef135a7 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java @@ -0,0 +1,173 @@ +package top.r3944realms.lib39.core.event; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.api.callback.ActionResult; +import top.r3944realms.lib39.api.callback.SyncManagerRegisterCallback; +import top.r3944realms.lib39.api.callback.client.ClientWorldCallback; +import top.r3944realms.lib39.core.sync.CachedSyncManager; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.SyncData2LookupManager; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.TestSyncData; +import top.r3944realms.lib39.util.IClientOnly; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +public class CommonEventHandler { + static volatile SyncData2LookupManager syncData2Manager; + private static boolean isSync2MInitialized = false; + private static ServerLevel sl; + + /** + * Gets server level. + * + * @return the server level + */ + public static ServerLevel getServerLevel() { + return sl; + } + /** + * Gets sync data 2 manager. + * + * @return the sync data 2 manager + */ + public static SyncData2LookupManager getSyncData2Manager() { + return syncData2Manager; + } + public static void onServerWorldLoad(MinecraftServer server, ServerLevel serverLevel) { + if (!serverLevel.dimension().equals(Level.OVERWORLD)) return; + synchronized (CommonEventHandler.class) { + if (!isSync2MInitialized) { + syncData2Manager = new SyncData2LookupManager(); + isSync2MInitialized = true; + sl = serverLevel; + SyncManagerRegisterCallback.EVENT.invoker().registerSyncManagers(new SyncManagerRegisterCallback.SyncManagerRegistrar((syncData2Manager))); + Lib39.LOGGER.info("SyncData2Manager initialized on Sever load"); + } + } + } + + + public static void onServerWorldUnLoad(MinecraftServer server, @NotNull ServerLevel serverLevel) { + if (!serverLevel.dimension().equals(Level.OVERWORLD)) return; + + sl = null; + isSync2MInitialized = false; + } + + public static void onServerTick(MinecraftServer server) { + if (syncData2Manager == null) return; + if (server.getTickCount() % 10 == 0) + syncData2Manager.forEach(((resourceLocation, iSyncManager) -> iSyncManager.foreach(ISyncData::markDirty))); + syncData2Manager.forEach(((resourceLocation, iSyncManager) -> iSyncManager.foreach(ISyncData::checkIfDirtyThenUpdate))); + } + + public static void onEntityJoinWorld(@NotNull Entity entity, ServerLevel level) { + if (entity.level().isClientSide) return; + + for (ResourceLocation id : syncData2Manager.getRegisteredKeys()) { + if (syncData2Manager.isEntityClassAllowed(id, entity.getClass())) { + syncData2Manager.trackEntityForManager(entity, id); + } + } + } + + public static void onEntityLeaveWorld(@NotNull Entity entity, ServerLevel level) { + if (entity.level().isClientSide) return; + + for (ResourceLocation id : syncData2Manager.getRegisteredKeys()) { + if (syncData2Manager.isEntityClassAllowed(id, entity.getClass())) { + syncData2Manager.untrackEntityForManager(entity, id); + } + } + } + private static final Map, ResourceKey[]> itemAddMap = new ConcurrentHashMap<>(); + private static final Map, List>> tabToItemsMap = new ConcurrentHashMap<>(); + /** + * Add item to tabs. + * + * @param item the item + * @param tabs the tabs + */ + @SafeVarargs + public static void addItemToTabs(Supplier item, ResourceKey... tabs) { + itemAddMap.put(item, tabs); + + // 更新反向映射 + for (ResourceKey tab : tabs) { + tabToItemsMap.computeIfAbsent(tab, k -> new ArrayList<>()).add(item); + } + } + + public static void initCommon() { + for (Map.Entry, List>> resourceKeyListEntry : tabToItemsMap.entrySet()) { + ItemGroupEvents.modifyEntriesEvent(resourceKeyListEntry.getKey()).register(content -> resourceKeyListEntry.getValue().forEach(i -> content.accept(i.get()))); + } + SyncManagerRegisterCallback.EVENT.register(registrar -> { + registrar.register( + TestSyncData.ID, + new CachedSyncManager<>() { + private final Map syncDataMap = new ConcurrentHashMap<>(); + + @Override + public Map getSyncMap() { + return syncDataMap; + } + }, + AbstractedTestSyncData.class + ); + return ActionResult.PASS; + }); + ServerTickEvents.END_SERVER_TICK.register(CommonEventHandler::onServerTick); + ServerWorldEvents.LOAD.register(CommonEventHandler::onServerWorldLoad); + ServerWorldEvents.UNLOAD.register(CommonEventHandler::onServerWorldUnLoad); + ServerEntityEvents.ENTITY_LOAD.register(CommonEventHandler::onEntityJoinWorld); + ServerEntityEvents.ENTITY_UNLOAD.register(CommonEventHandler::onEntityLeaveWorld); + } + public static class ClientOpt implements IClientOnly { + public static void onClientWorldLoad(@NotNull ClientLevel clientLevel) { + synchronized (CommonEventHandler.class) { + IClientOnly.check(() -> { + if (!clientLevel.dimension().equals(Level.OVERWORLD)) return; + if (!isSync2MInitialized) { + syncData2Manager = new SyncData2LookupManager(); + SyncManagerRegisterCallback.EVENT.invoker().registerSyncManagers(new SyncManagerRegisterCallback.SyncManagerRegistrar(syncData2Manager)); + Lib39.LOGGER.info("SyncData2Manager initialized on Client load"); + } + }); + } + } + } + + public static void initClient() { + ClientWorldCallback.LOAD.register(ClientOpt::onClientWorldLoad); + } + + /** + * Gets item add map. + * + * @return the item add map + */ + public static Map, ResourceKey[]> getItemAddMap() { + return itemAddMap; + } + +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java b/fabric/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java new file mode 100644 index 0000000..a6a4ea5 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java @@ -0,0 +1,19 @@ +package top.r3944realms.lib39.core.network; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import top.r3944realms.lib39.core.network.toClient.SyncNBTLookupDataEntityS2CPacket; + +public class NetworkHandler { + + /** + * 注册客户端接收的数据包 + */ + public static void registerClientReceivers() { + ClientPlayNetworking.registerGlobalReceiver( + SyncNBTLookupDataEntityS2CPacket.TYPE, + SyncNBTLookupDataEntityS2CPacket::receive + ); + + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTLookupDataEntityS2CPacket.java b/fabric/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTLookupDataEntityS2CPacket.java new file mode 100644 index 0000000..548b3cf --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTLookupDataEntityS2CPacket.java @@ -0,0 +1,73 @@ +package top.r3944realms.lib39.core.network.toClient; + +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PacketType; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.NBTEntitySyncData; +import top.r3944realms.lib39.core.sync.SyncData2Manager; + +import java.util.Optional; + +public record SyncNBTLookupDataEntityS2CPacket(int entityId, ResourceLocation id, CompoundTag data) implements FabricPacket { + public static final ResourceLocation SYNC_NBT_LOOKUP_PACKET_ID = + Lib39.rl("sync_nbt_lookup_data_entity"); + public static final PacketType TYPE = PacketType.create( + SYNC_NBT_LOOKUP_PACKET_ID, + buf -> new SyncNBTLookupDataEntityS2CPacket(buf.readInt(), buf.readResourceLocation(), buf.readNbt()) + ); + /** + * Instantiates a new Sync nbt data s 2 c pack. + * + * @param entityId the entity id + * @param data the data + */ + public SyncNBTLookupDataEntityS2CPacket(int entityId, @NotNull NBTEntitySyncData data) { + this(entityId, data.id(), data.serializeNBT()); + } + + @Override + public void write(@NotNull FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(entityId); + friendlyByteBuf.writeResourceLocation(id); + friendlyByteBuf.writeNbt(data); + } + + @Contract(value = " -> new", pure = true) + @Override + public @NotNull PacketType getType() { + return TYPE; + } + + + public static void receive(@NotNull SyncNBTLookupDataEntityS2CPacket packet, @NotNull LocalPlayer localPlayer, PacketSender packetSender) { + Level level = localPlayer.level(); + Entity entity = level.getEntity(packet.entityId); + if (entity != null) { + Optional>> lookupOpt = + CommonEventHandler + .getSyncData2Manager() + .getDataProvider(packet.id); + lookupOpt.flatMap(dataProvider -> dataProvider.getData(entity)) + .ifPresent(lookup -> { + if (lookup instanceof NBTEntitySyncData nbtLookup) { + CompoundTag current = nbtLookup.serializeNBT(); + if (!current.equals(packet.data)) { + nbtLookup.deserializeNBT(packet.data); + } + } else Lib39.LOGGER.debug("Unhandled sync data: {}", packet.data); + } + ); + } + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/sync/IFabricUpdate.java b/fabric/src/main/java/top/r3944realms/lib39/core/sync/IFabricUpdate.java new file mode 100644 index 0000000..3370cad --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/sync/IFabricUpdate.java @@ -0,0 +1,25 @@ +package top.r3944realms.lib39.core.sync; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.network.toClient.SyncNBTLookupDataEntityS2CPacket; + +import java.util.List; + +public interface IFabricUpdate extends IUpdate { + default void update() { + ServerLevel serverLevel = CommonEventHandler.getServerLevel(); + if (serverLevel != null) { + PlayerList playerList = serverLevel.getServer().getPlayerList(); + List players = playerList.getPlayers(); + for (ServerPlayer player : players) { + if (ServerPlayNetworking.canSend(player, SyncNBTLookupDataEntityS2CPacket.TYPE)) { + ServerPlayNetworking.send(player, new SyncNBTLookupDataEntityS2CPacket(getSyncData().entityId(), getSyncData())); + } + } + } + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/sync/SyncData2LookupManager.java b/fabric/src/main/java/top/r3944realms/lib39/core/sync/SyncData2LookupManager.java new file mode 100644 index 0000000..0b59cbd --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/sync/SyncData2LookupManager.java @@ -0,0 +1,96 @@ +package top.r3944realms.lib39.core.sync; + +import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.lookup.v1.entity.EntityApiLookup; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class SyncData2LookupManager extends SyncData2Manager { + protected final Map> typedEntries = Maps.newConcurrentMap(); + protected static class TypedSyncEntry> extends SyncData2Manager.TypedSyncEntry { + /** + * Instantiates a new Typed sync entry for Fabric + * + * @param manager the manager + * @param apiLookup the EntityApiLookup instance + */ + public TypedSyncEntry(ISyncManager manager, @Nullable EntityApiLookup apiLookup) { + super(manager, key -> { + if (apiLookup != null) { + T data = apiLookup.find(key, null); + return Optional.ofNullable(data); + } + return Optional.empty(); + }); + } + } + + /** + * 使用自定义提供器注册管理器 + * + * @param the type parameter + * @param key the key + * @param manager the manager + * @param dataClass the data class + */ + public > void registerManager( + ResourceLocation key, + ISyncManager manager, + Class dataClass + ) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + Objects.requireNonNull(dataClass, "Data class cannot be null"); + + // 创建 EntityApiLookup + EntityApiLookup apiLookup = EntityApiLookup.get(key, dataClass, Void.class); + typedEntries.put(key, new TypedSyncEntry<>(manager, apiLookup)); + } + + /** + * 绑定 EntityApiLookup(用于分离注册的情况) + * + * @param the type parameter + * @param key the key + * @param apiLookup the EntityApiLookup + */ + public > void bindApiLookup(ResourceLocation key, EntityApiLookup apiLookup) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(apiLookup, "EntityApiLookup cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + updateApiLookupInEntry(key, entry, apiLookup); + } else { + throw new IllegalArgumentException("No manager found for " + key); + } + } + + /** + * 解绑 EntityApiLookup + * + * @param key the key + */ + public void unbindApiLookup(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + updateApiLookupInEntry(key, entry, null); + } + } + + /** + * 更新条目的 EntityApiLookup + */ + protected > void updateApiLookupInEntry( + ResourceLocation id, + TypedSyncEntry entry, + EntityApiLookup newApiLookup + ) { + updateDataProviderInEntry(id, entry, key -> newApiLookup != null ? Optional.ofNullable(newApiLookup.find(key, null)) : Optional.empty()); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/FabricLib39Examples.java b/fabric/src/main/java/top/r3944realms/lib39/example/FabricLib39Examples.java new file mode 100644 index 0000000..ce05f42 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/FabricLib39Examples.java @@ -0,0 +1,5 @@ +package top.r3944realms.lib39.example; + +public class FabricLib39Examples { + //todo:注册网络 +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java b/fabric/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java new file mode 100644 index 0000000..497d457 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java @@ -0,0 +1,359 @@ +package top.r3944realms.lib39.example.content.data; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.sync.IFabricUpdate; +import top.r3944realms.lib39.util.nbt.NBTReader; +import top.r3944realms.lib39.util.nbt.NBTWriter; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 测试同步数据实现 + */ +@SuppressWarnings("unused") +public class TestSyncData extends AbstractedTestSyncData implements IFabricUpdate { + /** + * The constant ID. + */ + public static final ResourceLocation ID = Lib39.rl(Lib39.MOD_ID, "test_sync_data"); + + // NBT 键常量 + private static final String NBT_KEY_STRING = "test_string"; + private static final String NBT_KEY_INT = "test_int"; + private static final String NBT_KEY_BOOLEAN = "test_boolean"; + private static final String NBT_KEY_DOUBLE = "test_double"; + private static final String NBT_KEY_COUNTER = "counter"; + private static final String NBT_KEY_SYNC_TIME = "last_sync_time"; + private static final String NBT_KEY_CUSTOM_DATA = "custom_data"; + private static final String NBT_KEY_CUSTOM_NAME = "name"; + private static final String NBT_KEY_CUSTOM_VALUE = "value"; + private static final String NBT_KEY_CUSTOM_FLAG = "flag"; + + // 数据字段 + private String testString = "default_value"; + private int testInt = 42; + private boolean testBoolean = true; + private double testDouble = 3.14159; + private int counter = 0; + private long lastSyncTime = 0L; + private TestData customData = new TestData("default", 100, false); + private Entity self; + + /** + * 构造函数 + * + * @param entity 关联的实体 + */ + public TestSyncData(Entity entity) { + super(ID); + this.self = entity; + } + + /** + * 构造函数(用于测试) + * + * @param entityId 实体ID + * @param self the self + */ + public TestSyncData(int entityId, Entity self) { + super(ID); + this.self = self; + } + + /** + * 构造函数(用于数据包反序列化) + * + * @param buf 字节缓冲区 + */ + public TestSyncData(FriendlyByteBuf buf) { + super(ID); + this.self = null; // 实体在从数据包重建时可能为null,需要在接收端设置 + fromBytes(buf); + } + + /** + * 将数据写入字节缓冲区(用于网络传输) + * + * @param buf 字节缓冲区 + */ + @Override + public void toBytes(@NotNull FriendlyByteBuf buf) { + // 写入基本类型字段 + buf.writeUtf(testString != null ? testString : ""); + buf.writeInt(testInt); + buf.writeBoolean(testBoolean); + buf.writeDouble(testDouble); + buf.writeInt(counter); + buf.writeLong(lastSyncTime); + + // 写入自定义数据 + if (customData != null) { + buf.writeUtf(customData.getName() != null ? customData.getName() : ""); + buf.writeInt(customData.getValue()); + buf.writeBoolean(customData.isFlag()); + } else { + buf.writeUtf(""); + buf.writeInt(0); + buf.writeBoolean(false); + } + + // 写入实体ID(如果实体存在) + if (self != null) { + buf.writeInt(self.getId()); + } else { + buf.writeInt(-1); + } + + // 写入脏数据状态 + buf.writeBoolean(isDirty()); + } + + /** + * 从字节缓冲区读取数据(用于网络传输) + * + * @param buf 字节缓冲区 + */ + @Override + public void fromBytes(@NotNull FriendlyByteBuf buf) { + // 读取基本类型字段 + this.testString = buf.readUtf(32767); // Minecraft字符串最大长度 + this.testInt = buf.readInt(); + this.testBoolean = buf.readBoolean(); + this.testDouble = buf.readDouble(); + this.counter = buf.readInt(); + this.lastSyncTime = buf.readLong(); + + // 读取自定义数据 + String customName = buf.readUtf(); + int customValue = buf.readInt(); + boolean customFlag = buf.readBoolean(); + this.customData = new TestData(customName, customValue, customFlag); + + // 读取实体ID(在接收端可能需要额外处理) + int entityId = buf.readInt(); + + // 读取脏数据状态 + boolean wasDirty = buf.readBoolean(); + if (wasDirty) { + markDirty(); + } + } + + /** + * 静态方法:从字节缓冲区创建 TestSyncData 实例 + * + * @param buf 字节缓冲区 + * @return 新的 TestSyncData 实例 + */ + @Contract("_ -> new") + public static @NotNull TestSyncData staticFromBytes(FriendlyByteBuf buf) { + return new TestSyncData(buf); + } + + @Override + public String getTestString() { + return testString; + } + + @Override + public void setTestString(String value) { + if (!java.util.Objects.equals(this.testString, value)) { + this.testString = value; + markDirty(); + } + } + + @Override + public int getTestInt() { + return testInt; + } + + @Override + public void setTestInt(int value) { + if (this.testInt != value) { + this.testInt = value; + markDirty(); + } + } + + @Override + public boolean isTestBoolean() { + return testBoolean; + } + + @Override + public void setTestBoolean(boolean value) { + if (this.testBoolean != value) { + this.testBoolean = value; + markDirty(); + } + } + + @Override + public double getTestDouble() { + return testDouble; + } + + @Override + public void setTestDouble(double value) { + if (this.testDouble != value) { + this.testDouble = value; + markDirty(); + } + } + + @Override + public int getCounter() { + return counter; + } + + @Override + public void incrementCounter() { + this.counter++; + markDirty(); + } + + @Override + public void clearCounter() { + this.counter = 0; + } + + @Override + public long getLastSyncTime() { + return lastSyncTime; + } + + @Override + public void updateSyncTime() { + this.lastSyncTime = System.currentTimeMillis(); + markDirty(); + } + + @Override + public void clearSyncTime() { + this.lastSyncTime = 0L; + } + + @Override + public TestData getCustomData() { + return customData; + } + + @Override + public void setCustomData(TestData data) { + if (data == null) { + throw new IllegalArgumentException("Custom data cannot be null"); + } + if (!java.util.Objects.equals(this.customData, data)) { + this.customData = data; + markDirty(); + } + } + + @Override + public boolean validateData() { + return testString != null && + !testString.isEmpty() && + customData != null && + customData.getName() != null && + !customData.getName().isEmpty() && + counter >= 0 && + testInt >= 0; + } + + @Override + public CompoundTag serializeNBT() { + return NBTWriter.builder() + .string(NBT_KEY_STRING, testString) + .intValue(NBT_KEY_INT, testInt) + .booleanValue(NBT_KEY_BOOLEAN, testBoolean) + .doubleValue(NBT_KEY_DOUBLE, testDouble) + .intValue(NBT_KEY_COUNTER, counter) + .longValue(NBT_KEY_SYNC_TIME, lastSyncTime) + .compound( + NBT_KEY_CUSTOM_DATA, + NBTWriter.builder() + .string(NBT_KEY_CUSTOM_NAME, customData.getName()) + .intValue(NBT_KEY_CUSTOM_VALUE, customData.getValue()) + .booleanValue(NBT_KEY_CUSTOM_FLAG, customData.isFlag()) + .build() + ).build(); + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + NBTReader.of(nbt) + .intValue(NBT_KEY_INT, integer -> testInt = integer) + .string(NBT_KEY_STRING, string -> testString = string) + .booleanValue(NBT_KEY_BOOLEAN, bool -> testBoolean = bool) + .intValue(NBT_KEY_COUNTER, integer -> counter = integer) + .doubleValue(NBT_KEY_DOUBLE, dou -> testDouble = dou) + .longValue(NBT_KEY_SYNC_TIME, sync -> lastSyncTime = sync) + .compound(NBT_KEY_CUSTOM_DATA, customDataTag -> { + AtomicReference name = new AtomicReference<>(""); + AtomicInteger value = new AtomicInteger(-1); + AtomicBoolean flag = new AtomicBoolean(false); + NBTReader.of(customDataTag) + .string(NBT_KEY_CUSTOM_NAME, name::set) + .intValue(NBT_KEY_CUSTOM_VALUE, value::set) + .booleanValue(NBT_KEY_CUSTOM_FLAG, flag::set); + this.customData = new TestData(name.get(), value.get(), flag.get()); + }); + } + + @Override + public int entityId() { + return self != null ? self.getId() : -1; + } + + /** + * 设置关联的实体 + * + * @param entity 关联的实体 + */ + public void setEntity(Entity entity) { + this.self = entity; + } + + + /** + * 获取所有数据的字符串表示(用于调试) + */ + @Override + public String toString() { + return String.format( + "TestSyncData{id=%d, string='%s', int=%d, boolean=%s, double=%.2f, counter=%d, lastSync=%d, custom=%s}", + self.getId(), testString, testInt, testBoolean, testDouble, counter, lastSyncTime, customData + ); + } + + /** + * 创建一个不依赖实体的副本(用于网络传输) + * + * @return 不包含实体引用的副本 test sync data + */ + public TestSyncData createNetworkCopy() { + TestSyncData copy = new TestSyncData((Entity) null); + copy.testString = this.testString; + copy.testInt = this.testInt; + copy.testBoolean = this.testBoolean; + copy.testDouble = this.testDouble; + copy.counter = this.counter; + copy.lastSyncTime = this.lastSyncTime; + copy.customData = new TestData( + this.customData.getName(), + this.customData.getValue(), + this.customData.isFlag() + ); + return copy; + } + +} \ No newline at end of file diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java b/fabric/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java new file mode 100644 index 0000000..9e5d24a --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java @@ -0,0 +1,77 @@ +package top.r3944realms.lib39.example.content.item; + +import net.fabricmc.fabric.api.lookup.v1.entity.EntityApiLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.network.toClient.SyncNBTLookupDataEntityS2CPacket; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.TestSyncData; +import top.r3944realms.lib39.example.content.item.AbstractFabricItem; +import top.r3944realms.lib39.example.core.network.ClientDataPacket; + +import java.util.List; + +public class FabricItem extends AbstractFabricItem { + + public FabricItem(Properties properties) { + super(properties); + } + + @Override + protected AbstractedTestSyncData getData(Entity target) { + return getStaticData(target); + } + + @Override + protected void sendClientDataToServer(AbstractedTestSyncData clientData, int targetEntityId) { + ServerLevel serverLevel = CommonEventHandler.getServerLevel(); + if (serverLevel != null) { + PlayerList playerList = serverLevel.getServer().getPlayerList(); + List players = playerList.getPlayers(); + for (ServerPlayer player : players) { + if (ServerPlayNetworking.canSend(player, ClientDataPacket.TYPE)) { + ServerPlayNetworking.send(player, new ClientDataPacket(clientData, targetEntityId)); + } + } + } + } + + public static @Nullable AbstractedTestSyncData getStaticData(Entity target) { + try { + AbstractedTestSyncData abstractData = EntityApiLookup.get(TestSyncData.ID, AbstractedTestSyncData.class,null).find(target, null); + if (abstractData instanceof TestSyncData) { + return abstractData; + } + } catch (Exception e) { + Lib39.LOGGER.error("[FabricItem] 获取服务器端数据失败", e); + } + return null; + } + + public static void handleClientDataFromPacket(@NotNull ServerPlayer player, AbstractedTestSyncData clientData, int targetEntityId) { + Entity target = player.level().getEntity(targetEntityId); + + if (target instanceof LivingEntity livingTarget) { + // 获取服务器端数据 + AbstractedTestSyncData serverData = getStaticData(livingTarget); + + if (serverData != null) { + // 显示双端对比结果 + displayDualEndComparison(player, livingTarget, serverData, clientData); + } else { + player.sendSystemMessage(Component.literal("§c无法获取服务器端数据")); + } + } else { + player.sendSystemMessage(Component.literal("§c目标生物不存在或已消失")); + } + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java b/fabric/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java new file mode 100644 index 0000000..a2e0e51 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.world.entity.Entity; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.item.AbstractNeoForgeItem; + +public class NeoForgeItem extends AbstractNeoForgeItem { + + public NeoForgeItem(Properties properties) { + super(properties); + } + + @Override + protected AbstractedTestSyncData getData(Entity entity) { + return null; + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java b/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java new file mode 100644 index 0000000..3ce5595 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java @@ -0,0 +1,64 @@ +package top.r3944realms.lib39.example.core.network; + +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PacketType; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.TestSyncData; +import top.r3944realms.lib39.example.content.item.FabricItem; + +/** + * The type Client data packet. + */ +public class ClientDataPacket implements FabricPacket { + public static final ResourceLocation CLIENT_TEST_DATA = + Lib39.rl("client_test_data"); + public static final PacketType TYPE = PacketType.create( + CLIENT_TEST_DATA, + ClientDataPacket::new + ); + private final AbstractedTestSyncData clientData; + private final int targetEntityId; + + /** + * Instantiates a new Client data packet. + * + * @param clientData the client data + * @param targetEntityId the target entity id + */ + public ClientDataPacket(AbstractedTestSyncData clientData, int targetEntityId) { + this.clientData = clientData; + this.targetEntityId = targetEntityId; + } + + /** + * Instantiates a new Client data packet. + * + * @param buf the buf + */ + public ClientDataPacket(FriendlyByteBuf buf) { + this.clientData = TestSyncData.staticFromBytes(buf); + this.targetEntityId = buf.readInt(); + } + + + @Override + public void write(FriendlyByteBuf buf) { + clientData.toBytes(buf); + buf.writeInt(targetEntityId); + } + + @Override + public PacketType getType() { + return TYPE; + } + + public static void receive(@NotNull ClientDataPacket packet, @NotNull ServerPlayer serverPlayer, PacketSender packetSender) { + FabricItem.handleClientDataFromPacket(serverPlayer, packet.clientData, packet.targetEntityId); + } +} \ No newline at end of file diff --git a/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java b/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java new file mode 100644 index 0000000..ba26ca6 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java @@ -0,0 +1,15 @@ +package top.r3944realms.lib39.example.core.network; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +public class ExNetworkHandler { + /** + * 注册服务器接收的数据包 + */ + public static void registerServerReceivers() { + ServerPlayNetworking.registerGlobalReceiver( + ClientDataPacket.TYPE, + ClientDataPacket::receive + ); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinDedicateServer.java similarity index 98% rename from fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java rename to fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinDedicateServer.java index 222b7e8..48cccfc 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java +++ b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinDedicateServer.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.mixin.init; +package top.r3944realms.lib39.mixin.callback; import com.mojang.datafixers.DataFixer; import net.minecraft.server.MinecraftServer; diff --git a/fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinMinecraft.java similarity index 75% rename from fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java rename to fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinMinecraft.java index 8e3c8bb..4174778 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java +++ b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/MixinMinecraft.java @@ -1,5 +1,7 @@ -package top.r3944realms.lib39.mixin.init; +package top.r3944realms.lib39.mixin.callback; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.platform.WindowEventHandler; import net.minecraft.client.Minecraft; @@ -10,16 +12,22 @@ import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.util.thread.ReentrantBlockableEventLoop; import net.minecraft.world.level.storage.LevelStorageSource; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.r3944realms.lib39.api.callback.MinecraftSetUpServiceCallback; +import top.r3944realms.lib39.api.callback.client.ClientWorldCallback; + +import javax.annotation.Nullable; /** * The type Mixin minecraft. */ @Mixin(Minecraft.class) public abstract class MixinMinecraft extends ReentrantBlockableEventLoop implements WindowEventHandler { + @Shadow @Nullable public ClientLevel level; + /** * Instantiates a new Mixin minecraft. * @@ -28,7 +36,16 @@ public abstract class MixinMinecraft extends ReentrantBlockableEventLoop original) { + if (levelClient != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(levelClient); + original.call(levelClient); + } + @WrapMethod(method = "clearLevel()V") + public void clearLevel$callback(Operation original) { + if (level != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(level); + original.call(); + } /** * Set level setup. * diff --git a/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/client/MixinClientLevel.java b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/client/MixinClientLevel.java new file mode 100644 index 0000000..fb57c20 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/mixin/callback/client/MixinClientLevel.java @@ -0,0 +1,30 @@ +package top.r3944realms.lib39.mixin.callback.client; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.WritableLevelData; +import org.spongepowered.asm.mixin.Mixin; +import top.r3944realms.lib39.api.callback.client.ClientWorldCallback; + +import java.util.function.Supplier; + +@Mixin(ClientLevel.class) +public abstract class MixinClientLevel extends Level { + protected MixinClientLevel(WritableLevelData levelData, ResourceKey dimension, RegistryAccess registryAccess, Holder dimensionTypeRegistration, Supplier profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) { + super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates); + } + @WrapMethod(method = "") + private void init(ClientPacketListener connection, ClientLevel.ClientLevelData clientLevelData, ResourceKey dimension, Holder dimensionType, int viewDistance, int serverSimulationDistance, Supplier profiler, LevelRenderer levelRenderer, boolean isDebug, long biomeZoomSeed, Operation original) { + original.call(connection, clientLevelData, dimension, dimensionType, viewDistance, serverSimulationDistance, profiler, levelRenderer, isDebug, biomeZoomSeed); + ClientWorldCallback.LOAD.invoker().onWorldLoad(ClientLevel.class.cast(this)); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/platform/FabricHelpCommandHook.java b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricHelpCommandHook.java new file mode 100644 index 0000000..ea491a2 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricHelpCommandHook.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.platform; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import top.r3944realms.lib39.api.callback.RegisterCommandHelpCallback; +import top.r3944realms.lib39.core.command.ICommandHelpManager; +import top.r3944realms.lib39.platform.services.IHelpCommandHook; + +public enum FabricHelpCommandHook implements IHelpCommandHook { + INSTANCE; + + @Override + public void onRegister(LiteralArgumentBuilder tree, ICommandHelpManager manager, CommandBuildContext context) { + RegisterCommandHelpCallback.EVENT.invoker().register(new RegisterCommandHelpCallback.CommandHelpRegistrar(tree, manager, context)); + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java index 71e9d7a..0053417 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java +++ b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java @@ -4,6 +4,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.ModMetadata; import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.platform.services.IHelpCommandHook; import top.r3944realms.lib39.platform.services.IPlatformHelper; import net.fabricmc.loader.api.FabricLoader; import top.r3944realms.lib39.platform.services.IUtilHelper; @@ -46,4 +47,9 @@ public class FabricPlatformHelper implements IPlatformHelper { public IUtilHelper getUtilHelper() { return FabricUtilHelper.INSTANCE; } + + @Override + public IHelpCommandHook getHelpCommandHook() { + return FabricHelpCommandHook.INSTANCE; + } } diff --git a/fabric/src/main/java/top/r3944realms/lib39/util/FabricBlockRegistryBuilder.java b/fabric/src/main/java/top/r3944realms/lib39/util/FabricBlockRegistryBuilder.java index 6e594f0..457e316 100644 --- a/fabric/src/main/java/top/r3944realms/lib39/util/FabricBlockRegistryBuilder.java +++ b/fabric/src/main/java/top/r3944realms/lib39/util/FabricBlockRegistryBuilder.java @@ -3,7 +3,7 @@ package top.r3944realms.lib39.util; import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.level.block.Block; -import top.r3944realms.lib39.core.CreativeTabAdder; +import top.r3944realms.lib39.core.event.CommonEventHandler; import top.r3944realms.lib39.util.block.BlockRegistryBuilder; import java.util.function.Supplier; @@ -12,6 +12,6 @@ public class FabricBlockRegistryBuilder extends BlockRegistryBuilder { @SafeVarargs @Override protected final void registerBlockItem(Supplier blockObject, ResourceKey... creativeTabs) { - CreativeTabAdder.addItemToTabs(blockObject, creativeTabs); + CommonEventHandler.addItemToTabs(blockObject, creativeTabs); } } diff --git a/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper b/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper index f080f90..37f2d72 100644 --- a/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper +++ b/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper @@ -1 +1 @@ -top.r3944realms.lib39.platform.ForgePlatformHelper \ No newline at end of file +top.r3944realms.lib39.platform.FabricPlatformHelper \ No newline at end of file diff --git a/fabric/src/main/resources/lib39.fabric.mixins.json b/fabric/src/main/resources/lib39.fabric.mixins.json index d925eee..75a3d0a 100644 --- a/fabric/src/main/resources/lib39.fabric.mixins.json +++ b/fabric/src/main/resources/lib39.fabric.mixins.json @@ -5,10 +5,11 @@ "refmap": "${mod_id}.refmap.json", "compatibilityLevel": "JAVA_17", "mixins": [ - "init.MixinDedicateServer" + "callback.MixinDedicateServer" ], "client": [ - "init.MixinMinecraft" + "callback.client.MixinClientLevel", + "callback.MixinMinecraft" ], "server": [ ], diff --git a/forge/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java b/forge/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java new file mode 100644 index 0000000..fe267f9 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java @@ -0,0 +1,100 @@ +package top.r3944realms.lib39.api.event; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.Event; +import top.r3944realms.lib39.core.command.ICommandHelpManager; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.CommandPath; +import top.r3944realms.lib39.core.command.model.Parameter; + +/** + * The type Register command help event. + */ +public class RegisterCommandHelpEvent extends Event { + private final LiteralArgumentBuilder builder; + private final ICommandHelpManager helpManager; + private final CommandBuildContext context; + + /** + * Instantiates a new Register command help event. + * + * @param builder the builder + * @param helpManager the help manager + * @param source the source + */ + public RegisterCommandHelpEvent(LiteralArgumentBuilder builder, ICommandHelpManager helpManager, CommandBuildContext source) { + this.builder = builder; + this.helpManager = helpManager; + this.context = source; + } + + /** + * Gets id. + * + * @return the id + */ + public ResourceLocation getID() { + return helpManager.getID(); + } + + /** + * Add child. + * + * @param child the child + */ + public void addChild(LiteralArgumentBuilder child) { + this.builder.then(child); + } + + /** + * Get context command build context. + * + * @return the command build context + */ + public CommandBuildContext getContext(){ + return this.context; + } + + /** + * Get tree literal argument builder. + * + * @return the literal argument builder + */ + public LiteralArgumentBuilder getTree(){ + return this.builder; + } + + /** + * 注册命令帮助信息 + * + * @param CommandNode 命令节点 + * @param description 命令描述 + */ + public void registerHelp(CommandNode CommandNode, MutableComponent description) { + this.helpManager.registerCommandHelp(CommandNode, description); + } + + /** + * 注册命令帮助信息 + * + * @param CommandNode 命令节点 + * @param descriptionKey 命令描述的语言键 + */ + public void registerHelp(CommandNode CommandNode, String descriptionKey) { + this.helpManager.registerCommandHelp(CommandNode, descriptionKey); + } + + /** + * 注册命令参数 + * + * @param commandPath 命令节点 + * @param parametersBuilder 参数列表构造器 + */ + public void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder) { + this.helpManager.registerCommandParameters(commandPath, parametersBuilder); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java b/forge/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java new file mode 100644 index 0000000..dfd9a6a --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java @@ -0,0 +1,125 @@ +package top.r3944realms.lib39.api.event; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.eventbus.api.Event; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.ISyncManager; +import top.r3944realms.lib39.core.sync.SyncData2CapManager; + +/** + * The type Sync manager register event. + */ +@SuppressWarnings("unused") +public class SyncManagerRegisterEvent extends Event { + /** + * The Syncs 2 manager. + */ + protected final SyncData2CapManager syncs2Manager; + + /** + * Instantiates a new Sync manager register event. + * + * @param syncsManager the syncs manager + */ + public SyncManagerRegisterEvent(SyncData2CapManager syncsManager) { + this.syncs2Manager = syncsManager; + } + + /** + * Gets syncs manager. + * + * @return the syncs manager + */ + public SyncData2CapManager getSyncsManager() { + return syncs2Manager; + } + + /** + * 类型安全的同步管理器注册 + * + * @param the type parameter + * @param the type parameter + * @param id the id + * @param syncManager the sync manager + * @param capability the capability + */ + public > void registerSyncManager( + ResourceLocation id, + ISyncManager, T> syncManager, + Capability capability + ) { + syncs2Manager.registerManager(id, syncManager, capability); + } + + /** + * Unregister sync manager. + * + * @param id the id + */ + public void unregisterSyncManager(ResourceLocation id) { + syncs2Manager.removeManager(id); + } + + /** + * 允许实体类 + * + * @param id the id + * @param entityClasses the entity classes + */ + public final void addAllowEntityClass(ResourceLocation id, Class... entityClasses) { + syncs2Manager.allowEntityClass(id, entityClasses); + } + + /** + * 移除允许的实体类 + * + * @param id the id + * @param entityClasses the entity classes + */ + public final void removeAllowEntityClass(ResourceLocation id, Class... entityClasses) { + syncs2Manager.disallowEntityClass(id, entityClasses); + } + + /** + * 绑定能力(用于分离注册的情况) + * + * @param the type parameter + * @param id 必须先注册安全同步管理器,再绑定Cap,否则会抛出{@link IllegalStateException 未找到对应安全同步管理器} + * @param capability the capability + */ + public > void bindCapability(ResourceLocation id, Capability capability) { + syncs2Manager.bindCapability(id, capability); + } + + /** + * 解绑数据提供者 + * + * @param id the id + */ + public void unbindCapability(ResourceLocation id) { + syncs2Manager.unbindCapability(id); + } + + /** + * 完整的类型安全注册 + * + * @param the type parameter + * @param the type parameter + * @param id the id + * @param syncManager the sync manager + * @param capability the capability + * @param allowedEntityClasses the allowed entity classes + */ + public > void registerComplete( + ResourceLocation id, + ISyncManager, T> syncManager, + Capability capability, + Class... allowedEntityClasses + ) { + registerSyncManager(id, syncManager, capability); + if (allowedEntityClasses.length > 0) { + addAllowEntityClass(id, allowedEntityClasses); + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java new file mode 100644 index 0000000..a8fb271 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java @@ -0,0 +1,85 @@ +package top.r3944realms.lib39.base.datagen; + +import net.minecraft.data.DataProvider; +import net.minecraftforge.data.event.GatherDataEvent; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.base.datagen.provider.*; +import top.r3944realms.lib39.base.datagen.value.Lib39LangKey; +import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider; +import top.r3944realms.lib39.datagen.provider.SimpleLootTableProvider; +import top.r3944realms.lib39.datagen.provider.SubProvidersWrapper; +import top.r3944realms.lib39.datagen.value.McLocale; + +/** + * The type Lib 39 base data gen event. + */ +public class Lib39BaseDataGenEvent { + /** + * The Logger. + */ + static Logger logger = LoggerFactory.getLogger(Lib39BaseDataGenEvent.class); + + /** + * Gather data. + * + * @param event the event + */ + public static void gatherData(GatherDataEvent event) { + logger.info("GatherDataEvent thread: {}", Thread.currentThread().getName()); + LanguageGenerator(event, McLocale.EN_US); + LanguageGenerator(event, McLocale.ZH_CN); + LanguageGenerator(event, McLocale.ZH_TW); + LanguageGenerator(event, McLocale.LZH); + BlockModelDataGenerate(event); + BlockStateDataGenerate(event); + ItemModelDataGenerate(event); + LootTableDataGenerate(event); + SoundDefinitionDataGenerate(event); + RecipeGenerator(event); + } + private static void LanguageGenerator(@NotNull GatherDataEvent event, McLocale language) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new SimpleLanguageProvider(pOutput, Lib39.MOD_ID ,language , Lib39LangKey.INSTANCE) + ); + } + private static void ItemModelDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39ItemModelProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void BlockModelDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39BlockModelProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void BlockStateDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39BlockStatesProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void SoundDefinitionDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39SoundDefinitionsProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void LootTableDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeServer(), + (DataProvider.Factory) pOutput -> new SimpleLootTableProvider(pOutput, new SubProvidersWrapper().addBlockEntry(new Lib39BlockLootTable())) + ); + } + private static void RecipeGenerator(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeServer(), + (DataProvider.Factory) Lib39RecipeProvider::new + ); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockLootTable.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockLootTable.java new file mode 100644 index 0000000..0132b92 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockLootTable.java @@ -0,0 +1,18 @@ +package top.r3944realms.lib39.base.datagen.provider; + +import top.r3944realms.lib39.core.register.ForgeLib39Blocks; +import top.r3944realms.lib39.core.register.Lib39Blocks; +import top.r3944realms.lib39.datagen.provider.subprovider.BlockLootTables; + +/** + * The type Lib 39 block loot table. + */ +public class Lib39BlockLootTable extends BlockLootTables { + /** + * Instantiates a new Lib 39 block loot table. + */ + public Lib39BlockLootTable() { + super(ForgeLib39Blocks.BLOCKS); + dropSelf(Lib39Blocks.DOLL); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java new file mode 100644 index 0000000..3aee570 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java @@ -0,0 +1,44 @@ +package top.r3944realms.lib39.base.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.model.generators.BlockModelProvider; +import net.minecraftforge.common.data.ExistingFileHelper; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.util.PlantHelper; + +/** + * The type Lib 39 block model provider. + */ +public class Lib39BlockModelProvider extends BlockModelProvider { + /** + * Instantiates a new Lib 39 block model provider. + * + * @param output the output + * @param existingFileHelper the existing file helper + */ + public Lib39BlockModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + super(output, Lib39.MOD_ID, existingFileHelper); + } + + @Override + protected void registerModels() { +// registerPlants(); + } + + /** + * Register plants. + */ + protected void registerPlants() { + for (PlantHelper.Plant plant: PlantHelper.Plant.values()) { + createPlantsModel(plant); + } + } + private void createPlantsModel(PlantHelper.Plant plant) { + ResourceLocation rl = PlantHelper.getTextureRL(plant); + getBuilder("block/doll_item/" + plant) + .parent(getExistingFile(Lib39.rl("block/base_doll_item"))) + .texture("item", rl) + .ao(false); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java new file mode 100644 index 0000000..801b444 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java @@ -0,0 +1,74 @@ +package top.r3944realms.lib39.base.datagen.provider; + +import net.minecraft.core.Direction; +import net.minecraft.data.PackOutput; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraftforge.client.model.generators.BlockStateProvider; +import net.minecraftforge.client.model.generators.ConfiguredModel; +import net.minecraftforge.client.model.generators.ModelFile; +import net.minecraftforge.common.data.ExistingFileHelper; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.register.Lib39Blocks; + +/** + * The type Lib 39 block states provider. + */ +public class Lib39BlockStatesProvider extends BlockStateProvider { + /** + * Instantiates a new Lib 39 block states provider. + * + * @param output the output + * @param exFileHelper the ex file helper + */ + public Lib39BlockStatesProvider(PackOutput output, ExistingFileHelper exFileHelper) { + super(output, Lib39.MOD_ID, exFileHelper); + } + @Override + protected void registerStatesAndModels() { + generateDollBlockStatesSimple(); + } + private void generateDollBlockStatesSimple() { + Block doll = Lib39Blocks.DOLL.get(); + + // 创建GeckoLib模型引用 + ModelFile modelFile = new ModelFile.ExistingModelFile( + Lib39.rl( "block/base_doll"), + models().existingFileHelper + ); + + getVariantBuilder(doll).forAllStates(state -> { + Direction direction = state.getValue(BlockStateProperties.HORIZONTAL_FACING); + int rotationY = getMainNorthRotationY(direction); + + return ConfiguredModel.builder() + .modelFile(modelFile) + .rotationY(rotationY) + .build(); + }); + } + + @Contract(pure = true) + private int getMainWestRotationY(@NotNull Direction direction) { + return switch (direction) { + case WEST -> 0; // 西 - 基准方向,0度 + case NORTH -> 90; // 北 - 相对于西旋转90度 + case EAST -> 180; // 东 - 相对于西旋转180度 + case SOUTH -> 270; // 南 - 相对于西旋转270度 + default -> 0; + }; + } + + @Contract(pure = true) + private int getMainNorthRotationY(@NotNull Direction direction) { + return switch (direction) { + case WEST -> 270; // 西 - 基准方向,270度 + case NORTH -> 0; // 北 - 相对于西旋转0度 + case EAST -> 90; // 东 - 相对于西旋转90度 + case SOUTH -> 180; // 南 - 相对于西旋转180度 + default -> 0; + }; + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java new file mode 100644 index 0000000..563615f --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java @@ -0,0 +1,123 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.base.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.registries.ForgeRegistries; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.base.datagen.value.Lib39LangKey; +import top.r3944realms.lib39.datagen.value.LangKeyValue; +import top.r3944realms.lib39.datagen.value.ModPartEnum; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The type item model provider. + */ +public class Lib39ItemModelProvider extends net.minecraftforge.client.model.generators.ItemModelProvider { + private static List objectList; + /** + * The constant GENERATED. + */ + public static final String GENERATED = "item/generated"; + /** + * The constant HANDHELD. + */ + public static final String HANDHELD = "item/handheld"; + + /** + * Instantiates a new item model provider. + * + * @param output the output + * @param existingFileHelper the existing file helper + */ + public Lib39ItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + super(output, Lib39.MOD_ID, existingFileHelper); + objectList = new ArrayList<>(); + init(); + } + + @Override + protected void registerModels() { + defaultModItemModelRegister(); + generateDollItemModel(); + } + private void init() { + for(LangKeyValue obj : Lib39LangKey.INSTANCE.getValues()) { + if(!(obj.isDefault() && obj.getMPE().equals(ModPartEnum.ITEM))) continue; + objectList.add(obj.getItem()); + } + } + /** + * @implNote
 先有纹理才会成功构建 + */ + private void defaultModItemModelRegister() { + objectList.forEach(this::basicItem); + } + + /** + * Item generate model. + * + * @param item the item + * @param location the location + */ + public void itemGenerateModel(Item item, ResourceLocation location){ + withExistingParent(itemName(item), GENERATED).texture("layer0", location); + } + + /** + * Item hand held model. + * + * @param item the item + * @param location the location + */ + public void itemHandHeldModel(Item item, ResourceLocation location){ + withExistingParent(itemName(item), HANDHELD).texture("layer0", location); + } + + /** + * Generate doll item model. + */ + protected void generateDollItemModel() { + getBuilder("doll") + .parent(getExistingFile(Lib39.rl("block/base_doll"))); + } + + /** + * Item name string. + * + * @param item the item + * @return the string + */ + public String itemName(Item item){ + return Objects.requireNonNull(ForgeRegistries.ITEMS.getKey(item)).getPath(); + } + + /** + * Resource item resource location. + * + * @param path the path + * @return the resource location + */ + public ResourceLocation resourceItem(String path){ + return modLoc("item/" + path); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39SoundDefinitionsProvider.java b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39SoundDefinitionsProvider.java new file mode 100644 index 0000000..c5903d6 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39SoundDefinitionsProvider.java @@ -0,0 +1,45 @@ +package top.r3944realms.lib39.base.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.common.data.SoundDefinition; +import net.minecraftforge.common.data.SoundDefinitionsProvider; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.register.Lib39SoundEvents; + +/** + * The type Lib 39 sound definitions provider. + */ +public class Lib39SoundDefinitionsProvider extends SoundDefinitionsProvider { + /** + * Instantiates a new Lib 39 sound definitions provider. + * + * @param output the output + * @param helper the helper + */ + public Lib39SoundDefinitionsProvider(PackOutput output, ExistingFileHelper helper) { + super(output, Lib39.MOD_ID, helper); + } + + /** + * Gets sound definition. + * + * @param subTitle the sub title + * @param sounds the sounds + * @return the sound definition + */ + public SoundDefinition getSoundDefinition(String subTitle, SoundDefinition.Sound... sounds) { + return SoundDefinition.definition().subtitle(subTitle).with(sounds); + } + + @Override + public void registerSounds() { + add( + Lib39SoundEvents.DUCK_TOY, + getSoundDefinition( + Lib39SoundEvents.getSubTitleTranslateKey("duck_toy"), + sound(Lib39SoundEvents.RL_DUCK_TOY, SoundDefinition.SoundType.SOUND) + ) + ); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/content/item/ForgeDollItem.java b/forge/src/main/java/top/r3944realms/lib39/content/item/ForgeDollItem.java new file mode 100644 index 0000000..1e7ad8e --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/content/item/ForgeDollItem.java @@ -0,0 +1,24 @@ +package top.r3944realms.lib39.content.item; + +import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.client.renderer.item.DollItemRenderer; + +import java.util.function.Consumer; + +public class ForgeDollItem extends DollItem { + public ForgeDollItem(Properties properties) { + super(properties); + } + + @Override + public void initializeClient(@NotNull Consumer consumer) { + consumer.accept(new IClientItemExtensions() { + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return DollItemRenderer.getInstance(); + } + }); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/compat/ForgeCompatManager.java b/forge/src/main/java/top/r3944realms/lib39/core/compat/ForgeCompatManager.java new file mode 100644 index 0000000..a0af52d --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/compat/ForgeCompatManager.java @@ -0,0 +1,260 @@ +package top.r3944realms.lib39.core.compat; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Consumer; + +/** + * The type Compat manager. + */ +@SuppressWarnings("unused") +public abstract class ForgeCompatManager extends CompatManager { + protected final IEventBus modEventBus, gameEventBus; + protected final Map compats = new HashMap<>(); + // 存储事件监听器配置 + protected final List listenerConfigs = new ArrayList<>(); + + public ForgeCompatManager(ResourceLocation id, IEventBus modEventBus, IEventBus gameEventBus) { + super(id); + this.modEventBus = modEventBus; + this.gameEventBus = gameEventBus; + } + + @Override + protected void doRegisterCompat(ResourceLocation id, ICompat compat) { + if (compat instanceof IForgeCompat) { + super.doRegisterCompat(id, compat); + addListenerForCompat(id); + } else throw new IllegalArgumentException("Can't register compat " + id + " of type " + compat.getClass()); + } + + /** + * 为所有兼容模块配置事件监听器 + * + * @param dists the dists + * @param bus the bus + */ + public void addListenerForAll(@Nullable Dist dists, Mod.EventBusSubscriber.Bus bus) { + listenerConfigs.add(new ListenerConfig(null, dists, bus)); + } + + /** + * 为特定兼容模块配置事件监听器 + * + * @param compatId the compat id + * @param dists the dists + * @param bus the bus + */ + public void addListenerForCompat(ResourceLocation compatId, @Nullable Dist dists, Mod.EventBusSubscriber.Bus bus) { + listenerConfigs.add(new ListenerConfig(compatId, dists, bus)); + } + + /** + * 为特定兼容模块配置事件监听器 + * + * @param compatId the compat id + */ + public void addListenerForCompat(ResourceLocation compatId) { + addListenerForCompat(compatId, null, Mod.EventBusSubscriber.Bus.FORGE); + addListenerForCompat(compatId, null, Mod.EventBusSubscriber.Bus.MOD); + + addListenerForCompat(compatId, Dist.CLIENT, Mod.EventBusSubscriber.Bus.FORGE); + addListenerForCompat(compatId, Dist.CLIENT, Mod.EventBusSubscriber.Bus.MOD); + + addListenerForCompat(compatId, Dist.DEDICATED_SERVER, Mod.EventBusSubscriber.Bus.FORGE); + addListenerForCompat(compatId, Dist.DEDICATED_SERVER, Mod.EventBusSubscriber.Bus.MOD); + } + + /** + * 为已加载的兼容模块配置事件监听器 + * + * @param dists the dists + * @param bus the bus + * @param consumer the consumer + */ + public void addListenerForLoaded(@Nullable Dist dists, Mod.EventBusSubscriber.Bus bus, Consumer consumer) { + listenerConfigs.add(new ListenerConfig(null, dists, bus) { + @Override + boolean shouldApply(@NotNull IForgeCompat compat) { + return super.shouldApply(compat); + } + }); + } + + // ===================== 初始化和管理 ===================== + + /** + * 初始化所有兼容模块并应用事件监听器 + */ + protected synchronized void initializeAllCompat() { + logger.info("Initializing {} compatibility modules", compats.size()); + + // 先处理所有缓存的注册 + pendingTasks.forEach(Runnable::run); + pendingTasks.clear(); + + // 初始化所有兼容模块 + for (Map.Entry entry : compats.entrySet()) { + if (!entry.getValue().isInitialized() && entry.getValue().isModLoaded()) { + try { + entry.getValue().initialize(); + entry.getValue().setInitialize(true); + logger.info("Initialized compat: {}", entry.getKey()); + } catch (Exception e) { + logger.error("Failed to initialize compat: {}", entry.getKey(), e); + } + } + } + initialized = true; + // 2. 然后应用所有事件监听器 + applyAllEventListeners(); + } + + /** + * 应用所有配置的事件监听器到对应的 ICompat 实例 + */ + private void applyAllEventListeners() { + logger.info("Applying {} event listener configurations", listenerConfigs.size()); + + for (ListenerConfig config : listenerConfigs) { + if (config.compatId == null) { + // 应用到所有兼容模块 + applyListenerToAllCompats(config); + } else { + // 应用到特定兼容模块 + applyListenerToCompat(config.compatId, config); + } + } + } + + /** + * 将监听器应用到所有兼容模块 + */ + private void applyListenerToAllCompats(ListenerConfig config) { + for (IForgeCompat compat : compats.values()) { + if(!compat.isInitialized()) { + if (config.shouldApply(compat)) { + applyListenerToCompat(compat, config); + } + } + } + } + + /** + * 将监听器应用到特定兼容模块 + */ + private void applyListenerToCompat(ResourceLocation compatId, ListenerConfig config) { + IForgeCompat compat = compats.get(compatId); + if (compat != null && config.shouldApply(compat)) { + applyListenerToCompat(compat, config); + } + } + + /** + * 将监听器应用到具体的 ICompat 实例 + */ + private void applyListenerToCompat(IForgeCompat compat, ListenerConfig config) { + try { + // 根据配置调用对应的 ICompat 方法 + if (config.dists != null) { + switch (config.dists) { + case CLIENT -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT,() -> () -> { + if (config.bus == Mod.EventBusSubscriber.Bus.FORGE) { + compat.addClientGameListener(gameEventBus); + } else { + compat.addClientModListener(modEventBus); + } + }); + case DEDICATED_SERVER -> DistExecutor.unsafeRunWhenOn(Dist.DEDICATED_SERVER,() -> () -> { + if (config.bus == Mod.EventBusSubscriber.Bus.FORGE) { + compat.addServerGameListener(gameEventBus); + } else { + compat.addServerModListener(modEventBus); + } + }); + } + } else { + // 通用监听器 + if (config.bus == Mod.EventBusSubscriber.Bus.FORGE) { + compat.addCommonGameListener(gameEventBus); + } else { + compat.addCommonModListener(modEventBus); + } + } + + logger.debug("Applied {} listener to compat: {}", + getListenerTypeName(config), compat.id()); + + } catch (Exception e) { + logger.error("Failed to apply listener to compat: {}", compat.id(), e); + } + } + + /** + * 获取监听器类型名称(用于日志) + */ + private @NotNull String getListenerTypeName(@NotNull ListenerConfig config) { + if (config.dists != null) { + return config.dists.name().toLowerCase() + " " + + (config.bus == Mod.EventBusSubscriber.Bus.FORGE ? "game" : "mod"); + } else { + return "common " + (config.bus == Mod.EventBusSubscriber.Bus.FORGE ? "game" : "mod"); + } + } + + + /** + * Add listener for compat. + * + * @param compatId the compat id + * @param bus the bus + */ + public void addListenerForCompat(ResourceLocation compatId, Mod.EventBusSubscriber.Bus bus) { + addListenerForCompat(compatId, null, bus); + } + + protected static class ListenerConfig { + /** + * The Compat id. + */ + final ResourceLocation compatId; + /** + * The Dists. + */ + final Dist dists; + /** + * The Bus. + */ + final Mod.EventBusSubscriber.Bus bus; + + /** + * Instantiates a new Listener config. + * + * @param compatId the compat id + * @param dists the dists + * @param bus the bus + */ + ListenerConfig(ResourceLocation compatId, Dist dists, Mod.EventBusSubscriber.Bus bus) { + this.compatId = compatId; + this.dists = dists; + this.bus = bus; + } + + /** + * Should apply boolean. + * + * @param compat the compat + * @return the boolean + */ + boolean shouldApply(@NotNull IForgeCompat compat) { + return compat.isModLoaded(); + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/core/compat/IForgeCompat.java b/forge/src/main/java/top/r3944realms/lib39/core/compat/IForgeCompat.java new file mode 100644 index 0000000..595b51f --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/compat/IForgeCompat.java @@ -0,0 +1,62 @@ +package top.r3944realms.lib39.core.compat; + +import net.minecraftforge.eventbus.api.IEventBus; + +/** + * The interface Compat. + */ +public interface IForgeCompat extends ICompat { + /** + * Add common game listener. + * + * @param gameBus the game bus + */ + default void addCommonGameListener(IEventBus gameBus) { + // 实现通用游戏事件监听器添加逻辑 + } + + /** + * Add common mod listener. + * + * @param modBus the mod bus + */ + default void addCommonModListener(IEventBus modBus) { + // 实现通用模组事件监听器添加逻辑 + } + + /** + * Add client game listener. + * + * @param gameBus the game bus + */ + default void addClientGameListener(IEventBus gameBus) { + // 实现客户端游戏事件监听器添加逻辑 + } + + /** + * Add client mod listener. + * + * @param modBus the mod bus + */ + default void addClientModListener(IEventBus modBus) { + // 实现客户端模组事件监听器添加逻辑 + } + + /** + * Add server game listener. + * + * @param gameBus the game bus + */ + default void addServerGameListener(IEventBus gameBus) { + // 实现服务端游戏事件监听器添加逻辑 + } + + /** + * Add server mod listener. + * + * @param modBus the mod bus + */ + default void addServerModListener(IEventBus modBus) { + // 实现服务端模组事件监听器添加逻辑 + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java b/forge/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java index 282c9b8..df09a85 100644 --- a/forge/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java +++ b/forge/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java @@ -24,21 +24,17 @@ import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent; import net.minecraftforge.event.entity.EntityLeaveLevelEvent; import net.minecraftforge.event.level.LevelEvent; -import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.registries.RegistryObject; import top.r3944realms.lib39.Lib39; import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent; import top.r3944realms.lib39.base.command.Lib39HelpCommand; import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent; import top.r3944realms.lib39.content.item.DollItem; import top.r3944realms.lib39.content.register.Lib39Items; -import top.r3944realms.lib39.core.compat.CompatManager; import top.r3944realms.lib39.core.register.Lib39Items; import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.SyncData2CapManager; import top.r3944realms.lib39.core.sync.SyncData2Manager; import top.r3944realms.lib39.example.compat.Lib39CompatManager; import top.r3944realms.lib39.util.GameProfileHelper; @@ -73,7 +69,7 @@ public class CommonEventHandler { /** * The Sync data 2 manager. */ - static volatile SyncData2Manager syncData2Manager; + static volatile SyncData2CapManager syncData2Manager; private static boolean isSync2MInitialized = false; /** @@ -81,7 +77,7 @@ public class CommonEventHandler { * * @return the sync data 2 manager */ - public static SyncData2Manager getSyncData2Manager() { + public static SyncData2CapManager getSyncData2Manager() { return syncData2Manager; } @@ -98,7 +94,7 @@ public class CommonEventHandler { if (!serverLevel.dimension().equals(Level.OVERWORLD)) return; synchronized (Game.class) { if (!isSync2MInitialized) { - syncData2Manager = new SyncData2Manager(); + syncData2Manager = new SyncData2CapManager(); MinecraftForge.EVENT_BUS.post(new SyncManagerRegisterEvent(syncData2Manager)); isSync2MInitialized = true; sl = serverLevel; @@ -110,7 +106,7 @@ public class CommonEventHandler { synchronized (Game.class) { if (!clientLevel.dimension().equals(Level.OVERWORLD)) return; if (!isSync2MInitialized) { - syncData2Manager = new SyncData2Manager(); + syncData2Manager = new SyncData2CapManager(); MinecraftForge.EVENT_BUS.post(new SyncManagerRegisterEvent(syncData2Manager)); Lib39.LOGGER.info("SyncData2Manager initialized on Client load"); } @@ -215,7 +211,7 @@ public class CommonEventHandler { */ @SubscribeEvent public static void onRegisterCommand (RegisterCommandsEvent event) { - Lib39HelpCommand lib39HelpCommand = new Lib39HelpCommand(event); + Lib39HelpCommand lib39HelpCommand = new Lib39HelpCommand(event.getDispatcher(), event.getBuildContext()); } } diff --git a/forge/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java b/forge/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java new file mode 100644 index 0000000..ec646a8 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java @@ -0,0 +1,72 @@ +package top.r3944realms.lib39.core.network; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.network.simple.SimpleChannel; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.network.toClient.SyncNBTCapDataEntityS2CPacket; + +/** + * The type Network handler. + */ +@SuppressWarnings("unused") +public class NetworkHandler { + private static int cid = 0; + /** + * The constant INSTANCE. + */ + public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( + Lib39.rl(Lib39.MOD_ID, "main"), + () -> Lib39.ModInfo.VERSION, + Lib39.ModInfo.VERSION::equals, + Lib39.ModInfo.VERSION::equals + ); + + /** + * Register. + */ + public static void register() { + INSTANCE.messageBuilder(SyncNBTCapDataEntityS2CPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT) + .encoder(SyncNBTCapDataEntityS2CPacket::encode) + .decoder(SyncNBTCapDataEntityS2CPacket::decode) + .consumerNetworkThread(SyncNBTCapDataEntityS2CPacket::handle) + .add(); + } + + /** + * Send to player. + * + * @param the type parameter + * @param message the message + * @param player the player + */ + public static void sendToPlayer(MSG message, ServerPlayer player){ + INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message); + } + + /** + * Send to player. + * + * @param the type parameter + * @param the type parameter + * @param message the message + * @param entity the entity + * @param packetDistributor the packet distributor + */ + public static void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor packetDistributor){ + INSTANCE.send(packetDistributor.with(() -> entity), message); + } + + /** + * Send to player. + * + * @param the type parameter + * @param message the message + */ + public static void sendToAllPlayer(MSG message){ + INSTANCE.send(PacketDistributor.ALL.noArg(), message); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTCapDataEntityS2CPacket.java b/forge/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTCapDataEntityS2CPacket.java new file mode 100644 index 0000000..4fd34c3 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTCapDataEntityS2CPacket.java @@ -0,0 +1,93 @@ +package top.r3944realms.lib39.core.network.toClient; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.NBTEntitySyncData; +import top.r3944realms.lib39.core.sync.SyncData2Manager; + +import java.util.Optional; +import java.util.function.Supplier; + +/** + * The type Sync nbt data s 2 c pack. + */ +@SuppressWarnings("unused") +public record SyncNBTCapDataEntityS2CPacket(int entityId, ResourceLocation id, CompoundTag data) { + + /** + * Instantiates a new Sync nbt data s 2 c pack. + * + * @param entityId the entity id + * @param data the data + */ + public SyncNBTCapDataEntityS2CPacket(int entityId, @NotNull NBTEntitySyncData data) { + this(entityId, data.id(), data.serializeNBT()); + } + + /** + * Encode. + * + * @param msg the msg + * @param buffer the buffer + */ + public static void encode(@NotNull SyncNBTCapDataEntityS2CPacket msg, @NotNull FriendlyByteBuf buffer) { + buffer.writeInt(msg.entityId); + buffer.writeResourceLocation(msg.id); + buffer.writeNbt(msg.data); + } + + /** + * Decode sync nbt data s 2 c pack. + * + * @param buffer the buffer + * @return the sync nbt data s 2 c pack + */ + @Contract("_ -> new") + public static @NotNull SyncNBTCapDataEntityS2CPacket decode(@NotNull FriendlyByteBuf buffer) { + return new SyncNBTCapDataEntityS2CPacket(buffer.readInt(), buffer.readResourceLocation(), buffer.readNbt()); + } + + /** + * Handle. + * + * @param msg the msg + * @param ctx the ctx + */ + public static void handle(SyncNBTCapDataEntityS2CPacket msg, @NotNull Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + Entity entity = level.getEntity(msg.entityId); + if (entity != null) { + Optional>> capOpt = + CommonEventHandler.Game + .getSyncData2Manager() + .getDataProvider(msg.id); + capOpt.flatMap(dataProvider -> dataProvider.getData(entity)) + .ifPresent(cap -> { + if (cap instanceof NBTEntitySyncData nbtCap) { + CompoundTag current = nbtCap.serializeNBT(); + if (!current.equals(msg.data)) { + nbtCap.deserializeNBT(msg.data); + } + } else Lib39.LOGGER.debug("Unhandled sync data: {}", msg.data); + } + ); + } + } + }); + context.setPacketHandled(true); + } + +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java index eaa2ece..d548e23 100644 --- a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java +++ b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java @@ -7,6 +7,7 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; import top.r3944realms.lib39.Lib39; import top.r3944realms.lib39.content.item.DollItem; +import top.r3944realms.lib39.content.item.ForgeDollItem; /** * The type Ex lib 39 items. @@ -18,7 +19,7 @@ public class ForgeLib39Items { public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Lib39.MOD_ID); static { - Lib39Items.DOLL = ITEMS.register("doll", () -> new DollItem(new Item.Properties())); + Lib39Items.DOLL = ITEMS.register("doll", () -> new ForgeDollItem(new Item.Properties())); } /** diff --git a/forge/src/main/java/top/r3944realms/lib39/core/sync/IForgeUpdate.java b/forge/src/main/java/top/r3944realms/lib39/core/sync/IForgeUpdate.java new file mode 100644 index 0000000..95f0a16 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/sync/IForgeUpdate.java @@ -0,0 +1,10 @@ +package top.r3944realms.lib39.core.sync; + +import top.r3944realms.lib39.core.network.NetworkHandler; +import top.r3944realms.lib39.core.network.toClient.SyncNBTCapDataEntityS2CPacket; + +public interface IForgeUpdate extends IUpdate { + default void update() { + NetworkHandler.sendToAllPlayer(new SyncNBTCapDataEntityS2CPacket(getSyncData().entityId(), getSyncData().id, getSyncData().serializeNBT())); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/sync/SyncData2CapManager.java b/forge/src/main/java/top/r3944realms/lib39/core/sync/SyncData2CapManager.java new file mode 100644 index 0000000..6035af8 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/sync/SyncData2CapManager.java @@ -0,0 +1,85 @@ +package top.r3944realms.lib39.core.sync; + +import com.google.common.collect.Maps; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * The type Sync data 2 manager. + */ +@SuppressWarnings("unused") +public class SyncData2CapManager extends SyncData2Manager { + protected final Map> typedEntries = Maps.newConcurrentMap(); + protected static class TypedSyncEntry> extends SyncData2Manager.TypedSyncEntry, T> { + /** + * Instantiates a new Typed sync entry. + * + * @param manager the manager + * @param capability the capability + */ + public TypedSyncEntry(ISyncManager, T> manager,@Nullable Capability capability) { + super(manager, key -> capability != null ? key.getCapability(capability).resolve() : Optional.empty()); + } + } + + /** + * Register manager. + * + * @param the type parameter + * @param key the key + * @param manager the manager + * @param capability the capability + */ + public > void registerManager( + ResourceLocation key, + ISyncManager, T> manager, + Capability capability + ) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + Objects.requireNonNull(capability, "Capability cannot be null"); + + typedEntries.put(key, new TypedSyncEntry<>(manager, capability)); + } + + + /** + * 绑定能力(用于分离注册的情况) + * + * @param the type parameter + * @param key the key + * @param capability the capability + */ + public > void bindCapability(ResourceLocation key, Capability capability) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(capability, "Capability cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 更新现有条目的能力 + updateCapabilityInEntry(key, entry, capability); + } else throw new IllegalArgumentException("No manager found for " + key); + } + + /** + * 解绑能力 + * + * @param key the key + */ + public void unbindCapability(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 将能力设置为null,但保留管理器和其他配置 + updateCapabilityInEntry(key, entry, null); + } + } + + protected > void updateCapabilityInEntry(ResourceLocation id, TypedSyncEntry entry, Capability newCapability) { + updateDataProviderInEntry(id, entry, key -> newCapability != null ? key.getCapability(newCapability).resolve() : Optional.empty()); + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java index 767fa9a..57eae45 100644 --- a/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java +++ b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java @@ -19,7 +19,6 @@ import net.minecraft.world.level.storage.loot.predicates.MatchTool; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.RegistryObject; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -27,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -50,7 +50,7 @@ public class BlockLootTables extends BlockLootSubProvider { @Override protected @NotNull Iterable getKnownBlocks() { - return knowBlocks.getEntries().stream().map(RegistryObject::get).collect(Collectors.toSet()); + return knowBlocks.getEntries().stream().map(Supplier::get).collect(Collectors.toSet()); } // ==================== 流畅 API 构建方法 ==================== @@ -60,7 +60,7 @@ public class BlockLootTables extends BlockLootSubProvider { * * @param block the block */ - public void dropSelf(RegistryObject block) { + public void dropSelf(Supplier block) { addEntry(block, this::createSingleItemTable); } @@ -72,8 +72,8 @@ public class BlockLootTables extends BlockLootSubProvider { */ @Contract("_ -> this") @SafeVarargs - public final BlockLootTables dropSelf(RegistryObject @NotNull ... blocks) { - for (RegistryObject block : blocks) { + public final BlockLootTables dropSelf(Supplier @NotNull ... blocks) { + for (Supplier block : blocks) { dropSelf(block); } return this; @@ -85,7 +85,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropWhenSilkTouch(RegistryObject block) { + public BlockLootTables dropWhenSilkTouch(Supplier block) { return addEntry(block, BlockLootSubProvider::createSilkTouchOnlyTable); } @@ -96,7 +96,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param item the item * @return the block loot tables */ - public BlockLootTables dropOther(RegistryObject block, RegistryObject item) { + public BlockLootTables dropOther(Supplier block, Supplier item) { return addEntry(block, pBlock -> this.createSingleItemTable(item.get())); } @@ -106,7 +106,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropWhenShears(RegistryObject block) { + public BlockLootTables dropWhenShears(Supplier block) { return addEntry(block, BlockLootSubProvider::createShearsOnlyDrop); } @@ -117,7 +117,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param oreItem the ore item * @return the block loot tables */ - public BlockLootTables dropOre(RegistryObject block, RegistryObject oreItem) { + public BlockLootTables dropOre(Supplier block, Supplier oreItem) { return addEntry(block, b -> this.createOreDrop(b, oreItem.get())); } @@ -127,7 +127,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropRedstoneOre(RegistryObject block) { + public BlockLootTables dropRedstoneOre(Supplier block) { return addEntry(block, this::createRedstoneOreDrops); } @@ -137,7 +137,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropLapisOre(RegistryObject block) { + public BlockLootTables dropLapisOre(Supplier block) { return addEntry(block, this::createLapisOreDrops); } @@ -147,7 +147,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropCopperOre(RegistryObject block) { + public BlockLootTables dropCopperOre(Supplier block) { return addEntry(block, this::createCopperOreDrops); } @@ -157,7 +157,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropCarpet(RegistryObject block) { + public BlockLootTables dropCarpet(Supplier block) { return addEntry(block, b -> LootTable.lootTable() .withPool(LootPool.lootPool() .setRolls(ConstantValue.exactly(1)) @@ -171,7 +171,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropSlab(RegistryObject block) { + public BlockLootTables dropSlab(Supplier block) { return addEntry(block, this::createSlabItemTable); } @@ -181,7 +181,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropDoor(RegistryObject block) { + public BlockLootTables dropDoor(Supplier block) { return addEntry(block, this::createDoorTable); } @@ -191,7 +191,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables dropFlowerPot(RegistryObject block) { + public BlockLootTables dropFlowerPot(Supplier block) { return addEntry(block, (pBlock) -> this.createPotFlowerItemTable(((FlowerPotBlock)pBlock).getContent())); } @@ -203,8 +203,8 @@ public class BlockLootTables extends BlockLootSubProvider { * @param chances the chances * @return the block loot tables */ - public BlockLootTables dropLeaves(RegistryObject leavesBlock, - RegistryObject saplingBlock, + public BlockLootTables dropLeaves(Supplier leavesBlock, + Supplier saplingBlock, float... chances) { return addEntry(leavesBlock, b -> this.createLeavesDrops(b, saplingBlock.get(), chances)); } @@ -217,8 +217,8 @@ public class BlockLootTables extends BlockLootSubProvider { * @param chances the chances * @return the block loot tables */ - public BlockLootTables dropOakLeaves(RegistryObject leavesBlock, - RegistryObject saplingBlock, + public BlockLootTables dropOakLeaves(Supplier leavesBlock, + Supplier saplingBlock, float... chances) { return addEntry(leavesBlock, b -> this.createOakLeavesDrops(b, saplingBlock.get(), chances)); } @@ -233,9 +233,9 @@ public class BlockLootTables extends BlockLootSubProvider { * @param maxAge the max age * @return the block loot tables */ - public BlockLootTables dropCrop(RegistryObject cropBlock, - RegistryObject cropItem, - RegistryObject seedsItem, + public BlockLootTables dropCrop(Supplier cropBlock, + Supplier cropItem, + Supplier seedsItem, Property ageProperty, int maxAge) { return addEntry(cropBlock, b -> this.createCropDrops( @@ -255,7 +255,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param factory the factory * @return the block loot tables */ - public BlockLootTables custom(RegistryObject block, Function factory) { + public BlockLootTables custom(Supplier block, Function factory) { return addEntry(block, factory); } @@ -265,7 +265,7 @@ public class BlockLootTables extends BlockLootSubProvider { * @param block the block * @return the block loot tables */ - public BlockLootTables noDrop(RegistryObject block) { + public BlockLootTables noDrop(Supplier block) { return addEntry(block, b -> noDrop()); } @@ -278,9 +278,9 @@ public class BlockLootTables extends BlockLootSubProvider { * @param operation the operation * @return the block loot tables */ - public BlockLootTables batch(@NotNull Iterable> blocks, - Function, BlockLootTables> operation) { - for (RegistryObject block : blocks) { + public BlockLootTables batch(@NotNull Iterable> blocks, + Function, BlockLootTables> operation) { + for (Supplier block : blocks) { operation.apply(block); } return this; @@ -295,9 +295,9 @@ public class BlockLootTables extends BlockLootSubProvider { */ @Contract("_, _ -> this") @SafeVarargs - public final BlockLootTables applyToAll(Function, BlockLootTables> operation, - RegistryObject @NotNull ... blocks) { - for (RegistryObject block : blocks) { + public final BlockLootTables applyToAll(Function, BlockLootTables> operation, + Supplier @NotNull ... blocks) { + for (Supplier block : blocks) { operation.apply(block); } return this; @@ -323,12 +323,12 @@ public class BlockLootTables extends BlockLootSubProvider { // ==================== 内部类和方法 ==================== - private BlockLootTables addEntry(RegistryObject block, Function factory) { + private BlockLootTables addEntry(Supplier block, Function factory) { blockEntries.add(new BlockEntry(block, factory)); return this; } - private record BlockEntry(RegistryObject block, Function factory) {} + private record BlockEntry(Supplier block, Function factory) {} // ==================== 静态工厂方法 ==================== diff --git a/forge/src/main/java/top/r3944realms/lib39/example/ForgeLib39Example.java b/forge/src/main/java/top/r3944realms/lib39/example/ForgeLib39Example.java new file mode 100644 index 0000000..b03272a --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/ForgeLib39Example.java @@ -0,0 +1,5 @@ +package top.r3944realms.lib39.example; + +public class ForgeLib39Example { + //todo:订阅事件 +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39Compat.java b/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39Compat.java new file mode 100644 index 0000000..1f668cf --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39Compat.java @@ -0,0 +1,58 @@ +package top.r3944realms.lib39.example.compat; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.compat.IForgeCompat; + +/** + * The type Lib 39 compat. + */ +public class Lib39Compat implements IForgeCompat { + boolean initialized = false; + /** + * The constant INSTANCE. + */ + public static Lib39Compat INSTANCE = new Lib39Compat(); + /** + * The constant ID. + */ + public static ResourceLocation ID = Lib39.rl("lib39"); + + @Override + public void setInitialize(boolean initialize) { + this.initialized = initialize; + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public ResourceLocation id() { + return ID; + } + + @Override + public boolean isModLoaded() { + return ModList.get().isLoaded("lib39"); + } + + @Override + public void initialize() { + + } + + @Override + public void addCommonModListener(@NotNull IEventBus modBus) { + modBus.addListener(this::onSetUp); + } + + private void onSetUp (@NotNull FMLCommonSetupEvent event) { + event.enqueueWork(() -> Lib39.LOGGER.info("Loading Lib39 Compat")); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39CompatManager.java b/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39CompatManager.java new file mode 100644 index 0000000..68c31cd --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/compat/Lib39CompatManager.java @@ -0,0 +1,19 @@ +package top.r3944realms.lib39.example.compat; + +import net.minecraftforge.eventbus.api.IEventBus; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.compat.CompatManager; +import top.r3944realms.lib39.core.compat.ForgeCompatManager; + +public class Lib39CompatManager extends ForgeCompatManager { + /** + * Instantiates a new Compat manager. + * + * @param path the path + * @param modEventBus the mod event bus + * @param gameEventBus the game event bus + */ + public Lib39CompatManager(String path, IEventBus modEventBus, IEventBus gameEventBus) { + super(Lib39.rl(path), modEventBus, gameEventBus); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/content/data/ExCapabilityHandler.java b/forge/src/main/java/top/r3944realms/lib39/example/content/data/ExCapabilityHandler.java new file mode 100644 index 0000000..a65ea21 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/content/data/ExCapabilityHandler.java @@ -0,0 +1,40 @@ +package top.r3944realms.lib39.example.content.data; + +import net.minecraft.world.entity.Entity; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import org.jetbrains.annotations.NotNull; + +/** + * The type Ex capability handler. + */ +public class ExCapabilityHandler { + /** + * The constant TEST_CAP. + */ + public static final Capability TEST_CAP = CapabilityManager.get(new CapabilityToken<>() {}); + + /** + * Register capability. + * + * @param event the event + */ + public static void registerCapability(@NotNull RegisterCapabilitiesEvent event) { + event.register(AbstractedTestSyncData.class); + } + + /** + * Attach capability. + * + * @param event the event + */ + public static void attachCapability(@NotNull AttachCapabilitiesEvent event) { + Object object = event.getObject(); + if(object instanceof Entity entity ) { + event.addCapability(TestSyncCapProvider.TEST_SYNC_REL, new TestSyncCapProvider(entity)); + } + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncCapProvider.java b/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncCapProvider.java new file mode 100644 index 0000000..b02addf --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncCapProvider.java @@ -0,0 +1,49 @@ +package top.r3944realms.lib39.example.content.data; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.LazyOptional; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; + +/** + * The type Test sync cap provider. + */ +public class TestSyncCapProvider implements ICapabilitySerializable { + + /** + * The constant TEST_SYNC_REL. + */ + public static final ResourceLocation TEST_SYNC_REL = Lib39.rl(Lib39.MOD_ID, "test_sync_data"); + private final AbstractedTestSyncData instance; + private final LazyOptional optional; + + /** + * Instantiates a new Test sync cap provider. + * + * @param entity the entity + */ + public TestSyncCapProvider(Entity entity) { + this.instance = new TestSyncData(entity); + this.optional = LazyOptional.of(() -> instance); + } + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + return ExCapabilityHandler.TEST_CAP.orEmpty(cap, optional); + } + + @Override + public CompoundTag serializeNBT() { + return instance.serializeNBT(); + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + instance.deserializeNBT(nbt); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java b/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java new file mode 100644 index 0000000..f827144 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/content/data/TestSyncData.java @@ -0,0 +1,357 @@ +package top.r3944realms.lib39.example.content.data; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.sync.IForgeUpdate; +import top.r3944realms.lib39.util.nbt.NBTReader; +import top.r3944realms.lib39.util.nbt.NBTWriter; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 测试同步数据实现 + */ +@SuppressWarnings("unused") +public class TestSyncData extends AbstractedTestSyncData implements IForgeUpdate { + /** + * The constant ID. + */ + public static final ResourceLocation ID = Lib39.rl(Lib39.MOD_ID, "test_sync_data"); + + // NBT 键常量 + private static final String NBT_KEY_STRING = "test_string"; + private static final String NBT_KEY_INT = "test_int"; + private static final String NBT_KEY_BOOLEAN = "test_boolean"; + private static final String NBT_KEY_DOUBLE = "test_double"; + private static final String NBT_KEY_COUNTER = "counter"; + private static final String NBT_KEY_SYNC_TIME = "last_sync_time"; + private static final String NBT_KEY_CUSTOM_DATA = "custom_data"; + private static final String NBT_KEY_CUSTOM_NAME = "name"; + private static final String NBT_KEY_CUSTOM_VALUE = "value"; + private static final String NBT_KEY_CUSTOM_FLAG = "flag"; + + // 数据字段 + private String testString = "default_value"; + private int testInt = 42; + private boolean testBoolean = true; + private double testDouble = 3.14159; + private int counter = 0; + private long lastSyncTime = 0L; + private TestData customData = new TestData("default", 100, false); + private Entity self; + + /** + * 构造函数 + * + * @param entity 关联的实体 + */ + public TestSyncData(Entity entity) { + super(ID); + this.self = entity; + } + + /** + * 构造函数(用于测试) + * + * @param entityId 实体ID + * @param self the self + */ + public TestSyncData(int entityId, Entity self) { + super(ID); + this.self = self; + } + + /** + * 构造函数(用于数据包反序列化) + * + * @param buf 字节缓冲区 + */ + public TestSyncData(FriendlyByteBuf buf) { + super(ID); + this.self = null; // 实体在从数据包重建时可能为null,需要在接收端设置 + fromBytes(buf); + } + + /** + * 将数据写入字节缓冲区(用于网络传输) + * + * @param buf 字节缓冲区 + */ + @Override + public void toBytes(@NotNull FriendlyByteBuf buf) { + // 写入基本类型字段 + buf.writeUtf(testString != null ? testString : ""); + buf.writeInt(testInt); + buf.writeBoolean(testBoolean); + buf.writeDouble(testDouble); + buf.writeInt(counter); + buf.writeLong(lastSyncTime); + + // 写入自定义数据 + if (customData != null) { + buf.writeUtf(customData.getName() != null ? customData.getName() : ""); + buf.writeInt(customData.getValue()); + buf.writeBoolean(customData.isFlag()); + } else { + buf.writeUtf(""); + buf.writeInt(0); + buf.writeBoolean(false); + } + + // 写入实体ID(如果实体存在) + if (self != null) { + buf.writeInt(self.getId()); + } else { + buf.writeInt(-1); + } + + // 写入脏数据状态 + buf.writeBoolean(isDirty()); + } + + /** + * 从字节缓冲区读取数据(用于网络传输) + * + * @param buf 字节缓冲区 + */ + @Override + public void fromBytes(@NotNull FriendlyByteBuf buf) { + // 读取基本类型字段 + this.testString = buf.readUtf(32767); // Minecraft字符串最大长度 + this.testInt = buf.readInt(); + this.testBoolean = buf.readBoolean(); + this.testDouble = buf.readDouble(); + this.counter = buf.readInt(); + this.lastSyncTime = buf.readLong(); + + // 读取自定义数据 + String customName = buf.readUtf(); + int customValue = buf.readInt(); + boolean customFlag = buf.readBoolean(); + this.customData = new TestData(customName, customValue, customFlag); + + // 读取实体ID(在接收端可能需要额外处理) + int entityId = buf.readInt(); + + // 读取脏数据状态 + boolean wasDirty = buf.readBoolean(); + if (wasDirty) { + markDirty(); + } + } + + /** + * 静态方法:从字节缓冲区创建 TestSyncData 实例 + * + * @param buf 字节缓冲区 + * @return 新的 TestSyncData 实例 + */ + public static TestSyncData staticFromBytes(FriendlyByteBuf buf) { + return new TestSyncData(buf); + } + + @Override + public String getTestString() { + return testString; + } + + @Override + public void setTestString(String value) { + if (!java.util.Objects.equals(this.testString, value)) { + this.testString = value; + markDirty(); + } + } + + @Override + public int getTestInt() { + return testInt; + } + + @Override + public void setTestInt(int value) { + if (this.testInt != value) { + this.testInt = value; + markDirty(); + } + } + + @Override + public boolean isTestBoolean() { + return testBoolean; + } + + @Override + public void setTestBoolean(boolean value) { + if (this.testBoolean != value) { + this.testBoolean = value; + markDirty(); + } + } + + @Override + public double getTestDouble() { + return testDouble; + } + + @Override + public void setTestDouble(double value) { + if (this.testDouble != value) { + this.testDouble = value; + markDirty(); + } + } + + @Override + public int getCounter() { + return counter; + } + + @Override + public void incrementCounter() { + this.counter++; + markDirty(); + } + + @Override + public void clearCounter() { + this.counter = 0; + } + + @Override + public long getLastSyncTime() { + return lastSyncTime; + } + + @Override + public void updateSyncTime() { + this.lastSyncTime = System.currentTimeMillis(); + markDirty(); + } + + @Override + public void clearSyncTime() { + this.lastSyncTime = 0L; + } + + @Override + public TestData getCustomData() { + return customData; + } + + @Override + public void setCustomData(TestData data) { + if (data == null) { + throw new IllegalArgumentException("Custom data cannot be null"); + } + if (!java.util.Objects.equals(this.customData, data)) { + this.customData = data; + markDirty(); + } + } + + @Override + public boolean validateData() { + return testString != null && + !testString.isEmpty() && + customData != null && + customData.getName() != null && + !customData.getName().isEmpty() && + counter >= 0 && + testInt >= 0; + } + + @Override + public CompoundTag serializeNBT() { + return NBTWriter.builder() + .string(NBT_KEY_STRING, testString) + .intValue(NBT_KEY_INT, testInt) + .booleanValue(NBT_KEY_BOOLEAN, testBoolean) + .doubleValue(NBT_KEY_DOUBLE, testDouble) + .intValue(NBT_KEY_COUNTER, counter) + .longValue(NBT_KEY_SYNC_TIME, lastSyncTime) + .compound( + NBT_KEY_CUSTOM_DATA, + NBTWriter.builder() + .string(NBT_KEY_CUSTOM_NAME, customData.getName()) + .intValue(NBT_KEY_CUSTOM_VALUE, customData.getValue()) + .booleanValue(NBT_KEY_CUSTOM_FLAG, customData.isFlag()) + .build() + ).build(); + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + NBTReader.of(nbt) + .intValue(NBT_KEY_INT, integer -> testInt = integer) + .string(NBT_KEY_STRING, string -> testString = string) + .booleanValue(NBT_KEY_BOOLEAN, bool -> testBoolean = bool) + .intValue(NBT_KEY_COUNTER, integer -> counter = integer) + .doubleValue(NBT_KEY_DOUBLE, dou -> testDouble = dou) + .longValue(NBT_KEY_SYNC_TIME, sync -> lastSyncTime = sync) + .compound(NBT_KEY_CUSTOM_DATA, customDataTag -> { + AtomicReference name = new AtomicReference<>(""); + AtomicInteger value = new AtomicInteger(-1); + AtomicBoolean flag = new AtomicBoolean(false); + NBTReader.of(customDataTag) + .string(NBT_KEY_CUSTOM_NAME, name::set) + .intValue(NBT_KEY_CUSTOM_VALUE, value::set) + .booleanValue(NBT_KEY_CUSTOM_FLAG, flag::set); + this.customData = new TestData(name.get(), value.get(), flag.get()); + }); + } + + @Override + public int entityId() { + return self != null ? self.getId() : -1; + } + + /** + * 设置关联的实体 + * + * @param entity 关联的实体 + */ + public void setEntity(Entity entity) { + this.self = entity; + } + + + /** + * 获取所有数据的字符串表示(用于调试) + */ + @Override + public String toString() { + return String.format( + "TestSyncData{id=%d, string='%s', int=%d, boolean=%s, double=%.2f, counter=%d, lastSync=%d, custom=%s}", + self.getId(), testString, testInt, testBoolean, testDouble, counter, lastSyncTime, customData + ); + } + + /** + * 创建一个不依赖实体的副本(用于网络传输) + * + * @return 不包含实体引用的副本 test sync data + */ + public TestSyncData createNetworkCopy() { + TestSyncData copy = new TestSyncData((Entity) null); + copy.testString = this.testString; + copy.testInt = this.testInt; + copy.testBoolean = this.testBoolean; + copy.testDouble = this.testDouble; + copy.counter = this.counter; + copy.lastSyncTime = this.lastSyncTime; + copy.customData = new TestData( + this.customData.getName(), + this.customData.getValue(), + this.customData.isFlag() + ); + return copy; + } + +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java b/forge/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java new file mode 100644 index 0000000..f13c319 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java @@ -0,0 +1,61 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; +import top.r3944realms.lib39.example.core.network.ClientDataPacket; +import top.r3944realms.lib39.example.core.network.ExNetworkHandler; + +public class FabricItem extends AbstractFabricItem { + + public FabricItem(Properties properties) { + super(properties); + } + + @Override + protected AbstractedTestSyncData getData(Entity target) { + return getStaticData(target); + } + + @Override + protected void sendClientDataToServer(AbstractedTestSyncData clientData, int targetEntityId) { + ExNetworkHandler.INSTANCE.sendToServer(new ClientDataPacket(clientData, targetEntityId)); + } + + public static @Nullable AbstractedTestSyncData getStaticData(Entity target) { + try { + AbstractedTestSyncData abstractData = target.getCapability(ExCapabilityHandler.TEST_CAP).resolve().orElse(null); + if (abstractData instanceof TestSyncData) { + return abstractData; + } + } catch (Exception e) { + Lib39.LOGGER.error("[FabricItem] 获取服务器端数据失败", e); + } + return null; + } + + public static void handleClientDataFromPacket(@NotNull ServerPlayer player, AbstractedTestSyncData clientData, int targetEntityId) { + Entity target = player.level().getEntity(targetEntityId); + + if (target instanceof LivingEntity livingTarget) { + // 获取服务器端数据 + AbstractedTestSyncData serverData = getStaticData(livingTarget); + + if (serverData != null) { + // 显示双端对比结果 + displayDualEndComparison(player, livingTarget, serverData, clientData); + } else { + player.sendSystemMessage(Component.literal("§c无法获取服务器端数据")); + } + } else { + player.sendSystemMessage(Component.literal("§c目标生物不存在或已消失")); + } + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java b/forge/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java new file mode 100644 index 0000000..10b1042 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java @@ -0,0 +1,32 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; + +public class NeoForgeItem extends AbstractNeoForgeItem{ + + public NeoForgeItem(Properties properties) { + super(properties); + } + + @Override + protected AbstractedTestSyncData getData(Entity entity) { + return getStaticData(entity); + } + @SuppressWarnings("JavaExistingMethodCanBeUsed") + public static @Nullable AbstractedTestSyncData getStaticData(Entity target) { + try { + AbstractedTestSyncData abstractData = target.getCapability(ExCapabilityHandler.TEST_CAP).resolve().orElse(null); + if (abstractData instanceof TestSyncData) { + return abstractData; + } + } catch (Exception e) { + Lib39.LOGGER.error("[FabricItem] 获取服务器端数据失败", e); + } + return null; + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExClientEventHandler.java b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExClientEventHandler.java new file mode 100644 index 0000000..f391b11 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExClientEventHandler.java @@ -0,0 +1,16 @@ +package top.r3944realms.lib39.example.core.event; + +/** + * The type Client handler. + */ +public class ExClientEventHandler { + /** + * The type Mod. + */ + public static class Mod extends ExClientEventHandler {} + + /** + * The type Game. + */ + public static class Game extends ExClientEventHandler {} +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java new file mode 100644 index 0000000..1bbc5a3 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java @@ -0,0 +1,121 @@ +package top.r3944realms.lib39.example.core.event; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent; +import top.r3944realms.lib39.core.compat.CompatManager; +import top.r3944realms.lib39.core.event.CommonEventHandler; +import top.r3944realms.lib39.core.sync.CachedSyncManager; +import top.r3944realms.lib39.example.compat.Lib39Compat; +import top.r3944realms.lib39.example.compat.Lib39CompatManager; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The type Common handler. + */ +public class ExCommonEventHandler { + /** + * The type Game. + */ + @SuppressWarnings("unused") + + public static class Game extends ExCommonEventHandler { + /** + * Attach capability. + * + * @param event the event + */ + @SubscribeEvent + public static void attachCapability(AttachCapabilitiesEvent event) { + ExCapabilityHandler.attachCapability(event); + } + + /** + * On register sync. + * + * @param event the event + */ + @SubscribeEvent + public static void onRegisterSync (SyncManagerRegisterEvent event) { + event.registerSyncManager( + TestSyncData.ID, + new CachedSyncManager<>() { + private final Map, AbstractedTestSyncData> syncDataMap = new ConcurrentHashMap<>(); + @Override + public Map, AbstractedTestSyncData> getSyncMap() { + return syncDataMap; + } + }, + ExCapabilityHandler.TEST_CAP + + ); + } + + } + + + /** + * The type Mod. + */ + public static class Mod extends ExCommonEventHandler { + public static final IEventBus EVENT_BUS = FMLJavaModLoadingContext.get().getModEventBus(); + /** + * Gets compat manager. + * + * @return the compat manager + */ + public static CompatManager getOrCreateCompatManager() { + if (compatManager == null) { + synchronized (CommonEventHandler.Mod.class) { + if (compatManager == null) { + compatManager = new Lib39CompatManager("compat", EVENT_BUS, MinecraftForge.EVENT_BUS); + } + } + } + return compatManager; + } + + /** + * The Compat manager. + */ + static volatile CompatManager compatManager; + + + /** + * On construct mod. + * + * @param event the event + */ + @SubscribeEvent + public static void onConstructMod(FMLConstructModEvent event) { + event.enqueueWork(() -> { + CompatManager orCreateCompatManager = Mod.getOrCreateCompatManager(); + orCreateCompatManager + .registerCompat(Lib39Compat.ID, Lib39Compat.INSTANCE); + orCreateCompatManager.initialize(); + }); + } + + /** + * Register capability. + * + * @param event the event + */ + @SubscribeEvent + public static void registerCapability(RegisterCapabilitiesEvent event) { + ExCapabilityHandler.registerCapability(event); + } + + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExServerEventHandler.java b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExServerEventHandler.java new file mode 100644 index 0000000..9a46faf --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/event/ExServerEventHandler.java @@ -0,0 +1,16 @@ +package top.r3944realms.lib39.example.core.event; + +/** + * The type Server handler. + */ +public class ExServerEventHandler { + /** + * The type Mod. + */ + public static class Mod extends ExServerEventHandler {} + + /** + * The type Game. + */ + public static class Game extends ExServerEventHandler {} +} diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java b/forge/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java new file mode 100644 index 0000000..459946e --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java @@ -0,0 +1,70 @@ +package top.r3944realms.lib39.example.core.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.TestSyncData; +import top.r3944realms.lib39.example.content.item.FabricItem; + +import java.util.function.Supplier; + +/** + * The type Client data packet. + */ +public class ClientDataPacket { + private final AbstractedTestSyncData clientData; + private final int targetEntityId; + + /** + * Instantiates a new Client data packet. + * + * @param clientData the client data + * @param targetEntityId the target entity id + */ + public ClientDataPacket(AbstractedTestSyncData clientData, int targetEntityId) { + this.clientData = clientData; + this.targetEntityId = targetEntityId; + } + + /** + * Instantiates a new Client data packet. + * + * @param buf the buf + */ + public ClientDataPacket(FriendlyByteBuf buf) { + this.clientData = TestSyncData.staticFromBytes(buf); + this.targetEntityId = buf.readInt(); + } + + /** + * To bytes. + * + * @param buf the buf + */ + public void toBytes(FriendlyByteBuf buf) { + clientData.toBytes(buf); + buf.writeInt(targetEntityId); + } + + /** + * Handle. + * + * @param supplier the supplier + */ + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player != null) { + // 处理客户端发送的数据 + handleClientData(player, clientData, targetEntityId); + } + }); + context.setPacketHandled(true); + } + + private void handleClientData(ServerPlayer player, AbstractedTestSyncData clientData, int targetEntityId) { + FabricItem.handleClientDataFromPacket(player, clientData, targetEntityId); + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java b/forge/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java new file mode 100644 index 0000000..63a37c5 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/network/ExNetworkHandler.java @@ -0,0 +1,37 @@ +package top.r3944realms.lib39.example.core.network; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import top.r3944realms.lib39.Lib39; + +/** + * The type Ex network handler. + */ +public class ExNetworkHandler { + /** + * The constant INSTANCE. + */ + public static final SimpleChannel INSTANCE; + private static int ID = 0; + + static { + INSTANCE = NetworkRegistry.newSimpleChannel( + Lib39.rl(Lib39.MOD_ID, "test"), + () -> "1.0", + s -> true, + s -> true + ); + } + + /** + * Register. + */ + public static void register() { + // 注册数据包 + INSTANCE.registerMessage(ID++, ClientDataPacket.class, + ClientDataPacket::toBytes, + ClientDataPacket::new, + ClientDataPacket::handle); + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java b/forge/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java new file mode 100644 index 0000000..e19326a --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java @@ -0,0 +1,61 @@ +package top.r3944realms.lib39.example.core.register; + +import net.minecraft.world.item.Item; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.example.content.item.FabricItem; +import top.r3944realms.lib39.example.content.item.NeoForgeItem; + +/** + * The type Ex lib 39 items. + */ +public class ExLib39Items { + /** + * The constant ITEMS. + */ + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Lib39.MOD_ID); + /** + * The constant SUPER_LEAD_ROPE. + */ + public static final RegistryObject FABRIC = ITEMS.register( + "fabric", + () -> new FabricItem( + new Item.Properties() + .stacksTo(1) + .fireResistant() + ) + ); + /** + * The constant ETERNAL_POTATO. + */ + public static final RegistryObject NEOFORGE = + ITEMS.register("neoforge", + () -> new NeoForgeItem( + new Item.Properties() + .stacksTo(1) + .fireResistant() + )); + /** + * The constant FORGE. + */ + public static final RegistryObject FORGE = + ITEMS.register("forge", + () -> new NeoForgeItem( + new Item.Properties() + .stacksTo(1) + .fireResistant() + )); + + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + ITEMS.register(bus); + } + +} diff --git a/forge/src/main/java/top/r3944realms/lib39/platform/ForgeHelpCommandHook.java b/forge/src/main/java/top/r3944realms/lib39/platform/ForgeHelpCommandHook.java new file mode 100644 index 0000000..fc9d22e --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/platform/ForgeHelpCommandHook.java @@ -0,0 +1,19 @@ +package top.r3944realms.lib39.platform; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraftforge.common.MinecraftForge; +import top.r3944realms.lib39.api.event.RegisterCommandHelpEvent; +import top.r3944realms.lib39.core.command.ICommandHelpManager; +import top.r3944realms.lib39.platform.services.IHelpCommandHook; + +public enum ForgeHelpCommandHook implements IHelpCommandHook { + INSTANCE; + + @Override + public void onRegister(LiteralArgumentBuilder tree, ICommandHelpManager manager, CommandBuildContext context) { + RegisterCommandHelpEvent registerHelpCommandEvent = new RegisterCommandHelpEvent(tree, manager, context); + MinecraftForge.EVENT_BUS.post(registerHelpCommandEvent); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java b/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java index cc2b57b..7b39c12 100644 --- a/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java +++ b/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java @@ -4,6 +4,7 @@ import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLLoader; import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.platform.services.IHelpCommandHook; import top.r3944realms.lib39.platform.services.IPlatformHelper; import top.r3944realms.lib39.platform.services.IUtilHelper; @@ -41,4 +42,9 @@ public class ForgePlatformHelper implements IPlatformHelper { public IUtilHelper getUtilHelper() { return ForgeUtilHelper.INSTANCE; } + + @Override + public IHelpCommandHook getHelpCommandHook() { + return ForgeHelpCommandHook.INSTANCE; + } } diff --git a/src/main/java/top/r3944realms/lib39/example/content/capability/AbstractedTestSyncData.java b/src/main/java/top/r3944realms/lib39/example/content/capability/AbstractedTestSyncData.java index 2dc49ad..16cc09a 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/capability/AbstractedTestSyncData.java +++ b/src/main/java/top/r3944realms/lib39/example/content/capability/AbstractedTestSyncData.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.example.content.capability; +package top.r3944realms.lib39.example.content.data; import net.minecraft.resources.ResourceLocation; import top.r3944realms.lib39.core.sync.NBTEntitySyncData; diff --git a/src/main/java/top/r3944realms/lib39/example/content/capability/ExCapabilityHandler.java b/src/main/java/top/r3944realms/lib39/example/content/capability/ExCapabilityHandler.java index dbdffe4..a65ea21 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/capability/ExCapabilityHandler.java +++ b/src/main/java/top/r3944realms/lib39/example/content/capability/ExCapabilityHandler.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.example.content.capability; +package top.r3944realms.lib39.example.content.data; import net.minecraft.world.entity.Entity; import net.minecraftforge.common.capabilities.Capability; diff --git a/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncCapProvider.java b/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncCapProvider.java index b409e72..b02addf 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncCapProvider.java +++ b/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncCapProvider.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.example.content.capability; +package top.r3944realms.lib39.example.content.data; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; diff --git a/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncData.java b/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncData.java index 81559df..0b91786 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncData.java +++ b/src/main/java/top/r3944realms/lib39/example/content/capability/TestSyncData.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.example.content.capability; +package top.r3944realms.lib39.example.content.data; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java b/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java index f3ba19a..ee091ee 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java +++ b/src/main/java/top/r3944realms/lib39/example/content/item/FabricItem.java @@ -17,9 +17,9 @@ import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import top.r3944realms.lib39.Lib39; -import top.r3944realms.lib39.example.content.capability.AbstractedTestSyncData; -import top.r3944realms.lib39.example.content.capability.ExCapabilityHandler; -import top.r3944realms.lib39.example.content.capability.TestSyncData; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; import top.r3944realms.lib39.example.core.network.ClientDataPacket; import top.r3944realms.lib39.example.core.network.ExNetworkHandler; diff --git a/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java b/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java index 4d669f8..3725bfe 100644 --- a/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java +++ b/src/main/java/top/r3944realms/lib39/example/content/item/NeoForgeItem.java @@ -17,9 +17,9 @@ import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import top.r3944realms.lib39.Lib39; -import top.r3944realms.lib39.example.content.capability.AbstractedTestSyncData; -import top.r3944realms.lib39.example.content.capability.ExCapabilityHandler; -import top.r3944realms.lib39.example.content.capability.TestSyncData; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; import java.util.List; import java.util.Random; diff --git a/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java b/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java index b49d674..2577417 100644 --- a/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java +++ b/src/main/java/top/r3944realms/lib39/example/core/event/ExCommonEventHandler.java @@ -15,9 +15,9 @@ import top.r3944realms.lib39.core.event.CommonEventHandler; import top.r3944realms.lib39.core.sync.CachedSyncManager; import top.r3944realms.lib39.example.compat.Lib39Compat; import top.r3944realms.lib39.example.compat.Lib39CompatManager; -import top.r3944realms.lib39.example.content.capability.AbstractedTestSyncData; -import top.r3944realms.lib39.example.content.capability.ExCapabilityHandler; -import top.r3944realms.lib39.example.content.capability.TestSyncData; +import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData; +import top.r3944realms.lib39.example.content.data.ExCapabilityHandler; +import top.r3944realms.lib39.example.content.data.TestSyncData; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java b/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java index 81e5a46..f99e27d 100644 --- a/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java +++ b/src/main/java/top/r3944realms/lib39/example/core/network/ClientDataPacket.java @@ -3,7 +3,7 @@ package top.r3944realms.lib39.example.core.network; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; -import top.r3944realms.lib39.example.content.capability.TestSyncData; +import top.r3944realms.lib39.example.content.data.TestSyncData; import top.r3944realms.lib39.example.content.item.FabricItem; import java.util.function.Supplier;