CD Code Digest Hub /CS:GO

玩家外围系统:观战、接管 Bot、教练、投票、成就统计与 MVP、人质

43 分钟阅读 8 个章节 4 张图

玩家 页沿“出生 → 购买 → 受伤 → 死亡”的主生命周期展开。本页补全围绕这条主线的外围系统:死后的观察者视角与它的限制、死亡玩家接管同队 Bot、只观战不参战的教练、全局与队伍投票、成就与统计的上报链路、回合 MVP 的判定,以及人质营救的实体与规则。这些系统都挂在 CCSPlayer 或规则对象上,彼此交织——例如接管 Bot 会影响金钱、成就资格和 MVP 资格,教练身份会改变观战限制、语音和投票归属——因此放在同一页说明它们的接口点。

相关页面:玩家 · 规则与回合 · Bot · HUD · 投掷物与 C4 · Demo 与 GOTV · 匹配与 GC

相关源码文件#

  • game/server/player.cpp:CBasePlayer 观察者模式、观战命令、投票命令入口
  • game/server/cstrike15/cs_player.cpp、game/server/cstrike15/cs_player.h:CS 观战覆盖、死亡镜头状态、接管 Bot、教练命令、MVP 计数
  • game/shared/baseplayer_shared.cpp:教练身份 IsCoach / GetAssociatedTeamNumber
  • game/server/vote_controller.cpp、game/server/cstrike15/cs_voteissues.cpp:投票控制器与 CS 投票议题
  • game/shared/cstrike15/achievements_cs.cpp、game/shared/achievementmgr.cpp:成就定义与客户端成就管理器
  • game/server/cstrike15/cs_gamestats.cpp、game/client/cstrike15/cs_client_gamestats.cpp:统计采集与下发
  • game/shared/cstrike15/cs_gamerules.cpp:MVP 计算、投票议题注册、人质胜利判定
  • game/server/cstrike15/hostage/cs_simple_hostage.cpp、game/server/cstrike15/func_hostage_rescue.cpp:人质与营救区

1. 观察者模式#

1.1 模式与网络化#

模式含义证据
OBS_MODE_NONE不在观察者模式game/shared/shareddefs.h:522
OBS_MODE_DEATHCAM死亡镜头动画game/shared/shareddefs.h:523
OBS_MODE_FREEZECAM拉近击杀者并定格game/shared/shareddefs.h:524
OBS_MODE_FIXED固定机位game/shared/shareddefs.h:525
OBS_MODE_IN_EYE第一人称跟随game/shared/shareddefs.h:526
OBS_MODE_CHASE第三人称跟随game/shared/shareddefs.h:527
OBS_MODE_ROAMING自由视角game/shared/shareddefs.h:528

m_iObserverMode(3 位)与 m_hObserverTarget 都在 DT_BasePlayer 中网络化:game/server/player.cpp:8321、game/server/player.cpp:8328。StartObserverMode 把玩家变为不可见、不可伤害、非实体的死亡状态,然后调用 SetObserverMode:game/server/player.cpp:2398-2445;CS 覆盖额外清掉下蹲覆盖状态:game/server/cstrike15/cs_player.cpp:11130-11140。

SetObserverMode 的规则:

  1. 对队伍成员或教练,大于 OBS_MODE_FIXED 的模式按 mp_forcecamera 收紧:1(仅本队)一律压成 OBS_MODE_IN_EYE,2(不允许)压成 OBS_MODE_FIXED:game/server/player.cpp:2464-2473。
  2. 在 CSTRIKE_DLL 下,若当前目标是已下包的 C4 或投掷物,强制 OBS_MODE_CHASE:game/server/player.cpp:2475-2489。
  3. 记住上一个真正的观战模式;从死亡/冻结镜头切到跟随模式时清空目标,以便重新寻找(可接管的 Bot 优先,见注释):game/server/player.cpp:2491-2503。
  4. 按模式设置移动类型、FOV、视点偏移,最后 CheckObserverSettings 并在客户端触发 spec_mode_updated:game/server/player.cpp:2505-2556。

原本“禁止进入自由视角”的 spec_allow_roaming 检查在 SetObserverMode 中已被注释掉:game/server/player.cpp:2449-2459。

1.2 观察目标#

  • 有效性(基类):只能观察玩家;不能观察自己;死亡目标只在死亡动画后的短时间内有效(纯观众额外加 sv_spec_post_death_additional_time);非纯观众受 mp_forcecamera 约束——1 时只能看同“关联队伍”(比赛结束后不限),2 时任何人都不行:game/server/player.cpp:2949-3019。CS 覆盖额外允许观察已下包的 C4 与飞行中的投掷物:game/server/cstrike15/cs_player.cpp:3705-3727。
  • 切换:FindNextObserverTarget 从当前目标按方向循环所有客户端,返回第一个有效目标 game/server/player.cpp:3058-3100;CS 覆盖在没有任何有效目标时(例如竞技中最后一名队友阵亡)把镜头放到已下包的 C4 上并切到第三人称 game/server/cstrike15/cs_player.cpp:3729-3754。
  • 维持:第一人称时每帧复制目标的着地/下蹲标志与视点偏移,并同步雾控制器;目标失效时自动换下一个,找不到就固定在原地(OBS_MODE_FIXED):game/server/player.cpp:2588-2700。目标在做第三人称嘲讽动作时强制切到第三人称,结束后恢复:game/server/cstrike15/cs_player.cpp:11155-11182。

客户端命令:spec_mode 在 IN_EYE→CHASE→ROAMING 间循环,冻结镜头中则尝试退出,mp_forcecamera 2 下的队伍成员和教练不能切换,死亡镜头期间只记住选择 game/server/player.cpp:6573-6644;spec_next / spec_prev 切换目标 game/server/player.cpp:6645-6680;spec_player、spec_player_by_name、spec_player_by_accountid 指定目标 game/server/player.cpp:6725-6770;spec_goto 需要作弊模式或已死亡 game/server/player.cpp:6772-6800。

1.3 死亡之后的视角#

证据:进入死亡状态时开始 OBS_MODE_DEATHCAM,mp_forcecamera 2 时淡出黑屏并转入 STATE_DEATH_WAIT_FOR_KEY game/server/cstrike15/cs_player.cpp:10666-10702;State_PreThink_DEATH_ANIM 用 spec_freeze_deathanim_time、spec_freeze_traveltime、spec_freeze_time、spec_freeze_time_lock 计算各时间点,到时切冻结镜头或转入观察者/重生,mp_deathcam_skippable 允许锁定期后按键跳过 game/server/cstrike15/cs_player.cpp:10705-10794;进入 STATE_OBSERVER_MODE 时读取客户端 ConVar cl_spec_mode 并钳制到 IN_EYE…ROAMING 作为初始模式 game/server/cstrike15/cs_player.cpp:10918-10953。STATE_DEATH_WAIT_FOR_KEY 之后的流程本页未展开;击杀回放(HLTV Replay)见 Demo 与 GOTV。

1.4 相关 ConVar#

ConVar默认作用证据
mp_forcecamera10 任意队伍;1 仅本队且模式压成第一人称;2 不允许观战、死亡后黑屏game/shared/gamevars_shared.cpp:24-32
spec_allow_roaming0(作弊)仅在“无目标转向 C4”时被检查(见 1.2)game/server/player.cpp:117、game/server/cstrike15/cs_player.cpp:3742-3747
sv_spec_post_death_additional_time0纯观众在目标死亡后继续看它的额外秒数game/server/player.cpp:2949
spec_freeze_time / spec_freeze_time_lock / spec_freeze_traveltime / spec_freeze_deathanim_time3.0 / 1.0 / 0.3 / 0.8死亡与冻结镜头计时(CS 取值)game/server/player.cpp:109-117
mp_deathcam_skippable1锁定期后能否按键跳过冻结镜头game/server/cstrike15/cs_player.cpp:268
mp_spectators_max2观众人数上限(本次未追踪使用点)game/shared/cstrike15/cs_gamerules.cpp:1654-1657

2. 接管 Bot#

功能由编译开关 CS_CONTROLLABLE_BOTS_ENABLED(定义为 1)和 ConVar bot_controllable(默认 1)共同控制:game/shared/cstrike15/cs_shareddefs.h:51-53、game/server/cstrike15/bot/cs_bot_init.cpp:90。双方关系用两个句柄表示:玩家的 m_hControlledBot 指向被控 Bot,Bot 的 m_hControlledByPlayer 指回玩家;玩家接管前的金钱与战绩存进 m_PreControlData:game/server/cstrike15/cs_player.h:1626-1627、game/server/cstrike15/cs_player.h:1633-1645。

证据:

  • 提示与触发:观察者每帧判断第一人称/第三人称目标是否可接管,并网络化给客户端 game/server/cstrike15/cs_player.cpp:10984-10994、game/server/cstrike15/cs_player.cpp:542-545;ObserverUse 再检查存活、ConVar、待换队、是否在回放中,然后调用 TakeControlOfBot game/server/cstrike15/cs_player.cpp:16428-16470。
  • 前提(CanControlBot):Bot 存活、同队且是有效观察目标、未被他人控制、不在拆弹或下包、回合未结束、不在冻结期或热身期、玩家本人已死亡 game/server/cstrike15/cs_player.cpp:15418-15459。
  • 接管:保存数据 game/server/cstrike15/cs_player.cpp:15405-15416;复制 Bot 状态、令 Bot 休眠、在 Bot 处 Spawn、转移物品、移交人质跟随与携带、触发事件 game/server/cstrike15/cs_player.cpp:15461-15694。
  • 释放:归还物品、恢复金钱与“下回合才可用的钱” game/server/cstrike15/cs_player.cpp:15696-15737;玩家阵亡时立即释放 game/server/cstrike15/cs_player.cpp:2834-2839;每次 RestartRound 对所有玩家执行 RevertBotsFunctor game/shared/cstrike15/cs_gamerules.cpp:3502-3522、game/shared/cstrike15/cs_gamerules.cpp:7770-7773。

接管的连带规则:

方面规则证据
金钱被控 Bot 的 AddAccount 直接忽略;团队奖金另记入玩家的 m_PreControlData,在经典模式的回合进行中同样计入“下回合才可用”game/server/cstrike15/cs_player.cpp:5990-5994、game/server/cstrike15/cs_player.cpp:5943-5966
成就控制 Bot 期间 AwardAchievement 直接返回;本回合控制过或被控制过的攻击者不参与击杀类成就game/server/cstrike15/cs_player.cpp:5404-5413、game/server/cstrike15/cs_player.cpp:14247-14248
MVP默认 MVP 计算跳过本回合被控制过的玩家;下包/营救人质的 MVP 钩子在本回合控制过 Bot 时退回默认规则game/shared/cstrike15/cs_gamerules.cpp:6978-6979、game/shared/cstrike15/weapon_c4.cpp:834-838、game/server/cstrike15/hostage/cs_simple_hostage.cpp:720-723

观察:ReleaseControlOfBot 在 pBot 为空的分支之后,仍在 dev_reportmoneychanges 打开时调用 pBot->GetPlayerName():game/server/cstrike15/cs_player.cpp:15713-15729。这是一个只在开发 ConVar 打开时才可能触发的空指针解引用(推断,未运行验证)。

3. 教练模式#

教练是“处于 TEAM_SPECTATOR、但绑定到某一队”的玩家。sv_coaching_enabled(默认 0)关闭时,GetCoachingTeam() 总返回 0;GetAssociatedTeamNumber() 对教练返回所教队伍,对其他人返回自身队伍;IsSpectator() 只对非教练观众为真:game/shared/cstrike15/cs_gamerules.cpp:1776-1780、game/shared/baseplayer_shared.cpp:3513-3540。教练队伍号 m_iCoachingTeam 以 3 位网络化:game/server/player.cpp:8327。

  • 进入/退出:客户端命令 coach ct|t 只在热身或冻结期有效,实际调用 HandleCommand_JoinTeam( TEAM_SPECTATOR, false, 队伍 ) 并广播聊天提示,随后切到第一人称 game/server/cstrike15/cs_player.cpp:9019-9090;HandleCommand_JoinTeam 的教练参数默认为 0,因此经由它的普通换队都会清除教练身份 game/server/cstrike15/cs_player.h:596、game/server/cstrike15/cs_player.cpp:9586-9594。
  • 观战:教练与队伍成员一样受 mp_forcecamera 约束,且不能用 spec_mode 切换模式 game/server/player.cpp:2465、game/server/player.cpp:6597-6601;观察目标按“关联队伍”判断 game/server/player.cpp:3005;每回合重生观察者时重新套用相机限制(修复只能看 C4 时被弹到第三人称的问题)game/server/cstrike15/cs_player.cpp:6559-6574。
  • 语音与聊天:可听判定用关联队伍,教练视为队友 game/shared/cstrike15/cs_gamerules.cpp:15582-15594。
  • 换边:HandleSwapTeams 先换选手,再把教练的 m_iCoachingTeam 翻到对面 game/shared/cstrike15/cs_gamerules.cpp:12518-12562。
  • 投票:队伍投票控制器按关联队伍选择 game/server/player.cpp:9805-9818;但默认 ConVar 下有效投票者必须真正在 T/CT 队伍中 game/server/vote_controller.cpp:817-821,因此教练默认不能投票(由两段代码推断)。

4. 投票#

4.1 结构#

规则对象在 CreateStandardEntities 中创建三个 vote_controller 实体:全局、CT、T game/shared/cstrike15/cs_gamerules.cpp:14660-14662;该类名在回合清理地图时的保留列表中,因此跨回合存活 game/shared/cstrike15/cs_gamerules.cpp:2659、game/shared/cstrike15/cs_gamerules.cpp:2715。议题对象在构造时把自己注册到控制器:NewTeamIssue 为 T 与 CT 控制器各建一个实例,NewGlobalIssue 只建在全局控制器上 game/shared/cstrike15/cs_gamerules.cpp:248-259、game/server/vote_controller.cpp:947-963。控制器的网络表只同步当前议题、限定队伍、各选项票数、潜在票数与是否为是/否投票 game/server/vote_controller.cpp:26-32。

议题类控制器注册条件通过后执行证据
CKickIssue队伍总是sv_vote_kick_ban_duration > 0 且被踢者未崩溃时先向 GC 报告并 banid,再 kickid_exgame/shared/cstrike15/cs_gamerules.cpp:14665、game/server/cstrike15/cs_voteissues.cpp:135-165
CRestartGameIssue全局总是见 ExecuteCommandgame/shared/cstrike15/cs_gamerules.cpp:14666、game/server/cstrike15/cs_voteissues.cpp:45
CChangeLevelIssue / CNextLevelIssue全局总是换图 / 设定下一张图game/shared/cstrike15/cs_gamerules.cpp:14667-14668、game/server/cstrike15/cs_voteissues.cpp:537、game/server/cstrike15/cs_voteissues.cpp:674
CStartTimeOutIssue队伍竞技严格规则集按发起者队伍执行 timeout_ct_start / timeout_terrorist_startgame/shared/cstrike15/cs_gamerules.cpp:14670-14673、game/server/cstrike15/cs_voteissues.cpp:1141-1163
CPauseMatchIssue、CUnpauseMatchIssue、CLoadBackupIssue、CReadyForMatchIssue、CNotReadyForMatchIssue全局命令行带 -tournament 且为排队匹配暂停/恢复/读档/准备game/shared/cstrike15/cs_gamerules.cpp:14675-14683
CSurrender队伍排队匹配发起方所在队以 Terrorists_Surrender / CTs_Surrender 结束回合;匹配中另给对方记一胜并进入重赛投票状态;若回合已结束或处于半场则改为失败game/shared/cstrike15/cs_gamerules.cpp:14685-14690、game/server/cstrike15/cs_voteissues.cpp:1517-1560
CScrambleTeams / CSwapTeams全局非排队匹配打乱 / 交换队伍game/shared/cstrike15/cs_gamerules.cpp:14691-14696、game/server/cstrike15/cs_voteissues.cpp:902、game/server/cstrike15/cs_voteissues.cpp:1443

投降如何改写回合结束原因见 规则与回合。

4.2 流程#

证据:

  1. 发起:callvote 无参数时打开选择界面;有参数时先查全局控制器,再查发起者的队伍控制器;观众默认不能发起 game/server/vote_controller.cpp:111-169。CreateVote 检查总开关、本控制器是否已有投票、议题 CanCallVote、个人发起冷却;需要不止 1 票的议题还要求没有进行中的全局投票,且发起全局投票时对方队伍控制器没有进行中的投票(发起者本队控制器在此不另查)。然后向非观众发送 CS_UM_VoteStart、开始计时(排队匹配 45 秒,否则 sv_vote_timer_duration),并替发起者投赞成票 game/server/vote_controller.cpp:295-485。CanCallVote 的通用检查含热身、队伍、排队匹配可用性、赛点禁踢与近期失败冷却 game/server/vote_controller.cpp:1088-1215。踢人议题开始时替被踢者(非观众、非 Bot)自动投反对票 game/server/cstrike15/cs_voteissues.cpp:280-290。
  2. 投票:客户端命令 Vote 先找进行中的全局投票,再找本队投票 game/server/player.cpp:9823-9859。TryCastVote 校验投票资格;发行版中同一玩家(按 SteamID 记录)不能改票或重复投;是/否投票把选项 3 及以上压成“否”;记票后若人人已投则立即结算 game/server/vote_controller.cpp:522-633、game/server/vote_controller.cpp:785-797。默认只有 T/CT 中的真人能投票,Bot、GOTV、回放客户端都不算 game/server/vote_controller.cpp:809-839。
  3. 结算:赞成票达到门槛即通过;重赛投票在剩余可能票数不足时提前失败;计时到仍不够则失败 game/server/vote_controller.cpp:663-717。通过后广播 CS_UM_VotePass,在 sv_vote_command_delay 后执行议题命令;失败则记录冷却;5 秒后重置 game/server/vote_controller.cpp:719-780、game/server/vote_controller.cpp:1047-1075、game/server/vote_controller.cpp:1287-1290。
  4. 门槛:排队匹配的竞技规则下非队伍限定议题需要全部匹配账号同意;“全票”议题需要所有潜在投票者;其余在竞技规则下为“潜在票数 − 1”,否则为“潜在票数 × sv_vote_quorum_ratio(默认 0.501)”向上取整 game/server/vote_controller.cpp:1009-1037、game/server/vote_controller.cpp:50。

相关 ConVar 集中定义于 game/server/vote_controller.cpp:44-54。HUD 上的投票面板见 HUD。

观察:VoteControllerThink 遇到非是/否投票时打印提示后直接 return,没有再 SetNextThink game/server/vote_controller.cpp:675-680;这表明该控制器此后不再思考(推断)。CS 注册的议题是否都是是/否投票本次未逐一确认。

5. 成就、统计与 MVP#

5.1 成就#

成就在 achievements_cs.cpp 中用宏静态注册,同一文件被客户端和服务端两个工程编译:game/client/client_cstrike15.vpc:142、game/server/server_cstrike15_inc.vpc:82。

类型判定位置证据
统计目标型 DECLARE_ACHIEVEMENT_STATGOAL客户端:服务端下发的统计更新后,用生涯统计值与目标比较;achievements_easymode 时目标除以 10game/shared/cstrike15/achievements_cs.cpp:193-220、game/shared/cstrike15/achievements_cs.cpp:292-322
服务端授予型 DECLARE_ACHIEVEMENT_SERVERAWARDED服务端在事件发生处调用 AwardAchievementgame/shared/cstrike15/achievements_cs.cpp:458-480
自定义类(DECLARE_ACHIEVEMENT,含元成就)客户端成就类自行监听事件;元成就在所需成就全部完成时授予game/shared/cstrike15/achievements_cs.cpp:148-190、game/shared/cstrike15/achievements_cs.cpp:576

服务端授予链路:击杀相关成就集中在 ProcessPlayerDeathAchievements game/server/cstrike15/cs_player.cpp:14229-14250;CCSPlayer::AwardAchievement 在控制 Bot 时拒绝 game/server/cstrike15/cs_player.cpp:5404-5413;基类只向该玩家发送 CS_UM_AchievementEvent 用户消息 game/server/basemultiplayerplayer.cpp:193-206;客户端 CAchievementMgr 绑定该消息并调用 OnAchievementEvent game/shared/achievementmgr.cpp:204、game/shared/achievementmgr.cpp:2159-2190。也就是说,成就最终在客户端记账,服务端只发通知。

5.2 统计#

服务端 CCSGameStats 通过一组 Event_* 钩子(开火、命中、击杀、下包、拆包、营救、金钱、MVP 等)累积每名玩家的统计差量:game/server/cstrike15/cs_gamestats.cpp:274-372、game/server/cstrike15/cs_gamestats.cpp:448-495、game/server/cstrike15/cs_gamestats.cpp:919-960。PreClientUpdate 按高/低两档优先级的计时器周期性调用 SendStatsToPlayer,后者把非零差量打包进可靠的 CS_UM_PlayerStatsUpdate,附带版本号和以硬编码种子起算的 CRC(种子值不在此复制),发送后清零差量:game/server/cstrike15/cs_gamestats.cpp:1274-1382。客户端重新计算 CRC,不符或数值越界则丢弃,否则累加到回合、比赛与生涯统计,再写入 Steam 用户统计:game/client/cstrike15/cs_client_gamestats.cpp:537-620、game/client/cstrike15/cs_client_gamestats.cpp:367-378。

此外,服务端只对“竞技回合”或“Valve 专用服务器上的休闲回合”抽样约 10%(Valve 锦标赛服务器不抽样)上传回合级数据:game/server/cstrike15/cs_gamestats.cpp:1561-1580;排队匹配的命中率统计在子弹伤害监听器中记录(见 武器与射击)。

5.3 MVP#

证据:TerminateRound 在胜方为 T 或 CT 时调用钩子或默认规则 game/shared/cstrike15/cs_gamerules.cpp:13449-13458;拆包钩子在调用 CheckWinConditions 前安装、之后清除 game/shared/cstrike15/weapon_c4.cpp:720-752;爆炸钩子 game/shared/cstrike15/weapon_c4.cpp:830-865;营救钩子 game/server/cstrike15/hostage/cs_simple_hostage.cpp:714-735、game/server/cstrike15/hostage/cs_simple_hostage.cpp:783。钩子对象是调用处栈上的局部对象,指针只在 CheckWinConditions 同步执行期间有效,随后被清空(由上述安装/清除位置推断)。

默认规则 CalculateEndOfRoundMVP:军备竞赛递进模式中给完成最后一击的玩家;军备竞赛死亡竞赛中给得分最高者(同分取实体索引小者);其余模式只考虑胜方(平局除外),比较本回合击杀数,再以对敌造成的实际伤害打破平局;本回合被控制过的 Bot 不参与 game/shared/cstrike15/cs_gamerules.cpp:6945-7018。理由枚举见 game/shared/cstrike15/cs_shareddefs.h:377-386。

IncrementNumMVPs:sv_nomvp 为真时不授予;递增 m_iMVPs、同步排队匹配数据、记录 CSGameRules()->m_pMVP(若已有不同 MVP 则警告覆盖)、记统计并触发 round_mvp 事件;音乐盒 MVP 计数变量始终为 0,因为对应的 GC 通信不在此处 game/server/cstrike15/cs_player.cpp:229、game/server/cstrike15/cs_player.cpp:15012-15055。

6. 人质#

人质图的胜负判定在 规则与回合 中只列出了调用位置,这里补充实体与规则。

实体 / 开关作用证据
hostage_entity / info_hostage_spawn → CHostage人质game/server/cstrike15/hostage/cs_simple_hostage.cpp:106-107
func_hostage_rescue → CHostageRescueZone营救区触发器,触碰时向对方发送输入 OnRescueZoneTouchgame/server/cstrike15/func_hostage_rescue.cpp:11、game/server/cstrike15/func_hostage_rescue.cpp:83-90
HOSTAGE_RULE_CAN_PICKUP定义为 1:人质被“扛起”而不是跟随行走game/shared/cstrike15/cs_shareddefs.h:42
mp_hostages_max 等人质数量上限与出生点选择game/server/cstrike15/hostage/cs_simple_hostage.cpp:69-73
mp_hostages_takedamage / mp_hostages_rescuetowin人质是否受伤(默认否)/ 营救几名即胜(默认 1,仅开发用)game/shared/cstrike15/cs_gamerules.cpp:1746-1756

营救流程:

  1. 交互:热身期拒绝;距离超过 1000 单位忽略;已扛一名时拒绝;T 尝试使用只会收到提示 game/server/cstrike15/hostage/cs_simple_hostage.cpp:1465-1500、game/server/cstrike15/hostage/cs_simple_hostage.cpp:1568-1583。CT 开始“解绑”:有拆弹器或绳子已被割过时 1 秒,否则 4 秒(合作模式 5 秒),期间显示进度条并冻结玩家 game/server/cstrike15/hostage/cs_simple_hostage.cpp:1501-1559、game/shared/cstrike15/cs_player_shared.cpp:335-339。
  2. 进度:HostageThink 在解绑期间检查玩家是否仍按住使用键、仍对准人质且站在地面上,否则取消;完成后人质开始跟随/被扛起 game/server/cstrike15/hostage/cs_simple_hostage.cpp:1160-1228。首个接触该人质的 CT 获得奖励并计分 game/server/cstrike15/hostage/cs_simple_hostage.cpp:1598-1620。
  3. 携带:扛着人质的玩家最大速度固定为 200 game/shared/cstrike15/cs_player_shared.cpp:349-352、game/shared/cstrike15/cs_shareddefs.cpp:17。
  4. 送达:营救区触碰的是玩家;玩家的 RescueZoneTouch 把输入转发给所扛人质 game/server/cstrike15/cs_player.cpp:11431-11446。人质的 HostageRescueZoneTouch 给营救者现金奖励、把人质放在玩家前方、递增 m_iHostagesRescued、记录营救者、触发 hostage_rescued,然后安装 MVP 钩子并调用 CheckWinConditions,胜利时再检查营救类成就 game/server/cstrike15/hostage/cs_simple_hostage.cpp:629-700、game/server/cstrike15/hostage/cs_simple_hostage.cpp:714-783。
  5. 判胜:HostageRescueRoundEndCheck 统计仍存活且未营救的人质;营救数达到 mp_hostages_rescuetowin(0 表示全部)时 CT 胜,发团队奖金并以 All_Hostages_Rescued 结束回合 game/shared/cstrike15/cs_gamerules.cpp:6537-6589。

击杀人质:先调用 CheckWinConditions,再给攻击者罚款;T 方累计击杀达到 mp_hostagepenalty 时被踢出 game/server/cstrike15/hostage/cs_simple_hostage.cpp:570-622、game/server/cstrike15/hostage/cs_simple_hostage.cpp:541-563。

约束与边界#

  • 观察者状态是服务端权威的网络变量;客户端观战 HUD、击杀回放的客户端部分与 STATE_DEATH_WAIT_FOR_KEY 之后的流程未展开(回放见 Demo 与 GOTV)。
  • CS_CONTROLLABLE_BOTS_ENABLED 为编译期常量 1;若改为 0,接管、释放与回合回收代码都会被编译掉。
  • 投票:各议题 CanCallVote 的专有条件(如踢人的豁免规则、换图的地图列表)与重赛/继续议题(CQueuedMatchmakingRematch 等已在注册处注释掉)未逐一展开;cs_voteissues.cpp 引用的 GC 客户端头文件缺失问题见 概览。
  • 成就与统计:客户端成就管理器的存档、Steam 统计上传细节与 cs_app_lifetime_gamestats.cpp、勋章界面未展开;统计 CRC 种子是硬编码常量,属反篡改用途,本页不复制其值。
  • MVP:音乐盒 MVP 计数依赖的 GC 通信不在本快照中;sv_nomvp 的变更回调未追踪。
  • 人质:人质的寻路跟随(UpdateFollowing、梯子与跳跃)、出生点选择算法、合作/守护者模式下的人质规则未展开。
  • 本页所有结论来自静态阅读,未运行游戏验证;标注“推断”的条目缺少运行时证据。