REPENTOGON EntitiesSaveState 类完全指南:实体存档状态的结构、字段语义与底层 ESSM 机制

发布时间:2026/10/16 6:01:33

REPENTOGON EntitiesSaveState 类完全指南:实体存档状态的结构、字段语义与底层 ESSM 机制 游戏开发插件系统【免费下载链接】REPENTOGONScript extender for The Binding of Isaac: Repentance项目地址https://gitcode.com/gh_mirrors/re/REPENTOGON点击查看免费下载本指南以 REPENTOGON 官方文档 docs/docs/EntitiesSaveState.md 为骨架系统讲解EntitiesSaveState类的全部 39 个 Getter/Setter 方法、各存储字段的语义如主动道具充能、商店价格、ShopItemId、容器类EntitiesSaveStateVector/GridEntitiesSaveStateVector的用法并结合仓库源码剖析其背后的 EntitySaveStateManagementESSM劫持Hijacking机制。读完本文你将能够在 MOD 中安全地读取、修改并持久化实体在存档中的状态数据。一、EntitiesSaveState 是什么存档中的实体快照在《以撒的结合忏悔》的存档体系中游戏并不会把每个实体的完整运行状态直接写入存档而是为每个需要跨房间/跨存档保留的实体记录一份精简的快照Save State。EntitiesSaveState就是这份快照在 Lua 侧的可视化视图它记录了实体的Type、Variant、SubType、位置、若干整数/浮点/布尔存储槽以及生成种子等元信息。REPENTOGON 通过 repentogon/LuaInterfaces/LuaEntitySaveState.cpp 将原版结构体EntitySaveState包装为 Lua 类并注册为元表EntitySaveStateMT见该文件末尾RegisterEntitiesSaveStateMetatables。所有方法本质上都是对std::vectorEntitySaveState中某一元素的直接字段读写因此读方法无参数返回对应字段值写方法接受一个参数布尔、整数、浮点或Vector直接修改底层结构体通过EntitiesSaveStateVector的Get(Index)拿到的对象是对内存中真实数据的引用Set*立即生效。从源码结构看Lua 侧的每个EntitiesSaveState对象内部只保存两个指针级信息所属向量的地址vec和元素下标index见 repentogon/LuaInterfaces/LuaEntitySaveState.h后续所有 Get/Set 都通过(*ud-vec)[ud-index]定位真实数据。二、完整 API 速查表以下是官方文档列出的全部方法及签名与源码中注册的luaL_Reg methods[]逐一对应源码中SetGridSpawnIdx被注释掉故仅提供只读的GetGridSpawnIdx方法签名返回/参数类型字段语义GetType()/SetType(Type)EntityType实体类型写入时按unsigned int存GetVariant()/SetVariant(Variant)int实体变体GetSubType()/SetSubType(SubType)int实体子类型GetI1()/SetI1(Num)int/float通用整数槽 1实体类型为 4/33/292 时按float解释见第四节GetI2()/SetI2(Num)int通用整数槽 2主动道具拾取物的充能GetI3()/SetI3(Num)int通用整数槽 3拾取物的商店价格GetI4()/SetI4(Num)int通用整数槽 4拾取物的 ShopItemIdGetI5()/SetI5(Num)int通用整数槽 5GetI6()/SetI6(Num)int通用整数槽 6GetI7()/SetI7(Num)int通用整数槽 7被 ESSM 劫持实为外部 ID见第五节GetI8()/SetI8(Num)int通用整数槽 8GetU1()/SetU1(Num)int/boolean单字节槽实体类型为 5 时按boolean解释见第四节GetB1()/SetB1(B1)boolean布尔槽 1GetB2()/SetB2(B2)boolean布尔槽 2GetF1()/SetF1(Num)float浮点槽 1GetF2()/SetF2(Num)float浮点槽 2GetPos()/SetPos(Pos)Vector实体的目标位置targetPositionGetInitSeed()int实体初始化种子只读GetDropSeed()int实体掉落种子只读GetSpawnerType()EntityType生成者的实体类型只读GetSpawnerVariant()int生成者的实体变体只读GetGridSpawnIdx()int网格生成索引被 ESSM 劫持只读关联的容器类文档见 docs/docs/EntitiesSaveStateVector.md普通实体向量与 docs/docs/GridEntitiesSaveStateVector.md网格实体向量其方法在第三节详述。三、字段级 API 详解直接对应结构体成员3.1 实体身份三要素Type / Variant / SubType这三个方法直接映射EntitySaveState的type、variant、subtype成员。从源码看Lua_GetType/Lua_SetType等SetType会把参数强转为unsigned int写入。它们是判断存档条目属于哪个实体的第一依据也是GetByType筛选的核心字段。3.2 通用存储槽 I1–I8按实体类型重解释语义整数槽是字段级 API 中最常用的部分源码实现非常直接LUA_FUNCTION(Lua_GetI2) { ... lua_pushinteger(L, st.intStorage2); return 1; }其中三个槽位在游戏内部有固定语义官方文档明确标注I2主动道具拾取物Active Item Pickup的充能值charge。修改它可以在拾取前改变主动道具的充能状态。I3拾取物的商店价格shop price是商店相关 MOD 常用字段。I4拾取物的ShopItemId用于在商店物品池中唯一标识该商品。I1、I5–I8则属于通用槽位其含义由具体实体类型决定。特别地I7与GridSpawnIdx已被 REPENTOGON 的 ESSM 系统劫持见第五节普通读写行为与文档描述一致但其底层存储位置在劫持状态下已被迁移到外部表。3.3 布尔槽与浮点槽B1/B2、F1/F2B1/B2对应结构体的boolStorage1/boolStorage2F1/F2对应floatStorage1/floatStorage2。源码中 Getter 分别使用lua_pushboolean与lua_pushnumberSetter 分别使用luaL_checkboolean与luaL_checknumber是四组完全对称的存取器适合存放 MOD 自定义的开关标志或连续数值。3.4 位置字段PosGetPos返回一个新构造的VectortargetPosition的拷贝SetPos接受任意Vector对象并整体覆写目标位置。注意此处是目标位置而非实体当前渲染位置语义上更接近实体在恢复存档时将要放置的位置。3.5 种子与生成者信息只读元数据GetInitSeed/GetDropSeed分别返回_initSeed初始化种子与_dropSeed掉落种子GetSpawnerType/GetSpawnerVariant返回spawnerType/spawnerVariant。这四组方法在官方文档与源码注册表中都只有 Getter 没有 Setter属于只读元数据用于追溯实体的来源。四、类型敏感的隐藏行为I1 与 U1 的特殊解释官方文档只给出了普通签名但源码揭示了两个容易被忽略的类型相关重解释逻辑见LuaEntitySaveState.cpp中Lua_GetI1/Lua_SetI1与Lua_GetU1/Lua_SetU1I1的特殊行为当st.type等于4、33或292时底层intStorage1的内存被当作float解释if (st.type 4 || st.type 33 || st.type 292) { lua_pushnumber(L, *(float*)(st.intStorage1)); } else { lua_pushinteger(L, st.intStorage1); }写入侧对应地使用luaL_checknumber并执行*(float*)st.intStorage1 (float)value。这意味着同一段内存对某些实体类型是整数、对另一些实体类型是浮点数编写通用逻辑时必须以GetType()为前提分支处理。U1的特殊行为U1对应结构体的单字节成员byteStorage。当st.type 5时Getter 用lua_pushboolean返回布尔值Setter 用luaL_checkboolean写入其他类型则按int8_t有符号字节读写if (st.type 5) { st.byteStorage lua::luaL_checkboolean(L, 2); } else { st.byteStorage (int8_t)luaL_checkinteger(L, 2); }实践建议使用I1/U1前先检查实体类型对未知类型只做原样读写先 Get 后 Set避免破坏数据语义。五、容器类如何批量遍历与筛选EntitiesSaveState对象不会凭空出现必须通过容器类获取。REPENTOGON 注册了三个相关元表见RegisterEntitiesSaveStateMetatables5.1 EntitiesSaveStateVector普通实体快照向量方法实现于 repentogon/LuaInterfaces/LuaEntitySaveState.cpp 的Lua_EntitiesSaveStateVectorAPIGet(Index)按下标返回EntitiesSaveState对象源码带越界检查非法下标会触发 Lua 错误Invalid index for Get()。GetByType(Type, Variant 0, SubType 0)返回一个从 1 开始编号的 Lua 表内含所有匹配的EntitiesSaveState官方文档特别注明-1是通配符可用于匹配任意Variant和/或SubType。源码实现中variant -1时忽略变体匹配、subtype -1时忽略子类型匹配。Clear()清空整个向量。源码中先调用ESSM::EntitySaveState_ClearBatch(*vector)通知 ESSM 清理对应 ID再执行vector-clear()保证劫持状态与数据同步销毁。__len返回向量长度支持#vec运算符。5.2 GridEntitiesSaveStateVector网格实体快照向量Get(Index)返回GridEntityDesc对象对应原版GridEntityDesc结构元表GRID_ENTITY_DESC。GetByType(GridEntityType)按网格实体类型筛选返回编号表。Clear()注意其实现与普通向量不同——它不会缩小向量而是把每个元素的_type置为 0见Lua_GridEntitiesSaveStateVectorAPI::Lua_Clear以此标记空槽位。六、如何拿到这些对象获取途径与典型场景依据源码中所有出现EntitiesSaveStateVectorMT的位置搜索结果覆盖 repentogon/LuaInterfaces 目录当前有以下官方获取途径房间描述符RoomDescriptor:GetEntitiesSaveState()返回房间的SavedEntities向量对应descriptor-SavedEntitiesRoomDescriptor:GetDecoSaveState()返回装饰效果向量SavedEffectsRoomDescriptor:GetGridEntitiesSaveState()返回网格实体向量SavedGridEntities。注册代码见 repentogon/LuaInterfaces/Room/LuaRoomDescriptor.cpp。Myosotis 拾取物Level:GetMyosotisPickups()返回level-_myosotisPickups向量见 repentogon/LuaInterfaces/LuaLevel.cpp。移动盒内容EntityPlayer:GetMovingBoxContents()返回玩家移动盒中的实体快照向量见 repentogon/LuaInterfaces/Entities/LuaEntityPlayer.cpp。典型用途包括遍历房间存档中的拾取物并修改其商店价格I3或 ShopItemIdI4、在房间加载后批量调整实体位置、以及配合 ESSM 实现读档后仍保留 MOD 私有数据等。七、底层原理ESSM 劫持机制与 GetEntitySaveStateId7.1 为什么需要劫持原版EntitySaveState结构体字段有限且不同实体类型对字段的占用约定不同没有空闲空间可以安全地存放 MOD 的关联 ID。REPENTOGON 的 EntitySaveStateManagement 系统源码注释见 repentogon/SaveStateManagement/EntitySaveStateManagement.cpp 顶部SYSTEM OVERVIEW采用的方案是劫持Hijacking为每个活跃的存档状态生成一个全局 ID把原字段替换为(ID, Hijacked Marker)二元组并把被覆盖的原始值拷贝到外部hijackedStates表中恢复Restoring在游戏执行原版恢复函数前先把标记与 ID 换回原始值恢复完成后再写回标记清理/拷贝/序列化/反序列化分别对应存档删除、复制、写入磁盘与读档重建场景。被选中劫持的字段应该是那些在非恢复操作中不会被主动读取的字段这正对应了本文档中的I7与GridSpawnIdx。因此当你调用GetI7()/GetGridSpawnIdx()时源码实际上走的是EntitySaveState_GetI7/EntitySaveState_GetGridSpawnIdx这两个非劫持 API声明见 repentogon/SaveStateManagement/EntitySaveStateManagement.h若结构体已处于劫持状态则从外部表读取原始值并返回保证 Lua 侧感知不到字段被挪用。7.2 GetEntitySaveStateId拿到唯一关联 IDESSM 在_CBindings.ESSM表中暴露了GetEntitySaveStateId注册于RegisterLuaInternals见EntitySaveStateManagement.cpp第 3470–3478 行。其源码LUA_FUNCTION(GetEntitySaveStateId) { EntitySaveState state Lua_EntitySaveState::GetEntitySaveState(L, 1); if (!EntityHijackManager::IsHijacked(state)) { return luaL_argerror(L, 1, Provided EntitiesSaveState is not supported by EntitySaveStateManager.); } lua_pushinteger(L, EntityHijackManager::GetId(state) 1); return 1; }注意两点传入的EntitiesSaveState必须处于被劫持状态否则直接报错返回的 ID 在 Lua 侧按1 起始编号内部 ID 1。这个 ID 是连接存档快照与MOD 持久化数据的桥梁——配套的SaveData(mod, fileName, data)、LoadData(mod, fileName)、DeleteData(mod, fileName)会将数据写入 MOD 数据目录下的EntitySaveStateManagement/fileName.dat二进制文件路径构造见GetLuaSaveDataPath实现读档后 MOD 数据与实体状态同步恢复。7.3 Lua 侧的 ESSM 辅助模块仓库还内置了 repentogon/resources/scripts/repentogon_extras/entity_save_state_manager.lua它封装了以modId GetEntitySaveStateId(ess)为键的持久化数据查找逻辑ModLookup并定义了一套递归深拷贝排除 userdata/function/thread 键值与 JSON 序列化格式含_links、_specialKeyData等表结构供 MOD 直接复用。八、使用注意事项与边界综合官方文档与源码使用本类时需要注意只读字段不可写InitSeed、DropSeed、SpawnerType、SpawnerVariant、GridSpawnIdx仅有 Getter官方文档也未提供对应 Setter不要假设它们可写。类型敏感槽位I1type 4/33/292 时为浮点与U1type 5 时为布尔的行为随GetType()变化通用代码必须分支处理。I7 与 GridSpawnIdx 的劫持约束它们由 ESSM 管理虽然 API 对使用者透明但在配合_CBindings.ESSM使用时应通过GetEntitySaveStateId获取 ID而非自行解读这两个字段。越界与筛选语义Get(Index)越界会直接报错GetByType的-1通配符仅作用于后两个参数Type参数必须精确。向量生命周期EntitiesSaveStateVector持有的是指向内部std::vector的裸指针若在存档被清空后继续持有旧引用可能访问失效数据——建议在需要时现场获取不要长期缓存。掌握以上字段语义与底层机制后你就能安全地读写任意实体的存档快照并借助 ESSM 的 ID 关联机制实现存档可持久化、读档可恢复的完整 MOD 数据闭环。赞分享游戏开发插件系统【免费下载链接】REPENTOGONScript extender for The Binding of Isaac: Repentance项目地址https://gitcode.com/gh_mirrors/re/REPENTOGON点击查看免费下载相关推荐Kaolin 结构化点云容器 kaolin.rep.Spc 完全指南字段语义、懒加载机制与底层算子协同Kaolin 结构化点云容器 kaolin.rep.Spc 完全指南字段语义、懒加载机制与底层算子协同 本篇技术指南聚焦 NVIDIA KaolinPyTo深度学习计算机视觉图形学Velero Backup API 类型完全指南字段语义、生命周期状态与备份执行机制Velero Backup API 类型完全指南字段语义、生命周期状态与备份执行机制 导读 本文围绕 Velero及其前身 Ark核心 API 类型 Ba云原生灾备存储后端使用 Reloaded-II 启动器创建 Mod 合集包.r2pack完整实战指南使用 Reloaded II 启动器创建 Mod 合集包.r2pack完整实战指南 导读 本文讲解如何在 Reloaded II 启动器的图形界面中把多个游戏开发插件系统上一篇Nintendo Switch大气层系统技术架构与部署实践指南下一篇在M1/M2 Mac上完美运行iOS游戏PlayCover新手完全指南 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/16 5:56:33

车载实时通信中间件设计:基于UDP的确定性BSP协议实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 5:56:33

Python函数式编程思维:从PTA第六章看模块化设计本质

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 7:22:00

多微网电能交互的分布式优化:计及碳排放的ADMM调度策略

1. 多微电网电能交互:一个实际运营中的硬骨头我做过多年的微电网能量管理,最常被问的一句话就是:“几个微电网挨在一起,能不能互相借点电?”答案是能,但远不是拉一根线、签个协议那么简单。多微电网的电能交…

2026/10/16 7:22:00

Oracle 11g INSERT 全解析:批量插入、性能优化与避坑指南

1. 先搞清楚 Oracle 11g 的 INSERT 语法骨架我在接手维护一个订单系统的时候,发现最频繁出问题的 SQL 往往不是那些几百行的复杂查询,反而是看起来人畜无害的 INSERT。原因很简单,SELECT 写错了最多查不出数据,INSERT 写错了直接污…

2026/10/16 7:22:00

广义Benders分解求解综合能源系统规划:MATLAB实战与收敛加速

1. 为什么综合能源系统规划绕不开广义Benders分解做综合能源系统优化规划的人,几乎都会遇到同一个坎儿:模型规模太大,求解器直接罢工,或者跑一天一夜也出不了结果。尤其是当规划周期拉长、候选设备一多、网络拓扑稍作精细化之后&a…

2026/10/16 7:22:00

VLAN技术深度解析:从广播域隔离到802.1Q标签与链路类型实战

1. 从一个真实组网困境说起:为什么需要VLAN很多刚接触网络工程的朋友都有过这样的经历:公司规模不大,几十台电脑,一台核心交换机加几台接入交换机,所有人都在同一个网段里,互相能ping通,文件共享…

2026/10/16 7:22:00

MySQL连接报错排查:socket文件路径与状态详解

刚装完MySQL,兴冲冲敲下mysql -uroot -p,结果没看到期待的Enter password:,反而弹出一段红字——Cant connect to local MySQL server through socket /var/lib/mysql/mysql.sock。这个报错我前前后后排查过很多次,也帮别人处理过…

2026/10/16 7:17:00

从零实现CS231n SVM分类器:Hinge Loss、梯度与调参全解析

CS231n这门课的Spring 2020版Assignment1,做到SVM Classifier这一节的时候,很多人第一次意识到“原来我连一个简单的线性分类器都没真正写明白”。作业目标很直接:在CIFAR-10数据集上,从零实现一个多类线性SVM分类器,包…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑