This commit is contained in:
BRanulf_Explode 2025-08-23 23:11:53 +08:00
parent 7a8834d003
commit 8a64b024ef
13 changed files with 648 additions and 40 deletions

View File

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

View File

@ -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的处理程序均在单独的软件包(文件夹)中以便于维护
* <p>
* 为什么会存在这个mod
* 因为作者制作了多个小mod但是为了防止以后数量多导致的维护困难作者决定将这些mod进行整合并创建此mod
* 这不是个什么正经项目只是作者想把一些小mod整合起来方便管理
* 因为作者制作了多个小mod但是为了防止以后数量多导致的维护困难作者决定将这些mod进行整合并创建此mod
* 这不是个什么正经项目只是作者想把一些小mod整合起来方便管理
* <p>
* 我不确定以后我会不会做扩展包就像carpet那样
* 老实说我能改掉一些开发中的坏习惯已经很累了在这之前很多时候变量名我都是瞎写的我有几个py的脚本写的全是屎山从写jvav开始才改了我的部分坏习惯
* 同时人工智能真好用哈哈哈
* 我不确定以后我会不会做扩展包就像carpet那样
* 老实说我能改掉一些开发中的坏习惯已经很累了在这之前很多时候变量名我都是瞎写的我有几个py的脚本写的全是屎山从写jvav开始才改了我的部分坏习惯
* <p>
* 同时人工智能真好用之前很多部分都有依赖于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();

View File

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

View File

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

View File

@ -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,10 +89,46 @@ public class AutoCrossbowHandler {
wasRightClickPressed = false;
chargeProgress = 0;
lastActionTime = 0;
isCrossbowInternallyCharged = false;
}
private boolean isCrossbowEffectivelyCharged(ItemStack stack) {
if (config.compatibilityMode) {
List<ItemStack> 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) {
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 (isCrossbowEffectivelyCharged(stack) && isRightClickPressed) {
client.interactionManager.interactItem(player, hand);
return;
}
@ -97,6 +148,7 @@ public class AutoCrossbowHandler {
chargeProgress = 0;
}
}
}
private void handleRapidFireShootMode(MinecraftClient client, PlayerEntity player, Hand hand, ItemStack stack) {
if (!isRightClickPressed) {
@ -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;
}

View File

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

View File

@ -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<DamageRecord> recordQueue = new ConcurrentLinkedQueue<>();
private float lastKnownHealth = -1.0F;
/**
* 个人感觉这里不需要进行本土化所以这里直接使用中文
* 主要还是懒不想加太多键也不想让语言文件乱七八糟
* 部分方法使用了人工智能因为我不喜欢繁琐的重复写这些乱七八糟的
* <p>
* 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;
}
}
}

View File

@ -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的实用工具箱 - 鞘翅推进优化模块已加载!");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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