AutoStore 模组完整源码
这个提交包含在:
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152),
|
||||
* 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。
|
||||
*/
|
||||
package autostore.client.territory;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
/**
|
||||
* 区块坐标(record),以“区块”为单位表示二维坐标。
|
||||
*
|
||||
* <p>Minecraft 中 1 个区块为 16×16 方块,本类提供方块坐标与区块坐标的换算,
|
||||
* 以及曼哈顿距离等常用工具方法。</p>
|
||||
*
|
||||
* @param x 区块 X 坐标(= 方块 X / 16)
|
||||
* @param z 区块 Z 坐标(= 方块 Z / 16)
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public record ChunkPos(int x, int z) {
|
||||
|
||||
/**
|
||||
* 由方块坐标换算为区块坐标(向下取整,兼容负数坐标)。
|
||||
*
|
||||
* @param blockX 方块 X 坐标
|
||||
* @param blockZ 方块 Z 坐标
|
||||
* @return 对应的区块坐标
|
||||
*/
|
||||
public static ChunkPos fromBlock(int blockX, int blockZ) {
|
||||
return new ChunkPos(Math.floorDiv(blockX, 16), Math.floorDiv(blockZ, 16));
|
||||
}
|
||||
|
||||
/** @return 本区块中心的方块 X 坐标 */
|
||||
public int centerBlockX() {
|
||||
return this.x * 16 + 8;
|
||||
}
|
||||
|
||||
/** @return 本区块中心的方块 Z 坐标 */
|
||||
public int centerBlockZ() {
|
||||
return this.z * 16 + 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算与另一区块的曼哈顿距离(|dx| + |dz|)。
|
||||
*
|
||||
* @param other 另一个区块坐标
|
||||
* @return 曼哈顿距离
|
||||
*/
|
||||
public int manhattanDistance(ChunkPos other) {
|
||||
return Math.abs(this.x - other.x) + Math.abs(this.z - other.z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152),
|
||||
* 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。
|
||||
*/
|
||||
package autostore.client.territory;
|
||||
|
||||
import autostore.client.territory.ChunkPos;
|
||||
import autostore.client.territory.TerritoryModel;
|
||||
import autostore.client.territory.TerritoryState;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
@Environment(value=EnvType.CLIENT)
|
||||
/**
|
||||
* 领土数据服务:定时从远程地图服务拉取邦国领土数据(markers.json),
|
||||
* 解析为 {@link TerritoryModel} 供规划器与 GUI 使用。
|
||||
*
|
||||
* <p>数据来源是 AutoStore 服务器自带的动态地图(Dynmap)接口,包含:
|
||||
* <ul>
|
||||
* <li>union_claims:各邦国的领土多边形(区块归属)</li>
|
||||
* <li>union_capitals:各邦国的王城坐标</li>
|
||||
* </ul>
|
||||
* 解析出的领土数据会与本次会话新占领的区块合并,保证扩地过程中地图实时更新。
|
||||
*/
|
||||
public final class TerritoryDataService {
|
||||
/** 动态地图的 markers.json 接口地址(AutoStore 服务器) */
|
||||
private static final String MARKERS_URL = "http://autostore.simpfun.cn/tiles/minecraft_overworld/markers.json";
|
||||
/** 从 marker 弹窗文本中提取"疆土归属: xxx"的邦国名 */
|
||||
private static final Pattern CLAIM_PATTERN = Pattern.compile("\u7586\u571f\u5f52\u5c5e:\\s*([^<\\r\\n]+)");
|
||||
/** 从 marker 弹窗文本中提取"「xxx」的王城"的王城名 */
|
||||
private static final Pattern CAPITAL_PATTERN = Pattern.compile("\u300c([^\u300d]+)\u300d\u7684\u738b\u57ce");
|
||||
/** HTTP 客户端:10 秒连接超时,自动跟随重定向 */
|
||||
private final HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10L)).followRedirects(HttpClient.Redirect.NORMAL).build();
|
||||
/** 单线程调度器,用于周期刷新领土数据(守护线程,不阻塞游戏退出) */
|
||||
private volatile ScheduledExecutorService executor;
|
||||
/** 当前解析出的领土模型(volatile 保证多线程可见性) */
|
||||
private volatile TerritoryModel model = TerritoryModel.empty();
|
||||
/** 当前选中的己方邦国名称 */
|
||||
private volatile String selectedStateName = "";
|
||||
/** 最近一次拉取失败的错误信息(空串表示无错误) */
|
||||
private volatile String lastError = "";
|
||||
/** 最近一次成功拉取的时间戳(毫秒) */
|
||||
private volatile long lastSuccessfulFetchMillis;
|
||||
/** 最近一次尝试拉取的时间戳(毫秒) */
|
||||
private volatile long lastAttemptMillis;
|
||||
|
||||
public TerritoryDataService() {
|
||||
this.executor = TerritoryDataService.newExecutor();
|
||||
}
|
||||
|
||||
/** 创建单线程调度器(守护线程,不阻塞游戏退出) */
|
||||
private static ScheduledExecutorService newExecutor() {
|
||||
return Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread thread = new Thread(r, "AutoStore-TerritoryData");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时刷新:立即刷新一次,之后按固定间隔(至少 10 秒)循环拉取。
|
||||
*
|
||||
* @param refreshIntervalSeconds 刷新间隔(秒)
|
||||
*/
|
||||
public void start(long refreshIntervalSeconds) {
|
||||
this.refreshAsync();
|
||||
this.reschedule(refreshIntervalSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按新间隔重新调度定时刷新(立即生效)。
|
||||
* 会关闭旧的调度线程并创建一个新线程,用于 GUI 中修改刷新间隔后即时应用。
|
||||
*
|
||||
* @param refreshIntervalSeconds 新的刷新间隔(秒),最小 10 秒
|
||||
*/
|
||||
public void reschedule(long refreshIntervalSeconds) {
|
||||
ScheduledExecutorService fresh = TerritoryDataService.newExecutor();
|
||||
ScheduledExecutorService old = this.executor;
|
||||
this.executor = fresh;
|
||||
long interval = Math.max(10L, refreshIntervalSeconds);
|
||||
fresh.scheduleWithFixedDelay(this::refreshAsync, interval, interval, TimeUnit.SECONDS);
|
||||
old.shutdownNow();
|
||||
}
|
||||
|
||||
/** 停止定时刷新,关闭调度线程 */
|
||||
public void stop() {
|
||||
this.executor.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步刷新领土数据(在调度线程中执行)。
|
||||
* 拉取成功后解析 JSON,并把上一份模型中的"会话新占领区块"合并进新模型,
|
||||
* 避免刷新覆盖掉本次会话的占领记录。
|
||||
*/
|
||||
public void refreshAsync() {
|
||||
this.executor.execute(() -> {
|
||||
this.lastAttemptMillis = System.currentTimeMillis();
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(MARKERS_URL)).timeout(Duration.ofSeconds(15L)).header("User-Agent", "Mozilla/5.0 autostore/1.0").GET().build();
|
||||
HttpResponse<String> response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
this.lastError = "HTTP " + response.statusCode();
|
||||
return;
|
||||
}
|
||||
TerritoryModel parsed = TerritoryDataService.parse(response.body());
|
||||
TerritoryModel previous = this.model;
|
||||
if (previous != null) {
|
||||
for (ChunkPos sessionClaim : previous.sessionClaims()) {
|
||||
parsed.addSessionClaim(sessionClaim);
|
||||
}
|
||||
}
|
||||
parsed.setSelfStateName(this.selectedStateName);
|
||||
this.model = parsed;
|
||||
this.lastSuccessfulFetchMillis = System.currentTimeMillis();
|
||||
this.lastError = "";
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
this.lastError = "\u62c9\u53d6\u88ab\u4e2d\u65ad";
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.lastError = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public TerritoryModel model() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setSelectedStateName(String name) {
|
||||
this.selectedStateName = name == null ? "" : name;
|
||||
TerritoryModel current = this.model;
|
||||
if (current != null) {
|
||||
current.setSelfStateName(this.selectedStateName);
|
||||
}
|
||||
}
|
||||
|
||||
public String selectedStateName() {
|
||||
return this.selectedStateName;
|
||||
}
|
||||
|
||||
public void addSessionClaim(ChunkPos chunk) {
|
||||
TerritoryModel current = this.model;
|
||||
if (current != null) {
|
||||
current.addSessionClaim(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public String lastError() {
|
||||
return this.lastError;
|
||||
}
|
||||
|
||||
public long lastSuccessfulFetchMillis() {
|
||||
return this.lastSuccessfulFetchMillis;
|
||||
}
|
||||
|
||||
public long lastAttemptMillis() {
|
||||
return this.lastAttemptMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 markers.json 文本为领土模型。
|
||||
*
|
||||
* <p>JSON 结构为数组,每层包含 id 与 markers 列表:
|
||||
* <ul>
|
||||
* <li>union_claims 层:每个 marker 是一个领土多边形,弹窗文本含"疆土归属: 邦国名"</li>
|
||||
* <li>union_capitals 层:每个 marker 是一个王城坐标点,弹窗文本含"「邦国名」的王城"</li>
|
||||
* </ul>
|
||||
* 最终按邦国名聚合为 {@link TerritoryState} 列表。
|
||||
*/
|
||||
public static TerritoryModel parse(String json) {
|
||||
JsonElement root = JsonParser.parseString((String)json);
|
||||
if (!root.isJsonArray()) {
|
||||
throw new IllegalArgumentException("markers.json must be an array");
|
||||
}
|
||||
HashMap<String, Set<ChunkPos>> claims = new HashMap<String, Set<ChunkPos>>();
|
||||
HashMap<String, Integer> claimColors = new HashMap<String, Integer>();
|
||||
HashMap<String, ChunkPos> capitals = new HashMap<String, ChunkPos>();
|
||||
for (JsonElement layerElement : root.getAsJsonArray()) {
|
||||
JsonObject layer = layerElement.getAsJsonObject();
|
||||
String id = TerritoryDataService.stringOr(layer, "id", "");
|
||||
JsonArray markers = layer.getAsJsonArray("markers");
|
||||
if (markers == null) continue;
|
||||
if ("union_claims".equals(id)) {
|
||||
for (JsonElement markerElement : markers) {
|
||||
TerritoryDataService.parseClaimMarker(markerElement.getAsJsonObject(), claims, claimColors);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!"union_capitals".equals(id)) continue;
|
||||
for (JsonElement markerElement : markers) {
|
||||
TerritoryDataService.parseCapitalMarker(markerElement.getAsJsonObject(), capitals);
|
||||
}
|
||||
}
|
||||
HashMap<String, TerritoryState> states = new HashMap<String, TerritoryState>();
|
||||
for (Map.Entry entry : claims.entrySet()) {
|
||||
String name = (String)entry.getKey();
|
||||
ChunkPos capital = capitals.getOrDefault(name, TerritoryDataService.firstChunk((Set)entry.getValue()));
|
||||
int color = claimColors.getOrDefault(name, -9061088);
|
||||
states.put(name, new TerritoryState(name, capital, Set.copyOf((Collection)entry.getValue()), color));
|
||||
}
|
||||
return new TerritoryModel(states);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析一个领土多边形 marker:从弹窗文本提取邦国名,
|
||||
* 将多边形覆盖的区块加入该邦国的区块集合,并记录颜色。
|
||||
*/
|
||||
private static void parseClaimMarker(JsonObject marker, Map<String, Set<ChunkPos>> claims, Map<String, Integer> colors) {
|
||||
String popup = TerritoryDataService.stringOr(marker, "popup", "");
|
||||
Matcher matcher = CLAIM_PATTERN.matcher(popup);
|
||||
if (!matcher.find()) {
|
||||
return;
|
||||
}
|
||||
String name = matcher.group(1).trim();
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set chunks = claims.computeIfAbsent(name, ignored -> new HashSet());
|
||||
for (List<int[]> polygon : TerritoryDataService.extractPolygons(marker.get("points"))) {
|
||||
TerritoryDataService.fillPolygon(chunks, polygon);
|
||||
}
|
||||
colors.putIfAbsent(name, TerritoryDataService.parseColor(TerritoryDataService.stringOr(marker, "color", "#75bd20")));
|
||||
}
|
||||
|
||||
/** 解析一个王城 marker:从弹窗文本提取邦国名,记录王城的方块坐标(换算为区块坐标) */
|
||||
private static void parseCapitalMarker(JsonObject marker, Map<String, ChunkPos> capitals) {
|
||||
String popup = TerritoryDataService.stringOr(marker, "popup", "");
|
||||
Matcher matcher = CAPITAL_PATTERN.matcher(popup);
|
||||
if (!matcher.find()) {
|
||||
return;
|
||||
}
|
||||
JsonObject point = marker.getAsJsonObject("point");
|
||||
if (point == null) {
|
||||
return;
|
||||
}
|
||||
int x = TerritoryDataService.intOr(point, "x", 0);
|
||||
int z = TerritoryDataService.intOr(point, "z", 0);
|
||||
capitals.put(matcher.group(1).trim(), ChunkPos.fromBlock(x, z));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归提取多边形列表:points 可能是单个多边形(点数组),
|
||||
* 也可能是多边形数组(多层嵌套),统一展平为 List<List<int[]>>。
|
||||
*/
|
||||
private static List<List<int[]>> extractPolygons(JsonElement points) {
|
||||
ArrayList<List<int[]>> result = new ArrayList<List<int[]>>();
|
||||
if (points == null || !points.isJsonArray()) {
|
||||
return result;
|
||||
}
|
||||
JsonArray array = points.getAsJsonArray();
|
||||
if (array.size() == 0) {
|
||||
return result;
|
||||
}
|
||||
if (TerritoryDataService.isPointObject(array.get(0))) {
|
||||
List<int[]> polygon = TerritoryDataService.parsePointList((JsonElement)array);
|
||||
if (polygon.size() >= 3) {
|
||||
result.add(polygon);
|
||||
}
|
||||
} else {
|
||||
for (JsonElement element : array) {
|
||||
result.addAll(TerritoryDataService.extractPolygons(element));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 判断 JSON 元素是否是一个"点对象"(同时含 x 与 z 字段) */
|
||||
private static boolean isPointObject(JsonElement element) {
|
||||
return element != null && element.isJsonObject() && element.getAsJsonObject().has("x") && element.getAsJsonObject().has("z");
|
||||
}
|
||||
|
||||
/** 解析一个点数组为 int[] 列表(每个点取 x、z 两个坐标) */
|
||||
private static List<int[]> parsePointList(JsonElement element) {
|
||||
ArrayList<int[]> points = new ArrayList<int[]>();
|
||||
if (!element.isJsonArray()) {
|
||||
return points;
|
||||
}
|
||||
for (JsonElement pointElement : element.getAsJsonArray()) {
|
||||
JsonObject point = pointElement.getAsJsonObject();
|
||||
if (point == null) continue;
|
||||
points.add(new int[]{TerritoryDataService.intOr(point, "x", 0), TerritoryDataService.intOr(point, "z", 0)});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多边形覆盖的区块填充进集合:
|
||||
* 先算出多边形外接矩形(换算为区块坐标),再逐区块用"点在多边形内"算法判断,
|
||||
* 以区块中心点(cx*16+8, cz*16+8)是否落在多边形内为准。
|
||||
* 面积超过 600 万区块时直接放弃,防止异常数据导致卡死。
|
||||
*/
|
||||
private static void fillPolygon(Set<ChunkPos> output, List<int[]> polygon) {
|
||||
int area;
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int minZ = Integer.MAX_VALUE;
|
||||
int maxZ = Integer.MIN_VALUE;
|
||||
for (int[] point : polygon) {
|
||||
minX = Math.min(minX, Math.floorDiv(point[0], 16));
|
||||
maxX = Math.max(maxX, Math.floorDiv(point[0], 16));
|
||||
minZ = Math.min(minZ, Math.floorDiv(point[1], 16));
|
||||
maxZ = Math.max(maxZ, Math.floorDiv(point[1], 16));
|
||||
}
|
||||
int n = area = ((maxX - minX) + 1L) * ((maxZ - minZ) + 1L) > Integer.MAX_VALUE ? Integer.MAX_VALUE : (maxX - minX + 1) * (maxZ - minZ + 1);
|
||||
if (area > 6000000) {
|
||||
return;
|
||||
}
|
||||
for (int cx = minX; cx <= maxX; ++cx) {
|
||||
for (int cz = minZ; cz <= maxZ; ++cz) {
|
||||
if (!TerritoryDataService.pointInPolygon(cx * 16 + 8, cz * 16 + 8, polygon)) continue;
|
||||
output.add(new ChunkPos(cx, cz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 射线法判断点 (x, z) 是否在多边形内(经典奇偶规则) */
|
||||
private static boolean pointInPolygon(int x, int z, List<int[]> polygon) {
|
||||
boolean inside = false;
|
||||
int i = 0;
|
||||
int j = polygon.size() - 1;
|
||||
while (i < polygon.size()) {
|
||||
boolean intersects;
|
||||
int xi = polygon.get(i)[0];
|
||||
int zi = polygon.get(i)[1];
|
||||
int xj = polygon.get(j)[0];
|
||||
int zj = polygon.get(j)[1];
|
||||
boolean bl = intersects = zi > z != zj > z && x < (xj - xi) * (z - zi) / (zj - zi) + xi;
|
||||
if (intersects) {
|
||||
inside = !inside;
|
||||
}
|
||||
j = i++;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
/** 取集合中的第一个区块作为默认王城(当该邦国没有标记王城时) */
|
||||
private static ChunkPos firstChunk(Set<ChunkPos> chunks) {
|
||||
return chunks.stream().findFirst().orElse(new ChunkPos(0, 0));
|
||||
}
|
||||
|
||||
/** 安全读取 JSON 字符串字段,缺失或为 null 时返回默认值 */
|
||||
private static String stringOr(JsonObject object, String key, String fallback) {
|
||||
JsonElement element = object.get(key);
|
||||
return element == null || element.isJsonNull() ? fallback : element.getAsString();
|
||||
}
|
||||
|
||||
/** 安全读取 JSON 整数字段,缺失或为 null 时返回默认值 */
|
||||
private static int intOr(JsonObject object, String key, int fallback) {
|
||||
JsonElement element = object.get(key);
|
||||
return element == null || element.isJsonNull() ? fallback : element.getAsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析十六进制颜色字符串为 ARGB 整数:
|
||||
* 支持 #RRGGBB(自动补全不透明 alpha)与 #AARRGGBB 两种格式;
|
||||
* 解析失败时返回默认的邦国绿(#75bd20)。
|
||||
*/
|
||||
private static int parseColor(String hex) {
|
||||
try {
|
||||
String value;
|
||||
String string = value = hex.startsWith("#") ? hex.substring(1) : hex;
|
||||
if (value.length() == 8) {
|
||||
return (int)Long.parseLong(value, 16);
|
||||
}
|
||||
if (value.length() == 6) {
|
||||
return 0xFF000000 | Integer.parseInt(value, 16);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException numberFormatException) {
|
||||
// 颜色格式非法时忽略,使用默认颜色
|
||||
}
|
||||
return -9061088;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152),
|
||||
* 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。
|
||||
*/
|
||||
package autostore.client.territory;
|
||||
|
||||
import autostore.client.territory.ChunkPos;
|
||||
import autostore.client.territory.TerritoryState;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
@Environment(value=EnvType.CLIENT)
|
||||
/**
|
||||
* 领土模型:聚合所有邦国的领土状态,并提供全局查询能力。
|
||||
*
|
||||
* <p>维护三份数据:
|
||||
* <ul>
|
||||
* <li>states:邦国名 → 邦国领土状态(含区块集合、王城、颜色)</li>
|
||||
* <li>allClaimed:所有邦国已占领区块的并集(用于判断区块是否已被占用)</li>
|
||||
* <li>sessionClaims:本次会话新占领的区块(刷新地图时不会被覆盖)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class TerritoryModel {
|
||||
/** 邦国名 → 邦国领土状态 */
|
||||
private final Map<String, TerritoryState> states;
|
||||
/** 所有邦国已占领区块的并集 */
|
||||
private final Set<ChunkPos> allClaimed;
|
||||
/** 本次会话新占领的区块 */
|
||||
private final Set<ChunkPos> sessionClaims;
|
||||
/** 当前选中的己方邦国名称 */
|
||||
private String selfStateName = "";
|
||||
|
||||
public TerritoryModel(Map<String, TerritoryState> states) {
|
||||
this.states = new HashMap<String, TerritoryState>(states);
|
||||
this.allClaimed = new HashSet<ChunkPos>();
|
||||
for (TerritoryState state : states.values()) {
|
||||
this.allClaimed.addAll(state.chunks());
|
||||
}
|
||||
this.sessionClaims = new HashSet<ChunkPos>();
|
||||
}
|
||||
|
||||
/** 返回一个空的领土模型(无任何邦国数据) */
|
||||
public static TerritoryModel empty() {
|
||||
return new TerritoryModel(Map.of());
|
||||
}
|
||||
|
||||
/** 返回所有邦国领土状态(只读视图) */
|
||||
public Collection<TerritoryState> states() {
|
||||
return Collections.unmodifiableCollection(this.states.values());
|
||||
}
|
||||
|
||||
/** 按名称查询邦国领土状态,不存在返回 null */
|
||||
public TerritoryState state(String name) {
|
||||
return this.states.get(name);
|
||||
}
|
||||
|
||||
/** 返回所有邦国已占领区块的并集(只读视图) */
|
||||
public Set<ChunkPos> allClaimedChunks() {
|
||||
return Collections.unmodifiableSet(this.allClaimed);
|
||||
}
|
||||
|
||||
/** 返回本次会话新占领的区块(只读视图) */
|
||||
public Set<ChunkPos> sessionClaims() {
|
||||
return Collections.unmodifiableSet(this.sessionClaims);
|
||||
}
|
||||
|
||||
/** 返回当前选中的己方邦国名称 */
|
||||
public String selfStateName() {
|
||||
return this.selfStateName;
|
||||
}
|
||||
|
||||
/** 设置当前选中的己方邦国名称(null 视为空串) */
|
||||
public void setSelfStateName(String name) {
|
||||
this.selfStateName = name == null ? "" : name;
|
||||
}
|
||||
|
||||
/** 返回己方邦国的领土状态;未设置或不存在时返回 null */
|
||||
public TerritoryState selfState() {
|
||||
return this.states.get(this.selfStateName);
|
||||
}
|
||||
|
||||
/** 查找包含指定区块的邦国;没有邦国覆盖该区块时返回 null */
|
||||
public TerritoryState findContainingState(ChunkPos chunk) {
|
||||
for (TerritoryState state : this.states.values()) {
|
||||
if (!state.chunks().contains(chunk)) continue;
|
||||
return state;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次会话内新占领的区块:
|
||||
* 加入会话集合与全局已占领集合,并同步更新己方邦国的区块集合。
|
||||
*/
|
||||
public void addSessionClaim(ChunkPos chunk) {
|
||||
this.sessionClaims.add(chunk);
|
||||
this.allClaimed.add(chunk);
|
||||
TerritoryState self = this.states.get(this.selfStateName);
|
||||
if (self != null) {
|
||||
HashSet<ChunkPos> updated = new HashSet<ChunkPos>(self.chunks());
|
||||
updated.add(chunk);
|
||||
this.states.put(this.selfStateName, new TerritoryState(self.name(), self.capital(), updated, self.color()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152),
|
||||
* 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。
|
||||
*/
|
||||
package autostore.client.territory;
|
||||
|
||||
import java.util.Set;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
/**
|
||||
* 一个国家(邦国)的领土状态(record)。
|
||||
*
|
||||
* @param name 国家名称
|
||||
* @param capital 首都所在的区块坐标
|
||||
* @param chunks 该国占有的全部区块坐标集合
|
||||
* @param color 地图上用于绘制该国的 ARGB 颜色值
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public record TerritoryState(String name, ChunkPos capital, Set<ChunkPos> chunks, int color) {
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户