v1.2.0: 内置地图检测并标记飞地(疏漏区块),自动扩地时优先填补这些缺口;新增 analyze_holes.py 分析工具
这个提交包含在:
@@ -14,6 +14,7 @@ import autostore.client.territory.ChunkPos;
|
||||
import autostore.client.territory.TerritoryModel;
|
||||
import autostore.client.territory.TerritoryState;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
@@ -347,7 +348,7 @@ extends Screen {
|
||||
context.drawTextWithShadow(this.textRenderer, "\u9886\u571f\u5730\u56fe", this.mapX + 2, this.mapY - 12, -1513240);
|
||||
}
|
||||
|
||||
/** 绘制各邦国领土区块(己方高亮、其他半透明),并绘制会话新占领区块 */
|
||||
/** 绘制各邦国领土区块(己方高亮、其他半透明),并绘制会话新占领区块与飞地(疏漏区块) */
|
||||
private void drawTerritories(DrawContext context) {
|
||||
TerritoryModel model = this.mod.dataService().model();
|
||||
for (TerritoryState state : model.states()) {
|
||||
@@ -363,6 +364,11 @@ extends Screen {
|
||||
for (ChunkPos chunk : model.sessionClaims()) {
|
||||
this.drawChunkFill(context, chunk, -1938440928);
|
||||
}
|
||||
// 标记飞地(疏漏区块):己方用醒目的红色描边提示填补,其他邦国用暗灰色标示
|
||||
Set<ChunkPos> selfFlying = model.selfFlyingChunks();
|
||||
for (ChunkPos chunk : model.flyingChunks()) {
|
||||
this.drawChunkOutline(context, chunk, selfFlying.contains(chunk) ? -65536 : this.withAlpha(-8333060, 160));
|
||||
}
|
||||
}
|
||||
|
||||
/** 绘制玩家位置(白色方块)与当前目标区块(橙色描边) */
|
||||
|
||||
@@ -40,17 +40,47 @@ public final class ExpansionPlanner {
|
||||
* @return 建议占领的区块列表(按优先级排序)
|
||||
*/
|
||||
public List<ChunkPos> generate(TerritoryModel model, ExpansionConfig config, ChunkPos playerChunk) {
|
||||
List<ChunkPos> result;
|
||||
if (config.strategy == 1) {
|
||||
return this.generateHolePriority(model, config, playerChunk);
|
||||
result = this.generateHolePriority(model, config, playerChunk);
|
||||
} else if (config.strategy == 4) {
|
||||
result = this.generatePerimeter(model, config, playerChunk);
|
||||
} else if (config.strategy == 2 && config.regionMaxX >= config.regionMinX) {
|
||||
result = this.generateRectangle(model, config, playerChunk);
|
||||
} else {
|
||||
result = this.generateLine(model, config, playerChunk);
|
||||
}
|
||||
if (config.strategy == 4) {
|
||||
return this.generatePerimeter(model, config, playerChunk);
|
||||
return this.prefixFlyingChunks(model, config, playerChunk, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将己方领土内的"飞地(疏漏区块)"提到队列最前,优先填补。
|
||||
*
|
||||
* <p>飞地指未被占领、但四周(四个正方向)都被己方领土包围的区块,
|
||||
* 是自动扩地过程中最容易漏掉的疏漏。先把它们填上可保证领土闭合、不留缺口。</p>
|
||||
*/
|
||||
private List<ChunkPos> prefixFlyingChunks(TerritoryModel model, ExpansionConfig config, ChunkPos playerChunk, List<ChunkPos> planned) {
|
||||
Set<ChunkPos> flying = model.selfFlyingChunks();
|
||||
if (flying == null || flying.isEmpty()) {
|
||||
return planned;
|
||||
}
|
||||
// 策略 2 区域扩地:玩家圈选了矩形时,完全按矩形区域扩地
|
||||
if (config.strategy == 2 && config.regionMaxX >= config.regionMinX) {
|
||||
return this.generateRectangle(model, config, playerChunk);
|
||||
int limit = config.targetCount > 0 ? config.targetCount : Math.max(planned.size(), flying.size());
|
||||
Comparator<ChunkPos> byDistance = Comparator.comparingInt(playerChunk::manhattanDistance);
|
||||
ArrayList<ChunkPos> result = new ArrayList<ChunkPos>();
|
||||
// 飞地优先,且必须仍在配置区域内
|
||||
for (ChunkPos chunk : flying) {
|
||||
if (result.size() >= limit) break;
|
||||
if (!this.isInRegion(chunk, config)) continue;
|
||||
result.add(chunk);
|
||||
}
|
||||
return this.generateLine(model, config, playerChunk);
|
||||
result.sort(byDistance);
|
||||
// 其余规划区块依次补充,去重且不超过 limit
|
||||
for (ChunkPos chunk : planned) {
|
||||
if (result.size() >= limit) break;
|
||||
if (flying.contains(chunk)) continue;
|
||||
result.add(chunk);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,14 @@ public final class TerritoryModel {
|
||||
private final Set<ChunkPos> sessionClaims;
|
||||
/** 当前选中的己方邦国名称 */
|
||||
private String selfStateName = "";
|
||||
/**
|
||||
* 飞地(疏漏区块)缓存:邦国名 → 该国飞地区块集合。
|
||||
*
|
||||
* <p>飞地指"四周(四个正方向)都被同一邦国占领、但自身未被占领"的区块,
|
||||
* 通常是自动扩地时漏掉的疏漏。该结果在模型构建后懒计算并缓存,
|
||||
* 仅在新增会话占领(addSessionClaim)后失效重算。
|
||||
*/
|
||||
private transient Map<String, Set<ChunkPos>> cachedFlyingChunks;
|
||||
|
||||
public TerritoryModel(Map<String, TerritoryState> states) {
|
||||
this.states = new HashMap<String, TerritoryState>(states);
|
||||
@@ -94,6 +102,80 @@ public final class TerritoryModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有邦国的飞地(疏漏区块)并集(只读视图)。
|
||||
*
|
||||
* <p>飞地指“四周(四个正方向)都被同一邦国占领、但自身未被占领”的区块。
|
||||
* 这些往往是自动扩地过程中漏掉的疏漏,需要在地图上标出并优先填补。</p>
|
||||
*/
|
||||
public Set<ChunkPos> flyingChunks() {
|
||||
HashSet<ChunkPos> result = new HashSet<ChunkPos>();
|
||||
for (Set<ChunkPos> chunks : this.flyingChunksByState().values()) {
|
||||
result.addAll(chunks);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 返回己方邦国的飞地(疏漏区块)(只读视图);未选择邦国时为空集 */
|
||||
public Set<ChunkPos> selfFlyingChunks() {
|
||||
return Collections.unmodifiableSet(this.flyingChunksByState().getOrDefault(this.selfStateName, Set.of()));
|
||||
}
|
||||
|
||||
/** 计算并缓存各邦国的飞地区块(懒加载,addSessionClaim 后失效重算) */
|
||||
private Map<String, Set<ChunkPos>> flyingChunksByState() {
|
||||
Map<String, Set<ChunkPos>> cached = this.cachedFlyingChunks;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
HashMap<String, Set<ChunkPos>> map = new HashMap<String, Set<ChunkPos>>();
|
||||
for (TerritoryState state : this.states.values()) {
|
||||
map.put(state.name(), this.computeFlyingChunks(state.chunks()));
|
||||
}
|
||||
this.cachedFlyingChunks = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算某邦国的全部飞地区块:
|
||||
* 在该邦国的外接矩形内,找出“未被任何邦国占领、但四个正方向均属于该邦国”的区块。
|
||||
*/
|
||||
private Set<ChunkPos> computeFlyingChunks(Set<ChunkPos> own) {
|
||||
HashSet<ChunkPos> result = new HashSet<ChunkPos>();
|
||||
if (own.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int minZ = Integer.MAX_VALUE;
|
||||
int maxZ = Integer.MIN_VALUE;
|
||||
for (ChunkPos chunk : own) {
|
||||
minX = Math.min(minX, chunk.x());
|
||||
maxX = Math.max(maxX, chunk.x());
|
||||
minZ = Math.min(minZ, chunk.z());
|
||||
maxZ = Math.max(maxZ, chunk.z());
|
||||
}
|
||||
for (int cx = minX; cx <= maxX; ++cx) {
|
||||
for (int cz = minZ; cz <= maxZ; ++cz) {
|
||||
ChunkPos chunk = new ChunkPos(cx, cz);
|
||||
if (own.contains(chunk) || this.allClaimed.contains(chunk)) {
|
||||
continue;
|
||||
}
|
||||
if (this.surroundedBySameState(chunk, own)) {
|
||||
result.add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 判断区块的四个正方向邻居是否全部属于给定邦国的区块集合 */
|
||||
private boolean surroundedBySameState(ChunkPos chunk, Set<ChunkPos> own) {
|
||||
return own.contains(new ChunkPos(chunk.x() + 1, chunk.z()))
|
||||
&& own.contains(new ChunkPos(chunk.x() - 1, chunk.z()))
|
||||
&& own.contains(new ChunkPos(chunk.x(), chunk.z() + 1))
|
||||
&& own.contains(new ChunkPos(chunk.x(), chunk.z() - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次会话内新占领的区块:
|
||||
* 加入会话集合与全局已占领集合,并同步更新己方邦国的区块集合。
|
||||
@@ -101,6 +183,7 @@ public final class TerritoryModel {
|
||||
public void addSessionClaim(ChunkPos chunk) {
|
||||
this.sessionClaims.add(chunk);
|
||||
this.allClaimed.add(chunk);
|
||||
this.cachedFlyingChunks = null;
|
||||
TerritoryState self = this.states.get(this.selfStateName);
|
||||
if (self != null) {
|
||||
HashSet<ChunkPos> updated = new HashSet<ChunkPos>(self.chunks());
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"schemaVersion":1,"id":"autostore","version":"1.1.9","name":"AutoStore","description":"","authors":[],"contact":{},"license":"All-Rights-Reserved","environment":"client","entrypoints":{"fabric-datagen":["autostore.client.AutoStoreModDataGenerator"],"client":["autostore.client.AutoStoreModClient"],"main":["autostore.AutoStoreMod"]},"mixins":["autostore.mixins.json",{"config":"autostore.client.mixins.json","environment":"client"}],"depends":{"fabricloader":">=0.16.14","fabric-api":"*","minecraft":"1.21.4"},"jars":[{"file":"META-INF/jars/baritone-api-fabric-1.13.1.jar"}]}
|
||||
{"schemaVersion":1,"id":"autostore","version":"1.2.0","name":"AutoStore","description":"","authors":[],"contact":{},"license":"All-Rights-Reserved","environment":"client","entrypoints":{"fabric-datagen":["autostore.client.AutoStoreModDataGenerator"],"client":["autostore.client.AutoStoreModClient"],"main":["autostore.AutoStoreMod"]},"mixins":["autostore.mixins.json",{"config":"autostore.client.mixins.json","environment":"client"}],"depends":{"fabricloader":">=0.16.14","fabric-api":"*","minecraft":"1.21.4"},"jars":[{"file":"META-INF/jars/baritone-api-fabric-1.13.1.jar"}]}
|
||||
在新工单中引用
屏蔽一个用户