feat: 跨版本化,第三部分,未完成

This commit is contained in:
叁玖领域 2026-03-12 23:56:16 +08:00
parent 5a6729aa31
commit 0c62d360d9
107 changed files with 10032 additions and 149 deletions

View File

@ -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();
}
}
}

View File

@ -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";
}
}

View File

@ -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<CommandSourceStack> dispatcher, CommandBuildContext context) {
super(dispatcher, context);
if(Lib39.shouldRegisterExamples()) {
// 在這裡註冊測試命令
registerTestCommands(dispatcher);
}
}
@Override
public ICommandHelpManager getCommandHelpManager() {
return Lib39CommandHelpManager.INSTANCE;
}
/**
* 註冊測試命令
*/
private void registerTestCommands(@NotNull CommandDispatcher<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.game.resume.success")
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
}

View File

@ -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<FinishedRecipe> 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);
}
}

View File

@ -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<LangKeyValue> 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<LangKeyValue> getItems() {
return items;
}
}
/**
* The Lang key values.
*/
final List<LangKeyValue> 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<LangKeyValue> getValues() {
return List.copyOf(langKeyValues);
}
/**
* The type Test message.
*/
@SuppressWarnings("unused")
public static final class TestMessage {
private static final Set<LangKeyValue> 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<LangKeyValue> addConsumer = items::add;
private static LangKeyValue addAndRet(LangKeyValue item) {
items.add(item);
return item;
}
/**
* Gets items.
*
* @return the items
*/
public static Set<LangKeyValue> getItems() {
return items;
}
}
}

View File

@ -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<Integer, CommandNode> 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<CommandNode.Builder> configurator) {
CommandNode.Builder builder = CommandNode.Builder.of(this);
configurator.accept(builder);
registerCommand(builder);
}
/**
* 根据路径查找节点
*
* @param path the path
* @return the optional
*/
default Optional<CommandNode> 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<MutableComponent> 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<MutableComponent> getCommandTree(CommandSourceStack commandSourceStack) {
List<MutableComponent> 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<MutableComponent> 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<MutableComponent> commandTree = getCommandTree(commandSourceStack);
return buildHelpMessage(Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), commandTree);
}
}

View File

@ -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<CommandSourceStack> 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<CommandSourceStack> buildCommand(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext context) {
LiteralArgumentBuilder<CommandSourceStack> head = getHelpHead();
if (head == null) {
head = LiteralArgumentBuilder.literal(getCommandHelpManager().getID().getNamespace());
}
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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);
}
}

View File

@ -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<Integer, CommandNode> 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<Integer, CommandNode> 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;
}
}

View File

@ -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<CommandSourceStack> root;
/**
* Instantiates a new Simple help command.
*
* @param dispatcher the dispatcher
* @param context the context
*/
public SimpleHelpCommand(CommandDispatcher<CommandSourceStack> dispatcher,
CommandBuildContext context) {
root = buildCommand(dispatcher, context);
}
/**
* Gets root.
*
* @return the root
*/
public LiteralArgumentBuilder<CommandSourceStack> getRoot() {
return root;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<String> segments;
private final String fullPath;
private CommandPath(List<String> 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<String> newSegments = new ArrayList<>(this.segments);
newSegments.addAll(List.of(subSegments));
return new CommandPath(newSegments);
}
/**
* Parent optional.
*
* @return the optional
*/
public Optional<CommandPath> 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);
}
}
}
}

View File

@ -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<Parameter> 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();
}
}
}

View File

@ -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<ResourceLocation, ICompat> compats = new HashMap<>();
protected boolean initialized = false;
protected final List<Runnable> 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<ResourceLocation, ICompat> 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<ICompat> 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<ICompat> 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<ResourceLocation, ICompat> entry : compats.entrySet()) {
try {
entry.getValue().onLoadComplete();
} catch (Exception e) {
logger.error("Error in onLoadComplete for compat: {}", entry.getKey(), e);
}
}
}
}

View File

@ -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 <T> the type parameter
* @param callable the callable
* @return the t
* @throws Exception the exception
*/
default <T> T callIfPresent(Callable<T> callable) throws Exception {
if (isModLoaded()) return callable.call();
else return null;
}
/**
* Call if pesent t.
*
* @param <T> the type parameter
* @param callable the callable
* @param elseCall the else call
* @return the t
* @throws Exception the exception
*/
default <T> T callIfPresent(Callable<T> callable, Callable<T> 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;
}
}

View File

@ -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<Path> 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();
}
});
}
}
}

View File

@ -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<String, byte[]> 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);
}
}
}

View File

@ -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<String, ILocaleEntry> 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<ILocaleEntry> 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 + "]";
}
}
}

View File

@ -0,0 +1,74 @@
package top.r3944realms.lib39.core.sync;
import java.util.Map;
import java.util.Set;
/**
* The type Cached sync manager.
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
@SuppressWarnings("unused")
public abstract class CachedSyncManager<K, T extends ISyncData<?>> implements ISyncManager<K, T> {
private volatile Set<T> cachedSet;
private volatile int mapSize = -1;
@Override
public Set<T> getSyncSet() {
Map<K, T> 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<K, T> 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<K, T> 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<K, T> syncMap = getSyncMap();
if (syncMap != null) {
syncMap.clear();
}
invalidateCache();
}
}

View File

@ -0,0 +1,13 @@
package top.r3944realms.lib39.core.sync;
/**
* The interface Entity.
*/
public interface IEntity {
/**
* Entity id int.
*
* @return the int
*/
int entityId();
}

View File

@ -0,0 +1,9 @@
package top.r3944realms.lib39.core.sync;
import net.minecraft.nbt.Tag;
public interface INBTSerializable <T extends Tag>{
T serializeNBT();
void deserializeNBT(T var1);
}

View File

@ -0,0 +1,50 @@
package top.r3944realms.lib39.core.sync;
import net.minecraft.resources.ResourceLocation;
/**
* The interface Sync data.
*
* @param <T> the type parameter
*/
public interface ISyncData<T> {
/**
* 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();
}

View File

@ -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 <K> the type parameter
* @param <T> the type parameter
*/
@SuppressWarnings("unused")
public interface ISyncManager<K, T extends ISyncData<?>> {
/**
* 获取同步映射
*
* @return the sync map
*/
Map<K, T> getSyncMap();
/**
* 获取同步集合
*
* @return the sync set
*/
default Set<T> getSyncSet() {
Map<K, T> syncMap = getSyncMap();
return Set.copyOf(syncMap.values());
}
/**
* 跟踪实例
*
* @param key the key
* @param instance the instance
*/
default void track(K key, T instance) {
Map<K, T> 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<K, T> 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<T> consumer) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.values().forEach(consumer);
}
/**
* 批量操作
*
* @param instances the instances
*/
default void trackAll(Map<K, T> instances) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.putAll(instances);
}
/**
* 获取大小
*
* @return the int
*/
default int size() {
Map<K, T> syncMap = getSyncMap();
return syncMap != null ? syncMap.size() : 0;
}
/**
* 检查是否包含key
*
* @param key the key
* @return the boolean
*/
default boolean containsKey(K key) {
Map<K, T> syncMap = getSyncMap();
return syncMap != null && syncMap.containsKey(key);
}
/**
* 检查是否包含value
*
* @param value the value
* @return the boolean
*/
default boolean containsValue(T value) {
Map<K, T> syncMap = getSyncMap();
return syncMap != null && syncMap.containsValue(value);
}
/**
* 清空所有数据
*/
default void clear() {
Map<K, T> syncMap = getSyncMap();
if (syncMap != null) {
syncMap.clear();
}
}
}

View File

@ -0,0 +1,6 @@
package top.r3944realms.lib39.core.sync;
public interface IUpdate {
void update();
NBTEntitySyncData getSyncData();
}

View File

@ -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<NBTEntitySyncData>, INBTSerializable<CompoundTag>, 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;
}
}

View File

@ -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<ResourceLocation, TypedSyncEntry<?, ?>> typedEntries = Maps.newConcurrentMap();
/**
* 数据提供者接口 - 用于通过键获取数据
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
@FunctionalInterface
public interface DataProvider<K, T> {
/**
* 通过键获取数据的 Optional
*
* @param key
* @return 数据的 Optional
*/
Optional<T> getData(K key);
}
protected static class TypedSyncEntry<K, T extends ISyncData<?>> {
/**
* The Manager.
*/
final ISyncManager<K, T> manager;
/**
* The Data provider.
*/
@Nullable
final DataProvider<Entity, T> dataProvider;
/**
* The Allowed classes.
*/
final Set<Class<?>> allowedClasses;
/**
* Instantiates a new Typed sync entry.
*
* @param manager the manager
* @param dataProvider the data provider
*/
public TypedSyncEntry(ISyncManager<K, T> manager, @Nullable DataProvider<Entity, T> dataProvider) {
this.manager = manager;
this.dataProvider = dataProvider;
this.allowedClasses = Sets.newConcurrentHashSet();
}
}
/**
* Register manager with data provider.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param dataProvider the data provider
*/
public <K, T extends ISyncData<?>> void registerManagerWithProvider(
ResourceLocation key,
ISyncManager<K, T> manager,
DataProvider<Entity, T> 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 <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param getter the data getter function
*/
public <K, T extends ISyncData<?>> void registerManager(
ResourceLocation key,
ISyncManager<K, T> manager,
Function<Entity, Optional<T>> 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<?, ? extends ISyncData<?>> manager) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(manager, "Sync manager cannot be null");
// 创建一个没有数据提供者的 TypedSyncEntry
typedEntries.put(key, new TypedSyncEntry<>(
(ISyncManager<?, ISyncData<?>>) manager,
null
));
}
/**
* Gets manager.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @return the manager
*/
@SuppressWarnings("unchecked")
public <K, T extends ISyncData<?>> Optional<ISyncManager<K, T>> getManager(ResourceLocation key) {
TypedSyncEntry<?,?> entry = typedEntries.get(key);
return entry != null ? Optional.of((ISyncManager<K,T>) entry.manager) : Optional.empty();
}
/**
* Gets data provider.
*
* @param <T> the type parameter
* @param key the key
* @return the data provider
*/
@SuppressWarnings("unchecked")
public <T extends ISyncData<?>> Optional<DataProvider<Entity, T>> getDataProvider(ResourceLocation key) {
TypedSyncEntry<?, ?> entry = typedEntries.get(key);
if (entry != null && entry.dataProvider != null) {
return Optional.of((DataProvider<Entity, T>) entry.dataProvider);
}
return Optional.empty();
}
/**
* 获取实体数据
*
* @param <T> the type parameter
* @param key the key
* @param entity the entity
* @return the entity data
*/
@SuppressWarnings("unchecked")
public <T extends ISyncData<?>> Optional<T> getEntityData(ResourceLocation key, Entity entity) {
return getDataProvider(key)
.flatMap(provider -> {
Optional<ISyncData<?>> result = provider.getData(entity);
return (Optional<T>) 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 <T> the type parameter
* @param key the key
* @param dataProvider the data provider
*/
public <T extends ISyncData<?>> void bindDataProvider(ResourceLocation key, DataProvider<Entity, T> 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 <T> the type parameter
* @param key the key
* @param getter the data getter function
*/
public <T extends ISyncData<?>> void bindDataGetter(ResourceLocation key, @NotNull Function<Entity, Optional<T>> 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<UUID, ?> entry = (TypedSyncEntry<UUID, ?>) typedEntries.get(managerId);
if (entry != null) {
trackEntityWithTypedEntry(entity, entry);
}
}
private <T extends ISyncData<?>> void trackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<UUID, T> 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<UUID, ?> entry = (TypedSyncEntry<UUID, ?>) typedEntries.get(managerId);
if (entry != null) {
untrackEntityWithTypedEntry(entity, entry);
}
}
private <T extends ISyncData<?>> void untrackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<UUID, T> 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<Entity> entities, ResourceLocation managerId) {
for (Entity entity : entities) {
untrackEntityForManager(entity, managerId);
}
}
/**
* 从所有管理器中批量移除实体跟踪
*
* @param entities the entities
*/
public void untrackEntitiesFromAllManagers(@NotNull Iterable<Entity> 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 <K, T extends ISyncData<?>> void clearTrackedDataForEntry(@NotNull TypedSyncEntry<K, T> entry) {
Set<T> syncSet = entry.manager.getSyncSet();
if (syncSet != null) {
syncSet.clear();
}
}
/**
* 清理所有管理器的跟踪数据
*/
public void clearAllTrackedData() {
for (ResourceLocation id : getRegisteredKeys()) {
clearAllTrackedData(id);
}
}
// 辅助方法更新条目的数据提供者
@SuppressWarnings("unchecked")
protected <K, T extends ISyncData<?>> void updateDataProviderInEntry(
ResourceLocation id,
TypedSyncEntry<?,?> entry,
DataProvider<Entity, T> newDataProvider
) {
// 由于 DataProvider final我们需要创建一个新的 TypedSyncEntry
TypedSyncEntry<K, T> newEntry = new TypedSyncEntry<>(
(ISyncManager<K, T>) entry.manager,
newDataProvider
);
newEntry.allowedClasses.addAll(entry.allowedClasses);
typedEntries.put(id, newEntry);
}
/**
* Gets registered keys.
*
* @return the registered keys
*/
public Set<ResourceLocation> getRegisteredKeys() {
return Collections.unmodifiableSet(typedEntries.keySet());
}
/**
* For each.
*
* @param consumer the consumer
*/
public void forEach(BiConsumer<ResourceLocation, ISyncManager<?,?>> 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);
}
}

View File

@ -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() {
}
}

View File

@ -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<GuiGraphics, PoseStack, Integer, Integer> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<ItemStack> 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<Component> 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- 同步建议"));
}
}

View File

@ -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<ItemStack> 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<Component> 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- 玩家专属数据变换"));
}
}

View File

@ -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<ItemStack> 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)));
}
}
}

View File

@ -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<Item> FABRIC;
/**
* The constant ETERNAL_POTATO.
*/
public static Supplier<Item> NEOFORGE;
/**
* The constant FORGE.
*/
public static Supplier<Item> FORGE;
}

View File

@ -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<Renderable> getrRenderables();
}

View File

@ -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<CommandSourceStack> tree, ICommandHelpManager manager, CommandBuildContext context);
}

View File

@ -47,4 +47,6 @@ public interface IPlatformHelper {
String getModVersion();
IUtilHelper getUtilHelper();
IHelpCommandHook getHelpCommandHook();
}

View File

@ -9,6 +9,7 @@
"minecraft.CreativeModeTabsAccessor"
],
"client": [
"minecraft.ScreenAccessor"
],
"server": [
],

View File

@ -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.");
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,21 @@
package top.r3944realms.lib39.api.callback;
/**
* 结果枚举
*/
public enum ActionResult {
/**
* 继续处理
*/
PASS,
/**
* 取消
*/
CANCEL,
/**
* 允许加入但停止后续处理
*/
SUCCESS
}

View File

@ -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);
}

View File

@ -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<RegisterCommandHelpCallback> 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<CommandSourceStack> child);
/**
* Get context command build context.
*
* @return the command build context
*/
CommandBuildContext getContext();
/**
* Get tree literal argument builder.
*
* @return the literal argument builder
*/
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> builder, ICommandHelpManager helpManager, CommandBuildContext context) implements Registrar {
@Override
public ResourceLocation getID() {
return helpManager.getID();
}
@Override
public void addChild(LiteralArgumentBuilder<CommandSourceStack> child) {
this.builder.then(child);
}
@Override
public CommandBuildContext getContext(){
return this.context;
}
@Override
public LiteralArgumentBuilder<CommandSourceStack> 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);
}
}
}

View File

@ -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<SyncManagerRegisterCallback> 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 <T> the type parameter
* @param id the id
* @param syncManager the sync manager
* @param dataClass the data class
*/
<T extends ISyncData<?>> void register(
ResourceLocation id,
ISyncManager<Entity, T> syncManager,
Class<T> 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 <T> the type parameter
* @param id 必须先注册安全同步管理器再绑定 Lookup否则会抛出 {@link IllegalStateException}
* @param apiLookup the EntityApiLookup
*/
<T extends ISyncData<?>> void bindApiLookup(
ResourceLocation id,
net.fabricmc.fabric.api.lookup.v1.entity.EntityApiLookup<T, Void> apiLookup
);
/**
* 解绑 EntityApiLookup
*
* @param id the id
*/
void unbindApiLookup(ResourceLocation id);
/**
* 完整的类型安全注册
*
* @param <T> the type parameter
* @param id the id
* @param syncManager the sync manager
* @param dataClass the data class
* @param allowedEntityClasses the allowed entity classes
*/
default <T extends ISyncData<?>> void registerComplete(
ResourceLocation id,
ISyncManager<Entity, T> syncManager,
Class<T> dataClass,
Class<?>... allowedEntityClasses
) {
register(id, syncManager, dataClass);
if (allowedEntityClasses.length > 0) {
allowEntityClass(id, allowedEntityClasses);
}
}
}
record SyncManagerRegistrar(
SyncData2LookupManager manager) implements SyncManagerRegisterCallback.Registrar {
@Override
public <T extends ISyncData<?>> void register(
@NotNull ResourceLocation id,
@NotNull ISyncManager<Entity, T> syncManager,
@NotNull Class<T> 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 <T extends ISyncData<?>> void bindApiLookup(
@NotNull ResourceLocation id,
@NotNull EntityApiLookup<T, Void> apiLookup
) {
manager.bindApiLookup(id, apiLookup);
}
@Override
public void unbindApiLookup(@NotNull ResourceLocation id) {
manager.unbindApiLookup(id);
}
}
}

View File

@ -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<ClientWorldCallback.Load> LOAD = EventFactory.createArrayBacked(ClientWorldCallback.Load.class, (callbacks) -> (world) -> {
for (Load callback : callbacks) {
callback.onWorldLoad(world);
}
});
Event<ClientWorldCallback.Unload> 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);
}
}

View File

@ -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
);
}
}

View File

@ -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");
}
});
}
}

View File

@ -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<Supplier<Block>, ResourceKey<CreativeModeTab>[]> itemAddMap = new ConcurrentHashMap<>();
private static final Map<ResourceKey<CreativeModeTab>, List<Supplier<Block>>> tabToItemsMap = new ConcurrentHashMap<>();
/**
* Add item to tabs.
*
* @param item the item
* @param tabs the tabs
*/
@SafeVarargs
public static void addItemToTabs(Supplier<Block> item, ResourceKey<CreativeModeTab>... tabs) {
itemAddMap.put(item, tabs);
// 更新反向映射
for (ResourceKey<CreativeModeTab> tab : tabs) {
tabToItemsMap.computeIfAbsent(tab, k -> new ArrayList<>()).add(item);
}
}
public static void init() {
for (Map.Entry<ResourceKey<CreativeModeTab>, List<Supplier<Block>>> 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<Supplier<Block>, ResourceKey<CreativeModeTab>[]> getItemAddMap() {
return itemAddMap;
}
}

View File

@ -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<Supplier<Block>, ResourceKey<CreativeModeTab>[]> itemAddMap = new ConcurrentHashMap<>();
private static final Map<ResourceKey<CreativeModeTab>, List<Supplier<Block>>> tabToItemsMap = new ConcurrentHashMap<>();
/**
* Add item to tabs.
*
* @param item the item
* @param tabs the tabs
*/
@SafeVarargs
public static void addItemToTabs(Supplier<Block> item, ResourceKey<CreativeModeTab>... tabs) {
itemAddMap.put(item, tabs);
// 更新反向映射
for (ResourceKey<CreativeModeTab> tab : tabs) {
tabToItemsMap.computeIfAbsent(tab, k -> new ArrayList<>()).add(item);
}
}
public static void initCommon() {
for (Map.Entry<ResourceKey<CreativeModeTab>, List<Supplier<Block>>> 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<Entity, AbstractedTestSyncData> syncDataMap = new ConcurrentHashMap<>();
@Override
public Map<Entity, AbstractedTestSyncData> 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<Supplier<Block>, ResourceKey<CreativeModeTab>[]> getItemAddMap() {
return itemAddMap;
}
}

View File

@ -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
);
}
}

View File

@ -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<SyncNBTLookupDataEntityS2CPacket> 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<SyncData2Manager.DataProvider<Entity, ISyncData<?>>> 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);
}
);
}
}
}

View File

@ -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<ServerPlayer> players = playerList.getPlayers();
for (ServerPlayer player : players) {
if (ServerPlayNetworking.canSend(player, SyncNBTLookupDataEntityS2CPacket.TYPE)) {
ServerPlayNetworking.send(player, new SyncNBTLookupDataEntityS2CPacket(getSyncData().entityId(), getSyncData()));
}
}
}
}
}

View File

@ -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<ResourceLocation, TypedSyncEntry<?>> typedEntries = Maps.newConcurrentMap();
protected static class TypedSyncEntry<T extends ISyncData<?>> extends SyncData2Manager.TypedSyncEntry<Entity, T> {
/**
* Instantiates a new Typed sync entry for Fabric
*
* @param manager the manager
* @param apiLookup the EntityApiLookup instance
*/
public TypedSyncEntry(ISyncManager<Entity, T> manager, @Nullable EntityApiLookup<T, Void> apiLookup) {
super(manager, key -> {
if (apiLookup != null) {
T data = apiLookup.find(key, null);
return Optional.ofNullable(data);
}
return Optional.empty();
});
}
}
/**
* 使用自定义提供器注册管理器
*
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param dataClass the data class
*/
public <T extends ISyncData<?>> void registerManager(
ResourceLocation key,
ISyncManager<Entity, T> manager,
Class<T> 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<T, Void> apiLookup = EntityApiLookup.get(key, dataClass, Void.class);
typedEntries.put(key, new TypedSyncEntry<>(manager, apiLookup));
}
/**
* 绑定 EntityApiLookup用于分离注册的情况
*
* @param <T> the type parameter
* @param key the key
* @param apiLookup the EntityApiLookup
*/
public <T extends ISyncData<?>> void bindApiLookup(ResourceLocation key, EntityApiLookup<T, Void> 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 <T extends ISyncData<?>> void updateApiLookupInEntry(
ResourceLocation id,
TypedSyncEntry<?> entry,
EntityApiLookup<T, Void> newApiLookup
) {
updateDataProviderInEntry(id, entry, key -> newApiLookup != null ? Optional.ofNullable(newApiLookup.find(key, null)) : Optional.empty());
}
}

View File

@ -0,0 +1,5 @@
package top.r3944realms.lib39.example;
public class FabricLib39Examples {
//todo:注册网络
}

View File

@ -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<String> 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;
}
}

View File

@ -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<ServerPlayer> 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目标生物不存在或已消失"));
}
}
}

View File

@ -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;
}
}

View File

@ -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<ClientDataPacket> 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);
}
}

View File

@ -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
);
}
}

View File

@ -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;

View File

@ -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<Runnable> implements WindowEventHandler {
@Shadow @Nullable public ClientLevel level;
/**
* Instantiates a new Mixin minecraft.
*
@ -28,7 +36,16 @@ public abstract class MixinMinecraft extends ReentrantBlockableEventLoop<Runnabl
public MixinMinecraft(String name) {
super(name);
}
@WrapMethod(method = "setLevel")
public void setLevel$callback(ClientLevel levelClient, Operation<Void> original) {
if (levelClient != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(levelClient);
original.call(levelClient);
}
@WrapMethod(method = "clearLevel()V")
public void clearLevel$callback(Operation<Void> original) {
if (level != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(level);
original.call();
}
/**
* Set level setup.
*

View File

@ -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<Level> dimension, RegistryAccess registryAccess, Holder<DimensionType> dimensionTypeRegistration, Supplier<ProfilerFiller> profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) {
super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates);
}
@WrapMethod(method = "<init>")
private void init(ClientPacketListener connection, ClientLevel.ClientLevelData clientLevelData, ResourceKey<Level> dimension, Holder<DimensionType> dimensionType, int viewDistance, int serverSimulationDistance, Supplier<ProfilerFiller> profiler, LevelRenderer levelRenderer, boolean isDebug, long biomeZoomSeed, Operation<Void> original) {
original.call(connection, clientLevelData, dimension, dimensionType, viewDistance, serverSimulationDistance, profiler, levelRenderer, isDebug, biomeZoomSeed);
ClientWorldCallback.LOAD.invoker().onWorldLoad(ClientLevel.class.cast(this));
}
}

View File

@ -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<CommandSourceStack> tree, ICommandHelpManager manager, CommandBuildContext context) {
RegisterCommandHelpCallback.EVENT.invoker().register(new RegisterCommandHelpCallback.CommandHelpRegistrar(tree, manager, context));
}
}

View File

@ -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;
}
}

View File

@ -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<Block> blockObject, ResourceKey<CreativeModeTab>... creativeTabs) {
CreativeTabAdder.addItemToTabs(blockObject, creativeTabs);
CommonEventHandler.addItemToTabs(blockObject, creativeTabs);
}
}

View File

@ -1 +1 @@
top.r3944realms.lib39.platform.ForgePlatformHelper
top.r3944realms.lib39.platform.FabricPlatformHelper

View File

@ -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": [
],

View File

@ -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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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);
}
}

View File

@ -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 <K> the type parameter
* @param <T> the type parameter
* @param id the id
* @param syncManager the sync manager
* @param capability the capability
*/
public <K, T extends ISyncData<?>> void registerSyncManager(
ResourceLocation id,
ISyncManager<Capability<T>, T> syncManager,
Capability<T> 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 <T> the type parameter
* @param id 必须先注册安全同步管理器再绑定Cap否则会抛出{@link IllegalStateException 未找到对应安全同步管理器}
* @param capability the capability
*/
public <T extends ISyncData<?>> void bindCapability(ResourceLocation id, Capability<T> capability) {
syncs2Manager.bindCapability(id, capability);
}
/**
* 解绑数据提供者
*
* @param id the id
*/
public void unbindCapability(ResourceLocation id) {
syncs2Manager.unbindCapability(id);
}
/**
* 完整的类型安全注册
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param id the id
* @param syncManager the sync manager
* @param capability the capability
* @param allowedEntityClasses the allowed entity classes
*/
public <K, T extends ISyncData<?>> void registerComplete(
ResourceLocation id,
ISyncManager<Capability<T>, T> syncManager,
Capability<T> capability,
Class<?>... allowedEntityClasses
) {
registerSyncManager(id, syncManager, capability);
if (allowedEntityClasses.length > 0) {
addAllowEntityClass(id, allowedEntityClasses);
}
}
}

View File

@ -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<SimpleLanguageProvider>) pOutput -> new SimpleLanguageProvider(pOutput, Lib39.MOD_ID ,language , Lib39LangKey.INSTANCE)
);
}
private static void ItemModelDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39ItemModelProvider>) pOutput -> new Lib39ItemModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void BlockModelDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39BlockModelProvider>) pOutput -> new Lib39BlockModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void BlockStateDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39BlockStatesProvider>) pOutput -> new Lib39BlockStatesProvider(pOutput, event.getExistingFileHelper())
);
}
private static void SoundDefinitionDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39SoundDefinitionsProvider>) pOutput -> new Lib39SoundDefinitionsProvider(pOutput, event.getExistingFileHelper())
);
}
private static void LootTableDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeServer(),
(DataProvider.Factory<SimpleLootTableProvider>) pOutput -> new SimpleLootTableProvider(pOutput, new SubProvidersWrapper().addBlockEntry(new Lib39BlockLootTable()))
);
}
private static void RecipeGenerator(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeServer(),
(DataProvider.Factory<Lib39RecipeProvider>) Lib39RecipeProvider::new
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
};
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Item> 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 <br/>&nbsp;先有纹理才会成功构建
*/
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);
}
}

View File

@ -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)
)
);
}
}

View File

@ -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<IClientItemExtensions> consumer) {
consumer.accept(new IClientItemExtensions() {
@Override
public BlockEntityWithoutLevelRenderer getCustomRenderer() {
return DollItemRenderer.getInstance();
}
});
}
}

View File

@ -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<ResourceLocation, IForgeCompat> compats = new HashMap<>();
// 存储事件监听器配置
protected final List<ListenerConfig> 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<IEventBus> 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<ResourceLocation, IForgeCompat> 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();
}
}
}

View File

@ -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) {
// 实现服务端模组事件监听器添加逻辑
}
}

View File

@ -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());
}
}

View File

@ -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 <MSG> the type parameter
* @param message the message
* @param player the player
*/
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player){
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
}
/**
* Send to player.
*
* @param <MSG> the type parameter
* @param <T> the type parameter
* @param message the message
* @param entity the entity
* @param packetDistributor the packet distributor
*/
public static <MSG, T> void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor<T> packetDistributor){
INSTANCE.send(packetDistributor.with(() -> entity), message);
}
/**
* Send to player.
*
* @param <MSG> the type parameter
* @param message the message
*/
public static <MSG> void sendToAllPlayer(MSG message){
INSTANCE.send(PacketDistributor.ALL.noArg(), message);
}
}

View File

@ -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<NetworkEvent.Context> 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<SyncData2Manager.DataProvider<Entity, ISyncData<?>>> 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);
}
}

View File

@ -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<Item> 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()));
}
/**

View File

@ -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()));
}
}

View File

@ -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<ResourceLocation, TypedSyncEntry<?>> typedEntries = Maps.newConcurrentMap();
protected static class TypedSyncEntry<T extends ISyncData<?>> extends SyncData2Manager.TypedSyncEntry<Capability<T>, T> {
/**
* Instantiates a new Typed sync entry.
*
* @param manager the manager
* @param capability the capability
*/
public TypedSyncEntry(ISyncManager<Capability<T>, T> manager,@Nullable Capability<T> capability) {
super(manager, key -> capability != null ? key.getCapability(capability).resolve() : Optional.empty());
}
}
/**
* Register manager.
*
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param capability the capability
*/
public <T extends ISyncData<?>> void registerManager(
ResourceLocation key,
ISyncManager<Capability<T>, T> manager,
Capability<T> 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 <T> the type parameter
* @param key the key
* @param capability the capability
*/
public <T extends ISyncData<?>> void bindCapability(ResourceLocation key, Capability<T> 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 <T extends ISyncData<?>> void updateCapabilityInEntry(ResourceLocation id, TypedSyncEntry<?> entry, Capability<T> newCapability) {
updateDataProviderInEntry(id, entry, key -> newCapability != null ? key.getCapability(newCapability).resolve() : Optional.empty());
}
}

View File

@ -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<Block> 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> block) {
public void dropSelf(Supplier<Block> block) {
addEntry(block, this::createSingleItemTable);
}
@ -72,8 +72,8 @@ public class BlockLootTables extends BlockLootSubProvider {
*/
@Contract("_ -> this")
@SafeVarargs
public final BlockLootTables dropSelf(RegistryObject<Block> @NotNull ... blocks) {
for (RegistryObject<Block> block : blocks) {
public final BlockLootTables dropSelf(Supplier<Block> @NotNull ... blocks) {
for (Supplier<Block> 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> block) {
public BlockLootTables dropWhenSilkTouch(Supplier<Block> 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> block, RegistryObject<? extends ItemLike> item) {
public BlockLootTables dropOther(Supplier<Block> block, Supplier<? extends ItemLike> 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> block) {
public BlockLootTables dropWhenShears(Supplier<Block> 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> block, RegistryObject<Item> oreItem) {
public BlockLootTables dropOre(Supplier<Block> block, Supplier<Item> 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> block) {
public BlockLootTables dropRedstoneOre(Supplier<Block> 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> block) {
public BlockLootTables dropLapisOre(Supplier<Block> 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> block) {
public BlockLootTables dropCopperOre(Supplier<Block> 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> block) {
public BlockLootTables dropCarpet(Supplier<Block> 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> block) {
public BlockLootTables dropSlab(Supplier<Block> 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> block) {
public BlockLootTables dropDoor(Supplier<Block> 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> block) {
public BlockLootTables dropFlowerPot(Supplier<Block> 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<Block> leavesBlock,
RegistryObject<Block> saplingBlock,
public BlockLootTables dropLeaves(Supplier<Block> leavesBlock,
Supplier<Block> 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<Block> leavesBlock,
RegistryObject<Block> saplingBlock,
public BlockLootTables dropOakLeaves(Supplier<Block> leavesBlock,
Supplier<Block> 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<Block> cropBlock,
RegistryObject<Item> cropItem,
RegistryObject<Item> seedsItem,
public BlockLootTables dropCrop(Supplier<Block> cropBlock,
Supplier<Item> cropItem,
Supplier<Item> seedsItem,
Property<Integer> 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> block, Function<Block, LootTable.Builder> factory) {
public BlockLootTables custom(Supplier<Block> block, Function<Block, LootTable.Builder> 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> block) {
public BlockLootTables noDrop(Supplier<Block> 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<RegistryObject<Block>> blocks,
Function<RegistryObject<Block>, BlockLootTables> operation) {
for (RegistryObject<Block> block : blocks) {
public BlockLootTables batch(@NotNull Iterable<Supplier<Block>> blocks,
Function<Supplier<Block>, BlockLootTables> operation) {
for (Supplier<Block> block : blocks) {
operation.apply(block);
}
return this;
@ -295,9 +295,9 @@ public class BlockLootTables extends BlockLootSubProvider {
*/
@Contract("_, _ -> this")
@SafeVarargs
public final BlockLootTables applyToAll(Function<RegistryObject<Block>, BlockLootTables> operation,
RegistryObject<Block> @NotNull ... blocks) {
for (RegistryObject<Block> block : blocks) {
public final BlockLootTables applyToAll(Function<Supplier<Block>, BlockLootTables> operation,
Supplier<Block> @NotNull ... blocks) {
for (Supplier<Block> block : blocks) {
operation.apply(block);
}
return this;
@ -323,12 +323,12 @@ public class BlockLootTables extends BlockLootSubProvider {
// ==================== 内部类和方法 ====================
private BlockLootTables addEntry(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {
private BlockLootTables addEntry(Supplier<Block> block, Function<Block, LootTable.Builder> factory) {
blockEntries.add(new BlockEntry(block, factory));
return this;
}
private record BlockEntry(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {}
private record BlockEntry(Supplier<Block> block, Function<Block, LootTable.Builder> factory) {}
// ==================== 静态工厂方法 ====================

View File

@ -0,0 +1,5 @@
package top.r3944realms.lib39.example;
public class ForgeLib39Example {
//todo:订阅事件
}

View File

@ -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"));
}
}

View File

@ -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);
}
}

View File

@ -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<AbstractedTestSyncData> 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));
}
}
}

View File

@ -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<CompoundTag> {
/**
* 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<AbstractedTestSyncData> 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 <T> LazyOptional<T> getCapability(@NotNull Capability<T> 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);
}
}

View File

@ -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<String> 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;
}
}

View File

@ -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目标生物不存在或已消失"));
}
}
}

View File

@ -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;
}
}

View File

@ -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 {}
}

View File

@ -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<Capability<AbstractedTestSyncData>, AbstractedTestSyncData> syncDataMap = new ConcurrentHashMap<>();
@Override
public Map<Capability<AbstractedTestSyncData>, 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);
}
}
}

View File

@ -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 {}
}

View File

@ -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<NetworkEvent.Context> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Lib39.MOD_ID);
/**
* The constant SUPER_LEAD_ROPE.
*/
public static final RegistryObject<Item> FABRIC = ITEMS.register(
"fabric",
() -> new FabricItem(
new Item.Properties()
.stacksTo(1)
.fireResistant()
)
);
/**
* The constant ETERNAL_POTATO.
*/
public static final RegistryObject<Item> NEOFORGE =
ITEMS.register("neoforge",
() -> new NeoForgeItem(
new Item.Properties()
.stacksTo(1)
.fireResistant()
));
/**
* The constant FORGE.
*/
public static final RegistryObject<Item> 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);
}
}

View File

@ -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<CommandSourceStack> tree, ICommandHelpManager manager, CommandBuildContext context) {
RegisterCommandHelpEvent registerHelpCommandEvent = new RegisterCommandHelpEvent(tree, manager, context);
MinecraftForge.EVENT_BUS.post(registerHelpCommandEvent);
}
}

View File

@ -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;
}
}

View File

@ -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;

Some files were not shown because too many files have changed in this diff Show More