更新日志¶
按发布版本倒序排列。每个条目给出 API 版本、新增或变更的导出、行为变更,以及是否需要重新编译 C++ 消费方。 发布版本与 API 版本的对应关系见版本与 ABI 对照。
版本概览¶
| 发布版本 | API | 协议线 | 主要内容 | 新增导出 | C++ 消费方 |
|---|---|---|---|---|---|
26.51.6 |
1.31.0 | 26.51 | 内部重构(发包 / tick / 生命周期收敛)、ABI 契约工具、支付规划修复、工程化 | 0 | 无需重新编译(虚表未变) |
26.51.5 / 26.40.12 |
1.31.0 | 26.51 · 26.40 | 拉伸 / live 变换 / 动画预设扩展 / 停止配方 / 假玩家姿势装备骑乘 / FMBE 抽搐修复 / 图片平面与透明图层 / 容器会话隔离 | 若干 | 建议重新编译(虚表尾部追加) |
26.40.11 / 26.51.4 |
1.30.0 | 26.40 · 26.51 | 三个协议原语 + 一个时机通知 | 3 | 无需重新编译(虚表未变) |
26.40.10 / 26.51.3 |
1.29.0 | 26.40 · 26.51 | 视图覆盖补齐 AddItemActor 拦截、姿态/缩放/变种/颜色;修复元数据 tick |
1(viewItemSpawnHooked) |
需重新编译(EntityView 结构体变大) |
26.40.9 |
1.27.0 | 26.40 | EntityView::skinId(同 id 换模型)、真实实体交互事件、ABI 布局戳 |
类方法 | 需重新编译(虚表追加) |
26.40.8 |
1.26.0 | 26.40 | 逐观看者覆盖、四个界面域 | 若干 | 需重新编译 |
26.51.6 — API 1.31.0(内部重构与修复,无 API 变化)¶
API 版本与虚表槽位都没动,C++ 消费方无需重新编译。
重构¶
- 发包收敛:三份等价实现合一为
src/core/PacketSink.h(SculkPacketSend.h降为两行转发)。此前ItemDisplayManager.cpp里那份匿名副本不计数、不告警,现在统一计入失败计数。 - tick 收敛:5 个
Level::$tickdetour 收成 1 个(src/core/TickDriver.h),执行顺序改为显式声明,不再依赖跨 TU 的静态初始化顺序。 - 构建:
xmake.lua改用src/**.cpp通配;ghost/GhostInteractRouter.cpp恢复独立编译单元(此前被#include进另一个.cpp)。 - 生命周期:域注册收敛到
src/core/ServiceHost.h。启动按声明顺序、关停反向、LSE 导出一遍,新增域只改一处。 顺带修好一个既有缺陷:ContainerMenu/NpcDialogue/PlayerSensing/FakeInventory四个域的shutdown()早就写好,但ModEntry::disable()从来没有调用过 —— 关插件时已打开的虚拟容器 / NPC 对话框不会关闭、 伪造背包不会还原,客户端要重连才恢复。现在随IDomain::stop一起被调用。 关停顺序因此改为启动的逆序(此前是另一份手工清单);各域清理互相独立,无顺序依赖。 - 契约工具:新增
tools/abi_tool.py、abi/v1.31.0.json与abi/slots.md基线,abi.yml在 CI 上拦「中段插槽」。mirror子命令可由真头文件生成消费方镜像,并自证槽位表与头文件逐槽一致(312 槽 / 15 接口)。 - 对象表内核化:新增
src/core/Registry.h(id 分配 / runtimeId 与维度索引 / 脏集 / 快照遍历, 只依赖标准库)与tests/registry_test.cpp(36 项断言,含并发、锁策略、peek、withObject返回值语义; CI 用 g++ 直接跑)。锁是可配的:默认自持std::mutex,多表共用临界区的域传core::NoLock, 需要重入的传std::recursive_mutex。withObject在回调返回 bool 时原样透传,域里「找不到 id」和 「参数不合法」两种失败才能各自 return。 10 个有主表的域全部迁完(fakeinv主键是玩家名,不适用)。particles顺带把按值的Shape换成unique_ptr,tick改成 forEachLocked + 到期集;shape的networkId分配换成原子递减;sulfur的setInvisible/setScale原先持锁调CustomEntityManager(嵌套加锁 + 发包可重入), 改成锁内取快照、锁外调下游。playernpc/itemdisplay/customentity先并表再迁:两张 id 同键的表合成一个Entry。 并表模板、访问点数量和四个只能人工确认的点见架构总览的「内核化进度」。 - 虚拟 actor 号段:
0x6D/0x6E/0x6F三段 runtimeId 原先散在三个 Manager 的初始值里,ghost 路由又抄了一遍 6 个 constexpr 做判别;现在唯一出处是src/core/VirtualActorIds.h—— 分配起点取.first、判别用.contains(),三段相接 / 等长 / 落在[0x60000000,0x70000000)由static_assert守住。改号段再也不会出现「分配端改了、路由端没改」的静默派发失败。数值未变。
修复¶
- 交易支付规划:
planConsumption对同类型双费用(buyA/buyB是同一物品)分别遍历账本, 同一份余额可能被两笔费用各扣一次。现在先合并同类型费用再消费,需求量改在 64 位域累计。 离线夹具覆盖同类型与不同类型两条路径。 - 容器点击回传的
menuId:open()只把新 id 写进了局部变量,表里那份Menu::menuId仍是 0, 于是所有点击回传都是menuId=0,按 id 路由的调用方全部失效。现在插入后回填一次。 这条只能起服点击才发现,编译和静态检查都看不到。 - 热路径:ABI 检查缓存本模块句柄;ghost 事件格式化与请求动作转换复用;删除未用内部状态与空渲染 tick。
工程化¶
tests/纳入版本控制(字节级对拍的 C++ 夹具与 Python 断言),补tests/README.md说明怎么跑、缺什么会跳过。- 新增构建 CI:静态检查(ABI 契约 + 工具自测 + 内核层单测 + 只检查改动行的格式)必跑;真编译需要仓库变量
HOLO_PROTOCOL_URL,未配置时显式跳过,不伪装成绿。 - 新增
.editorconfig/.clang-format/.clang-tidy;xmake 增加--holo_werror(/WX)与--holo_asan(/fsanitize=address)。 - 构建封闭性:
HOLO_PROTOCOL_DIR/HOLO_REPO_DIR两个环境变量,缺协议库时直接报错并说明怎么设。 - 异常语义:去掉
/EHa,统一为标准 C++ 异常(set_exceptions("cxx"))。旧配置同时开了/EHa与「禁用异常」, 会让<chrono>等标准库里的 throw 退化成不展开栈(C4530),行为未定义。 - 悬浮字管理器改用递归锁:发包钩子会同线程回调进本类,非递归锁会自死锁。
.clang-format的Standard: c++23不是合法枚举值(只有 c++03/11/14/17/20/Latest/Auto),clang-format 读配置就报unknown enumerated scalar—— 格式检查在检查任何文件之前就失败。改为c++20。- 格式门禁改成只看改动的行(
tools/check_format.py):仓库没做过一次性格式化,整文件--Werror会被 历史 violation 顶红(CustomEntityManager.cpp单文件 298 处、ItemDisplayManager.cpp377 处)。 .clang-format打开AlignConsecutiveDeclarations/AlignConsecutiveAssignments:原来两项是 false, 与代码里手写的成员列表对齐冲突 —— 任何被改动到的对齐行都会被拆成单空格(内核层那几行就踩过)。 打开后 clang-format 按手写风格对齐,不再破坏周围排版。- CI 补一步内核层单测:
g++ -std=c++20 -pthread -I src tests/registry_test.cpp,不需要 levilamina / BDS。 tools/check_format.py增加--fix:只就地格式化改动的行,不做整文件重排(避免老文件几百行纯格式 diff)。- manifest 增加 ABI 版本展示:
extraInfo.apiVersion/apiVersionSemver/abiStamp。放在extraInfo是因为 LeviLamina 的Manifest里它是自由字符串表(LL 自己的 manifest 就用它带sentryDsn)—— 顶层自定义键会被反序列化忽略,任何工具都读不到。tools/abi_tool.py check现在一并校验这三项: 改头文件却忘改 manifest 会在 CI 上红。
26.51.5 / 26.40.12 — API 1.31.0¶
26.51 线把 26.40 线 26.40.12 的能力整体搬到 26.51(协议 2193),能力与 26.40.12 一致,仅协议线不同。
新增能力¶
- 非等比拉伸:
IItemDisplay的setStretch/setStretchLive,XZ 与 Y 两条通道;方块任意单轴用setExtend。 - live 变换:
setRotationLive/setOffsetLive/setScaleLive/setTransformLive,只重发动画包,不重生实体。 - 动画预设扩展:
ItemDisplayAnim增加 orbit / tumble / wobble / heartbeat / pendulum / billboard 俯仰 / LOD / 第一人称隐藏;新增单通道setAnimPreset与 LSEitemDisplaySetPreset。 - 动画停止配方:
animation.player.attack.positions+stopExpression=1;stopAnimation/stopAnimationTo。 - 假玩家姿势/装备/骑乘:
setPose/setEquipment/setRideUniqueId/setRidePlayer/setRideEntity/clearRide。 - FMBE 生成抽搐修复:装备延后到 sleeping / swelling / posrot 之后(物品 +5、方块 +7 tick),live 重发保留未发装备。
- 图片平面:
playerNpcRegisterImageSkin(pngPath, skinId, sizeBlocks, transparent);transparent=false时透明区域铺白,true时按 alpha 阈值挖空。 - 容器会话隔离:
uiOpen只向已打开界面的玩家刷新;ownerEpoch作废旧会话延迟包;containerId释放后 200 tick 内不复用。 - 皮肤语义:
PlayerSkin的TrustedSkinFlag=true;皮肤Id/FullId按 registryId 重建。
26.51 协议线¶
- 协议 2193;形状文本载荷含
LineGapHeight(26.51 必需)。 - 容器成功应答按 26.51 的
presence=false编码。 PlayerAuthInput输入位用EnumSet::contains逐位读取。
消费方¶
虚表尾部追加,HOLOGLIB_ABI_STAMP 不变;直接用旧头文件编译的消费方建议重新编译以使用新方法。
26.40.11 / 26.51.4 — API 1.30.0¶
26.40 与 26.51 两条协议线的发布内容一致。
新增导出¶
| 导出 | 说明 | 返回值 |
|---|---|---|
viewPollSpawnEvents() |
返回并清空出生事件队列。每条格式为 drop\|uid\|dim\|x\|y\|z\| 或 actor\|uid\|dim\|x\|y\|z\|<类型名>;同一实体只入队一次 |
Array<String> |
itemDisplayCreateFull(...) |
创建物品悬浮,并在创建时传入物品 NBT 与附魔光效,避免创建后再调用 itemDisplaySetItemWithNbt / itemDisplaySetGlint 引起的重建。参数与 itemDisplayCreateAdvanced 相同,末尾增加 nbt、glint |
悬浮 id |
itemDisplayEmitForEntity(playerName, uid, item, aux, offsetY, rotX, rotY, rotZ, scale, nbt, glint) |
对指定玩家,把 uniqueId 为 uid 的现有实体按 FMBE 规则渲染为物品模型:依次发送 RemoveActor、AddActor(fox)、MobEquipment、SetActorData 与动画序列。使用调用方给定的 id,库内不保存状态。rotY 传 "0" 时按该实体当前速度方向计算朝向 |
1 物品平面 · 2 3D 方块 · <0 失败 |
修复¶
- 附魔物品不显示光效。 此前的导出无法在创建或发射的同一步给出
glint,必须另调itemDisplaySetGlint才可能生效,漏调或晚调即无光效;同时附魔内容由nbt决定,调用方先itemDisplaySetItemWithNbt再itemDisplaySetGlint会触发一次重建。 本版本itemDisplayCreateFull与itemDisplayEmitForEntity都在首次发包时接受nbt与glint,附魔物品自第一帧起带光效,不依赖后续调用。
范围¶
本版本只新增上述三个导出。以下行为不由库实现,由调用方决定:是否接管某只实体、向哪些玩家发送、落地后如何恢复角度、方块与物品分别使用何种尺寸。
兼容性¶
本次只新增 LSE 导出(按名称注册)。include/hologramlib/HologramLib.h 的虚表未变,HOLOGLIB_ABI_STAMP 保持为 1,已编译的 C++ 消费方无需重新编译。
参考实现¶
ItemPhys 1.8.0(LSE 插件)以 viewEntity 的 hidden=1 配合 itemDisplayEmitForEntity 实现掉落物模型接管。
26.40.10 / 26.51.3 — API 1.29.0¶
26.51.3 将 26.40.8、26.40.9、26.40.10 三个批次迁移至 26.51 协议线,能力与 26.40.10 一致;26.51 侧仅有 PlayerAuthInput 输入位的适配。
新增与变更¶
AddItemActor(id 15)纳入拦截。 掉落物出生走该包而非AddActor,此前只拦后者,"换类型 / 隐藏"只能作用于设置覆盖时已在观看的玩家;引擎随后发送的AddItemActor会重新写入物品图标(2026-10-16 抓包:RemoveActor与AddActor(cod)之后 13 ms 出现同 uniqueId 的AddItemActor)。现为:hidden丢弃该包;identifier丢弃原包并手写一只同runtimeId/uniqueId的AddActor,属性采用 health 20 / scale 1 的已验证配方。EntityView增加变形后的外观项(尾部追加)。 新增pose、metaScale、variant、markVariant、colorIndex、color2,各带hasXxx标志,false表示该项不变。LSEviewEntity的 spec 串增加pose=、scale=、variant=、mark=、color=、color2=。协议项:PoseIndex(79)、Reserved38、Reserved53/Reserved54、Variant(2)为 Int、MarkVariant(43)为 Int、ColorIndex(3)为 Byte、Color2Index(82)为 Byte;类型取自解码后的真实出生包。- 新增导出
viewItemSpawnHooked(),用于脚本侧能力协商。
修复¶
SetActorDataPacket与UpdateAttributesPacket在 26.40 线格式中含mTick,视图覆盖路径此前填 0,客户端按过期包丢弃,导致元数据与属性更新不生效(抓包显示出生包内id2、id43、id3、id82值正确,画面仍为默认外观)。现统一填写currentViewTick(),并在出生包之后补发一条SetActorData、再排一条 +2 tick 的确认。ghost*的两个导出(ghostPollInteractions、ghostClearInteractions)此前未接入ModEntry::exportLseFunctions(),脚本侧ll.import返回null;现已接回。
需要重新编译¶
EntityView 结构体大小改变(尾部追加六个字段),直接构造该结构体的 C++ 消费方需重新编译;虚表槽位不变。
备注¶
26.51.2已撤回,使用26.51.3或26.51.4。- 26.40.9 与 26.40.10 关于实体元数据与
AddItemActor的结论基于 26.40 抓包;26.51 线只核对了协议常量逐字节一致,未在 26.51 客户端复测。
历史条目¶
以下为更早版本的原始条目,保持提交时的文本。
26.40.9(API 1.27.0):EntityView::skinId(+scale/yOffset)—— 非玩家实体"换成我们的模型"(拦下的出生包 + 库自己的包替换, 同一个 runtimeId/uniqueId): 客户端看到已注册皮肤(可自定义几何)的玩家模型, 服务端那边还是那只生物 —— 点它/打它/瞄准都是它本人。配套吃掉该实体的MoveActor*(18/111) 并每 tick 用MoveActorAbsolute推位置/朝向、PlayerList条目 20 tick 后摘掉、撤销时重发真实出生包;scale等比碰撞箱(元数据 53/54)、yOffset把模型原点对齐到脚位。消费方示例: MSkinventory 的"同 id 换皮"。- (批次内追加,未发布)移除:NPC 对话框
avatarViewSpec的skin=分支(收缩, 明记)。它会用PlayerList(Add)+AddPlayer把载体换成"玩家模型"—— 实测客户端在该载体上点进菜单会把界面关掉(同一次测试里type=载体:僵尸/鸡, 一切正常), 属该路径当前不可用。按"库只做机制"的定位, "让客户端认它是玩家"的路线留给消费方自建(playerNpcs()造真·假玩家 +npcUniqueIdOverride把对话挂到它 id 上)。保留:type=/name=(可自定义实体类型/标题)与avatarSkinVariant(NPC 内置皮肤变体)。avatarViewSpec/avatarSkinVariant字段本身不动(ABI 不变, 消费方无需重编译); 内部顺带掉一段从未生效的死代码(玩家模型载体的正段 id 分配)。 - 真实实体交互事件:
addActorInteractListener/removeActorInteractListener(类尾追加)—— 玩家右键 / 攻击服务端真实实体时回调;复用库已有的收包钩子(不新增 detour 层),多播互不覆盖,返回 token 用于移除。 - ABI 布局戳:头文件携带虚表布局描述符 + 消费方编译期自检(库侧运行期拦截"布局不一致的消费方")—— 把 2026-10-09 / 10-10 两次"中段挪槽"启动崩溃固化成机制;详见 版本与ABI对照.md「ABI 事故(明记)」。
- LSE:
viewEntity的 spec 串新增skin=<皮肤id>(等价EntityView::skinId;皮肤取自IPlayerNpc注册表)。 26.40.8(API 1.26.0):视图覆盖残骸清理 +IPlayerNpc追加五方法 + 悬浮字/text 域收缩与补全(收缩明记,见 版本与ABI对照.md)。- 视图覆盖(
IViewOverride)代码整理:行为不变 —— 出站钩子只决定"放行 / 丢弃",原包字段一律不改 (自 26.40.7 起即如此)。本次清掉与行为不符的旧世界残骸:重写过期的"能原包改的就原包改"顶注、 删除就地改写的死代码(dataListOf/setItemById/cloneItems/restoreItems)、判定链路全 const 化 (读包只为识别身份)。需要"改"的地方一律丢原包 + 库自己手写协议包(换类型出生包其余字段照抄原包; 名字牌由库的SetActorData紧随其后压过去;方块覆盖在收到区块后自动补发;被替换玩家的位移包丢掉、 位置由心跳用MoveActorAbsolute推)。view*LSE 导出补齐逐字段(6→16 函数;唯一 id 判定修正——玩家 id 本身是负数)。 IPlayerNpc尾部追加五个方法(+setEntitySpawnCallback;既有 ABI 不变):setPositionLight(id, x, y, z, dim):只发MoveActorAbsolute(不重建实体、不重发皮肤,无闪烁), 与setRotationLight共用同一条轻脏通道,同一 tick 内合并为一条包。用途是"每 tick 跟随会移动的东西" (典型场景:把自定义模型挂在移动中的生物身上)。dim与当前维度不同时返回false,跨维度请仍走setPosition重建。injectSkin(viewerName, targetName, skinId)/injectSkinAll(targetName, skinId):把注册表里的皮肤 按 target 自己的 UUID / uniqueId 发一条PlayerList(Add)就地更新皮肤条目。viewerName空串 = 所有在线玩家 (含 target 本人 —— 这是"让玩家自己看到自己被换肤"的路径;Geyser 对 session 玩家自身也是走 PlayerList 而不是PlayerSkinPacket)。 条目带真实 xuid、TrustedSkinFlag = true;皮肤对象自带OverridesPlayerAppearance = true(覆盖玩家客户端上已装备的皮肤,缺它客户端会拒绝)。 不自动重发:客户端偶发丢帧时由消费方自己再调一次(Geyser 的做法是 ~100ms 后重发)。playAnimation(id, animation, stopExpression, durationTicks)/playAnimationTo(id, playerName, ...):对已见过该 NPC 的玩家发AnimateEntityPacket(animation= 资源包里的动画标识符;controller名库内按 NPC id 自动唯一化, 不需要资源包里 预先存在同名控制器;stopExpression空 = 常驻,"query.any_animation"= 立刻停;durationTicks > 0到期自动补发停止包)。 同一版追加setEntitySpawnCallback(NPC 对某玩家出生完成回调, 供补发动画/状态)。与 customentity 域同款队列(+2 tick 发出)。setPositionLight/playAnimation(To)/setEntitySpawnCallback为 C++ 接口(暂无 LSE 导出); 皮肤一族已补 LSE 导出:playerNpcInjectSkin/playerNpcInjectSkinAll(注入给指定/全体观看者, 含被换肤者本人——自己看自己被换肤)与playerNpcGetSkinBlobB64/playerNpcRegisterSkinFromBlobB64(皮肤 blob 的 base64 持久化配对:消费方落盘,重启后源玩家不在线也能恢复)——playerNpc*25→29, 总计 265(Disguise / MSkinventory 类"玩家自视换肤"的脚本侧正路)。消费方用>= 0x012000门住这些方法。- 收缩(移除 8 个 holo 导出 + 2 组内部能力):
holoSetLineGradient(渐变)/holoSetLineRainbow(彩虹)/holoSetLineColor(行级颜色)/holoSetLineScroll(滚动)/holoSetVerticalAnimation(垂直动画)/holoSetLineSpacing(行距)/holoSetLineScale(行级缩放)/holoTick—— 这些在"整块文本 = 单一文本形状" 的模型下不可实现或空转(行级样式只有第 0 行生效、彩虹落成白色、行距字段本协议不存在、自驱 tick 的偏移 无人消费)。行级颜色请改用文本内嵌 § 颜色代码;行级缩放改holoSetScale(整块)。 - 补全(新增 6 个 holo 导出 + 3 个 shape 导出):
holoSetScale(整块缩放)/holoSetBackgroundColor/holoClearBackgroundColor(背景框颜色; 不设 = 客户端默认色)/holoSetDepthTest(穿墙可见性:false= 始终渲染(默认,穿墙可见),true= 被方块/实体遮挡)/holoSetRotation/holoClearRotation(三轴 Euler [Pitch, Yaw, Roll],单位 = 度(官方脚本 API 口径); 设置后文本不再面向相机); 形状域同步补shapeSetBackgroundColor/shapeClearBackgroundColor/shapeSetDepthTest。 - 动态行回归(重构自 Phantom——LGPL-3.0,已按许可标注来源,见文末「第三方来源标注」):
holoSetLinePool(id, line, [内容池], 间隔ms)(内容池按间隔时间取模轮播,无状态、多个浮字同池同相)+holoSetLineParseVariables(id, line, on)(行级变量开关,默认开)。轮播/变量刷新由库内 0.5s 节流承担、内容变了才重发 (区别于本版移除的自驱"偏移":那时每拍都在动、但驱出来的东西没有消费方);变量集补{dimension}/{x}/{y}/{z}。另修逐观看者(含{var})文本的刷新只改内存、不发包 (此前更新要等 15s 兜底重发才到客户端)——现在refresh/驱动会逐玩家原地重发(同 networkId,无闪烁)。holo*24→26,导出总数 265→267。 - NPC 对话框头像自定义:
NpcDialogSpec尾部新增两个参数 ——avatarSkinVariant(NPC 内置皮肤变体 0..59:写进载体 ActorData 的SkinId(104),聊天框里的头像/模型换内置皮肤;-1= 不加该项,与旧行为逐字节一致)+avatarViewSpec(在这只载体上叠一层 IViewOverride: spec 语法同viewEntity——type=换载体类型(测试矩阵: 僵尸 / 鸡);skin=分支已移除(见上, 未发布批次内的收缩)。原描述留档:skin=用 playernpc 注册表 里的皮肤(MHR/MeowSkin 注册/采集的都在这张表)→ 载体改为玩家模型(PlayerList+AddPlayer 同 id, Tab 条目 1s 后摘掉)、name=换界面标题;type换非 NPC 家族能否照常弹界面需实机验证)。 配套 LSE:npcDialogSetAvatar(id, skinVariant, viewSpec)(就地更新 + 重开,与翻页同一条 0 闪烁路径);npcDialog*8→9,导出总数 267→268。要"整只模型完全自定义"也可走既有npcUniqueIdOverride路线: 用视图域 / 自定义实体生成展示实体、把对话挂在它 id 上 (1.27.0 的EntityView::skinId同-id 换模型路线也可用于那只展示实体)。 - 修复:① 文本形状的
setRotation此前不生效(缺少useRotation标志,文本永远面向相机)—— 现在同时下发useRotation与双面渲染,clearRotation恢复面向相机;②shapeDestroyAll/shapeDestroyBatch只清服务端内存、不发移除包(客户端残留显示),现已补齐; ③ 跟随玩家不再依赖库内 tick —— 位置在setFollowPlayer/draw/refresh调用时就地解析; ④ 颜色 R/B 通道颠倒(int32 颜色沿用 v944 旧字节序,v2168 实际为 ARGB —— 填红显蓝): 打包toPacked与解包getColor已统一修正(形状 / 文本颜色 / 背景框全部受影响; 修好后配置里的颜色按字面显示,若此前为凑颜色反填过值请改回); ⑤ "伪装成某个玩家"被当成撤销(overrideEntity的"全空视为撤销"判空漏了asPlayer字段)—— 报"无法伪装"且顺手清掉旧覆盖;已把asPlayer计入判空; ⑥ 玩家变生物会把本人客户端卡死(覆盖对"自己"也生效:给本人发RemoveActor+ 同 runtimeId 的AddActor)—— 新增"自己的客户端不参与自己实体的覆盖"规则,覆盖/恢复/ 出站三条链路全部跳过本人(自己的SetActorData、MovePlayer也不再被吃); 换肤类例外:含本人(PlayerList(Add)发给本人是"自己看自己被换肤"的正路,自视也随之变); ⑦ 被替换玩家的位移从未推送(sendMoveActorAbsolute是死代码,替身站桩不动)—— 现在每次收到他的输入包就按输入位置(眼位 → 脚位)给看得见的观看者推MoveActorAbsolute(跳过本人)。 - 消费方提示:升级后在
>= 0x012000门住新方法;按旧 holo 行级 API 写的代码重新编译会得到弃用警告 ([[deprecated]]),LSE 侧ll.import得 null 显式失败——不会静默变行为。ABI 安全:被收缩的 8 个方法在 C++ 接口里保留为废弃空槽(非纯虚 + 空实现),虚表槽位与 1.25.0 一致——未重编译的旧 消费方二进制不会错位(与 26.40.3 交易菜单收缩的区别:那次删的是尾部方法,这次在中段,直接删会 整体前移虚表)。库内消费者已同步(MeowHolographicRenderer、ZXPanel、Meow)。 26.40.7(API 1.25.0):新增客户端视图覆盖IViewOverride(协议层拦截; 只拦不发)。- 拦截的是"服务端本来要发给这名玩家的包":库挂在 BDS 的按收件人发包汇合点
(
NetworkSystem::send/sendToMultiple)—— 在这些汇合点上库只决定拦不拦 (放行 / 丢弃),从不修改引擎包字段;需要"改"的一律丢原包 + 库自己手写协议包补发 (sculk 构造 → 回读校验 → 原始字节发送)。反编译实测实体出生包不走LoopbackPacketSender(原先挂在那里 → 换类型不生效),故改挂这里。 - 心跳兜底:服务器每 tick 自查一次 —— 实体"重新进入视野"、玩家换区块时把覆盖重新推一遍。 出生包拦漏 / 方块覆盖被区块重发冲掉都靠它恢复;没用到这个功能时只是一次原子读。
- 能做:实体换类型(丢掉原出生包,库用同一
runtimeId/uniqueId自己发一只目标类型,其余字段照抄原包)、 换名字牌(元数据Name(4)/NametagAlwaysShow(81):原包照发,库自己的SetActorData紧随其后压过去, 服务端后续改名也压得住)、对该玩家隐藏(出生包与更新包不发给他,已在客户端上的补一条RemoveActor)、 方块显示成另一种方块(丢掉原UpdateBlock/UpdateBlockSynced,库自己发一条带覆盖方块网络 id 的UpdateBlock;服务端发的本来就是覆盖方块时照发不改)。 - 逐玩家隔离:覆盖表按玩家名 + 一份全局表;多收件人路径(
sendToClients/ 广播)展开成逐收件人套用 —— 只为让"放行 / 丢弃"逐人生效;因为从不改字段,不存在"后一个收件人看到前一个覆盖"的串味。幂等: 判定认 id 不认位置,同一条包被多条路径各判一次结果相同;已是覆盖方块的更新包直接放行。 - 零开销:库内没有任何覆盖时,钩子一条分支直接放行(不查玩家、不读包);命中判定也只看 目标玩家有没有覆盖,没命中就原样放行。fail-safe:不认识的包、认不出来的实体一律原样放行。
- 26.40 做不到的(所以没进 API):逐玩家的缩放 / 发光 / 隐身 —— 元数据表里没有
scale, 也没有glowing/invisible旗标(基岩版隐身是效果,走MobEffectPacket)。 自定义实体的逐客户端缩放/装备槽仍归ICustomEntity的 per-viewer 覆盖(1.21.0)。 - 实体类改动都是立刻生效(换类型 / 隐藏 / 撤销): 库里对那只实体做一次"移除 + 按目标状态
重发一只"(同一个
runtimeId与uniqueId,服务端后续的移动/状态更新照常作用在它身上)。 代价是客户端那只实体会被重建 —— 只随出生包来过一次的细节(变体、装备初始态)要等服务端下次同步。 撤销时离得太远(>64 格)或已消失的实体不重建;玩家实体无法重建(AddPlayer没有类型字段)。 - 方块覆盖在区块重发后由库自动补发(收到区块时)。
- 判定逻辑独立在
src/view/ViewOverrideLogic.h(坐标打包 / 区块归属 / 幂等判断 / 名字牌计划 / 类型名清洗), 离线可测:tests\check-view-override.bat(54 项)。 - 玩家实体也能覆盖(同一版内):
EntityView::asPlayer = "<在线玩家名>"→ 只给该观看者重发一条PlayerList(Add)(该实体自己的 UUID + 源玩家皮肤),与原版"皮肤更新"同机制、可立刻撤销;identifier = "<生物>"→ 玩家模型渲染不了生物 → 库走替换: 吃掉他的出生包, 用同一个 runtimeId/uniqueId 发一只该类型的实体, 位置/朝向按他发来的输入包用MoveActorAbsolute推。 id 没变 → 服务端那边仍是那个真玩家, 打它就是打他本人。 撤销时把替代实体移走。 - 出生包配方对齐 customentity 域(线上验证过的那一套): 必带 flags 元数据项 +
minecraft:health/minecraft:scale属性, 且后面紧跟一条UpdateAttributesPacket(客户端对出生包里的属性只初始化 不生效)—— 之前缺这几项, 玩家进服时心跳一发重发就会让客户端报错("第一次进出错")。 另修正NametagAlwaysShow的协议值反转(0 = 常显), 并给刚进服的玩家约 2 秒宽限期(客户端还在 收区块时不塞实体包)。 - 输入操作 → 代理(协议层执行):入站
PlayerAuthInput当真相 —— 位置/朝向/头顶朝向、66 位输入位 (跳跃/潜行/疾跑/游泳/爬行/飞行…)、ClientPlayMode(界面状态)与物品操作字段;代理按它复现 A 的动作 (1 秒内无新输入才退回服务端状态)。动作类包(挥臂/攻击/受伤/死亡)从服务端的Animate/ActorEvent照搬到代理(runtimeId 换成代理的,100ms 去重防重复播放)。手持物品/快捷栏持续镜像。 - 代理落点修正:玩家在协议层上报的 y 在眼睛处(Bedrock 特有)—— 代理改用碰撞箱下沿取真实脚位, 输入包的 y 用"眼到脚"差值换算(站/潜行/游泳都对)。
-
两条纪律: 出站钩子只决定拦不拦(放行 / 丢弃), 从不修改引擎包字段 —— 需要"改"的一律 丢原包 + 库自己手写协议包补发; 数据来源只有协议包(入站
PlayerAuthInput/ 出生包载荷 /Animate·ActorEvent),不读服务端实体状态。 -
26.40.5(API 1.23.0):新增背包虚容器IFakeInventory与硫磺立方体展示ISulfurDisplay。 - 协议层改写客户端看到的玩家背包:一条
InventoryContentPacket(ContainerId = Inventory(0)+FullContainerName = InventoryContainer(29)+ 0..35 号描述符)把整份内容换成调用方给的那份, 单格改动走InventorySlotPacket;服务端背包一个字都不动。方案取自参考实现 GMLIB 的ChestUI(它逐格写InventorySlot, 容器 id 同样是Inventory(0)+InventoryContainer(29))。 - 功能项与虚拟容器一致:点自己背包里的伪造物品 → 回调报槽位号;物品不会真的被拿走 (客户端按伪造内容发请求, 服务端真实槽位对不上 → BDS 判失败 → 客户端撤回预测)。库随后把那一格 重发一次, 让伪造内容不被这次回滚冲掉。
- 与交易菜单 / 虚拟容器共存:打开界面时 BDS 会重发背包内容把伪造内容覆盖,所以本域按
refreshIntervalTicks周期重发(默认 20 tick = 1s),也可refresh()手动补。 - LSE:
fakeInvApply/fakeInvSetSlot/fakeInvRefresh/fakeInvClear/fakeInvClearAll/fakeInvIsActive/fakeInvGetPlayers/fakeInvPollClicks/fakeInvClearClicks。 - 离线检查
tests/check-fake-inventory.bat(本地工具):拿 BDS 真实抓包核对线格式(帧落在包尾、容器 id 0 / 36 格 / 容器名字节 0 / storage 空描述符、以及物品 UserData 的方言指纹:06 00 "Damage"而不是06 "Damage"), 10 项全过。物品本身由 BDS 序列化, 所以不再有"逐字节自造"的检查, 改为钉住布局事实。 - 物品请求钩子(147 + AuthInput 内嵌)现在由两个域共用,从
container/移到了src/interaction/PlayerInteractionHooks.cpp:容器域认LevelEntityContainer(7)与 dyn 101..199, 背包域认CombinedHotbarAndInventory(12)/Hotbar(28)/Inventory(29),互不抢。 - 修复(实测事故):第一版手写物品描述符写错了 User Data 的 NBT 方言 → 客户端卡死;且会话没随掉线清理,
重连时 tick 驱动又重发一遍 → "进不去服务器"。现在物品交给 BDS 序列化,并注册了
PlayerDisconnectEvent清理会话(FakeInventoryManager::onPlayerLeave),周期重发间隔也钳了下限(最快 5 tick)。 - 新增硫磺立方体展示
ISulfurDisplay(第二种摆放方式):生成minecraft:sulfur_cube,把方块装在它的主手 (行为包里立方体就是靠slot.weapon.mainhand拿"吞下去"的方块, 客户端按装备渲染)——SulfurDisplaySpec::block与setBlock是本域的核心 API;隐身是 spec 上的一个参数(invisible, 默认关)。外观档位用手写的ChangeMobProperty(182) 下发minecraft:sulfur_cube_archetype。本域全部委托给ICustomEntity,所以 缩放/视距/白名单/逐客户端朝向直接继承;ICustomEntity::setMobProperty同时开放给其它实体的client_sync属性。 "吞生物"不做:协议层实体没有 AI,试过"让实体骑在立方体上"的近似,实测观感不成立,已整体移除。 26.40.4(API 1.22.0):交易菜单改为纯展示 + 新域补齐 LSE 导出。- 交易菜单不再有任何点击监听:
TradeClickEvent/TradeActionCallback/TradeRawAction与ITradeMenu的六个监听方法整体删除,TradeMenuSpec去掉displayOnly/acceptPaymentPlacement—— 本域从此只负责"打开界面 + 摆出交易表"。原来的 147/AuthInput 钩子与放料接住逻辑随之移除(留在src/container/ContainerInteractionHooks.cpp:那两个钩子如今只服务虚拟容器的点击)。要"能点、点了有回调"的列表界面用虚拟容器(IContainerMenu)。 ITradeMenu新增addOffer/setTier:追加一条交易或改档位/经验条时就地重发交易表,不必关掉重开。- 四个新域补齐 LSE 导出:
trade*(7)、container*(9)、npcDialog*(7)、sensing*(3);容器与 NPC 对话的点击在脚本侧走轮询队列(containerPollClicks/npcDialogPollClicks,取走并清空,条目是可切分的字符串),ghost*早就用的同一套办法。 entity*补逐客户端渲染导出:entitySetPlayerNametag/entitySetPlayerScale/entitySetPlayerEquipmentSlot/entityClearPlayerAppearance。- API 版本因此抬到 1.22.0:撤回已发布过的东西属于收缩(正常该走大版本),这里抬次版本并在
版本与ABI对照.md明记原因。按 1.21.0 写过交易菜单点击监听的代码升上来会编译报错(不会静默变行为)。 26.40.3(API 1.21.0):四个新能力域(村民交易菜单 / NPC 对话框 / 虚拟容器 / 感知域)+ 逐客户端渲染 + 一次裁剪(清单见上表 1.21.0 各行)。- 村民交易菜单
ITradeMenu:协议层自建UpdateTrade开界面(包级字段与 Offers NBT 都与 BDS 抓包逐字节对拍),隐身载体实体驱动经验条;点击回传按配方 netId 精确定位条目(offerIndex/recipeNetId),另有与参考实现 GMLIB ChestUI 同语义的逐动作回调(src/dst/amount,关闭时以slot=-1, amount=-1哨兵收尾)。(本条的点击回传已在 26.40.4 按需求撤回 —— 详见上一条。)两条路径由usePacketOffers选:默认纯协议层(服务端不放交易表 → 天然只读),置false走真实交易表 + BDSopenTrading(成交真的换物品)。载体类型carrierIdentifier可选村民或流浪商人。 - NPC 对话框
INpcDialogue:NpcDialoguePacket+ 合成minecraft:npc载体(放在世界下方 → 客户端看不到实体,但对话框头像照常渲染);按钮 / 关闭回传,多层级对话按场景名路由。 - 虚拟容器
IContainerMenu:逐条复刻参考实现 GMLIB 的ChestUI—— 客户端侧箱子方块(UpdateBlock)+ 方块实体 NBT(BlockActorData,物品走Items、标题走CustomName)+ContainerOpen(类型Container(0)、绑方块坐标、目标实体-1);rows=3小容器 27 格、rows=6大容器 54 格(pairx/pairz/pairlead配对);点击回传槽位号 = 条目下标,关闭回传closed=true。任何输入设备都能点(点击就是一次物品拾取)。 - 感知域
IPlayerSensing:挂钩PlayerAuthInputPacket逐包捕获InputMode→KeyboardMouse/Touch/Gamepad/MotionController;玩家离线即清,换设备下一包即更新。 - 逐客户端渲染(千人千面):
ICustomEntity新增setPlayerNametag/setPlayerScale/setPlayerEquipmentSlot/clearPlayerAppearance(出生包与增量包都按观看者覆盖值下发,装备变更即时单发无闪烁);IHologramText文本含{var}时自动切换为逐观看者形状,{player}从此对全员绘制也逐人正确。 - 裁剪:移除逐客户端音效与短命飘字(讨论后不再需要,代码整体删除;两者是早期按"协议层手写包"做的实验)。
- 修复:①文本动画(滚动 / 弹跳)改由库内自驱(
ServerLevelTickEvent监听)—— 此前tick只有 LSE 导出,原生侧没有驱动源,动画既不动也不消失(该自驱与相关 API 已在 26.40.8 收缩中整体移除);②147 钩子原先只在交易菜单打开时才进入,虚拟容器点击因此全部漏掉;③点击上报的是背包侧槽位(现在优先报交易侧);④src/DiagLog.h遗留在发布产物里的诊断开关(check-no-diagnostics.bat会挡住)。 - 实测沉淀(详见下文各节):容器打开延迟 7 tick 可用 / 6 tick 不可用的下限;物品请求有
ItemStackRequestPacket(147)与PlayerAuthInputPacket(144)内嵌两条通道;命中虚拟容器的动作只回传不拦(自己代答失败应答会让客户端弹错误提示)。 26.40.2(API 1.20.0):修复假玩家 NPC 皮肤不渲染(客户端回退默认模型)—— PlayerList 皮肤条目的Id/FullId为空或残留原玩家的缓存键;PNG 注册、在线采集、blob 恢复统一走finalizeSkinIds补全Id并重建FullId,且在 Persona / 采集改写完成后才设置,不再沿用来源玩家的身份。PlayerList 发送前校验 2168 包体前缀(Add01 01 00,Remove01 00 01),前缀异常时丢弃并记录实际前缀;首次提交到 NetworkPeer 时输出PlayerList submitted日志。新增离线回归检查tests/check-npc-playerlist.ps1(用实际链接的静态库产生字节 + 独立 Python 解码器核对 2168 字段、可信标记位置、Id/FullId与包体完全消费)。配套 Protocol 静态库完成 PlayerList 2168 帧格式移植(variant 数组、ActionTypeAdd=0/Remove=1、可信标记改为皮肤内三态字符串);本插件 ABI / API 版本不变26.40.1(API 1.20.0):适配 LeviLamina 26.40 / BDS 1.26.40(协议 2168,形状渲染改用 Protocol v2168 静态库);修复事件 ID 与官方 LeviLamina 不一致导致监听器全部收不到事件(src/EventIdCompat.h);新增逐客户端朝向(setPlayerRotation/clearPlayerRotation/clearPlayerRotations,实体与 NPC 通用)与轻量朝向更新(setRotationLight);新增 ghost 交互多播监听(1.19.1);NPC 创建/脏刷新合并到 tick 末尾统一发包(同一 tick 内多次下发会让客户端收到密集"新玩家"而断线);不再下发 PlayerList 移除(客户端在皮肤条目仍活跃时移除该条目会崩,实体照常消失);PlayerList / AddPlayer 发送跳过 BDS 回读校验26.10.7(API 1.19.0):新增皮肤从内存导出(getSkinBlob/registerSkinFromBlob,消费方自行持久化);目录批量导入皮肤(一个子文件夹 = PNG + 可选.json模型);PlayerNpcSkin.geometryData自定义几何模型;库移除磁盘存储,改为纯 API;新增 NPC 缩放(PlayerNpcConfig.scale+playerNpcSetScale,0.0625~10,碰撞箱等比);修复 NPC 视距裁剪/脏刷新/Tab 移除失效(tick hook 未注册);修复 NPC 重生(缩放/换肤等脏刷新)皮肤丢失变默认史蒂夫(过期 Tab 移除条目误删新皮肤条目)26.10.6(API 1.17.1):皮肤采集永久存储修复