AutoStore 模组完整源码

这个提交包含在:
Lyizai
2026-08-17 04:12:56 +00:00
父节点 2593a219a2
当前提交 8ddec083bd
共修改 21 个文件,包含 3145 行新增和 0 行删除
@@ -0,0 +1,163 @@
/*
* 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152),
* 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。
*/
package autostore.client.monitor;
import autostore.client.territory.ChunkPos;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(value=EnvType.CLIENT)
/**
* 字幕/聊天监听器:解析游戏内消息,识别开垦进度与结果。
*
* <p>两条输入通道:
* <ul>
* <li>{@link #onSubtitle}:来自 HUD 字幕(如"开垦中 50%"),提取百分比进度</li>
* <li>{@link #onChat}:来自聊天栏(如"疆土(x,z) [bx,bz] 已为你的邦国开拓"),提取成功结果</li>
* </ul>
* 消息匹配采用模糊匹配(Levenshtein 距离),以兼容服务器端文案的细微差异。
*/
public final class SubtitleChatMonitor {
/** 匹配字幕中的百分比:如 "开垦中 50%" */
private static final Pattern PERCENT_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*%");
/** 匹配开垦成功的聊天消息:形如 "疆土(区块X,区块Z) [方块X,方块Z] 已为你的邦国开拓" */
private static final Pattern CLAIM_MESSAGE_PATTERN = Pattern.compile("\u7586\u571f[\uff08(]\\s*(-?\\d+)\\s*[\uff0c,]\\s*(-?\\d+)\\s*[)\uff09]\\s*\\[\\s*(-?\\d+)\\s*[\uff0c,]\\s*(-?\\d+)\\s*]\\s*\u5df2\u4e3a\u4f60\u7684\u90a6\u56fd\u5f00\u62d3");
/** 结果回调监听器 */
private final Listener listener;
public SubtitleChatMonitor(Listener listener) {
this.listener = listener;
}
/**
* 处理字幕消息:若包含"开垦"相关文本,则提取其中的百分比并回调进度。
* 使用模糊匹配容忍服务器文案的细微差异。
*/
public void onSubtitle(String text) {
if (text == null || text.isEmpty()) {
return;
}
if (!SubtitleChatMonitor.containsFuzzy(text, "\u5f00\u62d3\u4e2d", 2) && !SubtitleChatMonitor.containsFuzzy(text, "\u5f00\u62d3", 1)) {
return;
}
Matcher matcher = PERCENT_PATTERN.matcher(text);
if (matcher.find()) {
try {
int percent = (int)Math.round(Double.parseDouble(matcher.group(1)));
this.listener.onProgress(Math.max(0, Math.min(100, percent)));
}
catch (NumberFormatException numberFormatException) {
// 百分比解析失败时忽略
}
}
}
/**
* 处理聊天消息:优先匹配带坐标的开垦成功消息(回调 onSuccess),
* 否则模糊匹配"疆土已开拓/已为你的邦国开拓"等通用成功文案(回调 onGenericSuccess)。
*/
public void onChat(String text) {
if (text == null || text.isEmpty()) {
return;
}
Matcher matcher = CLAIM_MESSAGE_PATTERN.matcher(text);
if (matcher.find()) {
try {
ChunkPos chunk = new ChunkPos(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
int blockX = Integer.parseInt(matcher.group(3));
int blockZ = Integer.parseInt(matcher.group(4));
this.listener.onSuccess(chunk, blockX, blockZ);
return;
}
catch (NumberFormatException numberFormatException) {
// 坐标解析失败时忽略,尝试匹配通用消息
}
}
if (SubtitleChatMonitor.containsFuzzy(text, "\u7586\u571f\u5df2\u5f00\u62d3", 3) || SubtitleChatMonitor.containsFuzzy(text, "\u5df2\u4e3a\u4f60\u7684\u90a6\u56fd\u5f00\u62d3", 4)) {
this.listener.onGenericSuccess();
}
}
/**
* 模糊包含匹配:判断 text 中是否存在与 needle 编辑距离 ≤ maxEdits 的子串。
* 采用滑动窗口 + 有界 Levenshtein 距离,用于容忍服务器文案的细微差异。
*/
public static boolean containsFuzzy(String text, String needle, int maxEdits) {
if (needle == null || needle.isEmpty()) {
return true;
}
if (text == null) {
return false;
}
int n = text.length();
int m = needle.length();
if (m > n + maxEdits) {
return false;
}
boolean start = false;
int end = Math.min(n, m + maxEdits);
int best = maxEdits + 1;
for (int i = 0; i <= n; ++i) {
int from = Math.max(0, i - maxEdits);
int to = Math.min(n, i + m + maxEdits);
if (to - from < m) continue;
int j = from;
while (j + m <= to) {
int distance = SubtitleChatMonitor.boundedLevenshtein(text, j, needle, maxEdits + 1);
if (distance < best) {
best = distance;
}
if (best == 0) {
return true;
}
++j;
}
}
return best <= maxEdits;
}
/**
* 有界 Levenshtein 距离:计算 text 从 textOffset 起的子串与 needle 的编辑距离。
* 当某行最小距离超过 limit 时提前返回(剪枝优化),避免全量计算。
*/
private static int boundedLevenshtein(String text, int textOffset, String needle, int limit) {
int m = needle.length();
int[] previous = new int[m + 1];
int[] current = new int[m + 1];
for (int j = 0; j <= m; ++j) {
previous[j] = j;
}
int maxI = Math.min(m + limit, text.length() - textOffset);
for (int i = 1; i <= maxI; ++i) {
current[0] = i;
int rowMin = current[0];
for (int j = 1; j <= m; ++j) {
int cost = text.charAt(textOffset + i - 1) == needle.charAt(j - 1) ? 0 : 1;
current[j] = Math.min(Math.min(current[j - 1] + 1, previous[j] + 1), previous[j - 1] + cost);
rowMin = Math.min(rowMin, current[j]);
}
if (rowMin > limit) {
return limit + 1;
}
int[] swap = previous;
previous = current;
current = swap;
}
return previous[m];
}
/** 扩地结果回调接口:进度更新、开垦成功(带坐标)、开垦成功(通用) */
@Environment(value=EnvType.CLIENT)
public static interface Listener {
public void onProgress(int var1);
public void onSuccess(ChunkPos var1, int var2, int var3);
public void onGenericSuccess();
}
}