diff --git a/inc/engine.h b/inc/engine.h index f7a97da..ab9698a 100644 --- a/inc/engine.h +++ b/inc/engine.h @@ -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 diff --git a/inc/yapb.h b/inc/yapb.h index aeb4c67..ab13867 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -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 (); diff --git a/src/botlib.cpp b/src/botlib.cpp index 21911f5..e9e5b0c 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -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 ()) { @@ -2713,18 +2713,18 @@ void Bot::checkRadioQueue () { else { if (cv_radio_mode.as () == 2) { switch (numEnemiesNear (pev->origin, 384.0f)) { - case 1: - pushChatterMessage (Chatter::SpottedOneEnemy); - break; - case 2: - pushChatterMessage (Chatter::SpottedTwoEnemies); - break; - case 3: - pushChatterMessage (Chatter::SpottedThreeEnemies); - break; - default: - pushChatterMessage (Chatter::TooManyEnemies); - break; + case 1: + pushChatterMessage (Chatter::SpottedOneEnemy); + break; + case 2: + pushChatterMessage (Chatter::SpottedTwoEnemies); + break; + case 3: + pushChatterMessage (Chatter::SpottedThreeEnemies); + break; + default: + pushChatterMessage (Chatter::TooManyEnemies); + break; } } else if (cv_radio_mode.as () == 1) { @@ -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 teammates {}; - teammates.clear (); + static Array 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); } } - diff --git a/src/chatlib.cpp b/src/chatlib.cpp index 47d37be..d5753e3 100644 --- a/src/chatlib.cpp +++ b/src/chatlib.cpp @@ -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)); }; diff --git a/src/combat.cpp b/src/combat.cpp index a62e8a1..9f62423 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -401,18 +401,18 @@ bool Bot::lookupEnemies () { if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || m_hasHostage || !game.isNullEntity (m_targetEntity))) { if (cv_radio_mode.as () == 2) { switch (numEnemiesNear (pev->origin, 384.0f)) { - case 1: - pushChatterMessage (Chatter::SpottedOneEnemy); - break; - case 2: - pushChatterMessage (Chatter::SpottedTwoEnemies); - break; - case 3: - pushChatterMessage (Chatter::SpottedThreeEnemies); - break; - default: - pushChatterMessage (Chatter::TooManyEnemies); - break; + case 1: + pushChatterMessage (Chatter::SpottedOneEnemy); + break; + case 2: + pushChatterMessage (Chatter::SpottedTwoEnemies); + break; + case 3: + pushChatterMessage (Chatter::SpottedThreeEnemies); + break; + default: + pushChatterMessage (Chatter::TooManyEnemies); + break; } } else if (cv_radio_mode.as () == 1) { @@ -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 users {}; diff --git a/src/engine.cpp b/src/engine.cpp index 95b48f5..64b7afd 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -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... diff --git a/src/manager.cpp b/src/manager.cpp index 25cea3a..a1574b9 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -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 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; diff --git a/src/support.cpp b/src/support.cpp index 0bc0faf..e9022ae 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -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; diff --git a/src/tasks.cpp b/src/tasks.cpp index 30ce905..413a755 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -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);