MineMonopoly 将地图保存为可序列化定义,再用受约束的代码字段描述差异化行为。编辑器组织资源,客户端 Worker 按生命周期执行规则,命令系统负责状态变化。新增玩法主要发生在地图内容层,无须为每张地图增加客户端分支。
地图定义与资源关系
GameMap 当前包含 mapItems、mapItemTypes、mapPaths、chanceCards、roles、mapEvents、phases、modifierTemplates、uiTemplates、customUIs 和 extraLibs,以及地图信息、建筑模型和游戏设置。地图项保存坐标、旋转、类型和地产或事件引用;地产保存价格、等级、过路费、建筑模型及自定义配置;机会卡保存目标类型和 effectCode;角色保存 initCode;阶段保存 initEventCode。
静态数据和行为由不同字段承载。地图项复用 MapItemType,再通过 property 或 mapEventId 绑定功能。地产普通模式由价格和等级数据驱动,配置 custom 后执行自定义逻辑。机会卡的 type 先约束目标范围,脚本再处理目标。资源通过 ID 或模板引用关联,编辑器可以检查写入和删除操作的引用关系。这种关联方式也让地图内容能够独立版本化:地图元数据记录版本和更新日志,运行时只读取当前定义,旧存档则通过快照字段和模板标识恢复。编辑器在修改资源时应同时检查被哪些地图项、实例或阶段引用。
GameMap.phases 类型列出八个槽位,包括 gameOverRule、gameInited、playerPreInit、propertyPreInit、gameRoundStart、playerRound、gameRoundEnd、postRestore。阶段校验器当前允许的主类型较少,而 get_default_code 还提供预初始化、掷骰子、移动和到达事件等默认模板;扩展编辑器应以实际接口和工具参数为准。
代码字段的调用边界
| 资源 | 调用边界 |
|---|---|
| 角色 | (player, gameProcess) |
| 地产自定义逻辑 | (property, gameProcess) |
| 机会卡 | (sourcePlayer, target, gameProcess) |
| 地图事件 | (arrivedPlayer, gameProcess) |
| 游戏阶段 | (ctx, gameProcess) |
| 修饰器模板 | (player, gameProcess, cmd, ctx) |
脚本应通过公开异步 API 改变状态,例如 player.gain、player.cost、player.walk、property.setOwner 和 property.levelUp。这些方法经过命令总线,能够触发修饰器、破产检查和同步;直接修改 money、positionIndex 或 owner 会绕过这些机制。对话框结果、玩家和目标对象可能为空,代码需要处理取消、超时、离线和失效目标。
运行时参考文档说明,资源代码会与类型声明一起编译,再生成可执行函数;代码不能访问 Vue、DOM、Socket 或 Worker 消息接口。gameInited 用于一次性的初始条件,postRestore 用于恢复监听器、临时修饰器和动态按钮,不能在恢复阶段重复发奖或注册永久效果。GameProcess.start() 会先创建玩家和地皮,执行预初始化行为,再运行 gameInited;存在存档时先恢复快照,随后执行 postRestore,最后广播初始化数据。这个顺序决定了不同资源的职责:初始化脚本可以建立初始状态,恢复脚本只能重建运行时行为。
修饰器模板与实例
ModifierTemplate 包含 id、slug、名称、描述符和 effectCode。描述符声明 before/after 时机、监听的 commandType、剩余次数、优先级、自动消耗规则及 UI 展示元数据。运行时调用 modifierManager.add(template, options) 创建独立实例,并可附带 contextData。
管理器按时机和命令类型筛选实例,再按优先级执行。ctx.cancel、ctx.setResult、ctx.skip 和 ctx.consume 可取消命令、修改结果、跳过本次消耗或调整触发次数。存档保存模板 slug、剩余次数和上下文数据,恢复时按当前地图模板重新创建;模板 slug 属于存档兼容信息。
校验、MCP 与写入路径
地图编辑器的 MapContentService 同时服务界面表单和 MCP 操作。添加或更新机会卡、角色、地图事件、阶段、地产和修饰器模板时,先由 Zod schema 校验结构,再调用 validateEffectCode。validate_effect_code 使用 Monaco TypeScript 语言服务,支持 chance-card、map-event、role、game-phase、modifier、property、extra-libs,并可接收机会卡 targetType、修饰器 commandType、完整模板和 snippet/full 模式;失败结果包含行号、列号和诊断消息。
get_code_template 与编辑器使用同一模板源,get_default_code 读取阶段默认代码。服务默认校验代码,编辑器保留显式 skipCodeValidation 选项,因此忽略错误是例外路径。类型校验不能证明脚本在所有状态下正确,空卡池、多人目标、异步顺序和恢复阶段的重复执行仍须由脚本防御。
MCP 服务在 server.ts 中注册工具并响应 ListTools、CallTool。工具按机会卡、地图事件、角色、阶段、地产、修饰器模板、UI 模板、自定义 UI、资源和校验等类别组织,CRUD 通过桥接交给渲染进程的 MapContentService。plan_map_changes 只预检操作,apply_map_changes 默认以 atomic: true 执行,失败时回滚 Pinia 地图状态,且不负责资源文件操作。这种分层使 MCP 调用和编辑器表单共用同一套输入校验、ID 生成和事件通知逻辑。调用方可以先读取目标资源、代码模板和类型库,再把多个新增或更新操作作为一个计划提交,避免把半成品状态写入当前地图。批量接口的原子选项只作用于地图 Pinia 状态,模型、图片等资源仍由资源工具单独管理。
UISchema 与 UiRenderer
UISchema 是声明式组件树,节点支持静态或动态的 style/props、content、textBinding、children、vShow、vFor 和 variable。UiRenderer 合并 schema.variable 与上下文,计算显隐、文本、样式和属性;遇到 vFor 时为每项建立带索引的上下文并递归渲染子节点。带 SVG 坐标的 text 节点渲染为 SVG 文本,否则渲染为普通文本。
UITemplate 保存可复用 schema,CustomUI.uiSchema 保存模板 ID,实例另存布局。游戏页面读取模板,将游戏状态和玩家信息交给 UiRenderer。模板可以调整信息布局和显示条件,但仍受渲染器表达式边界约束,不能直接修改游戏状态。模板引用还带来一个维护约束:删除模板前必须处理仍在使用它的实例,否则实例无法解析显示结构。渲染器只读取上下文并生成组件,不向 Worker 发送命令;需要改变游戏状态时,仍应由 effectCode 调用游戏进程提供的异步 API。
设计边界
数据结构负责序列化和引用,代码字段负责有限行为,命令与阶段系统负责状态转换,UISchema 负责展示。扩展一条规则时,应先读取资源和模板,确认签名与目标范围,使用公开异步 API 编写最小行为,运行 validate_effect_code,再检查关联 ID、修饰器描述符和 UI 模板引用。数据驱动没有消除规则复杂度,但使规则可以在地图内容层复用、校验和审查。这也限定了编辑器的责任范围。编辑器可以保证字段形状、模板包装、引用 ID 和 TypeScript 类型,但无法替作者决定数值是否平衡,也无法覆盖所有异步分支。内容提交后仍应在正常路径、取消路径、空目标和多人局面中验证;当代码依赖旧字段或旧存档时,还要保留兼容读取逻辑。
事实依据
本文依据 packages/types/interfaces/game/map.ts、packages/types/interfaces/game/item.ts、packages/types/interfaces/game/game-process/events.ts、packages/types/interfaces/game/game-process/ui.ts、packages/types/interfaces/game/action-system/modifier.ts,以及地图编辑器的 MapContentService、validators、MCP tools、server.ts、客户端 ui-renderer.vue 和 Worker 编写参考文档整理。阶段类型与默认模板范围按当前源代码分别表述。