diff --git a/gradle.properties b/gradle.properties index 94a6ec2..780fde6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.4 yarn_mappings=1.21.4+build.8 loader_version=0.16.10 # Mod Properties -mod_version=1.14.514.004 +mod_version=1.14.514.005 maven_group=org.branulf archives_base_name=branulf_toolbox # Dependencies diff --git a/src/main/java/org/branulf/toolbox/Main.java b/src/main/java/org/branulf/toolbox/Main.java index d6c778b..489a873 100644 --- a/src/main/java/org/branulf/toolbox/Main.java +++ b/src/main/java/org/branulf/toolbox/Main.java @@ -2,6 +2,7 @@ package org.branulf.toolbox; import org.branulf.toolbox.autocrossbow.AutoCrossbowHandler; import org.branulf.toolbox.crazylook.CrazyLookHandler; +import org.branulf.toolbox.damagelogger.DamageLoggerHandler; import org.branulf.toolbox.elytraboost.ElytraBoostCommon; import org.branulf.toolbox.elytraboost.ElytraBoostHandler; import org.branulf.toolbox.playerdetector.PlayerDetectorHandler; @@ -16,24 +17,28 @@ import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.text.Text; import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Main implements ClientModInitializer { public static final String MOD_ID = "branulf_toolbox"; private static Main instance; private static ModConfig config; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); private static KeyBinding openMainGuiKeyBinding; /** 该mod为多个mod组成的拼好mod - * 每个mod的处理程序均在单独的软件包(文件夹)中以便于维护 + * 每个mod的处理程序均在单独的软件包(文件夹)中以便于维护。 *

* 为什么会存在这个mod? - * 因为作者制作了多个小mod,但是为了防止以后数量多导致的维护困难,作者决定将这些mod进行整合,并创建此mod - * 这不是个什么正经项目,只是作者想把一些小mod整合起来,方便管理 + * 因为作者制作了多个小mod,但是为了防止以后数量多导致的维护困难,作者决定将这些mod进行整合,并创建此mod。 + * 这不是个什么正经项目,只是作者想把一些小mod整合起来,方便管理。 *

- * 我不确定以后我会不会做扩展包,就像carpet那样 - * 老实说,我能改掉一些开发中的坏习惯已经很累了,在这之前很多时候变量名我都是瞎写的,我有几个py的脚本写的全是屎山,从写jvav开始才改了我的部分坏习惯 - * 同时,人工智能真好用(),哈哈哈。 + * 我不确定以后我会不会做扩展包,就像carpet那样。 + * 老实说,我能改掉一些开发中的坏习惯已经很累了,在这之前很多时候变量名我都是瞎写的,我有几个py的脚本写的全是屎山,从写jvav开始才改了我的部分坏习惯。 + *

+ * 同时,人工智能真好用(之前很多部分都有依赖于AI,现在算是没之前那样了,之前真的啥都丢给AI,我是干啥的?不过话又说回来,没AI我也学不会这个),哈哈哈。 */ public AutoCrossbowHandler autoCrossbowHandler; @@ -41,6 +46,7 @@ public class Main implements ClientModInitializer { public CrazyLookHandler crazyLookHandler; public ElytraBoostHandler elytraBoostHandler; public PlayerDetectorHandler playerDetectorHandler; + public DamageLoggerHandler damageLoggerHandler; @Override public void onInitializeClient() { @@ -61,6 +67,7 @@ public class Main implements ClientModInitializer { crazyLookHandler = new CrazyLookHandler(config); elytraBoostHandler = new ElytraBoostHandler(config); playerDetectorHandler = new PlayerDetectorHandler(config); + damageLoggerHandler = new DamageLoggerHandler(config.damageLogger); ClientTickEvents.END_CLIENT_TICK.register(client -> { while (openMainGuiKeyBinding.wasPressed()) { @@ -74,6 +81,14 @@ public class Main implements ClientModInitializer { elytraBoostHandler.onClientTick(client); scrafitiptyPlusHandler.onClientTick(client); playerDetectorHandler.onClientTick(client); + + if (damageLoggerHandler != null) { + if (config.damageLogger.enabled && !damageLoggerHandler.isLogging()) { + damageLoggerHandler.startLogging(); + } else if (!config.damageLogger.enabled && damageLoggerHandler.isLogging()) { + damageLoggerHandler.stopLogging(); + } + } }); new ElytraBoostCommon().onInitialize(); diff --git a/src/main/java/org/branulf/toolbox/MainScreen.java b/src/main/java/org/branulf/toolbox/MainScreen.java index c00e663..91bdb9e 100644 --- a/src/main/java/org/branulf/toolbox/MainScreen.java +++ b/src/main/java/org/branulf/toolbox/MainScreen.java @@ -3,6 +3,7 @@ package org.branulf.toolbox; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.util.math.MathHelper; import org.branulf.toolbox.autocrossbow.AutoCrossbowMode; +import org.branulf.toolbox.damagelogger.DamageLoggerHandler; import org.branulf.toolbox.scrafitiptyplus.ScrafitiptyPlusHandler; import me.shedaniel.autoconfig.AutoConfig; import net.minecraft.client.MinecraftClient; @@ -35,6 +36,11 @@ public class MainScreen extends Screen { private ButtonWidget configButton; private TextFieldWidget playerDetectorRangeField; + /** + * 使用无法点击的按钮作为标题,是为了方便将所有UI元素(包括标题)都作为一个子类来管理, + * 尤其是在有滚动条的界面中,这样可以统一处理布局和渲染逻辑,绝对不是因为懒。 + */ + public MainScreen(Text title) { super(title); } @@ -80,6 +86,11 @@ public class MainScreen extends Screen { currentContentY = addPlayerDetectorButtons(currentContentY); currentContentY += SECTION_SPACING; + // 伤害记录 + currentContentY = addSectionTitle(Text.translatable("gui.branulf_toolbox.damage_logger.title").formatted(Formatting.RED), currentContentY); + currentContentY = addDamageLoggerButtons(currentContentY); + currentContentY += SECTION_SPACING; + totalContentHeight = currentContentY; // 配置 @@ -279,6 +290,50 @@ public class MainScreen extends Screen { return row2Y + BUTTON_HEIGHT + PADDING; } + // 伤害记录 + private int addDamageLoggerButtons(int currentContentY) { + DamageLoggerHandler handler = Main.getInstance().damageLoggerHandler; + ModConfig.DamageLoggerConfig config = Main.getConfig().damageLogger; + + int centerX = this.width / 2; + int row1StartX = centerX - (BUTTON_WIDTH * 2 + PADDING) / 2; + + // 启用 + ButtonWidget damageLoggerOnButton = ButtonWidget.builder( + Text.translatable("gui.branulf_toolbox.damage_logger.enable").formatted(Formatting.GREEN), + button -> { + config.enabled = true; + Main.saveConfig(); + updateButtonStates(); + }) + .dimensions(row1StartX, currentContentY, BUTTON_WIDTH, BUTTON_HEIGHT) + .build(); + scrollableWidgets.add(damageLoggerOnButton); + + // 禁用 + ButtonWidget damageLoggerOffButton = ButtonWidget.builder( + Text.translatable("gui.branulf_toolbox.damage_logger.disable").formatted(Formatting.RED), + button -> { + config.enabled = false; + Main.saveConfig(); + updateButtonStates(); + }) + .dimensions(row1StartX + BUTTON_WIDTH + PADDING, currentContentY, BUTTON_WIDTH, BUTTON_HEIGHT) + .build(); + scrollableWidgets.add(damageLoggerOffButton); + + // 打开文件夹 + int row2Y = currentContentY + BUTTON_HEIGHT + PADDING; + ButtonWidget openLogFolderButton = ButtonWidget.builder( + Text.translatable("gui.branulf_toolbox.damage_logger.open_folder"), + button -> handler.openLogFolder()) + .dimensions(centerX - BUTTON_WIDTH / 2, row2Y, BUTTON_WIDTH, BUTTON_HEIGHT) + .build(); + scrollableWidgets.add(openLogFolderButton); + + return row2Y + BUTTON_HEIGHT + PADDING; + } + private void updateButtonStates() { ModConfig config = Main.getConfig(); ScrafitiptyPlusHandler scrafitiptyPlusHandler = Main.getInstance().scrafitiptyPlusHandler; @@ -327,6 +382,12 @@ public class MainScreen extends Screen { .append(Text.literal(" (" + (config.playerDetector.enabled ? Text.translatable("branulf_toolbox.enabled").getString() : Text.translatable("branulf_toolbox.disabled").getString()) + ")")) .formatted(config.playerDetector.enabled ? Formatting.GREEN : Formatting.RED)); } + // 伤害记录 + else if (button.getMessage().getString().contains(Text.translatable("gui.branulf_toolbox.damage_logger.enable").getString())) { + button.active = !config.damageLogger.enabled; + } else if (button.getMessage().getString().contains(Text.translatable("gui.branulf_toolbox.damage_logger.disable").getString())) { + button.active = config.damageLogger.enabled; + } } } if (playerDetectorRangeField != null) { diff --git a/src/main/java/org/branulf/toolbox/ModConfig.java b/src/main/java/org/branulf/toolbox/ModConfig.java index 820f8b2..4c446ad 100644 --- a/src/main/java/org/branulf/toolbox/ModConfig.java +++ b/src/main/java/org/branulf/toolbox/ModConfig.java @@ -35,8 +35,15 @@ public class ModConfig implements ConfigData { @ConfigEntry.Gui.TransitiveObject public PlayerDetectorConfig playerDetector = new PlayerDetectorConfig(); + // 伤害记录 + @ConfigEntry.Category("damage_logger_settings") + @ConfigEntry.Gui.TransitiveObject + public DamageLoggerConfig damageLogger = new DamageLoggerConfig(); + // ————————————————————分割线———————————————————— + + // 诸葛连弩 public static class AutoCrossbowConfig implements ConfigData { @ConfigEntry.Gui.Tooltip(count = 0) public boolean enabled = false; @@ -44,8 +51,15 @@ public class ModConfig implements ConfigData { @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) @ConfigEntry.Gui.Tooltip(count = 0) public AutoCrossbowMode currentMode = AutoCrossbowMode.NORMAL; + + @ConfigEntry.Gui.Tooltip(count = 1) + public boolean compatibilityMode = false; + + @ConfigEntry.Gui.Tooltip(count = 1) + public boolean ultimateCompatibilityMode = false; } + // Scrafitipty public static class ScrafitiptyPlusConfigPart implements ConfigData { @ConfigEntry.Gui.Tooltip(count = 1) public int port = 8765; @@ -68,6 +82,7 @@ public class ModConfig implements ConfigData { } } + // 挂机 public static class CrazyLookConfig implements ConfigData { @ConfigEntry.Gui.Tooltip(count = 0) public boolean enabled = false; @@ -76,12 +91,14 @@ public class ModConfig implements ConfigData { public int shakeFrequency = 2; } + // 鞘翅推进优化 public static class ElytraBoostConfigPart implements ConfigData { @ConfigEntry.Gui.Tooltip(count = 1) public boolean modEnabled = true; } + // 玩家探测器 public static class PlayerDetectorConfig implements ConfigData { @ConfigEntry.Gui.Tooltip(count = 0) public boolean enabled = false; @@ -94,4 +111,13 @@ public class ModConfig implements ConfigData { @ConfigEntry.BoundedDiscrete(min = 1, max = 20) public int alertFrequency = 1; } + + // 伤害记录 + public static class DamageLoggerConfig implements ConfigData { + @ConfigEntry.Gui.Tooltip(count = 0) + public boolean enabled = false; + + @ConfigEntry.Gui.Tooltip(count = 1) + public boolean rememberState = false; + } } diff --git a/src/main/java/org/branulf/toolbox/autocrossbow/AutoCrossbowHandler.java b/src/main/java/org/branulf/toolbox/autocrossbow/AutoCrossbowHandler.java index fd2046b..1869c53 100644 --- a/src/main/java/org/branulf/toolbox/autocrossbow/AutoCrossbowHandler.java +++ b/src/main/java/org/branulf/toolbox/autocrossbow/AutoCrossbowHandler.java @@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.MinecraftClient; +import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.CrossbowItem; @@ -22,6 +23,7 @@ import net.minecraft.text.Text; import net.minecraft.util.Hand; import java.util.Arrays; +import java.util.List; public class AutoCrossbowHandler { @@ -33,14 +35,19 @@ public class AutoCrossbowHandler { private long lastActionTime = 0; private static final long ACTION_COOLDOWN_MS = 50; + private boolean isCrossbowInternallyCharged = false; + private ItemStack lastHeldCrossbowStack = ItemStack.EMPTY; + public AutoCrossbowHandler(ModConfig mainConfig) { this.config = mainConfig.autocrossbow; registerCommands(); + Main.LOGGER.info("BRanulf的实用工具箱 - 诸葛连弩模块已加载!"); } public void onClientTick(MinecraftClient client) { if (!config.enabled || client.player == null || client.interactionManager == null) { resetActionStates(); + lastHeldCrossbowStack = ItemStack.EMPTY; return; } @@ -52,9 +59,17 @@ public class AutoCrossbowHandler { if (!(stack.getItem() instanceof CrossbowItem)) { resetActionStates(); + lastHeldCrossbowStack = ItemStack.EMPTY; return; } + if (!ItemStack.areItemsAndComponentsEqual(stack, lastHeldCrossbowStack)) { + isCrossbowInternallyCharged = false; + chargeProgress = 0; + lastHeldCrossbowStack = stack.copy(); + } + + switch (config.currentMode) { case NORMAL: handleNormalMode(client, player, hand, stack); @@ -74,27 +89,64 @@ public class AutoCrossbowHandler { wasRightClickPressed = false; chargeProgress = 0; lastActionTime = 0; + isCrossbowInternallyCharged = false; + } + + private boolean isCrossbowEffectivelyCharged(ItemStack stack) { + if (config.compatibilityMode) { + List chargedProjectiles = stack.get(DataComponentTypes.CHARGED_PROJECTILES).getProjectiles(); + return chargedProjectiles != null && !chargedProjectiles.isEmpty(); + } else { + return CrossbowItem.isCharged(stack); + } } private void handleNormalMode(MinecraftClient client, PlayerEntity player, Hand hand, ItemStack stack) { - if (CrossbowItem.isCharged(stack) && isRightClickPressed) { - client.interactionManager.interactItem(player, hand); - return; - } - - if (isRightClickPressed) { - if (!player.isUsingItem()) { - client.interactionManager.interactItem(player, hand); - chargeProgress = 0; - } else if (player.getActiveHand() == hand) { - chargeProgress++; - int pullTime = CrossbowItem.getPullTime(stack, player); - if (chargeProgress >= pullTime * 1.1f) { - client.interactionManager.stopUsingItem(player); + if (config.ultimateCompatibilityMode) { + if (isRightClickPressed) { + if (isCrossbowInternallyCharged) { + client.interactionManager.interactItem(player, hand); + isCrossbowInternallyCharged = false; + chargeProgress = 0; + } else { + if (!player.isUsingItem()) { + client.interactionManager.interactItem(player, hand); + chargeProgress = 0; + } else if (player.getActiveHand() == hand) { + chargeProgress++; + int pullTime = CrossbowItem.getPullTime(stack, player); + if (chargeProgress >= pullTime * 1.1f) { + client.interactionManager.stopUsingItem(player); + isCrossbowInternallyCharged = true; + chargeProgress = 0; + } + } + } + } else { + if (wasRightClickPressed) { + chargeProgress = 0; } } - } else if (wasRightClickPressed) { - chargeProgress = 0; + } else { + if (isCrossbowEffectivelyCharged(stack) && isRightClickPressed) { + client.interactionManager.interactItem(player, hand); + return; + } + + if (isRightClickPressed) { + if (!player.isUsingItem()) { + client.interactionManager.interactItem(player, hand); + chargeProgress = 0; + } else if (player.getActiveHand() == hand) { + chargeProgress++; + int pullTime = CrossbowItem.getPullTime(stack, player); + if (chargeProgress >= pullTime * 1.1f) { + client.interactionManager.stopUsingItem(player); + } + } + } else if (wasRightClickPressed) { + chargeProgress = 0; + } } } @@ -107,7 +159,7 @@ public class AutoCrossbowHandler { return; } - if (CrossbowItem.isCharged(stack)) { + if (isCrossbowEffectivelyCharged(stack)) { client.interactionManager.interactItem(player, hand); player.swingHand(hand); lastActionTime = System.currentTimeMillis(); @@ -118,7 +170,7 @@ public class AutoCrossbowHandler { swapToSlot(client, player, nextSlot); } else { player.sendMessage(Text.translatable("branulf_toolbox.autocrossbow.no_more_charged"), true); - config.currentMode = AutoCrossbowMode.NORMAL; + // config.currentMode = AutoCrossbowMode.NORMAL; Main.saveConfig(); } } @@ -142,11 +194,11 @@ public class AutoCrossbowHandler { swapToSlot(client, player, nextSlot); } else { player.sendMessage(Text.translatable("branulf_toolbox.autocrossbow.no_more_uncharged"), true); - config.currentMode = AutoCrossbowMode.NORMAL; + // config.currentMode = AutoCrossbowMode.NORMAL; Main.saveConfig(); } } - } else if (!CrossbowItem.isCharged(stack)) { + } else if (!isCrossbowEffectivelyCharged(stack)) { if (System.currentTimeMillis() - lastActionTime < ACTION_COOLDOWN_MS) { return; } @@ -163,7 +215,7 @@ public class AutoCrossbowHandler { lastActionTime = System.currentTimeMillis(); } else { player.sendMessage(Text.translatable("branulf_toolbox.autocrossbow.no_more_uncharged"), true); - config.currentMode = AutoCrossbowMode.NORMAL; + // config.currentMode = AutoCrossbowMode.NORMAL; Main.saveConfig(); } } @@ -174,7 +226,7 @@ public class AutoCrossbowHandler { if (i == currentSelectedSlot) continue; ItemStack stack = inventory.getStack(i); if (stack.getItem() instanceof CrossbowItem) { - boolean isCharged = CrossbowItem.isCharged(stack); + boolean isCharged = isCrossbowEffectivelyCharged(stack); if (chargedRequired == isCharged) { return i; } @@ -183,7 +235,7 @@ public class AutoCrossbowHandler { for (int i = PlayerInventory.HOTBAR_SIZE; i < inventory.main.size(); i++) { ItemStack stack = inventory.getStack(i); if (stack.getItem() instanceof CrossbowItem) { - boolean isCharged = CrossbowItem.isCharged(stack); + boolean isCharged = isCrossbowEffectivelyCharged(stack); if (chargedRequired == isCharged) { return i; } diff --git a/src/main/java/org/branulf/toolbox/crazylook/CrazyLookHandler.java b/src/main/java/org/branulf/toolbox/crazylook/CrazyLookHandler.java index 44f1677..4ab0c92 100644 --- a/src/main/java/org/branulf/toolbox/crazylook/CrazyLookHandler.java +++ b/src/main/java/org/branulf/toolbox/crazylook/CrazyLookHandler.java @@ -5,12 +5,19 @@ import org.branulf.toolbox.ModConfig; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; + public class CrazyLookHandler { private ModConfig.CrazyLookConfig config; private int shakeCounter = 0; + /** + * 你来这里干啥? + * 我随便写的,我记得那天喝多了。 + */ + public CrazyLookHandler(ModConfig mainConfig) { this.config = mainConfig.crazyLook; + Main.LOGGER.info("BRanulf的实用工具箱 - 夏季八看摇头晃脑挂机模块已加载!"); } public void onClientTick(MinecraftClient client) { diff --git a/src/main/java/org/branulf/toolbox/damagelogger/DamageLoggerHandler.java b/src/main/java/org/branulf/toolbox/damagelogger/DamageLoggerHandler.java new file mode 100644 index 0000000..b3aba7f --- /dev/null +++ b/src/main/java/org/branulf/toolbox/damagelogger/DamageLoggerHandler.java @@ -0,0 +1,410 @@ +package org.branulf.toolbox.damagelogger; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.DamageTypes; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.registry.tag.DamageTypeTags; +import net.minecraft.registry.tag.FluidTags; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import org.branulf.toolbox.Main; +import org.branulf.toolbox.ModConfig; + +import java.awt.*; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class DamageLoggerHandler { + private final ModConfig.DamageLoggerConfig config; + private boolean isLogging = false; + private String logFileName; + private FileWriter csvWriter; + private final Queue recordQueue = new ConcurrentLinkedQueue<>(); + + private float lastKnownHealth = -1.0F; + + /** + * 个人感觉,这里不需要进行本土化,所以这里直接使用中文。 + * 主要还是懒,不想加太多键,也不想让语言文件乱七八糟。 + * 部分方法使用了人工智能,因为我不喜欢繁琐的重复写这些乱七八糟的。 + *

+ * if ur Chinese not good, or u never learned Chinese, recommended to download Du####go.(or modify these method) + */ + + public DamageLoggerHandler(ModConfig.DamageLoggerConfig config) { + this.config = config; + setupEventListeners(); + Main.LOGGER.info("BRanulf的实用工具箱 - 伤害记录模块已加载!"); + } + + private void setupEventListeners() { + ServerLivingEntityEvents.AFTER_DAMAGE.register((entity, source, baseDamageTaken, damageTaken, blocked) -> { + if (entity instanceof PlayerEntity player && player == MinecraftClient.getInstance().player && isLogging) { + if (MinecraftClient.getInstance().isIntegratedServerRunning()) { + float newHealth = player.getHealth(); + float originalHealth = newHealth + damageTaken; + recordDamage(source, damageTaken, originalHealth, newHealth); + } + } + }); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (client.player == null) { + lastKnownHealth = -1.0F; + return; + } + + if (isLogging) { + float currentHealth = client.player.getHealth(); + + if (lastKnownHealth == -1.0F) { + lastKnownHealth = currentHealth; + } else if (currentHealth < lastKnownHealth) { + float damageAmount = lastKnownHealth - currentHealth; + if (!client.isIntegratedServerRunning()) { + recordClientSideDamage(damageAmount, lastKnownHealth, currentHealth); + } + lastKnownHealth = currentHealth; + } else if (currentHealth > lastKnownHealth) { + lastKnownHealth = currentHealth; + } + } else { + lastKnownHealth = -1.0F; + } + + if (isLogging && !recordQueue.isEmpty()) { + processRecordQueue(); + } + }); + + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + if (isLogging) { + stopLogging(); + if (!config.rememberState) { + config.enabled = false; + Main.saveConfig(); + } + } + }); + } + + public void startLogging() { + if (isLogging) return; + + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null) { + if (client.inGameHud != null) { + client.inGameHud.getChatHud().addMessage(Text.literal("§c[伤害记录]§f 无法开始记录:玩家未加载或未进入世界。")); + } else { + System.err.println("[伤害记录] 无法开始记录:玩家未加载或未进入世界。"); + } + return; + } + + if (!client.isIntegratedServerRunning()) { + client.player.sendMessage(Text.literal("§e[伤害记录]§f 注意:此功能在专用服务器上只能通过客户端状态推断伤害,可能不完全准确。详细记录仅在单人/局域网游戏有效。"), false); + } + + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + String timestamp = dateFormat.format(new Date()); + logFileName = "伤害记录_" + timestamp + ".csv"; + + Path logDir = Paths.get("damage_logs"); + if (!Files.exists(logDir)) { + Files.createDirectories(logDir); + } + + Path logFile = logDir.resolve(logFileName); + csvWriter = new FileWriter(logFile.toFile(), true); + + csvWriter.write("时间,伤害来源,伤害类型,伤害数值,原血量,新血量,世界坐标,维度,原始伤害源数据\n"); + csvWriter.flush(); + + isLogging = true; + lastKnownHealth = client.player.getHealth(); + client.player.sendMessage(Text.literal("§a[伤害记录]§f 已开始记录伤害。"), false); + + } catch (IOException e) { + e.printStackTrace(); + if (client.player != null) { + client.player.sendMessage(Text.literal("§c[伤害记录]§f 启动失败: " + e.getMessage()), false); + } + } + } + + public void stopLogging() { + if (!isLogging) return; + + if (csvWriter != null) { + try { + processRecordQueue(); + csvWriter.close(); + csvWriter = null; + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.sendMessage(Text.literal("§a[伤害记录]§f 已停止记录伤害,日志已保存。"), false); + } + } catch (IOException e) { + e.printStackTrace(); + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.sendMessage(Text.literal("§c[伤害记录]§f 停止失败: " + e.getMessage()), false); + } + } + } + isLogging = false; + lastKnownHealth = -1.0F; + } + + private void recordDamage(DamageSource source, float amount, float originalHealth, float newHealth) { + if (MinecraftClient.getInstance().player == null) return; + + String sourceName = getSourceName(source); + String damageType = getDamageType(source); + String rawSourceData = source.toString(); + + DamageRecord record = new DamageRecord( + new Date(), + sourceName, + damageType, + amount, + originalHealth, + newHealth, + MinecraftClient.getInstance().player.getBlockPos(), + MinecraftClient.getInstance().player.getWorld().getRegistryKey().getValue().toString(), + rawSourceData + ); + recordQueue.add(record); + } + + private void recordClientSideDamage(float amount, float originalHealth, float newHealth) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null) return; + + String sourceName = "未知来源 (客户端推断)"; + String damageType = inferClientSideDamageType(client.player); + String rawSourceData = "客户端推断伤害,无原始DamageSource数据"; + + if (client.player.getLastAttacker() != null && client.player.age - client.player.getLastAttackedTime() < 20) { + sourceName = client.player.getLastAttacker().getName().getString() + " (客户端推断)"; + damageType = "攻击伤害 (客户端推断)"; + } + + DamageRecord record = new DamageRecord( + new Date(), + sourceName, + damageType, + amount, + originalHealth, + newHealth, + client.player.getBlockPos(), + client.player.getWorld().getRegistryKey().getValue().toString(), + rawSourceData + ); + recordQueue.add(record); + } + + private void processRecordQueue() { + try { + while (!recordQueue.isEmpty()) { + DamageRecord record = recordQueue.poll(); + if (record != null) { + String csvLine = String.format("%s,%s,%s,%.1f,%.1f,%.1f,%s,%s,%s\n", + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(record.timestamp), + escapeCsv(record.sourceName), + escapeCsv(record.damageType), + record.amount, + record.originalHealth, + record.newHealth, + escapeCsv(record.position.toString()), + escapeCsv(record.dimension), + escapeCsv(record.rawDamageSourceData) + ); + csvWriter.write(csvLine); + } + } + csvWriter.flush(); + } catch (IOException e) { + e.printStackTrace(); + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.sendMessage(Text.literal("§c[伤害记录]§f 写入日志失败: " + e.getMessage()), false); + } + } + } + + private String escapeCsv(String input) { + if (input == null) return ""; + if (input.contains(",") || input.contains("\"") || input.contains("\n")) { + return "\"" + input.replace("\"", "\"\"") + "\""; + } + return input; + } + + public boolean isLogging() { + return isLogging; + } + + public void openLogFolder() { + Path logDir = Paths.get("damage_logs").toAbsolutePath(); + MinecraftClient client = MinecraftClient.getInstance(); + + try { + if (!Files.exists(logDir)) { + Files.createDirectories(logDir); + } + + boolean opened = false; + + try { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { + Desktop.getDesktop().open(logDir.toFile()); + opened = true; + } + } catch (HeadlessException e) { + System.err.println("[伤害记录] 桌面API报告了无头环境。尝试替代方法。"); + } catch (IOException | SecurityException e) { + e.printStackTrace(); + if (client.player != null) { + client.player.sendMessage(Text.literal("§c[伤害记录]§f 尝试使用Java Desktop API打开日志文件夹失败: " + e.getMessage()), false); + } + } + + if (!opened && System.getProperty("os.name").toLowerCase().contains("win")) { + try { + String command = "cmd /c start \"\" \"" + logDir.toAbsolutePath().toString() + "\""; + Process process = Runtime.getRuntime().exec(command); + opened = true; + if (client.player != null) { + client.player.sendMessage(Text.literal("§a[伤害记录]§f 已尝试使用Windows命令打开日志文件夹。"), false); + } + } catch (IOException e) { + e.printStackTrace(); + if (client.player != null) { + client.player.sendMessage(Text.literal("§c[伤害记录]§f 尝试使用Windows命令打开日志文件夹失败: " + e.getMessage()), false); + } + } + } + + if (!opened) { + if (client.player != null) { + client.player.sendMessage(Text.literal("§e[伤害记录]§f 自动打开日志文件夹失败。请手动前往: " + logDir.toAbsolutePath()), false); + } + System.err.println("[伤害记录] 无法自动打开日志文件夹。请手动打开: " + logDir.toAbsolutePath()); + } + } catch (IOException e) { + e.printStackTrace(); + if (client.player != null) { + client.player.sendMessage(Text.literal("§c[伤害记录]§f 创建日志文件夹失败: " + e.getMessage()), false); + } + } + } + + private String getSourceName(DamageSource source) { + if (source.getAttacker() != null) { + return source.getAttacker().getName().getString(); + } + return source.getName(); + } + + private String getDamageType(DamageSource source) { + if (source.isOf(DamageTypes.OUT_OF_WORLD)) return "虚空伤害"; + if (source.isOf(DamageTypes.BAD_RESPAWN_POINT)) return "错误重生点爆炸伤害"; + if (source.isOf(DamageTypes.SONIC_BOOM)) return "音爆伤害"; + if (source.isOf(DamageTypes.MACE_SMASH)) return "狼牙棒猛击伤害"; + if (source.isOf(DamageTypes.WITHER)) return "凋零伤害"; + if (source.isOf(DamageTypes.MAGIC) || source.isOf(DamageTypes.INDIRECT_MAGIC)) return "魔法伤害"; + if (source.isOf(DamageTypes.DRAGON_BREATH)) return "龙息伤害"; + if (source.isOf(DamageTypes.STARVE)) return "饥饿伤害"; + if (source.isOf(DamageTypes.DROWN)) return "溺水伤害"; + if (source.isOf(DamageTypes.FREEZE)) return "冰冻伤害"; + if (source.isOf(DamageTypes.LIGHTNING_BOLT)) return "闪电伤害"; + if (source.isOf(DamageTypes.THORNS)) return "荆棘伤害"; + if (source.isOf(DamageTypes.CACTUS)) return "仙人掌伤害"; + if (source.isOf(DamageTypes.SWEET_BERRY_BUSH)) return "甜浆果丛伤害"; + if (source.isOf(DamageTypes.STALAGMITE)) return "钟乳石伤害"; + if (source.isOf(DamageTypes.FLY_INTO_WALL)) return "撞墙伤害"; + if (source.isOf(DamageTypes.IN_WALL)) return "窒息伤害 (卡墙)"; + if (source.isOf(DamageTypes.CRAMMING)) return "挤压伤害"; + if (source.isOf(DamageTypes.DRY_OUT)) return "脱水伤害"; + if (source.isOf(DamageTypes.HOT_FLOOR)) return "热地板伤害"; + if (source.isOf(DamageTypes.FALLING_ANVIL) || source.isOf(DamageTypes.FALLING_STALACTITE) || source.isOf(DamageTypes.FALLING_BLOCK)) return "掉落方块伤害"; + + if (source.isOf(DamageTypes.IN_FIRE) || source.isOf(DamageTypes.ON_FIRE) || source.isOf(DamageTypes.LAVA) || source.isOf(DamageTypes.CAMPFIRE) || source.isOf(DamageTypes.FIREBALL) || source.isOf(DamageTypes.UNATTRIBUTED_FIREBALL)) return "火焰/燃烧伤害"; + + if (source.isOf(DamageTypes.ARROW) || source.isOf(DamageTypes.TRIDENT) || source.isOf(DamageTypes.MOB_PROJECTILE) || + source.isOf(DamageTypes.SPIT) || source.isOf(DamageTypes.WIND_CHARGE) || source.isOf(DamageTypes.FIREWORKS) || + source.isOf(DamageTypes.WITHER_SKULL) || source.isOf(DamageTypes.THROWN) || source.isOf(DamageTypes.ENDER_PEARL)) return "弹射物伤害"; + + if (source.isOf(DamageTypes.EXPLOSION) || source.isOf(DamageTypes.PLAYER_EXPLOSION)) return "爆炸伤害"; + + if (source.isOf(DamageTypes.MOB_ATTACK) || source.isOf(DamageTypes.PLAYER_ATTACK) || source.isOf(DamageTypes.MOB_ATTACK_NO_AGGRO) || source.isOf(DamageTypes.STING)) return "近战/攻击伤害"; + + if (source.isIn(DamageTypeTags.IS_PROJECTILE)) return "弹射物伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_EXPLOSION)) return "爆炸伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_FIRE)) return "火焰伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_FALL)) return "坠落伤害"; + if (source.isIn(DamageTypeTags.IS_DROWNING)) return "溺水伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_FREEZING)) return "冰冻伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_LIGHTNING)) return "闪电伤害 (通用)"; + if (source.isIn(DamageTypeTags.IS_PLAYER_ATTACK)) return "玩家攻击伤害"; + if (source.isIn(DamageTypeTags.MACE_SMASH)) return "狼牙棒猛击伤害 (通用)"; + + if (source.isOf(DamageTypes.GENERIC)) return "环境伤害 (通用)"; + if (source.isOf(DamageTypes.GENERIC_KILL)) return "通用击杀伤害"; + if (source.isOf(DamageTypes.OUTSIDE_BORDER)) return "世界边界伤害"; + if (source.isOf(DamageTypes.FALL)) return "坠落伤害 (通用)"; + + if (source.getAttacker() != null) { + return "攻击伤害 (未分类)"; + } + + return "其他伤害 (" + source.getName() + ")"; + } + + private String inferClientSideDamageType(PlayerEntity player) { + if (player.isOnFire()) return "火焰/燃烧伤害 (推断)"; + if (player.isInLava()) return "岩浆伤害 (推断)"; + if (player.isSubmergedIn(FluidTags.WATER) && player.getAir() == 0) return "溺水伤害 (推断)"; + if (player.hasStatusEffect(StatusEffects.POISON)) return "中毒伤害 (推断)"; + if (player.hasStatusEffect(StatusEffects.WITHER)) return "凋零伤害 (推断)"; + + return "未知伤害 (客户端推断)"; + } + + private static class DamageRecord { + Date timestamp; + String sourceName; + String damageType; + float amount; + float originalHealth; + float newHealth; + BlockPos position; + String dimension; + String rawDamageSourceData; + + public DamageRecord(Date timestamp, String sourceName, String damageType, float amount, + float originalHealth, float newHealth, + BlockPos position, String dimension, String rawDamageSourceData) { + this.timestamp = timestamp; + this.sourceName = sourceName; + this.damageType = damageType; + this.amount = amount; + this.originalHealth = originalHealth; + this.newHealth = newHealth; + this.position = position; + this.dimension = dimension; + this.rawDamageSourceData = rawDamageSourceData; + } + } +} diff --git a/src/main/java/org/branulf/toolbox/elytraboost/ElytraBoostCommon.java b/src/main/java/org/branulf/toolbox/elytraboost/ElytraBoostCommon.java index 9a71deb..92d8186 100644 --- a/src/main/java/org/branulf/toolbox/elytraboost/ElytraBoostCommon.java +++ b/src/main/java/org/branulf/toolbox/elytraboost/ElytraBoostCommon.java @@ -2,15 +2,11 @@ package org.branulf.toolbox.elytraboost; import org.branulf.toolbox.Main; import net.fabricmc.api.ModInitializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class ElytraBoostCommon implements ModInitializer { - public static final String MOD_ID = Main.MOD_ID; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override public void onInitialize() { - LOGGER.info("BRanulf的实用工具箱 - 鞘翅推进优化模块已加载!"); + Main.LOGGER.info("BRanulf的实用工具箱 - 鞘翅推进优化模块已加载!"); } } diff --git a/src/main/java/org/branulf/toolbox/playerdetector/PlayerDetectorHandler.java b/src/main/java/org/branulf/toolbox/playerdetector/PlayerDetectorHandler.java index 45fa267..cfdc947 100644 --- a/src/main/java/org/branulf/toolbox/playerdetector/PlayerDetectorHandler.java +++ b/src/main/java/org/branulf/toolbox/playerdetector/PlayerDetectorHandler.java @@ -6,14 +6,20 @@ import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; +import org.branulf.toolbox.Main; import org.branulf.toolbox.ModConfig; public class PlayerDetectorHandler { private final ModConfig config; private int ticksSinceLastAlert = 0; + /** + * 我已经不止一次在服务器挂机被偷屁股了。 + */ + public PlayerDetectorHandler(ModConfig config) { this.config = config; + Main.LOGGER.info("BRanulf的实用工具箱 - 玩家检测模块已加载!"); } public void onClientTick(MinecraftClient client) { diff --git a/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusDebug.java b/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusDebug.java index c8ca102..c08ed57 100644 --- a/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusDebug.java +++ b/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusDebug.java @@ -10,7 +10,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class ScrafitiptyPlusDebug { - private static Logger logger = LogManager.getLogger(Main.MOD_ID); + public static Logger logger = LogManager.getLogger(Main.MOD_ID); public static void setLogger(String modId) { logger = LogManager.getLogger(modId); diff --git a/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusHandler.java b/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusHandler.java index a853782..406598b 100644 --- a/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusHandler.java +++ b/src/main/java/org/branulf/toolbox/scrafitiptyplus/ScrafitiptyPlusHandler.java @@ -1,5 +1,7 @@ package org.branulf.toolbox.scrafitiptyplus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.branulf.toolbox.Main; import org.branulf.toolbox.ModConfig; import com.google.gson.JsonObject; @@ -23,6 +25,7 @@ import java.util.*; public class ScrafitiptyPlusHandler { public static final String MOD_ID = Main.MOD_ID; + private static Logger LOGGER = ScrafitiptyPlusDebug.logger; private ModConfig.ScrafitiptyPlusConfigPart config; private ScriptWebSocketServer webSocketServerInstance; @@ -35,11 +38,13 @@ public class ScrafitiptyPlusHandler { public ScrafitiptyPlusHandler(ModConfig mainConfig) { this.config = mainConfig.scrafitiptyPlus; ScrafitiptyPlusDebug.setLogger(Main.MOD_ID); - ScrafitiptyPlusDebug.info("Scrafitipty Plus Mod 正在初始化..."); + ScrafitiptyPlusDebug.info("Scrafitipty Plus 模块正在初始化..."); if (this.config.enableMode == ModConfig.ScrafitiptyPlusConfigPart.WebSocketEnableMode.ALWAYS) { startWebSocketServer(); } + + LOGGER.info("BRanulf的实用工具箱 - Scrafitipty Plus(Websocket控制)模块已加载!"); } public boolean isServerRunning() { diff --git a/src/main/resources/assets/branulf_toolbox/lang/en_us.json b/src/main/resources/assets/branulf_toolbox/lang/en_us.json index 984222f..7e7a911 100644 --- a/src/main/resources/assets/branulf_toolbox/lang/en_us.json +++ b/src/main/resources/assets/branulf_toolbox/lang/en_us.json @@ -6,12 +6,15 @@ "branulf_toolbox.enabled": "Enabled", "branulf_toolbox.disabled": "Disabled", + "branulf_toolbox.commit": "im really frustrated, why i cant use comments on JSON!", + "text.autoconfig.branulf_toolbox.title": "BRanulf's Toolbox Configuration", "text.autoconfig.branulf_toolbox.category.autocrossbow_settings": "Auto Crossbow Settings", "text.autoconfig.branulf_toolbox.category.scrafitipty_plus_settings": "Scrafitipty Plus Settings", "text.autoconfig.branulf_toolbox.category.crazylook_settings": "Crazy Look Settings", "text.autoconfig.branulf_toolbox.category.elytraboost_settings": "Elytra Boost Settings", "text.autoconfig.branulf_toolbox.category.player_detector_settings": "Player Detector Settings", + "text.autoconfig.branulf_toolbox.category.damage_logger_settings": "Damage Logger Settings", "gui.branulf_toolbox.autocrossbow.title": "Auto Crossbow", "gui.branulf_toolbox.autocrossbow.enabled_on": "Enable Auto Crossbow", @@ -29,6 +32,10 @@ "branulf_toolbox.autocrossbow.command.invalid_mode": "Invalid mode: '%s'. Available modes: normal, rapid_shoot, rapid_load.", "text.autoconfig.branulf_toolbox.option.autocrossbow.enabled": "Enable Auto Crossbow", "text.autoconfig.branulf_toolbox.option.autocrossbow.currentMode": "Current Mode", + "text.autoconfig.branulf_toolbox.option.autocrossbow.compatibilityMode": "Compatibility Mode", + "text.autoconfig.branulf_toolbox.option.autocrossbow.compatibilityMode.@Tooltip": "If enabled, a crossbow with any projectile loaded will be treated as charged, regardless of its visual state. Useful for servers where loaded crossbows don't visually update.", + "text.autoconfig.branulf_toolbox.option.autocrossbow.ultimateCompatibilityMode": "Ultimate Compatibility Mode (Normal Mode Only)", + "text.autoconfig.branulf_toolbox.option.autocrossbow.ultimateCompatibilityMode.@Tooltip": "Overrides all other crossbow charge checks in Normal Mode. If charging is complete, it's considered charged. If just fired, it's uncharged. Ignores actual item state.", "gui.branulf_toolbox.scrafitipty_plus.title": "Scrafitipty Plus (WebSocket)", "gui.branulf_toolbox.scrafitipty_plus.toggle_server": "Toggle WebSocket Server", @@ -77,5 +84,13 @@ "text.autoconfig.branulf_toolbox.option.playerDetector.detectionRange": "Detection Range (Blocks)", "text.autoconfig.branulf_toolbox.option.playerDetector.detectionRange.@Tooltip": "The radius in blocks to detect other players (1-64).", "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency": "Alert Frequency (times/sec)", - "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency.@Tooltip": "How many times per second to play the alert sound when a player is detected (1-20)." + "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency.@Tooltip": "How many times per second to play the alert sound when a player is detected (1-20).", + + "gui.branulf_toolbox.damage_logger.title": "Damage Logger", + "gui.branulf_toolbox.damage_logger.enable": "Enable Damage Logger", + "gui.branulf_toolbox.damage_logger.disable": "Disable Damage Logger", + "gui.branulf_toolbox.damage_logger.open_folder": "Open Log Folder", + "text.autoconfig.branulf_toolbox.option.damageLogger.enabled": "Enable Damage Logger", + "text.autoconfig.branulf_toolbox.option.damageLogger.rememberState": "Remember Logging State", + "text.autoconfig.branulf_toolbox.option.damageLogger.rememberState.@Tooltip": "If enabled, the damage logger will resume logging automatically when you restart the game if it was active. If disabled, it will turn off on game exit." } diff --git a/src/main/resources/assets/branulf_toolbox/lang/zh_cn.json b/src/main/resources/assets/branulf_toolbox/lang/zh_cn.json index 303759c..0907826 100644 --- a/src/main/resources/assets/branulf_toolbox/lang/zh_cn.json +++ b/src/main/resources/assets/branulf_toolbox/lang/zh_cn.json @@ -6,12 +6,15 @@ "branulf_toolbox.enabled": "已启用", "branulf_toolbox.disabled": "已禁用", + "branulf_toolbox.commit": "真的服了,为毛JSON不让我使用注释!", + "text.autoconfig.branulf_toolbox.title": "BRanulf的实用工具箱配置", "text.autoconfig.branulf_toolbox.category.autocrossbow_settings": "诸葛连弩设置", "text.autoconfig.branulf_toolbox.category.scrafitipty_plus_settings": "Scrafitipty Plus 设置", - "text.autoconfig.branulf_toolbox.category.crazylook_settings": "疯狂视角设置", + "text.autoconfig.branulf_toolbox.category.crazylook_settings": "摇晃挂机设置", "text.autoconfig.branulf_toolbox.category.elytraboost_settings": "鞘翅推进优化设置", "text.autoconfig.branulf_toolbox.category.player_detector_settings": "玩家探测器设置", + "text.autoconfig.branulf_toolbox.category.damage_logger_settings": "伤害记录设置", "gui.branulf_toolbox.autocrossbow.title": "诸葛连弩", "gui.branulf_toolbox.autocrossbow.enabled_on": "启用诸葛连弩", @@ -29,6 +32,10 @@ "branulf_toolbox.autocrossbow.command.invalid_mode": "无效模式:“%s”。可用模式:normal, rapid_shoot, rapid_load。", "text.autoconfig.branulf_toolbox.option.autocrossbow.enabled": "启用诸葛连弩", "text.autoconfig.branulf_toolbox.option.autocrossbow.currentMode": "当前模式", + "text.autoconfig.branulf_toolbox.option.autocrossbow.compatibilityMode": "兼容模式", + "text.autoconfig.branulf_toolbox.option.autocrossbow.compatibilityMode.@Tooltip": "启用时,只要弩有填充物(无论是否已装填完毕),就判断为已装填好的弩。适用于服务器插件导致已装填弩外观不改变的情况。", + "text.autoconfig.branulf_toolbox.option.autocrossbow.ultimateCompatibilityMode": "最终兼容模式 (仅正常模式)", + "text.autoconfig.branulf_toolbox.option.autocrossbow.ultimateCompatibilityMode.@Tooltip": "在正常模式下,此选项会覆盖所有其他弩装填状态检查。如果蓄力完成,则视为已装填;如果刚射击过,则视为未装填。不依赖物品实际状态。", "gui.branulf_toolbox.scrafitipty_plus.title": "Scrafitipty Plus (WebSocket)", "gui.branulf_toolbox.scrafitipty_plus.toggle_server": "切换WebSocket服务器", @@ -77,5 +84,13 @@ "text.autoconfig.branulf_toolbox.option.playerDetector.detectionRange": "探测范围(方块)", "text.autoconfig.branulf_toolbox.option.playerDetector.detectionRange.@Tooltip": "探测其他玩家的半径范围(1-64方块)。", "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency": "提示频率(次/秒)", - "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency.@Tooltip": "当探测到玩家时,每秒播放提示音的次数(1-20)。" + "text.autoconfig.branulf_toolbox.option.playerDetector.alertFrequency.@Tooltip": "当探测到玩家时,每秒播放提示音的次数(1-20)。", + + "gui.branulf_toolbox.damage_logger.title": "伤害记录", + "gui.branulf_toolbox.damage_logger.enable": "启用伤害记录", + "gui.branulf_toolbox.damage_logger.disable": "禁用伤害记录", + "gui.branulf_toolbox.damage_logger.open_folder": "打开日志文件夹", + "text.autoconfig.branulf_toolbox.option.damageLogger.enabled": "启用伤害记录", + "text.autoconfig.branulf_toolbox.option.damageLogger.rememberState": "记住开关状态", + "text.autoconfig.branulf_toolbox.option.damageLogger.rememberState.@Tooltip": "如果启用,当您退出游戏时,伤害记录功能将保存当前状态并在下次进入游戏时自动恢复。如果禁用,退出游戏后将关闭伤害记录功能。" }