EasyPortal
[AI-generated] Every 5 s, finds portals within 60 m, highlights them for 10 s and draws a screen arrow with tag and distance. Places a named map pin (portal icon) once per portal and removes it when the portal is gone. Client-only.EasyPortal
传送门探测器。 纯客户端模组 —— 每 5 秒扫一遍周围 60 米内的传送门,探测到就高亮 10 秒、 在屏幕上用箭头指向它(带标签名与距离),并在地图上按标签名自动打图钉。
AI 生成声明:本模组由 AI 智能体(WorkBuddy)在用户的明确需求与逐项验收下编写; 需求描述、交互设计与发布决定均由用户主导。
功能
| # | 功能 | 默认 | 配置节 |
|---|---|---|---|
| 1 | 每 N 秒探测周围 R 米内的传送门 | 5 秒 / 60 米 | 1 - Detection |
| 2 | 探测到就高亮 M 秒(光柱 + 脉动点光) | 10 秒 | 1 - Detection / 2 - Highlight |
| 3 | 屏幕上画箭头指向它,显示标签名 + 距离(m) | 开 | 3 - Screen Marker |
| 4 | 地图上按标签名打图钉;同名旧图钉自动挪过来 | 开 | 4 - Map Pin |
| 5 | 传送门群(家里那一片)整体跳过 | 开 | 1 - Detection |
| 6 | 已标记过的传送门不再重复检测 | 开 | 4 - Map Pin |
| 7 | 传送门被拆 → 自动删除地图标记 | 开 | 4 - Map Pin |
1.0.3 改动: ① 标签底衬改成 OreFinder 同款(不画底块、纯黑描边),并修掉"文字被乘黑"的 bug →
LabelBackground; ② 已标记过的门不再重复检测(不再重复高亮 / 重复建图标)→SkipAlreadyPinned; ③ 门被拆掉会自动删掉地图图标 → 由SkipAlreadyPinned一并启用。 详见CHANGELOG.md。1.0.2 改动: ① 地图图钉图标默认改为
Portal,且已有的同名图钉会自动换成新图标(不必手动删); ② 修掉两处误导性日志/策略 —— 传送后"登记册为空"属正常,不再错报Warning; 兜底扫描改为带冷却重试(原来只试一次)。
已标记不重复 & 被拆自动清理(SkipAlreadyPinned)
| 行为 | 说明 |
|---|---|
| 门第一次被探测到 | 高亮 M 秒 + 建图钉 + 记入追踪表 |
| 之后再探测到同一个门 | 直接略过 —— 不再高亮、不再画箭头、不再重复建图钉 |
| 门被拆掉 | 玩家在它附近、连续 2 轮探测不到 → 自动删掉地图图钉 |
| 门被搬走重建 | 是新的网络对象 id → 当作新门重新标记(图钉在新位置重新出现) |
🔑 识别「同一个门」用的是游戏的网络对象 id(ZDOID),不是标签名。
这点很重要:如果按标签名识别,「搬门」(拆掉重建、标签没变但位置变了)
就会被误判成"已标记过"而永远不再标记。
⚠️ 两条防误删约束(避免图钉莫名其妙消失):
- 只在玩家靠近时才判定被拆 —— 远处的门无法验证(ZDO 按距离流式加载,查不到 ≠ 被删)。 玩家走远时保留图钉并重置计数,不会因为"走远了"而删掉。
- 需要连续 2 轮探测不到 —— 单轮漏看可能是瞬时抖动(刚传送完、场景还在加载)。
想回到旧行为(每次靠近都重新高亮)把 SkipAlreadyPinned 设为 false,
再配合 1 - Detection → RehighlightOnRedetect 使用。
传送门群(建在一起的门)整体跳过
家里的传送门通常是建在一起的。如果逐个高亮 + 逐个建图钉,等于在一个地图坐标上叠一堆图标、
屏幕上还挂一排箭头。所以本模组会先把探测到的门按物理位置聚簇,
任何一片里门数达到 ClusterSkipMin 就整片都不标记(不高亮、不画箭头、不建图钉)。
配置(1 - Detection) |
默认 | 说明 |
|---|---|---|
SkipClusters |
true | 总开关。关掉就按普通规则逐个处理 |
ClusterRadius |
20 米 | 判定「聚在一起」的间距。链式判定 —— 一排门只要彼此相邻,就会连成同一大片 |
ClusterSkipMin |
2 | 一片里达到几个门就整体跳过 |
判定用的是本轮探测到的、且会参与标记的门(没起名的门不算),所以刚放下一个还没命名的门, 不会把旁边正常的门误带进"群"里。
想让小规模(两三个门)照常标记、只有大群才跳过,把
ClusterSkipMin调大即可。被跳过的门若此前已建过同名图钉,会被一并清掉(受
TakeOverSameName约束), 免得地图上留一个永不更新的图标。
地图图钉的规则(重点)
| 情况 | 行为 |
|---|---|
| 该位置附近已有同名图钉 | 什么都不做("已经有图标了不用标记") |
| 该标签只探测到 1 个传送门,且地图上同名图钉在别处 | 删掉旧位置的、在新位置重建("=> 移动了同标签传送门,不用手工去删旧图标") |
| 该标签同时探测到多个传送门 | 各自保一个图钉,互不删除 |
| 没设标签的传送门 | 默认跳过,不标记也不高亮 |
最后一条是多加的保护:原版里两个同标签的传送门是互相连通的(很常见), 若不加区分,两头会被反复"删旧建新",图钉一直在闪。 只有"该标签当前只看到一个门"时才执行自动迁移 —— 那才是真正的"把门搬走了"。
⚠️
TakeOverSameName开启时会一并接管你手工放的同名图钉。 不想要就把它关掉(那样只补建、不删除)。
配置
BepInEx/config/easyportal.client.only.cfg
1 - DetectionRadius60 /ScanInterval5 /ActiveSeconds10 —— 三个主参数RequireTag:没名字的门是否跳过(默认跳过)RehighlightOnRedetect:默认 false —— 每个门只高亮一次,站在自己家门口不会一直亮; 想让范围内的门一直保持高亮就改 trueToggleKey:游戏里开关用的快捷键(默认未绑定,别和 OreFinder 的 F9 撞)
2 - HighlightBeamColor格式R,G,B,A,默认0.95,0.62,0.10,0.75(琥珀色半透明)BeamHeight22 /BeamWidth0.6 /LightRange12 /LightIntensity2
3 - Screen MarkerArrowSize26 /FontSize16 /EdgeMargin60 /ShowDistancetrue
4 - Map Pin—— 见上面那张规则表5 - Debug→Verbose:排查用,每次扫描与建图钉都打日志
⚠️ 改完必须重启游戏(BepInEx 只在插件 Awake 读一次配置)。
实现要点(来自 IL 实证,2026-09-17)
- 传送门类叫
TeleportWorld(不叫Portal);标签名读TeleportWorld.GetText(), 其内部是 ZDO 字符串ZDOVars.s_tag再过一遍敏感词过滤。 - 枚举传送门:不在扫描时全场景搜 —— Valheim 的 ZDO 是按距离流式实例化的,
远处的门根本不在场景里,搜也搜不到,而且每 5 秒搜一遍代价太大。
改为
TeleportWorld.Awake时登记进册(PortalRegistry),扫描时只做"剔除已销毁 + 距离筛选"。 另带一条兜底:登记册为空时会用ZNetScene.m_instances(反射)扫一次并打警告, 免得像 EasyGear 1.0.0 那样静默失效。 - 地图图钉走原版
Minimap.AddPin / RemovePin(public)。 参数照抄原版玩家放图钉的调用方式(ShowPinNameInput的 IL):类型由PinIcon决定,save=true跟着地图存档。RemovePin内部会DestroyPinMarker,图标与名字标签一起清掉。Minimap.PinType/Minimap.PinData是Minimap的嵌套类型(踩过,编译期才会发现)。 - 换图钉图标为什么必须"删旧建新":
Minimap.UpdatePins的 IL 显示m_iconElement.set_sprite(m_icon)只在 UI 元素被重建时才执行 (先判m_uiElement == null或父节点不对)⇒ 已存在的图钉不会重新set_sprite, 光改PinData.m_icon字段界面不会刷新。所以本模组在检测到图标不符时删掉重建。 另有防死循环保护:AddPin在类型不可用时会悄悄把类型改写成 Dot, 若不设限就会无限删建 —— 故每个标签最多升级一次(IconUpgradeTried)。 - 屏幕箭头走 IMGUI(
OnGUI),完全不碰游戏的 uGUI 层级 —— 所以不怕后续版本改 UI 预制体。世界坐标 → 屏幕用Camera.main.WorldToViewportPoint, 相机背后的情况要手动把镜像坐标翻回来。 - 中文字体:IMGUI 内置字体没有中文字形,直接画中文会是方块。
解发是
Font.CreateDynamicFontFromOSFont从系统字体现造一个(优先"微软雅黑")。 - 箭头/底色贴图用
Texture2D.SetPixels32运行时生成,不依赖任何美术资源。
兼容性
- 纯客户端:无 Jotunn、无 ServerSync、无
NetworkCompatibilityAttribute(NotEnforced)。 - 联机:主机与其他玩家都不需要装;只读场景与本地地图数据,不改任何网络权威数据。
- 与 OreFinder 功能重叠但不冲突 (都往地图加图钉,但图钉互不干扰)。默认快捷键未绑定,避免与它的 F9 撞车。
安装
r2modman「Install from file」选 EasyPortal-1.0.0.zip,
或把 EasyPortal.dll 丢进 BepInEx/plugins/。
前置:BepInExPack_Valheim 5.4.2350。
版本
- 1.0.0(2026-09-17):首个版本。
