Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

511 lines
13 KiB

#include "bot_common.h"
bool HasDefaultPistol(CCSBot *me)
{
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(me->m_rgpPlayerItems[ PISTOL_SLOT ]);
if (pistol == NULL)
return false;
if (me->m_iTeam == TERRORIST && pistol->m_iId == WEAPON_GLOCK18)
return true;
if (me->m_iTeam == CT && pistol->m_iId == WEAPON_USP)
return true;
return false;
}
// Buy weapons, armor, etc.
void BuyState::OnEnter(CCSBot *me)
{
CCSBotManager *ctrl = TheCSBots();
m_retries = 0;
m_prefRetries = 0;
m_prefIndex = 0;
m_doneBuying = false;
m_isInitialDelay = true;
// this will force us to stop holding live grenade
me->EquipBestWeapon();
m_buyDefuseKit = false;
m_buyShield = false;
if (me->m_iTeam == CT)
{
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
// CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
if (g_pGameRules->IsCareer() == false)
{
const float buyDefuseKitChance = 50.0f; // 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
if (RANDOM_FLOAT(0.0f, 100.0f) < buyDefuseKitChance)
{
m_buyDefuseKit = true;
}
}
}
// determine if we want a tactical shield
if (!me->m_bHasPrimary && ctrl->AllowTacticalShield())
{
if (me->m_iAccount > 2500)
{
if (me->m_iAccount < 4000)
m_buyShield = (RANDOM_FLOAT(0, 100.0f) < 33.3f) ? true : false;
else
m_buyShield = (RANDOM_FLOAT(0, 100.0f) < 10.0f) ? true : false;
}
}
}
if (ctrl->AllowGrenades())
{
m_buyGrenade = (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f) ? true : false;
}
else
{
m_buyGrenade = false;
}
m_buyPistol = false;
if (ctrl->AllowPistols())
{
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(me->m_rgpPlayerItems[ PISTOL_SLOT ]);
// check if we have a pistol
if (pistol != NULL)
{
// if we have our default pistol, think about buying a different one
if (HasDefaultPistol(me))
{
// if everything other than pistols is disallowed, buy a pistol
if (ctrl->AllowShotguns() == false &&
ctrl->AllowSubMachineGuns() == false &&
ctrl->AllowRifles() == false &&
ctrl->AllowMachineGuns() == false &&
ctrl->AllowTacticalShield() == false &&
ctrl->AllowSnipers() == false)
{
m_buyPistol = (RANDOM_FLOAT(0, 100) < 75.0f);
}
else if (me->m_iAccount < 1000)
{
// if we're low on cash, buy a pistol
m_buyPistol = (RANDOM_FLOAT(0, 100) < 75.0f);
}
else
{
m_buyPistol = (RANDOM_FLOAT(0, 100) < 33.3f);
}
}
}
else
{
// we dont have a pistol - buy one
m_buyPistol = true;
}
}
}
enum WeaponType
{
PISTOL,
SHOTGUN,
SUB_MACHINE_GUN,
RIFLE,
MACHINE_GUN,
SNIPER_RIFLE,
GRENADE,
NUM_WEAPON_TYPES,
};
struct BuyInfo
{
WeaponType type;
bool preferred; // more challenging bots prefer these weapons
char *buyAlias; // the buy alias for this equipment
};
// These tables MUST be kept in sync with the CT and T buy aliases
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
{ SUB_MACHINE_GUN, false, "mp5" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "famas" }, // WEAPON_FAMAS
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "m4a1" }, // WEAPON_M4A1
{ RIFLE, false, "aug" }, // WEAPON_AUG
{ SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fn57" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
{ SUB_MACHINE_GUN, false, "mp5" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "galil" }, // WEAPON_GALIL
{ RIFLE, true, "ak47" }, // WEAPON_AK47
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "sg552" }, // WEAPON_SG552
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "elites" }
};
// Given a weapon alias, return the kind of weapon it is
inline WeaponType GetWeaponType(const char *alias)
{
int i;
for (i = 0; i < PRIMARY_WEAPON_BUY_COUNT; ++i)
{
if (!Q_stricmp(alias, primaryWeaponBuyInfoCT[i].buyAlias))
return primaryWeaponBuyInfoCT[i].type;
if (!Q_stricmp(alias, primaryWeaponBuyInfoT[i].buyAlias))
return primaryWeaponBuyInfoT[i].type;
}
for (i = 0; i < SECONDARY_WEAPON_BUY_COUNT; ++i)
{
if (!Q_stricmp(alias, secondaryWeaponBuyInfoCT[i].buyAlias))
return secondaryWeaponBuyInfoCT[i].type;
if (!Q_stricmp(alias, secondaryWeaponBuyInfoT[i].buyAlias))
return secondaryWeaponBuyInfoT[i].type;
}
return NUM_WEAPON_TYPES;
}
void BuyState::OnUpdate(CCSBot *me)
{
// wait for a Navigation Mesh
if (!TheNavAreaList.Count())
return;
// apparently we cant buy things in the first few seconds, so wait a bit
if (m_isInitialDelay)
{
const float waitToBuyTime = 2.0f; // 0.25f;
if (gpGlobals->time - me->GetStateTimestamp() < waitToBuyTime)
return;
m_isInitialDelay = false;
}
// if we're done buying and still in the freeze period, wait
if (m_doneBuying)
{
if (g_pGameRules->IsMultiplayer () && g_pGameRules->IsFreezePeriod ())
{
// make sure we're locked and loaded
me->EquipBestWeapon (MUST_EQUIP);
me->Reload ();
me->ResetStuckMonitor ();
return;
}
me->Idle();
return;
}
// is the bot spawned outside of a buy zone?
if (!(me->m_signals.GetState() & SIGNAL_BUY))
{
m_doneBuying = true;
UTIL_DPrintf("%s bot spawned outside of a buy zone (%d, %d, %d)\n", (me->m_iTeam == CT) ? "CT" : "Terrorist", me->pev->origin.x, me->pev->origin.y, me->pev->origin.z);
return;
}
CCSBotManager *ctrl = TheCSBots();
// try to buy some weapons
const float buyInterval = 0.2f; // 0.02f
if (gpGlobals->time - me->GetStateTimestamp() > buyInterval)
{
me->m_stateTimestamp = gpGlobals->time;
bool isPreferredAllDisallowed = true;
// try to buy our preferred weapons first
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount())
{
// need to retry because sometimes first buy fails??
const int maxPrefRetries = 2;
if (m_prefRetries >= maxPrefRetries)
{
// try to buy next preferred weapon
++m_prefIndex;
m_prefRetries = 0;
return;
}
int weaponPreference = me->GetProfile()->GetWeaponPreference(m_prefIndex);
// don't buy it again if we still have one from last round
CBasePlayerWeapon *weapon = me->GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == weaponPreference)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
if (weaponPreference == WEAPON_SHIELDGUN)
{
if (ctrl->AllowTacticalShield())
buyAlias = "shield";
}
else
{
buyAlias = WeaponIDToAlias(weaponPreference);
WeaponType type = GetWeaponType(buyAlias);
switch (type)
{
case PISTOL:
if (!ctrl->AllowPistols())
buyAlias = NULL;
break;
case SHOTGUN:
if (!ctrl->AllowShotguns())
buyAlias = NULL;
break;
case SUB_MACHINE_GUN:
if (!ctrl->AllowSubMachineGuns())
buyAlias = NULL;
break;
case RIFLE:
if (!ctrl->AllowRifles())
buyAlias = NULL;
break;
case MACHINE_GUN:
if (!ctrl->AllowMachineGuns())
buyAlias = NULL;
break;
case SNIPER_RIFLE:
if (!ctrl->AllowSnipers())
buyAlias = NULL;
break;
}
}
if (buyAlias)
{
me->ClientCommand(buyAlias);
me->PrintIfWatched("Tried to buy preferred weapon %s.\n", buyAlias);
isPreferredAllDisallowed = false;
}
++m_prefRetries;
// bail out so we dont waste money on other equipment
// unless everything we prefer has been disallowed, then buy at random
if (isPreferredAllDisallowed == false)
return;
}
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
if (!me->m_bHasPrimary && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
{
if (m_buyShield)
{
// buy a shield
me->ClientCommand("shield");
me->PrintIfWatched("Tried to buy a shield.\n");
}
else
{
// build list of allowable weapons to buy
BuyInfo *masterPrimary = (me->m_iTeam == TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
int stockPrimaryCount = 0;
// dont choose sniper rifles as often
const float sniperRifleChance = 50.0f;
bool wantSniper = (RANDOM_FLOAT(0, 100) < sniperRifleChance) ? true : false;
for (int i = 0; i < PRIMARY_WEAPON_BUY_COUNT; ++i)
{
if ((masterPrimary[i].type == SHOTGUN && ctrl->AllowShotguns()) ||
(masterPrimary[i].type == SUB_MACHINE_GUN && ctrl->AllowSubMachineGuns()) ||
(masterPrimary[i].type == RIFLE && ctrl->AllowRifles()) ||
(masterPrimary[i].type == SNIPER_RIFLE && ctrl->AllowSnipers() && wantSniper) ||
(masterPrimary[i].type == MACHINE_GUN && ctrl->AllowMachineGuns()))
{
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
}
}
if (stockPrimaryCount)
{
// buy primary weapon if we don't have one
int which;
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
if (m_retries == 0 && ctrl->GetDifficultyLevel() >= BOT_HARD)
{
// count up available preferred weapons
int prefCount = 0;
for (which = 0; which < stockPrimaryCount; ++which)
{
if (stockPrimary[which]->preferred)
++prefCount;
}
if (prefCount)
{
int whichPref = RANDOM_LONG(0, prefCount - 1);
for (which = 0; which < stockPrimaryCount; ++which)
{
if (stockPrimary[which]->preferred && whichPref-- == 0)
break;
}
}
else
{
// no preferred weapons available, just pick randomly
which = RANDOM_LONG(0, stockPrimaryCount - 1);
}
}
else
{
which = RANDOM_LONG(0, stockPrimaryCount - 1);
}
me->ClientCommand(stockPrimary[ which ]->buyAlias);
me->PrintIfWatched("Tried to buy %s.\n", stockPrimary[ which ]->buyAlias);
}
}
}
// If we now have a weapon, or have tried for too long, we're done
if (me->m_bHasPrimary || m_retries++ > 5)
{
// primary ammo
if (me->m_bHasPrimary)
{
me->ClientCommand("primammo");
}
// buy armor last, to make sure we bought a weapon first
me->ClientCommand("vesthelm");
me->ClientCommand("vest");
// pistols - if we have no preferred pistol, buy at random
if (ctrl->AllowPistols() && !me->GetProfile()->HasPistolPreference())
{
if (m_buyPistol)
{
int which = RANDOM_LONG(0, SECONDARY_WEAPON_BUY_COUNT - 1);
if (me->m_iTeam == TERRORIST)
me->ClientCommand(secondaryWeaponBuyInfoT[ which ].buyAlias);
else
me->ClientCommand(secondaryWeaponBuyInfoCT[ which ].buyAlias);
// only buy one pistol
m_buyPistol = false;
}
me->ClientCommand("secammo");
}
// buy a grenade if we wish, and we don't already have one
if (m_buyGrenade && !me->HasGrenade())
{
if (UTIL_IsTeamAllBots(me->m_iTeam))
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
float rnd = RANDOM_FLOAT(0, 100);
if (rnd < 10.0f)
{
// smoke grenade
me->ClientCommand("sgren");
}
else if (rnd < 35.0f)
{
// flashbang
me->ClientCommand("flash");
}
else
{
// he grenade
me->ClientCommand("hegren");
}
}
else
{
if (RANDOM_FLOAT(0, 100) < 10.0f)
{
// smoke grenade
me->ClientCommand("sgren");
}
else
{
// he grenade
me->ClientCommand("hegren");
}
}
}
if (m_buyDefuseKit)
{
me->ClientCommand("defuser");
}
m_doneBuying = true;
}
}
}
void BuyState::OnExit(CCSBot *me)
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}