跳转至

10. 和 FPSMatch / 团队竞技联动(数值平衡)

做玩法服时,最常见的需求不是"加新枪",而是"把现有枪的数值按玩法血量校准"。这一节讲怎么做一套可复现、可校验的数值补丁。

10.1 为什么要校准

原版枪械数值是按"原版血量 + 原版战斗节奏"设计的。玩法服常见改动(例如玩家血量 32、无护甲或有特殊护甲)会让原数值失衡:

  • 爆头倍率过高 → 一枪秒杀,没有交火过程;
  • 霰弹"单颗弹丸伤害"口径 → 贴脸一枪毙命;
  • 射速/移速组合 → 跑打流无敌。

校准的目标:让 TTK(Time To Kill,击杀所需时间)落在玩法想要的区间(例如 0.3~0.8 秒)。

10.2 只改 data,不动其它

数值补丁只覆盖 data/guns/<id>_data.json:

要改的:data/<ns>/data/guns/*.json      ← 只动这里
不动的:index / display / geo_models / animations / textures / tacz_sounds

好处:文件小、可审查、不碰美术资源、出问题好回滚。

常改字段:

字段 用途
bullet.damage 单发伤害(躯干击杀发数 = ceil(血量 / damage))
bullet.bullet_amount 霰弹弹丸数(口径统一按"弹丸合计")
bullet.extra_damage.head_shot_multiplier 爆头倍率
bullet.extra_damage.damage_adjust 距离衰减
ammo_amount 弹匣容量
rpm 射速(TTK 的另一个乘数)

TTK 估算(单发武器):

击杀发数 = ceil(玩家血量 / 单发伤害)          # 躯干
爆头发数 = ceil(玩家血量 / (单发伤害 × 爆头倍率))
TTK(秒) ≈ (击杀发数 - 1) × 60 / rpm

例:血量 32、伤害 9、rpm 600 → ceil(32/9)=4 发 → TTK ≈ 3 × 0.1 = 0.3s。

10.3 用脚本批量改(附最小示例)

手改十几把枪容易漏字段。用脚本做,并只改白名单内的字段:

import json, glob, os

RULE = {                    # 枪 id → 要改的字段
    "m4a1":   {"head": 2.0},
    "ak47":   {"head": 2.0},
    "m870":   {"damage": 7},          # 霰弹:按"弹丸合计"口径削弱
    "aa12":   {"damage": 6},
    "glock_17": {"damage": 8},
}

def patch(path):
    with open(path, encoding="utf-8") as f:
        data = json.load(f)
    gun_id = os.path.basename(path).replace("_data.json", "")
    rule = RULE.get(gun_id)
    if not rule:
        return "skip"
    before = json.dumps(data, sort_keys=True)
    if "head" in rule:
        data["bullet"]["extra_damage"]["head_shot_multiplier"] = rule["head"]
    if "damage" in rule:
        data["bullet"]["damage"] = rule["damage"]
    changed = json.dumps(data, sort_keys=True) != before
    if changed:
        with open(path, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)   # 干净 JSON,无注释
    return "changed" if changed else "same"

for p in sorted(glob.glob("data/*/data/guns/*_data.json")):
    print(p, "→", patch(p))

⚠️ 真实枪包的 JSON 可能带注释或尾逗号(非标准 JSON)。脚本改之前先确认能解析;不能解析就先清洗,或者只对"干净"的目标文件做补丁。

10.4 必须做校验(不然改坏了都不知道)

补丁脚本写完,配一个断言脚本:

import json, glob

def check(path):
    data = json.load(open(path, encoding="utf-8"))
    gun = path.split("/")[-1].replace("_data.json", "")
    # ① 该改的改了
    if gun in ("m4a1", "ak47"):
        assert data["bullet"]["extra_damage"]["head_shot_multiplier"] == 2.0, gun
    # ② 不该动的一个字节都没动(和"原包备份"逐字段比对)
    #    …这里放你保存的原包副本对比逻辑…
    return "PASS"

for p in sorted(glob.glob("patched/**/*_data.json", recursive=True)):
    print(check(p), p)

校验点: 1. 目标字段确实变了; 2. 其它字段与原包完全一致(逐字段 diff); 3. 数值在合理范围(伤害 > 0、倍率 < 10、rpm 在合理区间); 4. 所有枪的 JSON 都能解析。

10.5 两端同步与发布

① 生成补丁(只含 data/guns/*.json)
② 校验报告全 PASS
③ 服务端:覆盖 tacz/<枪包>/data/<ns>/data/guns/ → 重启/重载
④ 客户端:同样覆盖(或打一个"补丁包"叠加在枪包之上)→ 玩家重进游戏
⑤ 记录改动:枪 / 字段 / 原值 / 新值(方便回滚与公示)
坑 说明
只改客户端 服务端仍按旧数值判定伤害 → 玩家看到的与服务器算的不一致
只改服务端 客户端 HUD/预估显示旧值 → 手感怪
忘了 ammo_amount 与显示 改了弹匣容量但没看 HUD,玩法体感对不上
超模枪未处理 只调数值不够时,用黑名单禁枪(从 index/guns/ 移除或改 id)

10.6 和 FPSMatch 类玩法的配合

玩法模组(如 FPSMatch)负责对局流程(队伍、计分、商店、HUD),枪包负责武器本身。两者配合时的注意点:

场景 注意
商店/装备系统按枪 id 发枪 枪包改 id 或删枪会让商店配置失效
玩法自己算伤害 若玩法侧有独立伤害逻辑,枪包的 damage 可能只影响部分场景,需实测
超模枪黑名单 玩法侧有黑名单配置的,枪包改动要同步维护
数值公示 服务器/社区通常会公开武器数值表,补丁后记得同步更新

💡 团队的实践:数值补丁 + "逐项 PASS/FAIL 校验报告" 一起交付,改数值这件事就变成可回滚、可复现的工程活了。