武器与射击
本页追踪一把普通枪械从“按下开火键”到“对目标造成伤害”的完整路径:武器的帧逻辑、射击时序、散布随机、子弹追踪与穿透、伤害结算,以及客户端如何在不重复追踪的情况下呈现效果。投掷物与 C4 见 投掷物与 C4。
相关页面:玩家 · 特殊武器机制 · 延迟补偿 · 输入与预测 · 物品与经济
相关源码文件#
game/shared/cstrike15/weapon_csbase.cpp:所有 CS 武器基类CWeaponCSBasegame/shared/cstrike15/weapon_csbasegun.cpp:枪械基类CWeaponCSBaseGungame/shared/cstrike15/fx_cs_shared.cpp:FX_FireBulletsgame/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:- 弹匣为空时播放空膛声并延迟 0.2 秒(左轮另算),返回 false:
game/shared/cstrike15/weapon_csbasegun.cpp:688-711。 - 计算下次攻击时间,播放视图模型与玩家射击动画:
game/shared/cstrike15/weapon_csbasegun.cpp:713-729。 - 以
GetInaccuracy()、GetSpread()、GetAccuracyFishtail()、预测随机种子(& 255)等参数调用FX_FireBullets:game/shared/cstrike15/weapon_csbasegun.cpp:733-746。 - 枪口效果、空闲时间,然后射击后才累加精度惩罚
m_fAccuracyPenalty += GetInaccuracyFire、调用Recoil、递增m_iShotsFired与m_flRecoilIndex、扣一发子弹:game/shared/cstrike15/weapon_csbasegun.cpp:748-771。
- 弹匣为空时播放空膛声并延迟 0.2 秒(左轮另算),返回 false:
快照缺口:
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#
- 读取弹药类型的穿透能力与穿透距离;
sv_penetration_type == 1时改用武器自身穿透值;方向向量叠加散布偏移:game/shared/cstrike15/cs_player_shared.cpp:1156-1194。 - 在
while ( fCurrentDamage > 0 )循环中追踪射线(忽略射击者与上一个命中的玩家),并沿射线延伸裁剪到玩家命中盒:game/shared/cstrike15/cs_player_shared.cpp:1257-1269;什么都没打中就结束:game/shared/cstrike15/cs_player_shared.cpp:1385。 - 根据命中表面取伤害修正,累计飞行距离,伤害按
pow(rangeModifier, distance/500)衰减:game/shared/cstrike15/cs_player_shared.cpp:1595、game/shared/cstrike15/cs_player_shared.cpp:1694-1695。 - 服务端把这次命中的
CTakeDamageInfo(攻击者、武器、伤害、DMG_BULLET|DMG_NEVERGIB、已穿透物体数、子弹编号等)暂存到待结算列表,而不是立即施加:game/shared/cstrike15/cs_player_shared.cpp:1728、game/shared/cstrike15/cs_player_shared.cpp:1809-1830。 HandleBulletPenetration计算能否继续穿透并更新起点、剩余伤害与穿透次数;不能穿透则退出循环:game/shared/cstrike15/cs_player_shared.cpp:1858-1865、game/shared/cstrike15/cs_player_shared.cpp:1955。- 循环结束后,服务端依次对每个暂存命中执行
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 都是内容文件,不在仓库中(见 物品与经济)。