玩家外围系统:观战、接管 Bot、教练、投票、成就统计与 MVP、人质
玩家 页沿“出生 → 购买 → 受伤 → 死亡”的主生命周期展开。本页补全围绕这条主线的外围系统:死后的观察者视角与它的限制、死亡玩家接管同队 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/GetAssociatedTeamNumbergame/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 的规则:
- 对队伍成员或教练,大于
OBS_MODE_FIXED的模式按mp_forcecamera收紧:1(仅本队)一律压成OBS_MODE_IN_EYE,2(不允许)压成OBS_MODE_FIXED:game/server/player.cpp:2464-2473。 - 在
CSTRIKE_DLL下,若当前目标是已下包的 C4 或投掷物,强制OBS_MODE_CHASE:game/server/player.cpp:2475-2489。 - 记住上一个真正的观战模式;从死亡/冻结镜头切到跟随模式时清空目标,以便重新寻找(可接管的 Bot 优先,见注释):
game/server/player.cpp:2491-2503。 - 按模式设置移动类型、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_forcecamera | 1 | 0 任意队伍;1 仅本队且模式压成第一人称;2 不允许观战、死亡后黑屏 | game/shared/gamevars_shared.cpp:24-32 |
spec_allow_roaming | 0(作弊) | 仅在“无目标转向 C4”时被检查(见 1.2) | game/server/player.cpp:117、game/server/cstrike15/cs_player.cpp:3742-3747 |
sv_spec_post_death_additional_time | 0 | 纯观众在目标死亡后继续看它的额外秒数 | game/server/player.cpp:2949 |
spec_freeze_time / spec_freeze_time_lock / spec_freeze_traveltime / spec_freeze_deathanim_time | 3.0 / 1.0 / 0.3 / 0.8 | 死亡与冻结镜头计时(CS 取值) | game/server/player.cpp:109-117 |
mp_deathcam_skippable | 1 | 锁定期后能否按键跳过冻结镜头 | game/server/cstrike15/cs_player.cpp:268 |
mp_spectators_max | 2 | 观众人数上限(本次未追踪使用点) | 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、待换队、是否在回放中,然后调用TakeControlOfBotgame/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对所有玩家执行RevertBotsFunctorgame/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_ex | game/shared/cstrike15/cs_gamerules.cpp:14665、game/server/cstrike15/cs_voteissues.cpp:135-165 |
CRestartGameIssue | 全局 | 总是 | 见 ExecuteCommand | game/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_start | game/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 流程#
证据:
- 发起:
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。 - 投票:客户端命令
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。 - 结算:赞成票达到门槛即通过;重赛投票在剩余可能票数不足时提前失败;计时到仍不够则失败
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。 - 门槛:排队匹配的竞技规则下非队伍限定议题需要全部匹配账号同意;“全票”议题需要所有潜在投票者;其余在竞技规则下为“潜在票数 − 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,没有再SetNextThinkgame/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 时目标除以 10 | game/shared/cstrike15/achievements_cs.cpp:193-220、game/shared/cstrike15/achievements_cs.cpp:292-322 |
服务端授予型 DECLARE_ACHIEVEMENT_SERVERAWARDED | 服务端在事件发生处调用 AwardAchievement | game/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 | 营救区触发器,触碰时向对方发送输入 OnRescueZoneTouch | game/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 |
营救流程:
- 交互:热身期拒绝;距离超过 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。 - 进度:
HostageThink在解绑期间检查玩家是否仍按住使用键、仍对准人质且站在地面上,否则取消;完成后人质开始跟随/被扛起game/server/cstrike15/hostage/cs_simple_hostage.cpp:1160-1228。首个接触该人质的 CT 获得奖励并计分game/server/cstrike15/hostage/cs_simple_hostage.cpp:1598-1620。 - 携带:扛着人质的玩家最大速度固定为 200
game/shared/cstrike15/cs_player_shared.cpp:349-352、game/shared/cstrike15/cs_shareddefs.cpp:17。 - 送达:营救区触碰的是玩家;玩家的
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。 - 判胜:
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、梯子与跳跃)、出生点选择算法、合作/守护者模式下的人质规则未展开。 - 本页所有结论来自静态阅读,未运行游戏验证;标注“推断”的条目缺少运行时证据。