修改bot_stuff来控制爆头率

你好!本指南将带你一步步了解,怎么在你使用的插件Manico's CSGOBetterbots里,增加一个自定义爆头率的功能。当然还会聊聊为什么你设置的,和Bot实际打出来的有差异。

📚 SourcePawn & ConVar 初级入门

在开始修改前,先来简单了解一下插件的几个小概念:

💻 代码修改步骤

下面,我们将从 Manico 的原始版本开始,一步步把它升级成带可配置爆头率的版本。

1. Manico 的原始函数:最初的爆头逻辑

最开始的时候,Bot 瞄准目标的代码可能长这样。这里面的爆头率是固定写死的,调整起来很不方便:

public void SelectBestTargetPos(int client, float fTargetPos[3])
{
	if(IsValidClient(g_iTarget[client]) && IsPlayerAlive(g_iTarget[client]))
	{
		int iBone = LookupBone(g_iTarget[client], "head_0");
		int iSpineBone = LookupBone(g_iTarget[client], "spine_3");
		if (iBone < 0 || iSpineBone < 0)
			return;
		
		bool bShootSpine; // 默认值为 false,意味着倾向于射击头部
		float fHead[3], fBody[3], fBad[3];
		GetBonePosition(g_iTarget[client], iBone, fHead, fBad);
		GetBonePosition(g_iTarget[client], iSpineBone, fBody, fBad);
		
		fHead[2] += 4.0;
		
		if (BotIsVisible(client, fHead, false, -1))
		{
			if (BotIsVisible(client, fBody, false, -1))
			{
				if (!IsValidEntity(g_iActiveWeapon[client])) return;
				
				int iDefIndex = GetEntProp(g_iActiveWeapon[client], Prop_Send, "m_iItemDefinitionIndex");
				
				switch(iDefIndex)
				{
					// 这些是大多数步枪、冲锋枪等等的武器ID,这里有个硬编码的概率
					case 7, 8, 10, 13, 14, 16, 17, 19, 23, 24, 25, 26, 27, 28, 29, 33, 34, 35, 39, 60:
					{
						float fTargetDistance = GetVectorDistance(g_fBotOrigin[client], fHead);
						// 核心判断:如果IsItMyChance(70.0) 返回true (有70%概率发生) 并且距离近
						if (IsItMyChance(70.0) && fTargetDistance < 2000.0)
							bShootSpine = true; // 目标瞄准身体(非爆头)
					}
					// 这些是AWP、散弹枪等等的武器ID,它们总是瞄准身体
					case 9, 11, 38: // 9 是 AWP
					{
						bShootSpine = true; // 总是瞄准身体(非爆头)
					}
				}
			}
		}
		else
		{
			// 头部不可见时的备用逻辑:尝试寻找其他可见骨骼
			for (int b = 0; b <= sizeof(g_szBoneNames) - 1; b++)
			{
				iBone = LookupBone(g_iTarget[client], g_szBoneNames[b]);
				if (iBone < 0)
					return;
				
				GetBonePosition(g_iTarget[client], iBone, fHead, fBad);
				
				if (BotIsVisible(client, fHead, false, -1))
					break;
				else
					fHead[2] = 0.0;
			}
		}
		
		if(bShootSpine)
			fTargetPos = fBody; // 最终瞄准身体
		else
			fTargetPos = fHead; // 最终瞄准头部
	}
}

简单分析一下这个版本:

2. un1 的修改版:让爆头率可控

现在我们将上面的函数修改。这个版本引入了 ConVar ,实现了更精细的控制,比如全局强制爆头开关,以及 CT 和 T 阵营 Bot 不同的爆头率调整:

public void SelectBestTargetPos(int client, float fTargetPos[3])
{
	if(IsValidClient(g_iTarget[client]) && IsPlayerAlive(g_iTarget[client]))
	{
		int iBone = LookupBone(g_iTarget[client], "head_0");
		int iSpineBone = LookupBone(g_iTarget[client], "spine_3");
		if (iBone < 0 || iSpineBone < 0)
			return;
		
		bool bShootSpine = false; // **重要!**在这里明确初始化为 false,表示默认瞄准头部
		float fHead[3], fBody[3], fBad[3];
		GetBonePosition(g_iTarget[client], iBone, fHead, fBad);
		GetBonePosition(g_iTarget[client], iSpineBone, fBody, fBad);
		
		fHead[2] += 4.0; // 稍微抬高瞄准点,让子弹更容易命中真实头部区域

		if (BotIsVisible(client, fHead, false, -1))
		{
			if (BotIsVisible(client, fBody, false, -1))
			{
				if (!IsValidEntity(g_iActiveWeapon[client])) return;
				int iDefIndex = GetEntProp(g_iActiveWeapon[client], Prop_Send, "m_iItemDefinitionIndex");

				// 优先判断:如果开启了全局强制爆头模式 (g_cvHeadShotOnly.BoolValue)
				// 并且当前武器不是 AWP (武器ID 9),或者这个 Bot 被特殊标记为强制爆头 (g_bHeadShotOnly[client])
				if((g_cvHeadShotOnly.BoolValue && iDefIndex != 9) || g_bHeadShotOnly[client])
				{
					bShootSpine = false; // 强制瞄准头部
				}
				else // 如果没有强制爆头,就根据概率和武器类型来判断
				{
					switch(iDefIndex)
					{
						// 特定武器ID (AWP, 散弹枪等),总是强制瞄准身体
						case 9, 11, 38: // 9: Weapon_AWP, 11: Weapon_XM1014, 38: Weapon_SawedOff
						{
							bShootSpine = true;	// 强制射击脊椎(身体),即不爆头
						}
						// 大多数其他武器类型 (步枪、冲锋枪等)
						default:
						{
                            float fCurrentHeadshotChance = 0.0;
                            // 根据 Bot 的阵营,获取对应的爆头率 ConVar 值
                            if (g_bIsCT[client])
                            {
                                fCurrentHeadshotChance = g_cvBotHSC.FloatValue; // 获取 CT 的爆头率参数
                            }
                            else if (g_bIsT[client])
                            {
                                fCurrentHeadshotChance = g_cvBotHST.FloatValue; // 获取 T 的爆头率参数
                            }

                            // 关键的爆头率判断:
                            // 记住,IsItMyChance(X) 返回 true 的概率是 X%。
                            // 如果返回 true,Bot 就会切换瞄准身体 (bShootSpine = true)。
                            // 所以,如果想实现 fCurrentHeadshotChance% 的爆头率,
                            // 那就需要 (100.0 - fCurrentHeadshotChance)% 的概率去瞄准身体。
                            // 因此 IsItMyChance 的参数是 100.0 - fCurrentHeadshotChance。
							if (IsItMyChance(100.0 - fCurrentHeadshotChance))
							{
								bShootSpine = true;	// Bot 决定射击脊椎(身体)
							}
                            // 如果 If 条件不满足,bShootSpine 仍然保持默认的 false,表示瞄准头部
						}
					}
				}				
			}
		}
		else
		{
			// 如果头部不可见,这里是尝试寻找其他可见骨骼的备用逻辑。
			// 这个循环会找到第一个能看到的骨骼,并把它的位置赋给 fHead。
			// 如果所有骨骼都看不到,fHead[2] 会被设为0.0,可能导致Bot瞄准地面,
			// 在更复杂的Bot行为中,这块逻辑可能需要更精细的处理。
			for (int b = 0; b <= sizeof(g_szBoneNames) - 1; b++)
			{
				iBone = LookupBone(g_iTarget[client], g_szBoneNames[b]);
				if (iBone < 0)
					return;
				
				GetBonePosition(g_iTarget[client], iBone, fHead, fBad);
				
				if (BotIsVisible(client, fHead, false, -1))
					break; // 找到可见骨骼,跳出循环
				else
					fHead[2] = 0.0; // 当前骨骼不可见,重置,继续尝试下一个
			}
		}

		// 最后,根据 bShootSpine 的值来确定最终 Bot 要瞄准的位置
		if(bShootSpine)
			fTargetPos = fBody; // 瞄准身体
		else
			fTargetPos = fHead; // 瞄准头部
	}
}

⚠️ 关于 IsItMyChance() 的知识

在代码里,if (IsItMyChance(X)) bShootSpine = true; 这句的意思是说:当 IsItMyChance(X) 返回 true 的时候(它的成功概率是 X%),Bot 就会瞄准身体,也就是**不爆头**。

所以,如果你想要 Bot 有 V% 的概率爆头,那么你给 IsItMyChance 传的参数应该是 (100.0 - V)。这样,当它以 (100-V)% 的概率返回 true 时,就瞄准身体;当它以 V% 的概率返回 false 时,就瞄准头部,这正好符合“V%爆头”的逻辑!

上面的代码会按这个逻辑来设置 ConVar,所以 ConVar 的值直接就是你想要的爆头率百分比(0.0到100.0)。

3. 声明 ConVar

在你的 SourceMod (SP) 插件文件最上面,或者全局变量声明的地方,你需要告诉程序你将会有这些 ConVar。根据 SourceMod 的常用写法,会像这样声明:

#include <sourcemod> // SourceMod 插件必备的库
#include <sdktools>  // 如果你的插件用到 LookupBone, GetBonePosition 等函数,也需要这个,通常不用自己改动

// ... 其他你插件里本来就有的全局变量 ...

// 强制爆头模式的 ConVar
ConVar g_cvHeadShotOnly;

// Bot CT 爆头率的 ConVar
ConVar g_cvBotHSC;

// Bot T 爆头率的 ConVar
ConVar g_cvBotHST;

// 假设这些是你插件里已经有了的变量,用来判断 Bot 阵营或特殊标记
bool g_bIsCT[MAXPLAYERS+1]; 
bool g_bIsT[MAXPLAYERS+1];
bool g_bHeadShotOnly[MAXPLAYERS+1]; // 每个 Bot 的强制爆头标记

4. 初始化 ConVar

ConVar 声明之后,你还需要告诉程序它们叫什么名字、默认值是多少、有什么用。这些初始化工作通常在你的插件启动函数 OnPluginStart 里完成:

public void OnPluginStart()
{
    // ... 其他你插件里本来就有的初始化代码 ...

    // 创建“强制爆头模式”ConVar。注意这里的命名通常会自动加上 'sm_' 前缀
    g_cvHeadShotOnly = CreateConVar(
        "bot_headshot_only",          // ConVar 在控制台里的名字
        "0",                             // 默认值是“0”,表示关闭强制爆头
        "如果开启(1), Bot 将总是尝试爆头 (AWP除外).", // 在控制台输入 'sm_cvarlist' 时能看到的描述
        _,                               // 这个下划线表示使用默认的 FCVAR_PLUGIN 标志
        true, 0.0,                       // 最小值是 0.0
        true, 1.0                        // 最大值是 1.0 (因为是个开关,只有0和1)
    );

    // 创建 CT 阵营 Bot 爆头率 ConVar
    g_cvBotHSC = CreateConVar(
        "bot_hsc",            // CT 爆头率 ConVar 的名字
        "75.0",                          // 默认 CT 爆头率是 75.0%。注意:这是你提供的默认值。
        "Bot Stuff - HSC parameter.\nDefault: 75.0", // 描述
        _,                               // 默认标志
        true, 0.0,                       // 最小值 0.0
        true, 100.0                      // 最大值 100.0
    );

    // 创建 T 阵营 Bot 爆头率 ConVar
    g_cvBotHST = CreateConVar(
        "bot_hst",            // T 爆头率 ConVar 的名字
        "75.0",                         // 默认 T 爆头率是 75.0%。注意:这是你提供的默认值。
        "Bot Stuff - HST parameter.\nDefault: 75.0", // 描述
        _,                               // 默认标志
        true, 0.0,                      // 最小值 0.0
        true, 100.0                     // 最大值 100.0
    );
}

关于 ConVar 名字的小提醒:

根据 SourceMod 的规则,如果你在 CreateConVar 里没有给 ConVar 名字加上 sm_ 前缀,但使用了 FCVAR_PLUGIN 标志(这个在你提供的代码里用 _ 占位符默认是开启的),那么 SourceMod 会自动帮你加上 sm_。所以,虽然你代码里写的是 "bot_hst",但在控制台里你可能需要用 sm_bot_hst 来设置它。

5. 编译和部署

好啦,代码部分搞定!现在你需要把这些代码保存成一个 .sp 文件(最好备份一下再保存),然后用 SourceMod 的编译器把它编译成 .smx 文件。如果你不知道怎么编译,可以去 SourceMod 官网 找教程。

编译好之后,把生成的 .smx 文件放到你的 CS:GO 服务器的 addons/sourcemod/plugins 文件夹里。

服务器启动或加载插件后,你就可以在游戏控制台里输入以下命令来调整 Bot 的爆头率:

如果你想让这些设置在服务器重启后也生效,你可以在服务器的 cfg/sourcemod/ 文件夹里,创建一个对应 ConVar 名字的 .cfg 文件(比如 sm_bot_hsc.cfg),在里面写入你的值,这样服务器每次启动都会自动加载它们。

🤔 为什么我设置的爆头率与实际不同?

这是一个很常见也很容易让人困惑的问题。你通过 ConVar 设置的比如 sm_bot_hsc,它只是 Bot 在特定情况下决定瞄准头部的一个概率,而不是他们实际打出的爆头总数的百分比。

核心概念:条件概率与执行限制

你设定的爆头率 ConVar 只有在满足了一系列 前提条件 之后,才会真正“生效”:

  1. 爆头的伤害高: 这导致死亡的击杀往往是因为爆头,结算就是爆头+1。
  2. 具体原因在这:爆头率计算的数学原理

  3. 头部和身体“都”能看见:
  4. 武器类型限制:

    像 AWP (9)、XM1014 (11) 和 Sawed-Off (38) 这些武器,Bot 是永远瞄准身体的(bShootSpine = true)。无论 ConVar 爆头率设得多高,这些武器的爆头率在代码层面就是 0%。对于ProBot,狙击手的爆头率会低一些。