《道德经》里写:“为而不恃,功成而不居。”
一个好的系统设计也该如此——节日活动结束了,系统应该自动恢复到平时状态,不留痕迹,不改底层数据。但怎么做到?
魔兽世界每个节日都有真实的内容:冬节的礼物盒,从烟林猎手掉落;仲夏火焰节有篝火和节日buff;情人节有玫瑰花和爱情火箭坐骑。这些内容只在活动期间出现,活动结束就消失——但游戏世界的"基础状态"从来没有被改过。
AzerothCore 用一套叫game_event的叠加层机制做到了这件事。
叠加层思路:不动底层,加一层
先设想一个反面方案。
圣诞节想让森林里刷出圣诞老人和驯鹿。最直接的做法:去creature表和creature_template表,把平时的数据加上圣诞相关的内容。活动结束再改回来。
这套做法的问题:
- 改数据有风险——活动脚本出错,改了一半没还原,世界乱了
- 难以并行——多个节日同时存在,每套节日都要改一遍基础数据,容易冲突
- 维护成本高——每次活动都要手动操作
game_event的思路完全反过来:不动原始数据,在另一层叠加节日内容。
底层表是"基础状态",game_event系列表是"装饰层"。基础状态永远不变,装饰层随时间叠加和移除。这套思路在软件设计里叫装饰器模式。
game_event 表:总开关
game_event是整个机制的总开关:
| 字段 | 说什么 |
|---|---|
eventEntry | 事件编号(其他表通过这个 ID 关联) |
start_time/end_time | 开始/结束时间 |
occurence/length | 循环间隔和持续时长 |
holiday | 关联游戏内假日 ID |
description | 事件说明 |
活动期间,服务器读取时间字段判断事件是否在"活跃期"。活跃期内叠加层生效,活跃期外自动失效——不需要任何人手动操作。
叠加层覆盖什么
game_event系列表覆盖了游戏中几乎所有可能被节日影响的内容:
| 表名 | 叠加什么 |
|---|---|
game_event_creature | 活动期间额外刷新的生物 |
game_event_gameobject | 活动期间额外刷新的游戏对象 |
game_event_npc_vendor | 活动期间 NPC 额外出售的物品 |
game_event_npcflag | 活动期间 NPC 标志位变更 |
game_event_model_equip | 活动期间生物的模型/装备变更 |
game_event_creature_quest | 活动期间生物关联的节日任务 |
game_event_seasonal_questrelation | 节日任务与季节的关联 |
game_event_pool | 对象池与事件的关联 |
最常用的就是前面几张:临时多刷几只怪、多挂几个对象、让某个 NPC 临时卖节日物品。冬节的礼物盒就是这么实现的——活动期间game_event_creature里加一批节日怪物,打死了掉落礼物盒;活动结束,这条叠加记录失效,怪物消失,礼物盒停止掉落。基础表里什么也没改。
一个具体例子:节日商人
情人节想在暴风城银行门口加一个卖玫瑰花的 NPC。
活动期间,通过叠加层做到:
game_event:情人节事件编号=5,时间范围 2月10日~2月16日game_event_npc_vendor:插一条记录,eventEntry=5,entry=某NPC ID,item=玫瑰花
活动期间,这个 NPC 自动出现在商贩列表里。活动结束后记录失效,NPC 自动从列表消失——原始npc_vendor表没有任何改动。
同理,万圣节的"万圣节狼"(普通狼模型换成节日皮肤)、仲夏节的篝火游戏对象,都用这套叠加逻辑。
game_event_save:进度保留
有个细节值得单独说:活动期间的玩家进度需要保存。
万圣节有个累计击杀任务"不给糖就捣蛋",玩家做了三天好不容易做到80%,活动最后一天服务器维护了。活动结束后,game_event_save表里的累计进度被保留——下次同类型活动开始时,进度还在。
这是叠加层机制里少有的"持久化需求":大多数叠加数据随活动结束而消失,但玩家在活动期间的贡献值得保留。
叠加层的边界
这套机制很优雅,但有清晰的使用边界:
能叠加的:生物刷新位置、NPC 标志、NPC 出售物品、生物模型、游戏对象、任务关联
叠加不了的:物品模板属性(物品的数值要直接改item_template)、已有任务的奖励内容修改
这说明:叠加层擅长"加内容",不擅长"改内容"。节日活动想改某个物品的掉率,叠加层做不到——需要直接改creature_loot_template。
和篇3的关联
篇3聊过模板与实例模式——模板定义"是什么",实例记录"在哪里"。game_event是另一套分层思路——用叠加层在不改原始数据的前提下添加内容。
两套思路的区别:
- 模板/实例:同一套内容在不同位置、不同时间的"分身"问题
- 叠加层:同一段时间里,在原始内容上"叠加额外内容"的问题
理解这两个维度,再去看 World 库里其他复杂表,就不容易被绕晕。
这个思路同样适用于技能系统。下篇聊一个相关的问题:为什么 WoW 的技能数据不是一张宽表,而是一堆spell_*表——以及这和 game_event 的叠加层思路有什么内在联系。