CD Code Digest Hub /CS:GO

比赛模式:game_type / game_mode

12 分钟阅读 6 个章节

CS:GO 用两个复制型 ConVar 组合出“经典休闲/竞技、军备竞赛、爆破、死亡竞赛、训练、自定义、合作”等模式;模式的具体参数(回合数、金钱、时间等)并不硬编码,而是由 GameModes.txt 中为每个模式列出的 cfg 与 ConVar 决定。

相关页面:CS:GO 总览 · 规则与回合 · 匹配与 GC

相关源码文件#

  • game/shared/cstrike15/gametypes.h、game/shared/cstrike15/gametypes.cpp
  • public/gametypes/igametypes.h
  • 调用方:game/shared/cstrike15/cs_gamerules.cpp、game/server/gameinterface.cpp

1. 两个 ConVar 与枚举#

  • game_type 与 game_mode 都带 FCVAR_REPLICATED,同时在 game/client DLL 中可见:game/shared/cstrike15/gametypes.cpp:48-49。GetCurrentGameType/Mode 直接返回它们的值:game/shared/cstrike15/gametypes.cpp:1641-1652。
  • 类型枚举:经典、军备竞赛、训练、自定义、合作;注释说明数值与 GameModes.txt 中的顺序对应:game/shared/cstrike15/gametypes.h:27-38。模式枚举按类型分组,例如经典下的休闲、竞技、非排位竞技,军备竞赛下的渐进式、爆破等:game/shared/cstrike15/gametypes.h:40-60。
  • 规则对象以一组 IsPlaying* 谓词包装它们,例如 IsPlayingClassic、IsPlayingGunGameProgressive、IsPlayingAnyCompetitiveStrictRuleset(只认经典-竞技):game/shared/cstrike15/cs_gamerules.cpp:16828-16943。
谓词判定证据
IsPlayingClassicgame_type == Classicgame/shared/cstrike15/cs_gamerules.cpp:16931-16934
IsPlayingGunGameProgressive/TRBomb/DeathmatchGunGame 类型 + 对应模式game/shared/cstrike15/cs_gamerules.cpp:16828-16844
IsPlayingAnyCompetitiveStrictRuleset经典 + 竞技game/shared/cstrike15/cs_gamerules.cpp:16852-16855
IsPlayingCoopGuardian/Mission合作类型 + 对应模式game/shared/cstrike15/cs_gamerules.cpp:16857-16867
IsQueuedMatchmakingsv_mmqueue_reservation 以 Q 开头(服务端)game/shared/cstrike15/cs_gamerules.cpp:16878-16884

2. 模式表的加载#

GameTypes::Initialize 读取 GameModes.txt,追加 DLC 中的同名数据,再合并 gamemodes_server.txt(可用 -gamemodes_serverfile 指定);其中每个模式的 exec 段以“追加”方式合并而不是覆盖:game/shared/cstrike15/gametypes.cpp:257-330。之后按类型/模式、地图、地图组、自定义 Bot 难度分别解析:game/shared/cstrike15/gametypes.cpp:544、game/shared/cstrike15/gametypes.cpp:780、game/shared/cstrike15/gametypes.cpp:933、game/shared/cstrike15/gametypes.cpp:1026。

地图的“侧车”文件 maps/<map>.kv 不在初始化时加载,而是在规则对象 InitializeGameTypeAndMode 中按需加载(源码注释说明这是为了避免大量创意工坊订阅拖慢启动):game/shared/cstrike15/gametypes.cpp:291-294、game/shared/cstrike15/cs_gamerules.cpp:17594-17629。

3. 应用模式#

服务端 CCSGameRules 构造时调用 InitializeGameTypeAndMode:game/shared/cstrike15/cs_gamerules.cpp:4132。其中:

  1. 若地图未知则加载侧车文件;
  2. CheckShouldSetDefaultGameModeAndType 根据启动参数或地图默认值设置类型/模式(客户端版本直接返回):game/shared/cstrike15/cs_gamerules.cpp:17633、game/shared/cstrike15/gametypes.cpp:1535-1559;
  3. ApplyConvarsForCurrentMode 遍历该模式的配置项,只处理名为 exec 的项,逐个执行对应 cfg(服务端用 ServerCommand + ServerExecute);自定义类型直接返回。离线模式下还会应用自定义 Bot 难度,但只允许 bot_difficulty、bot_dont_shoot、bot_quota 三个 ConVar:game/shared/cstrike15/cs_gamerules.cpp:17636、game/shared/cstrike15/gametypes.cpp:1697-1764、game/shared/cstrike15/gametypes.cpp:1770-1815;
  4. 再应用地图级 ConVar:game/shared/cstrike15/cs_gamerules.cpp:17758、game/shared/cstrike15/gametypes.cpp:2161。

引擎在 Host_NewGame 时还会通过 ExecGameTypeCfg 让游戏 DLL ApplyGameSettings,并读取 members/numSlots 作为服务器槽位数:engine/host.cpp:6210、engine/sv_main.cpp:2847-2877。

4. 实例归属(跨模块)#

gametypes.cpp 被编译进 client、server、matchmaking 三个模块(game/client/client_cstrike15.vpc:239、game/server/server_cstrike15_inc.vpc:177、matchmaking/matchmaking_cstrike15_inc.vpc:35),每个模块都有自己的静态单例,并用 EXPOSE_SINGLE_INTERFACE_GLOBALVAR 以 VENGINE_GAMETYPES_VERSION 导出:game/shared/cstrike15/gametypes.cpp:206-209。源码里用 CLIENT_DLL/GAME_DLL/MATCHMAKING_DLL/MATCHMAKING_DS_DLL 分支选择执行 cfg 的方式;注释坦言这种做法“horrible design”,并说明只有 server.dll 真正调用应用函数:game/shared/cstrike15/gametypes.cpp:1728-1760。

运行时 client 与 server 拿到的是 matchmaking.dll 中的实例(静态调用链已闭环,未运行验证):

  1. 两个游戏 DLL 都在初始化时用传入的工厂重新赋值 g_pGameTypes:game/server/gameinterface.cpp:793-796、game/client/cdll_client_int.cpp:1528-1530。传入的都是 g_GameSystemFactory,也就是 GameFactory:engine/sv_main.cpp:1095、engine/cdll_engine_int.cpp:2720、engine/sys_dll.cpp:1062。
  2. GameFactory 先问 AppSystem 组,再问 matchmaking,最后才问 client.dll:engine/sys_dll.cpp:1018-1036。
  3. 组内的 matchmaking 系统只以 IMATCHFRAMEWORK_VERSION_STRING 注册,其 QueryInterface 也只认这个名字,所以组查找不会返回 IGameTypes:engine/sys_dll2.cpp:2231、matchmaking/main.cpp:160-166、appframework/AppSystemGroup.cpp:322-358。随后 matchmaking 工厂会返回它自己导出的单例。

因此 server 调用 ApplyConvarsForCurrentMode 一类函数时,实际执行的是 matchmaking 模块编译出的 MATCHMAKING_DLL/MATCHMAKING_DS_DLL 分支;client 与 server 模块各自的静态单例在初始化后不再经由 g_pGameTypes 使用。专用服务器加载 matchmaking_ds:engine/sys_dll2.cpp:2191-2193。

约束与边界#

  • GameModes.txt、gamemodes_server.txt 与各 gamemode_*.cfg 都是游戏内容文件,不在仓库中;因此本页只能描述加载与应用机制,不能给出任何模式的具体参数。
  • 军备竞赛的武器进阶表也来自 GameModes.txt:game/shared/cstrike15/gametypes.cpp:497-529。