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
+ * 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": "如果启用,当您退出游戏时,伤害记录功能将保存当前状态并在下次进入游戏时自动恢复。如果禁用,退出游戏后将关闭伤害记录功能。"
}