feat: 跨版本化,第三部分,未完成
This commit is contained in:
parent
5a6729aa31
commit
0c62d360d9
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package top.r3944realms.lib39.core.sync;
|
||||
|
||||
/**
|
||||
* The interface Entity.
|
||||
*/
|
||||
public interface IEntity {
|
||||
/**
|
||||
* Entity id int.
|
||||
*
|
||||
* @return the int
|
||||
*/
|
||||
int entityId();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package top.r3944realms.lib39.core.sync;
|
||||
|
||||
public interface IUpdate {
|
||||
void update();
|
||||
NBTEntitySyncData getSyncData();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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- 同步建议"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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- 玩家专属数据变换"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -47,4 +47,6 @@ public interface IPlatformHelper {
|
|||
String getModVersion();
|
||||
|
||||
IUtilHelper getUtilHelper();
|
||||
|
||||
IHelpCommandHook getHelpCommandHook();
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
"minecraft.CreativeModeTabsAccessor"
|
||||
],
|
||||
"client": [
|
||||
"minecraft.ScreenAccessor"
|
||||
],
|
||||
"server": [
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package top.r3944realms.lib39.api.callback;
|
||||
|
||||
/**
|
||||
* 结果枚举
|
||||
*/
|
||||
public enum ActionResult {
|
||||
/**
|
||||
* 继续处理
|
||||
*/
|
||||
PASS,
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
CANCEL,
|
||||
|
||||
/**
|
||||
* 允许加入但停止后续处理
|
||||
*/
|
||||
SUCCESS
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package top.r3944realms.lib39.example;
|
||||
|
||||
public class FabricLib39Examples {
|
||||
//todo:注册网络
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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目标生物不存在或已消失"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
top.r3944realms.lib39.platform.ForgePlatformHelper
|
||||
top.r3944realms.lib39.platform.FabricPlatformHelper
|
||||
|
|
@ -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": [
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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/> 先有纹理才会成功构建
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
// 实现服务端模组事件监听器添加逻辑
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
|
||||
// ==================== 静态工厂方法 ====================
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package top.r3944realms.lib39.example;
|
||||
|
||||
public class ForgeLib39Example {
|
||||
//todo:订阅事件
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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目标生物不存在或已消失"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue
Block a user