- 版权类型
- 原创
- 版权链接
- #
- Wiki/文档
- https://github.com/Ieckseyr/Mtps
- 语言支持
- 中文(简体)
- 前置组件
- HologramLib https://github.com/Ieckseyr/HologramLib
- 适配版本(基岩)
- 最新版本
[MD]
# Mtps传送系统
## 功能
玩家侧
- 私人传送点:保存当前位置,点击直达,支持"免申请"共享给别人
- 公共传送点:管理员维护,所有人可传送
- 免申请传送点:别人无需申请即可传送到你的点
- 玩家互传(TPA):发起请求,对方 `/y` 同意、`/n` 拒绝,侧边栏可用 PAPI 提示
- 请求管理:待处理请求一览,可逐条同意 / 取消;同意入口也用于响应召集(拒绝用 `/n`)
- 随机传送:只走管理员添加的预设(半径、维度、冷却、费用各自可配),落点避开危险方块;
优先命中**预落点池**(预先算好的候选落点,含"按世界种子推算的未生成区域"),
池未命中才回落四级数据源;排除海洋/河流群系(与 ZXDash 群系底图同源)
- 召集:发起后其他人可一键聚过来(再点一次 = 取消)
- 跨服传送:一键转到其它服务器(地址由管理员维护)
- 黑名单:名单里的玩家不能向你发起传送
- 个人设置:拒绝所有 TPA、关闭弹窗、拒绝传送点申请
- 传送保护:出生流程没跑完、或玩家正在跨维度切换(含切换后客户端还在加载的那几秒)时一律不传送,
提示"正在切换维度"稍后再试 —— 否则玩家会被撕成两个实体(幽灵状态,可刷物)。被挡下的 TPA 请求会保留
- 模糊搜索:按玩家名、传送点名、拼音/首字母查找
管理侧
- 全部系统参数可在菜单里直接改:传送点上限、传送参数、经济系统与各项费用、默认规则
- 公共传送点增删改;填入坐标修改(输入框默认当前坐标)或一键用当前位置
- 随机传送预设增删改:名称、原点(固定 / 以玩家为原点)、半径、维度、冷却、费用
- NPC 传送点:载体可选假玩家 NPC 或虚假实体,实体类型可填,右键 / 左键触发传送
- 每个点可单独调:传送冷却、触发方式、看向玩家、悬浮字、高度与水平微调、皮肤
- 皮肤:从 `npc_skins` 目录取,没配就用内置史蒂夫;MHR 存过的皮肤也能直接用
- 编辑工具:手持指定物品或蹲下右键 NPC / 实体,直接打开该点的管理表单
- 传送类型可在菜单里互切(定点 ↔ 随机),切换时自动补齐对侧参数
- 搜索任意玩家的传送点(含私人点)并直接修改或删除
## 命令
默认名字沿用 JS 版那套(老玩家不用重新记):
| 命令 | 说明 |
| --- | --- |
| `/mtps` | 主菜单 |
| `/warp` | 传送点系统 |
| `/mywarp [编号]` | 我的传送点(编号直达) |
| `/pubwarp [编号]` `/napwarp [编号]` | 公共 / 免申请传送点(编号直达) |
| `/pwarp` | 玩家传送点浏览与搜索 |
| `/tpa` `/y` `/n` `/req` | 玩家互传、同意 / 加入召集、拒绝、请求管理 |
| `/tpr [编号]` | 随机传送(编号直达预设) |
| `/rally` | 发起召集(再执行一次 = 取消) |
| `/tpset` | 个人设置 |
| `/tpblock` | 黑名单管理 |
| `/cswarp` | 跨服传送 |
| `/tpadmin` | 系统管理(OP) |
| `/blocktp` | NPC 传送点管理(OP) |
| `/mtps reload` | 重载配置与数据(OP,控制台可用;子参数名可在 `commands.reload` 改) |
名字都能在 `Config.json` 的 `commands` 段里改:键名固定、值就是指令名,**留空 = 不注册该指令**。
插件**只读取**这段配置:不会替你改写或不填的名字、也不会删键(只是把配置里还没有的键按默认值补进去,
好让你能在文件里看见并修改)。
名字首字符必须是字母或下划线,其余只能是字母 / 数字 / 下划线;重名或不合法的会被跳过并在启动日志里告警。
自己起的名字超过 4 个字母会提示改短(出厂默认名不提示)。
重载不是独立指令, 而是 `/mtps` 的子参数(跟 JS 版一致): `/mtps reload`。
同名指令已被其它插件注册时会跳过这一条并告警 —— 从 JS 版迁过来时,JS 版还装着就会出现这种情况。
整段 `commands` 的名字全是空串时按配置执行(不注册任何指令)并在日志里报错提示;
只有这一段整个缺失/形状不对(没法"尊重"一份不存在的配置)时才临时用出厂名字。
## 配置文件
`Meowdata/Mtps/Config.json`。改动可以直接编辑文件后 `/rld`,也可以在管理菜单里点。
```json
{
"debug": false,
"commands": { "menu": "mtps", "private": "pw", "reload": "rld" },
"skins": {
"extraDirs": ["plugins/MeowHolographicRenderer/config/npc_skins"]
},
"blockTeleport": {
"editTool": {
"enabled": true,
"item": "minecraft:nether_star",
"itemRequiresSneak": false,
"sneak": true,
"requireAdmin": true
}
},
"randomTeleport": {
"cooldownSeconds": 120,
"preferKnownLandings": false,
"precompute": { "enabled": true, "maxLandings": 0 },
"presets": [
{ "name": "主世界随机", "radius": 1000, "dimid": 0, "cooldown": 0 }
]
},
"papi": {
"enabled": true,
"formats": {
"mtps_tpa": "§6[TPA] §e{player} §7{type}",
"mtps_tpa_count": "§e{count}",
"mtps_warps": "§e{count}§7/§a{max}",
"mtps_shared": "§e{count}",
"mtps_public": "§e{count}"
}
}
}
```
上面是节选,实际文件里各段都是完整的。几处要点:
- `debug`:顶层调试日志开关(默认 `false`)。打开后会输出启用步骤、指令注册逐条结果、
随机传送的详细流程(选点/扩圈/等待/落点来源)等;平时保持关掉即可。
- `commands`:指令名,规则见上一节。
- `skins.extraDirs`:额外的皮肤来源目录(默认指向 MHR 的皮肤目录,认其中的 `*.bin` 快照)。
- `blockTeleport.editTool`:管理员编辑工具,`item` 填物品名(留空 = 只认蹲下)。
- `blockTeleport.fixedCooldownMs`:NPC / 实体交互的防抖窗口(毫秒)。一次右键会连发多个事件
(右键 + 交互更新),窗口内的重复事件会被丢掉,免得连开好几次表单、连发好几次传送;
`<= 0` 关闭防抖。
- `randomTeleport.cooldownSeconds`:全局随机传送冷却;预设自己的 `cooldown` 优先,
`0` = 用全局、`>0` = 用该值。
- `randomTeleport.maxAttempts`:一次随机传送最多换几个**候选点**(默认 50,实际生效上限 16)。
每次尝试都可能要生成一整个区块(半径 2 的常加载区域 = 25 块),所以上限封在 16 ——
会话总超时(60 秒)是最终兜底。调大 = 更不容易"没找到安全位置"失败,代价是最坏情况下更慢。
> 这个键以前只被读取、从未生效(真正的重试次数写死在代码里的 6),现在按名字的意思起作用了。
- `randomTeleport.debug`:随机传送的详细流程日志,**默认 `false`**(顶层 `debug` 是总开关,
两者任一开着都会输出)。
> 排查"随机传送很烂"就把开关打开,然后看每次传送末尾的 `[RTP][汇总]` 那一行 ——
> 它把过程压成了结论:候选从哪来、试了几轮、扩圈多深、每种判定各走了几次、等了几轮生成。
> 配合 `[RTP][落点复核]` 能直接看到**落点脚下是什么方块**(落在树冠上会是 `*_leaves`、
> 落在水里会是 `water`)。逐条过程日志(选点来源 / 探测结论 / 扩圈逐圈 / 等待状态)都在
> 同一次传送里按顺序输出,前缀统一是 `[RTP]`。
- `randomTeleport.precompute`:落点表预计算的开关与上限。
`enabled: false` = 完全不碰存档(不建索引、不扫 `.ldb`、不读缓存),随机传送只走内存 + 区块生成;
`maxLandings` = 预计算落点上限(chunk 数,`0` = 不限制,每个约 16 字节内存)。
大存档想压开服时间/内存就调这两个;改了上限会自动重算并覆盖缓存。
- `randomTeleport.biome`:群系判定(按世界种子推算,与 ZXDash 群系底图**同库同种子同切片**)。
`excludeBiomes` 里的群系不会被选作落点(默认 10 个海洋 + 2 个河流;**按名字写,不写 ID**);
`neighborAgreeRadius` = 邻域半径(群系格,1 格 = 4 方块),邻域内出现被排除群系就丢弃该候选点
—— 这是"别落在海边/水里"的那道闸(默认 2 格 = 20×20 方块)。
注意:洞穴群系(`dripstone_caves` / `sulfur_caves` 等)**不是水**,别加进排除清单。
- `randomTeleport.pool`:预落点池(随机传送的首选数据源)。
- `loadedTarget`:**已加载类**目标数量(默认 1000)。落在存档里已有数据的区域 → 传送只需把区块
载入内存,**不生成地形**。
- `unloadedTarget`:**未加载类**目标数量(默认 3000)。cubiomes 按种子推算的候选点,落在没生成过的
区域 → 传送需要生成地形(这是唯一允许的等待)。
- `hitRadius`:**同一玩家连续两次传送的最小间隔**(默认 500 格)。主要作用于已加载类
—— 它们聚集在已探索区,容易连传两次落到同一片。池里没有更远的点了会自动放开这条约束,
不会因此失败。(池点**就是**目标点,不做抖动:以前那版抖动会让生成的区块与池点不在同一个
区块,进而把成功的传送误判成"该池点失效"。)
- `minSeparation`:池内点最小间距(默认 64 格,防扎堆);同时决定半径能容纳的点数上限
(圆盘内约 `πR²/s²` 个,超了会自动下调实际目标数并写日志)。只作用于未加载类的生成。
- `sampling`:`bucket`(默认,推荐)| `point`(严格按字面:先圆盘内均匀随机一点,再查该点
`hitRadius` 邻域内的预落点 —— 半径大时命中率≈0,只作备选)。
- `approxHeightMin`:近似地表高度下限(默认 63 ≈ 海平面),低于它的候选点丢弃。
- `replaceRatio` / `rebuildOnRadiusChange`:改了预设半径后**静默重建**未加载类并向新区域
替换掉该比例的旧点(默认 0.5)。半径缩小也会清理掉落在新区域外的旧点。
- `warm`:**后台预热**——把"待生成"的预落点逐个在后台生成出来并验证,变成本地已生成的点。
之后玩家落到它们身上就只需**加载**(常驻的那些是**瞬时**)。
- `startDelaySeconds`(默认 90):开服后等多久开始预热,把开服负载让过去。
- `intervalSeconds`(默认 30):两次预热之间的间隔。一次预热 = 生成 25 个区块(r=2 的区域),
实测在闲置的服上约 0.7~1.0 秒完成。调小 = 池子热得更快,代价是后台生成更密。
- `keepResident`(默认 4):预热好之后保留为"常驻"的点数(区块留在内存里 → 抽到它瞬时传送)。
每个约占 25 个区块的内存;用完即释放,位子留给下一个。设 0 = 只求"已生成"、不占内存。
- `onlyWhenIdle`(默认 true):有玩家正在传送时让路,不抢生成队列。
> ⚠ 预热是**按池里的点抽样**做的,不是"预生成整张地图":预设半径只是个抽样范围 ——
> 半径 500000 的圆盘约 30.7 亿个区块(每秒 100 块也要 355 天、存档 60TB),物理上不可能整片生成。
> 池里那 3000 个点本身就是对该半径的均匀抽样,逐个热过去即"大致确保覆盖到这个范围"。
- `enabled: false` = 完全回退到改造前的行为(四级数据源,无池)。
池落盘在 `Meowdata/Mtps/landingpool.bin`,**再开服只补缺口**(不重算已有的点)。
- `randomTeleport.preferKnownLandings`:选点时优先取"存档里已知安全"的已生成区块。
打开后传送基本是即时的(不用等地形生成),代价是落点只会落在已探索范围内;
关(默认)时仍按圆盘均匀随机选点,适合想让玩家散到新地形的服。
(预落点池就绪后这条退居次席 —— 池本身就是"预先算好的已知安全点 + 预先算好的新地形候选点"。)
- `papi`:占位符注册开关与格式(某条设为空串 = 关掉该占位符)。
## PAPI 占位符
走 MeowPAPI 的 C ABI 注册(不链接它的静态库,也就不依赖 lrca)。MeowPAPI 没装或者
版本不符时自动跳过,不影响其它功能。
| 占位符 | 内容 | 可用的替换字段 |
| --- | --- | --- |
| `mtps_tpa` | 最近一条待处理的传送请求,没人请求时为空 | `{player}` `{type}` |
| `mtps_tpa_count` | 待处理请求数量 | `{count}` |
| `mtps_warps` | 自己的私人传送点数量 | `{count}` `{max}` |
| `mtps_shared` | 自己共享出去的传送点数量 | `{count}` |
| `mtps_public` | 公共传送点总数 | `{count}` |
## 数据文件
| 文件 | 说明 |
| --- | --- |
| `PrivateWarps.json` | 私人传送点(按 xuid) |
| `PublicWarps.json` | 公共传送点 |
| `BlockTeleportPoints.json` | NPC 传送点 |
| `CrossServer.json` | 跨服目的地 |
| `WarpRequests.json` | 传送点申请 |
| `TpaCachePool.json` | TPA 请求缓存 |
| `PersonalRuleSettings.json` | 个人设置与规则 |
| `landings.cache` / `.meta` | 落点预计算表(每 chunk 一个安全落点,`.meta` 存 `.ldb` 指纹) |
| `landingpool.bin` / `.meta` | 预落点池(已加载类 + 未加载类;`.meta` 存种子/算法版本/半径/目标) |
| `npc_skins/` | 皮肤目录(`<id>.png` 或 `<id>/` 子文件夹;`default.png` 为内置默认) |
| `pinyin.txt` | 搜索用的拼音表(可选,每行 `汉字 拼音`) |
写盘是合并式的:改动先记脏,最多 3 秒落一次盘,写文件走临时文件 + 改名,写失败下次重试。
关服时会全部落盘。
## 对外接口
Mtps 的虚假实体载体可以交给 MeowHolographicRenderer(MHR)托管,前提是对方提供这几个
导出(C ABI;Mtps 用 GetProcAddress 解析 + 版本握手,没有或主版本不符就回退为自建实体):
```c
uint32_t MHR_GetAbiVersion(void);
int64_t MHR_SpawnEntity(const char* ownerKey, const char* identifier, const char* nametag,
float x, float y, float z, int dim, float yaw, float scale, int64_t* outLibId);
bool MHR_DespawnEntity(const char* ownerKey);
```
`ownerKey` 是幂等键(`mtps:<点位名>`),同一个键重复登记只会有一个实体。
### 假玩家 NPC 与虚假实体
两种载体的差异(管理器、接口签名、名字牌字段名)只允许出现在 `NpcTeleport.cpp` 的 `CarrierApi` 适配层,上层只认「载体 id + isEntity」。两种载体的 id 段互不相交(NPC 自增段从 1 起,虚假实体固定在 `[0x10000000, 0x7FFFFFFF)`),所以点击路由共用一张表。
库内 id 不保证稳定(例如经 MHR 改过实体属性之后会变),所以 id 只当缓存:失效时按"创建时给它的名字牌"认回来,连续 3 秒认不回来才重建。
高度微调按载体各一个(客户端对 `AddActor` 与 `AddPlayer` 的 y 原点未必一致),竖直方向不再另设共用的 Y —— 两个设成同值就是整体平移。悬浮字高度相对载体算,载体抬高时字跟着走。
### 皮肤来源
- `Meowdata/Mtps/npc_skins/`:`<id>.png`,或 `<id>/` 子文件夹(内含 PNG + 可选 `.json` 几何)
- `Config.skins.extraDirs`(默认指向 MHR 的目录):认 `*.bin` 快照(HologramLib 的持久化格式)
- 库里已注册的(与 MHR 共用同一个 HologramLib 实例,它注册过的直接可用)
库不预置任何皮肤,`skinId` 未注册时 `create()` 直接返回 -3,所以"没配皮肤"用内置史蒂夫配色:首次启动生成 `npc_skins/default.png`,覆盖它即可换。
## 随机传送怎么生成新区块
选点先看**预落点池**(`randomTeleport.pool`),没命中才回落到四级数据源。池是"预先算好的候选落点",
分两类(分界 = 存档里有没有这块地):
| 类 | 来源 | 传送时发生什么 |
| --- | --- | --- |
| 已加载类 | 落点预计算表(存档里已有该 chunk) | 只需把区块**载入内存**,不生成地形 |
| 未加载类 | cubiomes 按世界种子推算的候选点(存档里没有) | 需要**生成地形**(唯一允许的等待) |
未加载类的候选点带一个"近似地表高度"(`mapApproxHeight`,实测与真实地表平均差 6 格),
只用来预筛和诊断 —— **绝不作为传送落点的 Y**,真实 Y 永远由生成后的地表扫描决定。
生成未加载类候选点时会做群系排除(`randomTeleport.biome`,默认排除 10 个海洋 + 2 个河流,
并要求候选点周围 20×20 方块内不出现被排除群系),所以池里的点不会是"海里/海边"。
池在开服后由后台线程补齐,**分两轮**:
- 第一轮不等落点表(延后 5 秒就开跑)——**未加载类那 3000 个点不依赖存档**(纯 cubiomes
推算),所以池在"Mtps 启用后约 6 秒"就能用;
- 落点表就绪后再跑第二轮,补上"已加载类"(那批点要从落点表采样)。
**传送路径上不做任何群系计算与存档 IO**,只有一次 O(1) 的桶查找。池还没就绪、或圆内没有点时,
降级路径也会**按群系预筛**随机点(约三成均匀随机点落在海洋/河流上,那些点必然白生成一个区块
才判定失败)—— 所以即便在池就绪前的那几秒,也不会把玩家往大洋里扔。
**命中的是哪一类点,决定了要不要等**:已加载类只需把区块载入内存(通常瞬间完成);
未加载类落在没生成过的地形上,必须等引擎生成那一个区块 —— 单次约 1.4~2.4 秒(半径 2 的
常加载区域 = 25 块),这是物理成本、不是缺陷。想让传送"几乎不等待",就提高
`pool.loadedTarget`(受限于存档里已探索的范围)或缩小预设半径。
> 区域交接有个坑值得记一笔:换点时**必须先挂新区域、再撤旧区域**,而且旧区域要延后撤。
> 同 tick 先撤后挂会让这些区块失去唯一引用,引擎当场把它们卸掉(日志表现为等待中途
> 状态从 `Loaded` 掉回 `Unloaded` 并卡住)—— 实测一次传送因此白等 10 秒。
> 现在还有个更省的短路:**区域是个圆、有半径**,落点只要仍被现有区域覆盖就直接复用,不做登记。
没命中池时的判定按代价从低到高:内存里的区块(数据最新)→ 后台预先算好的落点表(内存二分)→
直读存档 `.ldb`(已生成但没加载)。三者都没有 = 这块地从没生成过,此时登记一个**临时常加载
区域**,让引擎自己把它生成出来。
临时区域走引擎 API(`TickingAreasManager::_addArea`),不是 `/tickingarea` 命令:不落盘、
重启不会被预加载、跳过区域个数上限,用完直接移除。注意 `Bounds` 的 x/z 单位是**区块**(y 是方块)。
区块就绪后才传送;落点若落在区域之外(扩圈命中的角落)会先把区域改挂到落点、等它就绪再传
—— 否则玩家会落进没有区块数据的位置(表现为一片虚空)。
几个提速上的取舍:等生成时只挂半径 2 的小区域(引擎按块处理,区域越大中心越晚轮到,
而且一次性塞几十块进生成队列也会顶起 MSPT),所以等待期与宽限期都用半径 2。扩圈搜索里
"表里没有这个区块"也算廉价查询(一次二分),否则整片没生成过的荒野会把每 tick 的扫描额度吃光;
重随名额用尽时会退回"存档已知安全点"再试一次,而不是直接放弃。选点策略见
`preferKnownLandings`。
主线程开销是硬限制的:内存扫一块约 1ms(实测一圈 42 块 ~44ms),所以每个会话每 tick 只允许
扫 2 块内存 + 直读 2 块存档(代价高得多),落点表那种零 IO 查询才放开到每 tick 512 次;
整段 tick 的耗时上限是 2ms,超过 5ms 会在 debug 日志里报一行 `[RTP][开销]`。也就是说扫描被摊到
多个 tick 上——传送可能慢几百毫秒,但 MSPT 不会因为随机传送抖一下。这几个数都在
`RandomTeleportInternal.h` 里(`RTP_SCAN_CHUNKS_PER_TICK` / `RTP_IO_CHUNKS_PER_TICK` /
`RTP_TICK_BUDGET_MS`)。
传送期间还剩两处 MSPT 开销,已经压低:
- **内存扫描**改为**先扫中心 5×5 内圈**(陆地通常直接命中),不再每次都先跑 64 列水域快筛 + 全列主扫;
取面扫描也去掉了"同一方块查两次"的回退;顺带修好了一直失效的 `hintY`(上一列地表高度提示,那句
更新写在 `return` 之后,永远执行不到)。
- **等待"周围区块加载完"期间不再每 tick 重扫同一个区块**(改用缓存的落点,超过 10 拍才复核一次)——
原来最多 40 拍 × ~1ms 的额外开销归零。
剩下的地板在引擎侧:`player.teleport()`(玩家跨几十万格移动、旧区域卸载 / 新区域发给客户端)
和"生成 25 个区块"。插件改不了它们的单次成本,只能**降低它们出现的频率** —— 这正是
`pool.warm` 在做的事。
### 落点质量:树冠不算地表
表面扫描是"从 y=200 往下找第一个非空气方块"。树叶在基岩版是实心方块,所以**以前整片森林的
落点都停在树冠顶上** —— 玩家传过去站在树上,得砸树叶才能下来。实测(存档直读统计,出生点附近
81×81 chunk):树叶占陆地列的 14.2%;把落点表逐 chunk 做新旧对照,**24% 的落点因此下移了
9~30 格**(树冠到地面的距离)。
现在的规则:**`*_leaves` 一律不算地表**,扫描时穿过它继续往下,落点取它下面的地面
(判断按后缀,覆盖所有树种,不用维护名字表)。这条规则在三个取面点保持一致:
内存扫描 `findSurfaceSafe`、同步存档读取 `scanChunkSurface`、落点预计算 `decodeSubchunkColumns`
—— 三者口径不一致的话,同一坐标在不同数据源上会给出不同落点。
> 规则一改就会换 `kLandingRulesVersion`(`SurfaceRules.h`),老的 `landings.cache` 与预落点池里
> 的"已加载类"点会自动重算 —— 光看 `.ldb` 指纹是发现不了规则变化的(世界没变,判定结论变了)。
### 预落点池怎么维护自己
- **只补缺口**:池落盘在 `Meowdata/Mtps/landingpool.bin`(+ `.meta`),再开服读回来,
只算"目标数 − 现有可用数"的缺口 —— 不会把上一次算好的点重算一遍(实测再开服 11ms、补充 0 个)。
- **自愈**:未加载类的点被真正生成过、扫描通过后,会**升级为已加载类**(下次传送不再需要生成);
判定无安全列/传送失败的会被标失效,下次补齐替换掉。已加载类不回写(它们本来就来自落点表,
表才是权威,不该被一次超时拉黑)。
- **半径变更**:改了预设半径保存后,只打一个"待重建"标记(秒回、不阻塞表单、不打断传送),
后台按新半径重算未加载类并替换掉 `replaceRatio`(默认 50%)比例的旧点 —— 新旧混合,
既覆盖新的大范围,也不丢掉近处常用的点。半径缩小还会清掉落在新区域外的点。
- **自检**:每次建池后自动跑 500 次合成抽取并打一行日志(命中率 / 落点是否在半径内 / 抖动是否
在命中半径内)。因为 `pick()` 坏掉的表现是"静默永远走降级",不报错、看着正常、只是慢 ——
只有把这个数字打出来才看得见。
- **诊断日志**(都在开服后台补齐时输出,前缀 `[RTP][预落点池]`):
`已载入落盘池` / `本次补充 已加载 x / 未加载 y` / `已加载类未配满 …`(说明为什么没配满)/
`区域变更: 半径 a → b` / `自检 …`。
设计文档(含实测数据与踩过的坑):`docs/MTPS随机传送预落点优化方案.md`。
## 构建
需要 xmake 与 MSVC(C++23):
```
xmake build
```
产物是 `Mtps.dll`,构建后自动拷到工程目录和 `bin/Mtps/`。
依赖:LeviLamina 26.40、HologramLib(协议实体 / 假玩家 NPC / 悬浮字)、zlib、zstd、
**cubiomes-bedrock**(`third_party/cubiomes/`,按世界种子推算群系与近似地表高度)。
> `third_party/cubiomes/` 是从 `plugins/ZXPanel/third_party/cubiomes` **整目录拷贝**的
> (ZXDash 的群系底图用同一个库)。升级 BDS 版本、或 ZXDash 那边换了 cubiomes 版本时,
> 两边要一起同步 —— 否则"MTPS 判定的群系"会与"地图上看到的群系底图"不一致。
> 改动这个目录时注意:`src/BiomeSampler.cpp` 是唯一允许引入 cubiomes 头的翻译单元
> (cubiomes 的 `enum Dimension` 与 BDS 头重定义冲突)。
## 目录
```
src/
Mtps.cpp 命令注册、事件监听、tick 驱动
Menu.cpp 主菜单、随机传送菜单、搜索、黑名单、个人设置
WarpMenu.cpp 传送点系统、私人 / 公共 / 免申请、跨服
BlockTpMenu.cpp NPC 传送点管理(创建 / 编辑 / 载体 / 外观)
TpaMenu.cpp 玩家互传、请求管理
AdminMenu.cpp 管理员参数表单
RandomTeleport.cpp 随机传送状态机(会话 / 探测 / 扫描 / 扩圈)
RandomTeleportInternal.h 状态机内部共享声明
SurfaceScan.cpp 地表扫描与落点判定
LandingScan.cpp 落点预计算
TickingAreaUtil.cpp 常加载区域(引擎 API)与残留清理
BedrockLevelReader.* 存档 .ldb 直读
ArchiveScanner.* 存档直读的后台封装(索引 + 落点表)
BiomeSampler.* cubiomes 群系/近似高度采样(唯一引 cubiomes 头的翻译单元)
PreLandingPool.* 预落点池(分桶 / 持久化 / 补齐 / 半径重建 / 自愈)
LandingPoolService.* 预落点池的开服装配与后台补齐线程
NpcTeleport.* 假玩家 NPC / 虚假实体载体
NpcSkin.* / NpcSkinSteve.h 皮肤来源管理与内置默认皮肤
WarpManager.cpp / TpaRally.cpp / Economy.cpp
DataStore.* / Config.* / DataTypes.h
MhrAbi.* 与 MHR 的 C ABI 对接
MtpsPapi.* 与 MeowPAPI 的 C ABI 对接
Pinyin.* / TpUtil.* / MenuCommon.h / MemoryOperators.cpp
```
[/MD]