自动标记
三个 watcher 事件监听器,在满足条件时自动往地图上写标记。全部走 AtlasAPI,不消耗任何物品,也不需玩家手动操作。
效果总览
| watcher | 类 | 触发事件 | 标记类型 | 本地 / 全局 | 控制开关 | 影响的维度 |
|---|---|---|---|---|---|---|
| 死亡点 | DeathWatcher |
LivingDeathEvent |
tomb |
本地(每本图册一份) | auto_death_marker |
玩家所在维度 |
| 村庄 | VillageWatcher |
WorldEvent.Load / PopulateChunkEvent.Post |
village |
全局 | auto_village_markers |
仅 dimensionId == 0 |
| 下界传送门 | NetherPortalWatcher |
DummyWorldAccess.onEntityCreate/Destroy |
nether_portal |
本地(每本图册一份) | 无开关 | 传送的源维与目标维 |
VillageWatcher 和 NetherFortressWatcher 还会在同一批事件里往伪群系表写 tile(村庄建筑、下界要塞房间),那部分不是标记,见 群系检测。
DeathWatcher
hunternif.mc.atlas.ext.DeathWatcher。类注释:Puts a skull marker to the player’s death spot.
@SubscribeEvent
public void onPlayerDeath(LivingDeathEvent event) {
if (event.entity instanceof EntityPlayer && AntiqueAtlasMod.settings.autoDeathMarker) {
EntityPlayer player = (EntityPlayer) event.entity;
for (int atlasID : AtlasAPI.getPlayerAtlases(player)) {
AtlasAPI.markers.putMarker(player.worldObj, true, atlasID, "tomb",
"gui.antiqueatlas.marker.tomb " + player.getCommandSenderName(),
(int)player.posX, (int)player.posZ);
}
}
}
| 属性 | 值 |
|---|---|
| 来源物品 | 玩家主物品栏里所有 antiqueatlas:antiqueAtlas(AtlasAPI.getPlayerAtlases 遍历 mainInventory 收集所有 getItemDamage()) |
| 触发条件 | event.entity instanceof EntityPlayer 且 settings.autoDeathMarker == true |
| 坐标 | (int)player.posX, (int)player.posZ —— 死亡瞬间的位置,不处理 Y |
| 可见性 | visibleAhead = true(未探索区域也显示) |
| 标签 | "gui.antiqueatlas.marker.tomb " + 玩家名,按 Marker.getLocalizedLabel() 的「空格前为键、空格后为参数」规则渲染成 Here lies <名字> |
| 数量 | 每本图册各一个(N 本图册 → N 个墓碑) |
不检查死亡原因,也不判断是否已经存在同名墓碑——同一次死亡带 N 本图册就写 N 条。
VillageWatcher
hunternif.mc.atlas.ext.VillageWatcher,MARKER = "village"。
触发
两个订阅点,条件相同(服务端 + dimensionId == 0):
| 事件 | 优先级 |
|---|---|
WorldEvent.Load |
EventPriority.LOWEST |
PopulateChunkEvent.Post |
默认 |
两者都调 visitAllUnvisitedVillages(world)。
流程
world.perWorldStorage.loadData(MapGenStructureData.class, "Village"),为 null 直接返回。- 取
data.func_143041_a(),遍历func_150296_c()返回的 tag 名集合(格式"[x, y]")。 - 对未在
visited集合里的坐标,取 tag;tag.getId() == 10(NBTTagCompound)才处理,随后加入visited。 visitVillage里先看tag.getBoolean("Valid"):- false → 调
removeVillage删除该村庄遗留的标记和伪群系 tile(这个村庄实际没生成),返回。 - true → 继续。
- false → 调
标记写入
遍历 Children 列表,每个 child 取 child.getString("id")、child.getIntArray("BB") 建成 StructureBoundingBox,取包围盒中心 (getCenterX(), getCenterZ()),再 >> 4 得区块坐标。
只有 childID == START("ViStart",常量注释 Same as WELL)时才放标记:
- 先在以该区块为中心 3×3 个
MarkersData.CHUNK_STEP桶的范围内(j, k ∈ [-1, 1])搜索是否已有type == "village"的全局标记。找到就foundMarker = true。 - 注释说明这是 Legacy support: don’t place new marker if there’s already one in a wider area。
- 未找到且
settings.autoVillageMarkers为 true 时才putGlobalMarker(world, false, "village", "gui.antiqueatlas.marker.village", x, z)。
| 属性 | 值 |
|---|---|
| 可见性 | visibleAhead = false |
| 范围 | 全局标记,所有图册都显示 |
| 贴图 | antiqueatlas:textures/marker/village.png |
| 数量 | 每个村庄 1 个 |
visited 集合的缺陷
visited 是普通 HashSet<String>,源码 TODO 明说:list of visited villages must be reset when changing worlds! And the same goes for Nether Fortress Watcher。后果是切换存档后同坐标字符串会被误判为「已访问」而跳过。NetherFortressWatcher 有完全相同的问题。
NetherPortalWatcher
hunternif.mc.atlas.marker.NetherPortalWatcher extends DummyWorldAccess,MARKER_PORTAL = "nether_portal"。
为什么要两个事件
类注释解释了机制:玩家传送时先从源维度被移除(此时传送门检测可靠),把 entityId 记进 teleportingPlayerIDs;随后在目标维度被创建,而目标维度的传送门检测因为某种原因不工作——但可以靠「这个 id 在集合里」判断他到了。
| 事件 | 动作 |
|---|---|
WorldEvent.Load(服务端) |
world.addWorldAccess(this) |
WorldEvent.Unload(服务端) |
world.removeWorldAccess(this) |
onEntityDestroy(Entity) |
若 isEntityInPortal(entity),记下 entityId 到 teleportingPlayerIDs,并给源维度加标记 |
onEntityCreate(Entity) |
若 teleportingPlayerIDs.remove(id) 命中,给目标维度加标记 |
传送门检测用 ObfuscationReflectionHelper.getPrivateValue(Entity.class, entity, {"inPortal", "field_71087_bX", "bX"}) 反射读私有字段——三个名字对应不同映射环境。
写入逻辑
addPortalMarkerIfNone(player, dimension):
MinecraftServer.getServer().worldServerForDimension(dimension)取世界。注释:Due to switching dimensions this player entity’s worldObj is lagging. We need the very specific dimension each time.- 遍历
player.inventory.mainInventory,只取stack.getItem() == AntiqueAtlasMod.itemAtlas的每一本图册。 - 对每本图册,在
getMarkersAtChunk((x >> 4) / CHUNK_STEP, (z >> 4) / CHUNK_STEP)桶里查是否已有type == "nether_portal"。找到任意一个就直接return——不是continue,所以后面还没检查的图册也不会再查。 - 没有才
putMarker(world, false, stack.getItemDamage(), "nether_portal", "gui.antiqueatlas.marker.netherPortal", x, z)。
| 属性 | 值 |
|---|---|
| 可见性 | visibleAhead = false |
| 范围 | 本地,每本图册一份 |
| 贴图 | antiqueatlas:textures/marker/nether_portal.png |
| 数量 | 一次传送最多 1 个(受上述 return 限制),但源维度和目标维度各算一次 |
| 开关 | 无配置项,始终生效 |
三者对比
| tomb | village | nether_portal | |
|---|---|---|---|
| 写入方 | 玩家事件 | 世界生成数据 | 实体生命周期 |
| 范围 | 本地 | 全局 | 本地 |
visibleAhead |
true |
false |
false |
| 配置开关 | auto_death_marker |
auto_village_markers |
无 |
| 覆盖所有图册 | 是(逐本循环) | 是(全局表) | 部分(见上文 return) |
| 去重 | 无 | 有(3×3 桶范围) | 有(单桶内查首个命中) |
相关条目
- Antique Atlas - tomb 与 nether_portal 的逐本写入依据
- 空白 Antique Atlas - 死亡前若没建过图册则不会产生墓碑
- 标记系统 - 标记的数据结构、NBT 字段与贴图表
- 群系检测 - 村庄 / 要塞 watcher 同时写入的伪群系 tile
- 网络消息 - 标记新增 / 删除的封包路径
- SettingsConfig -
auto_death_marker/auto_village_markers