bot: improved logic when in freezetime

fix: full reset entvars of  fakeclient upon entity creation
fix: high skilled bots can't shoot with sniper weapons on short distances (ref #582)
fix: fill server will not overflow number of bots anymore
fix: do  not treat dormant  entites as valid clients for bots
mgr: : single bot kick is now working in non-sequential order
Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
This commit is contained in:
jeefo 2024-06-01 10:57:57 +03:00
commit 4aa6aff8b6
No known key found for this signature in database
GPG key ID: D696786B81B667C8
9 changed files with 195 additions and 68 deletions

View file

@ -230,6 +230,9 @@ public:
// ensure prosperous gaming environment as per: https://github.com/yapb/yapb/issues/575
void ensureHealthyGameEnvironment ();
// creates a fake client's a nd resets all the entvars
edict_t *createFakeClient (StringRef name);
// public inlines
public:
// get the current time on server

View file

@ -377,7 +377,8 @@ private:
int bestPrimaryCarried ();
int bestSecondaryCarried ();
int bestGrenadeCarried ();
int bestWeaponCarried ();
int getBestOwnedWeapon ();
int getBestOwnedPistol ();
int changeNodeIndex (int index);
int numEnemiesNear (const Vector &origin, const float radius);
int numFriendsNear (const Vector &origin, const float radius);
@ -416,6 +417,7 @@ private:
bool reactOnEnemy ();
bool selectBestNextNode ();
bool hasAnyWeapons ();
bool hasAnyAmmoInClip ();
bool isKnifeMode ();
bool isGrenadeWar ();
bool isDeadlyMove (const Vector &to);
@ -506,6 +508,7 @@ private:
void refreshCreatureStatus (char *infobuffer);
void updateRightRef ();
void donateC4ToHuman ();
void clearAmmoInfo ();
void completeTask ();
void executeTasks ();

View file

@ -427,8 +427,8 @@ void Bot::updatePickups () {
pickupType = Pickup::Weapon;
if (cv_pickup_ammo_and_kits) {
const int primaryWeaponCarried = bestPrimaryCarried ();
const int secondaryWeaponCarried = bestSecondaryCarried ();
const int primaryWeaponCarried = getBestOwnedWeapon ();
const int secondaryWeaponCarried = getBestOwnedPistol ();
const auto &config = conf.getWeapons ();
const auto &primary = config[primaryWeaponCarried];
@ -1872,13 +1872,13 @@ void Bot::setConditions () {
}
}
else {
auto currentTime = game.time();
auto currentTime = game.time ();
m_killsInterval = currentTime - m_lastVictimTime;
if (m_killsInterval <= 5) {
m_killsCount++;
if (m_killsCount > 2) {
pushChatterMessage(Chatter::OnARoll);
pushChatterMessage (Chatter::OnARoll);
}
}
else {
@ -1961,7 +1961,7 @@ void Bot::filterTasks () {
float tempFear = m_fearLevel;
float tempAgression = m_agressionLevel;
// decrease fear if teammates near
// decrease fear if players near
int friendlyNum = 0;
if (!m_lastEnemyOrigin.empty ()) {
@ -3020,7 +3020,13 @@ void Bot::update () {
}
void Bot::logicDuringFreezetime () {
pev->button = 0;
if (m_isStale) {
return;
}
pev->button &= ~IN_DUCK;
updateLookAngles ();
runMovement ();
if (m_changeViewTime > game.time ()) {
return;
@ -3030,20 +3036,50 @@ void Bot::logicDuringFreezetime () {
pev->button |= IN_JUMP;
m_jumpTime = game.time ();
}
static Array <Bot *> teammates {};
teammates.clear ();
static Array <edict_t *> players {};
players.clear ();
for (const auto &bot : bots) {
if (bot->m_isAlive && bot->m_team == m_team && bot.get () != this && seesEntity (bot->pev->origin)) {
teammates.push (bot.get ());
// search for visible enemies
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used)
|| !(client.flags & ClientFlags::Alive)
|| client.team == m_team
|| client.ent == ent ()
|| !client.ent
|| !seesEntity (client.origin)) {
continue;
}
players.push (client.ent);
}
if (!teammates.empty ()) {
auto bot = teammates.random ();
// use teammates
if (players.empty ()) {
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used)
|| !(client.flags & ClientFlags::Alive)
|| client.team != m_team
|| client.ent == ent ()
|| !client.ent
|| !seesEntity (client.origin)) {
continue;
}
players.push (client.ent);
}
}
else {
selectBestWeapon ();
}
if (bot) {
m_lookAt = bot->pev->origin + bot->pev->view_ofs;
if (!players.empty ()) {
auto ent = players.random ();
if (ent) {
m_lookAt = ent->v.origin + ent->v.view_ofs;
if (m_buyingFinished) {
m_enemy = ent;
m_enemyOrigin = ent->v.origin;
}
}
// good time too greet everyone
@ -3067,6 +3103,10 @@ void Bot::executeTasks () {
void Bot::checkSpawnConditions () {
// this function is called instead of ai when buying finished, but freezetime is not yet left.
if (!game.isNullEntity (m_enemy)) {
return;
}
// switch to knife if time to do this
if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + rg (5.0f, 7.5f) < game.time ()) {
if (rg (1, 100) < 2 && cv_spraypaints) {
@ -3203,7 +3243,7 @@ void Bot::logic () {
}
}
// if bomb planted warn teammates !
// if bomb planted warn players !
if (bots.hasBombSay (BombPlantedSay::Chatter) && bots.isBombPlanted () && m_team == Team::CT) {
pushChatterMessage (Chatter::GottaFindC4);
bots.clearBombSay (BombPlantedSay::Chatter);
@ -3881,7 +3921,7 @@ bool Bot::isOutOfBombTimer () {
}
bool hasTeammatesWithDefuserKit = false;
// check if our teammates has defusal kit
// check if our players has defusal kit
for (const auto &bot : bots) {
// search players with defuse kit
if (bot.get () != this && bot->m_team == Team::CT && bot->m_hasDefuser && bombOrigin.distanceSq (bot->pev->origin) < cr::sqrf (512.0f)) {
@ -4246,4 +4286,3 @@ void Bot::donateC4ToHuman () {
MDLL_Touch (bomb, recipient);
}
}

View file

@ -202,7 +202,7 @@ void Bot::prepareChatMessage (StringRef message) {
};
// get bot's victim
auto getMyVictim = [&] () -> String {;
auto getMyVictim = [&] () -> String {
return humanizedName (game.indexOfPlayer (m_lastVictim));
};

View file

@ -1167,8 +1167,7 @@ void Bot::fireWeapons () {
// is the bot carrying this weapon?
if (weapons & cr::bit (wid)) {
if (getAmmo (wid) >= tab[selectIndex].minPrimaryAmmo) {
if (getAmmo (wid) >= tab[selectIndex].minPrimaryAmmo && wid == m_currentWeapon) {
// available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
m_isReloading = true;
@ -1201,12 +1200,12 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
}
const auto weaponType = info[weaponIndex].type;
if (weaponType == WeaponType::Melee) {
if (weaponType == WeaponType::Melee || !(weaponType == WeaponType::Shotgun || weaponType == WeaponType::Sniper)) {
return false;
}
// check is ammo available for secondary weapon
if (m_ammoInClip[info[bestSecondaryCarried ()].id] >= 3) {
if (m_ammoInClip[info[getBestOwnedPistol ()].id] <= 0) {
return false;
}
@ -1270,6 +1269,11 @@ void Bot::focusEnemy () {
}
}
}
// fire anyway at close distance
if (distanceSq < cr::sqrf (90.0f)) {
m_wantsToFire = true;
}
}
}
@ -1650,8 +1654,27 @@ bool Bot::hasAnyWeapons () {
return !!(pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask));
}
bool Bot::hasAnyAmmoInClip () {
bool hasAmmo = false;
if (!hasAnyWeapons ()) {
return false;
}
const auto pri = getBestOwnedWeapon ();
const auto sec = getBestOwnedPistol ();
if (pri > 0 || sec > 0) {
const auto &info = conf.getWeapons ();
hasAmmo = pri > 0 && m_ammoInClip[info[pri].id] > 0 || sec > 0 && m_ammoInClip[info[sec].id] > 0;
}
return hasAmmo;
}
bool Bot::isKnifeMode () {
return cv_jasonmode || (usesKnife () && !hasAnyWeapons ()) || m_isCreature;
return cv_jasonmode ||
(usesKnife () && !hasAnyWeapons ())
|| m_isCreature
|| ((m_states & Sense::SeeingEnemy) && usesKnife () && !hasAnyAmmoInClip ());
}
bool Bot::isGrenadeWar () {
@ -1737,7 +1760,7 @@ void Bot::selectSecondary () {
pev->weapons = oldWeapons;
}
int Bot::bestWeaponCarried () {
int Bot::getBestOwnedWeapon () {
auto tab = conf.getRawWeapons ();
int weapons = pev->weapons;
@ -1756,6 +1779,29 @@ int Bot::bestWeaponCarried () {
return num;
}
int Bot::getBestOwnedPistol () {
auto tab = conf.getRawWeapons ();
int weapons = pev->weapons;
int num = 0;
int i = 0;
// loop through all the weapons until terminator is found...
while (tab->id) {
// is the bot carrying this weapon?
if (weapons & cr::bit (tab->id)) {
num = i;
}
++i;
++tab;
if (i > kPrimaryWeaponMinIndex - 1) {
break;
}
}
return num;
}
void Bot::decideFollowUser () {
// this function forces bot to follow user
static Array <edict_t *> users {};

View file

@ -1198,6 +1198,28 @@ void Game::ensureHealthyGameEnvironment () {
}
}
edict_t *Game::createFakeClient (StringRef name) {
auto ent = engfuncs.pfnCreateFakeClient (name.chars ());
if (game.isNullEntity (ent)) {
return nullptr;
}
auto netname = ent->v.netname;
ent->v = {}; // reset entire the entvars structure (fix from regamedll)
// restore containing entity, name and client flags
ent->v.pContainingEntity = ent;
ent->v.flags = FL_FAKECLIENT | FL_CLIENT;
ent->v.netname = netname;
if (ent->pvPrivateData != nullptr) {
engfuncs.pfnFreeEntPrivateData (ent);
}
ent->pvPrivateData = nullptr;
return ent;
}
void LightMeasure::initializeLightstyles () {
// this function initializes lighting information...

View file

@ -258,7 +258,7 @@ BotCreateResult BotManager::create (StringRef name, int difficulty, int personal
// buffer has been modified, copy to real name
resultName = cr::move (prefixed);
}
bot = engfuncs.pfnCreateFakeClient (resultName.chars ());
bot = game.createFakeClient (resultName);
if (game.isNullEntity (bot)) {
return BotCreateResult::MaxPlayersReached;
@ -622,8 +622,15 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int
else {
selection = 5;
}
const auto maxToAdd = maxClients - (getHumansCount () + getBotCount ());
constexpr char kTeams[6][12] = { "", {"Terrorists"}, {"CTs"}, "", "", {"Random"}, };
const auto toAdd = numToAdd == -1 ? maxClients - (getHumansCount () + getBotCount ()) : numToAdd;
auto toAdd = numToAdd == -1 ? maxToAdd : numToAdd;
// limit manually added count as well
if (toAdd > maxToAdd - 1) {
toAdd = maxToAdd - 1;
}
for (int i = 0; i <= toAdd; ++i) {
addbot ("", difficulty, personality, selection, -1, true);
@ -763,12 +770,23 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
return true;
}
static Array <Bot *> kickable;
kickable.clear ();
// worst case, just kick some random bot
for (const auto &bot : m_bots) {
// is this slot used?
if (belongsTeam (bot.get ())) {
kickable.push (bot.get ());
}
}
// kick random from collected
if (!kickable.empty ()) {
auto bot = kickable.random ();
if (bot) {
updateQuota ();
bot->kick ();
@ -1083,13 +1101,6 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
const int clientIndex = game.indexOfEntity (bot);
pev = &bot->v;
if (bot->pvPrivateData != nullptr) {
engfuncs.pfnFreeEntPrivateData (bot);
}
bot->pvPrivateData = nullptr;
bot->v.frags = 0;
// create the player entity by calling MOD's player function
bots.execGameEntity (bot);
@ -1191,9 +1202,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
m_baseFearLevel = rg (0.4f, 0.7f);
break;
}
plat.bzero (&m_ammoInClip, sizeof (m_ammoInClip));
plat.bzero (&m_ammo, sizeof (m_ammo));
clearAmmoInfo ();
m_currentWeapon = 0; // current weapon is not assigned at start
m_weaponType = WeaponType::None; // current weapon type is not assigned at start
@ -1227,6 +1236,12 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
newRound ();
}
void Bot::clearAmmoInfo () {
plat.bzero (&m_ammoInClip, sizeof (m_ammoInClip));
plat.bzero (&m_ammo, sizeof (m_ammo));
}
float Bot::getConnectionTime () {
const auto current = plat.seconds ();
@ -1565,8 +1580,7 @@ void Bot::newRound () {
// if bot died, clear all weapon stuff and force buying again
if (!m_isAlive) {
plat.bzero (&m_ammoInClip, sizeof (m_ammoInClip));
plat.bzero (&m_ammo, sizeof (m_ammo));
clearAmmoInfo ();
m_currentWeapon = 0;
m_weaponType = 0;

View file

@ -383,7 +383,7 @@ void BotSupport::updateClients () {
edict_t *player = game.playerOfIndex (i);
Client &client = m_clients[i];
if (!game.isNullEntity (player) && (player->v.flags & FL_CLIENT)) {
if (!game.isNullEntity (player) && (player->v.flags & FL_CLIENT) && !(player->v.flags & FL_DORMANT)) {
client.ent = player;
client.flags |= ClientFlags::Used;

View file

@ -946,7 +946,7 @@ void Bot::defuseBomb_ () {
|| timeToBlowUp < fullDefuseTime + 7.0f
|| ((getAmmoInClip () > 8 && m_reloadState == Reload::Primary) || (getAmmoInClip () > 5 && m_reloadState == Reload::Secondary))) {
const int weaponIndex = bestWeaponCarried ();
const int weaponIndex = getBestOwnedWeapon ();
// just select knife and then select weapon
selectWeaponById (Weapon::Knife);
@ -1530,7 +1530,7 @@ void Bot::pickupItem_ () {
}
else {
// primary weapon
int weaponIndex = bestWeaponCarried ();
int weaponIndex = getBestOwnedWeapon ();
const bool niceWeapon = rateGroundWeapon (m_pickupItem);
const auto tab = conf.getRawWeapons ();
@ -1564,7 +1564,7 @@ void Bot::pickupItem_ () {
// near to shield?
else if (itemDistanceSq < cr::sqrf (50.0f)) {
// get current best weapon to check if it's a primary in need to be dropped
int weaponIndex = bestWeaponCarried ();
int weaponIndex = getBestOwnedWeapon ();
if (weaponIndex > 6) {
selectWeaponByIndex (weaponIndex);