自动标记

三个 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)。

流程

  1. world.perWorldStorage.loadData(MapGenStructureData.class, "Village"),为 null 直接返回。
  2. 取 data.func_143041_a(),遍历 func_150296_c() 返回的 tag 名集合(格式 "[x, y]")。
  3. 对未在 visited 集合里的坐标,取 tag;tag.getId() == 10(NBTTagCompound)才处理,随后加入 visited。
  4. visitVillage 里先看 tag.getBoolean("Valid"):
    • false → 调 removeVillage 删除该村庄遗留的标记和伪群系 tile(这个村庄实际没生成),返回。
    • true → 继续。

标记写入

遍历 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):

  1. MinecraftServer.getServer().worldServerForDimension(dimension) 取世界。注释:Due to switching dimensions this player entity’s worldObj is lagging. We need the very specific dimension each time.
  2. 遍历 player.inventory.mainInventory,只取 stack.getItem() == AntiqueAtlasMod.itemAtlas 的每一本图册。
  3. 对每本图册,在 getMarkersAtChunk((x >> 4) / CHUNK_STEP, (z >> 4) / CHUNK_STEP) 桶里查是否已有 type == "nether_portal"。找到任意一个就直接 return——不是 continue,所以后面还没检查的图册也不会再查。
  4. 没有才 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 桶范围) 有(单桶内查首个命中)

相关条目