CD Code Digest Hub /CS:GO

武器与射击

13 分钟阅读 8 个章节 1 张图

本页追踪一把普通枪械从“按下开火键”到“对目标造成伤害”的完整路径:武器的帧逻辑、射击时序、散布随机、子弹追踪与穿透、伤害结算,以及客户端如何在不重复追踪的情况下呈现效果。投掷物与 C4 见 投掷物与 C4。

相关页面:玩家 · 特殊武器机制 · 延迟补偿 · 输入与预测 · 物品与经济

相关源码文件#

  • game/shared/cstrike15/weapon_csbase.cpp:所有 CS 武器基类 CWeaponCSBase
  • game/shared/cstrike15/weapon_csbasegun.cpp:枪械基类 CWeaponCSBaseGun
  • game/shared/cstrike15/fx_cs_shared.cpp:FX_FireBullets
  • game/shared/cstrike15/cs_player_shared.cpp:CCSPlayer::FireBullet、穿透
  • game/server/cstrike15/cs_player.cpp:命中部位倍率与伤害结算

1. 调用链#

证据:玩家 PostThink 时调用当前武器的 ItemPostFrame:game/shared/baseplayer_shared.cpp:341-350;武器按按键与下次攻击时间分派到主攻、副攻、换弹、空闲:game/shared/cstrike15/weapon_csbase.cpp:921-963;主攻处理最终调用 PrimaryAttack:game/shared/cstrike15/weapon_csbase.cpp:968-1027;其余各步见下文。

2. PrimaryAttack 与 CSBaseGunFire#

  • PrimaryAttack:水下不能开火;读取射击间隔,连发模式和开镜状态会通过属性钩子改写间隔;调用 CSBaseGunFire;开镜后需退镜的武器(栓动狙击)射击后退镜:game/shared/cstrike15/weapon_csbasegun.cpp:305-350。
  • CSBaseGunFire:
    1. 弹匣为空时播放空膛声并延迟 0.2 秒(左轮另算),返回 false:game/shared/cstrike15/weapon_csbasegun.cpp:688-711。
    2. 计算下次攻击时间,播放视图模型与玩家射击动画:game/shared/cstrike15/weapon_csbasegun.cpp:713-729。
    3. 以 GetInaccuracy()、GetSpread()、GetAccuracyFishtail()、预测随机种子(& 255)等参数调用 FX_FireBullets:game/shared/cstrike15/weapon_csbasegun.cpp:733-746。
    4. 枪口效果、空闲时间,然后射击后才累加精度惩罚 m_fAccuracyPenalty += GetInaccuracyFire、调用 Recoil、递增 m_iShotsFired 与 m_flRecoilIndex、扣一发子弹:game/shared/cstrike15/weapon_csbasegun.cpp:748-771。

快照缺口:CWeaponCSBase::Recoil 的函数体是 /** Removed for partner depot **/,即本仓库中后坐力(视角上跳)的实现被删除:game/shared/cstrike15/weapon_csbase.cpp:3833-3836。散布与精度惩罚仍在,但“表驱动后坐力”无法从本仓库重建。

物品定义索引固定为 0(审计 U2):CSBaseGunFire 把传给 FX_FireBullets 的物品定义索引固定为 0:game/shared/cstrike15/weapon_csbasegun.cpp:731-735,连发后续弹与四个霰弹枪类也一样;而 FX_FireBullets 用该索引查物品定义,查不到就直接返回:game/shared/cstrike15/fx_cs_shared.cpp:237-242。在 CSTRIKE_DLL 下,GetItemDefinition 找不到且没有默认定义时返回 NULL:game/shared/econ/econ_item_schema.cpp:8501-8526。后果分两种:schema 中没有 0 号或 default 定义时,射击在发送临时实体、播放枪声、开启延迟补偿和追踪子弹之前就被丢弃,但调用方仍会扣弹;有时则按该定义的类取武器脚本,所有枪械共用同一份不带物品视图读取的数据(如弹丸数)。R8 与 Negev 按索引 64/28 设计的散布曲线因此不可达。完整推导见 特殊武器机制 第 11 节;schema 的加载问题(U1)见 物品与经济。

3. 精度与散布#

GetInaccuracy 从当前精度惩罚出发,叠加:移动惩罚(水平速度在“蹲伏速度”到“95% 最大速度”之间映射为 0–1,非静步时再做幂曲线)、空中惩罚(按竖直速度的平方根映射,最大不超过起跳惩罚的 2 倍),结果钳制到 1:game/shared/cstrike15/weapon_csbase.cpp:1208-1284。精度惩罚随时间的衰减在 UpdateAccuracyPenalty:game/shared/cstrike15/weapon_csbase.cpp:3685。

FX_FireBullets 用传入的种子初始化随机数,生成一个“精度偏移”(半径 = 随机密度 × inaccuracy,方向随机),并为每颗弹丸预先生成“散布偏移”(半径 = 随机密度 × spread),注释说明预生成是为了避免 FireBullet 内部消耗随机数导致不同步:game/shared/cstrike15/fx_cs_shared.cpp:418-480。R8 左轮副攻与 Negev 前几发有特殊的密度曲线:game/shared/cstrike15/fx_cs_shared.cpp:420-433。最终每颗弹丸的偏移为 x0 + x1[i], y0 + y1[i]:game/shared/cstrike15/fx_cs_shared.cpp:548-567。

因为种子来自命令的预测随机种子,客户端预测与服务器会算出相同的散布(前提是两端参数一致)。

4. 服务端与客户端在 FX_FireBullets 中的分工#

端行为证据
客户端同一武器 20ms 内只追踪一次,防止预测重放导致重复效果game/shared/cstrike15/fx_cs_shared.cpp:201-214
服务端播放射击动画事件;发送 TE_FireBullets 临时实体,让其他客户端重现弹道与声音;关闭本端特效game/shared/cstrike15/fx_cs_shared.cpp:288-332
两端从武器数据读取伤害、射程、穿透、距离衰减、弹药类型game/shared/cstrike15/fx_cs_shared.cpp:336-343
服务端对整批弹丸开启/关闭延迟补偿game/shared/cstrike15/fx_cs_shared.cpp:380-383、game/shared/cstrike15/fx_cs_shared.cpp:575-577
服务端伤害监听器:只要任一弹丸爆头,同批对同一目标的其它弹丸也标记为爆头(用于表现);记录命中统计game/shared/cstrike15/fx_cs_shared.cpp:482-546

5. 子弹追踪与穿透:CCSPlayer::FireBullet#

  1. 读取弹药类型的穿透能力与穿透距离;sv_penetration_type == 1 时改用武器自身穿透值;方向向量叠加散布偏移:game/shared/cstrike15/cs_player_shared.cpp:1156-1194。
  2. 在 while ( fCurrentDamage > 0 ) 循环中追踪射线(忽略射击者与上一个命中的玩家),并沿射线延伸裁剪到玩家命中盒:game/shared/cstrike15/cs_player_shared.cpp:1257-1269;什么都没打中就结束:game/shared/cstrike15/cs_player_shared.cpp:1385。
  3. 根据命中表面取伤害修正,累计飞行距离,伤害按 pow(rangeModifier, distance/500) 衰减:game/shared/cstrike15/cs_player_shared.cpp:1595、game/shared/cstrike15/cs_player_shared.cpp:1694-1695。
  4. 服务端把这次命中的 CTakeDamageInfo(攻击者、武器、伤害、DMG_BULLET|DMG_NEVERGIB、已穿透物体数、子弹编号等)暂存到待结算列表,而不是立即施加:game/shared/cstrike15/cs_player_shared.cpp:1728、game/shared/cstrike15/cs_player_shared.cpp:1809-1830。
  5. HandleBulletPenetration 计算能否继续穿透并更新起点、剩余伤害与穿透次数;不能穿透则退出循环:game/shared/cstrike15/cs_player_shared.cpp:1858-1865、game/shared/cstrike15/cs_player_shared.cpp:1955。
  6. 循环结束后,服务端依次对每个暂存命中执行 DispatchTraceAttack + ApplyMultiDamage,并统计一枪穿杀数:game/shared/cstrike15/cs_player_shared.cpp:1880-1901。

设计含义(推断):延迟结算保证了“一颗子弹穿过多名敌人”时,所有命中都基于同一时刻的世界状态计算,而不会因为前一个目标被击杀/移除而改变后续追踪。

命中后的部位倍率与护甲结算见 玩家。

6. 武器数据从哪里来#

FX_FireBullets 通过物品定义的类名查找 CCSWeaponInfo(武器脚本数据):game/shared/cstrike15/fx_cs_shared.cpp:273-286。伤害、射程、穿透、射速、精度参数都通过 GetDamage(pItem) 之类带物品视图参数的接口读取,可被物品属性改写:game/shared/cstrike15/fx_cs_shared.cpp:339-343、game/shared/cstrike15/weapon_csbasegun.cpp:323。武器脚本与物品 schema 都是内容文件,不在仓库中(见 物品与经济)。

约束与边界#

  • 本页只覆盖普通枪械主攻。刀、电击枪、霰弹枪逐发装填与弹丸、左轮扳机延迟与副攻、连发模式、消音器装卸、开镜对精度与移速的影响,以及客户端弹道特效(TE_FireBullets 接收端、弹孔、曳光、穿墙轨迹)见 特殊武器机制。
  • 后坐力实现被删空(见上文快照缺口),因此无法从本仓库重建“表驱动后坐力”与视角上跳。
  • Bot 的后坐补偿读取 aim punch,而 aim punch 依赖被删空的后坐力实现,见 Bot 决策。
  • 武器数值(伤害、射程、射速、散布等)来自武器脚本与物品 schema,均不在仓库中;本页只描述读取路径。