diff --git a/inc/constant.h b/inc/constant.h index cdcdc7f..ebc8794 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -447,7 +447,7 @@ constexpr auto kSprayDistanceX2 = kSprayDistance * 2; constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kViewFrameUpdate = 1.0f / 25.0f; constexpr auto kGrenadeDamageRadius = 385.0f; -constexpr auto kMinMovedDistance = 3.0f; +constexpr auto kMinMovedDistance = 2.5f; constexpr auto kInfiniteDistanceLong = static_cast (kInfiniteDistance); constexpr auto kMaxWeapons = 32; diff --git a/inc/engine.h b/inc/engine.h index d7d8d2c..60d67ef 100644 --- a/inc/engine.h +++ b/inc/engine.h @@ -265,7 +265,7 @@ public: void searchEntities (const Vector &position, float radius, EntitySearch functor) const; // check if map has entity - bool hasEntityInGame (StringRef classname); + bool hasEntityInGame (StringRef classname) const; // print the version to server console on startup void printBotVersion () const; @@ -282,6 +282,38 @@ public: // is developer mode ? bool isDeveloperMode () const; + // entity utils +public: + // check if entity is alive + bool isAliveEntity (edict_t *ent) const; + + // checks if entity is fakeclient + bool isFakeClientEntity (edict_t *ent) const; + + // check if entity is a player + bool isPlayerEntity (edict_t *ent) const ; + + // check if entity is a monster + bool isMonsterEntity (edict_t *ent) const; + + // check if entity is a item + bool isItemEntity (edict_t *ent) const; + + // check if entity is a hostage entity + bool isHostageEntity (edict_t *ent) const; + + // check if entity is a door entity + bool isDoorEntity (edict_t *ent) const; + + // this function is checking that pointed by ent pointer obstacle, can be destroyed + bool isBreakableEntity (edict_t *ent, bool initialSeed = false) const; + + // checks if same model omitting the models directory + bool isEntityModelMatches (const edict_t *ent, StringRef model) const; + + // check if entity is a vip + bool isPlayerVIP (edict_t *ent) const; + // public inlines public: // get the current time on server @@ -343,7 +375,7 @@ public: } // get the wroldspawn entity - edict_t *getStartEntity () { + edict_t *getStartEntity () const { return m_startEntity; } @@ -353,7 +385,7 @@ public: } // gets the player team - int getTeam (edict_t *ent) const { + int getPlayerTeam (edict_t *ent) const { if (isNullEntity (ent)) { return Team::Unassigned; } @@ -361,7 +393,7 @@ public: } // gets the player team (real in ffa) - int getRealTeam (edict_t *ent) const { + int getRealPlayerTeam (edict_t *ent) const { if (isNullEntity (ent)) { return Team::Unassigned; } @@ -369,8 +401,8 @@ public: } // get real gamedll team (matches gamedll indices) - int getGameTeam (edict_t *ent) const { - return getRealTeam (ent) + 1; + int getPlayerTeamGame (edict_t *ent) const { + return getRealPlayerTeam (ent) + 1; } // sets the precache to uninitialized @@ -723,6 +755,98 @@ public: } }; +// offload bot manager class from things it shouldn't do +class GameState final : public Singleton { +private: + bool m_bombPlanted {}; // is bomb planted ? + bool m_roundOver {}; // well, round is over> + bool m_resetHud {}; // reset HUD is called for some one + + float m_timeBombPlanted {}; // time the bomb were planted + float m_timeRoundStart {}; // time round has started + float m_timeRoundEnd {}; // time round ended + float m_timeRoundMid {}; // middle point timestamp of a round + + Vector m_bombOrigin {}; // stored bomb origin + + Array m_activeGrenades {}; // holds currently active grenades on the map + Array m_interestingEntities {}; // holds currently interesting entities on the map + + IntervalTimer m_interestingEntitiesUpdateTime {}; // time to update interesting entities + IntervalTimer m_activeGrenadesUpdateTime {}; // time to update active grenades + +public: + GameState () = default; + ~GameState () = default; + +public: + const Vector &getBombOrigin () const { + return m_bombOrigin; + } + + bool isBombPlanted () const { + return m_bombPlanted; + } + + float getTimeBombPlanted () const { + return m_timeBombPlanted; + } + + float getRoundStartTime () const { + return m_timeRoundStart; + } + + float getRoundMidTime () const { + return m_timeRoundMid; + } + + float getRoundEndTime () const { + return m_timeRoundEnd; + } + + bool isRoundOver () const { + return m_roundOver; + } + + bool isResetHUD () const { + return m_resetHud; + } + + void setResetHUD (bool resetHud) { + m_resetHud = resetHud; + } + + void setRoundOver (bool roundOver) { + m_roundOver = roundOver; + } + + const Array &getActiveGrenades () { + return m_activeGrenades; + } + + const Array &getInterestingEntities () { + return m_interestingEntities; + } + + bool hasActiveGrenades () const { + return !m_activeGrenades.empty (); + } + + bool hasInterestingEntities () const { + return !m_interestingEntities.empty (); + } + +public: + float getBombTimeLeft () const; + + void setBombPlanted (bool isPlanted); + void setBombOrigin (bool reset = false, const Vector &pos = nullptr); + void roundStart (); + void updateActiveGrenade (); + void updateInterestingEntities (); +}; + // expose globals CR_EXPOSE_GLOBAL_SINGLETON (Game, game); +CR_EXPOSE_GLOBAL_SINGLETON (GameState, gameState); CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum); diff --git a/inc/graph.h b/inc/graph.h index 11f9e5e..d3e5518 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -173,7 +173,6 @@ private: Vector m_learnVelocity {}; Vector m_learnPosition {}; - Vector m_bombOrigin {}; Vector m_lastNode {}; IntArray m_terrorPoints {}; @@ -253,7 +252,6 @@ public: void clearVisited (); void eraseFromBucket (const Vector &pos, int index); - void setBombOrigin (bool reset = false, const Vector &pos = nullptr); void unassignPath (int from, int to); void convertFromPOD (Path &path, const PODPath &pod) const; void convertToPOD (const Path &path, PODPath &pod); @@ -292,10 +290,6 @@ public: m_editFlags &= ~flag; } - const Vector &getBombOrigin () const { - return m_bombOrigin; - } - // access paths Path &operator [] (int index) { return m_paths[index]; diff --git a/inc/manager.h b/inc/manager.h index c2748f6..579d006 100644 --- a/inc/manager.h +++ b/inc/manager.h @@ -24,32 +24,19 @@ public: using UniqueBot = UniquePtr ; private: - float m_timeRoundStart {}; // time round has started - float m_timeRoundEnd {}; // time round ended - float m_timeRoundMid {}; // middle point timestamp of a round - float m_difficultyBalanceTime {}; // time to balance difficulties ? float m_autoKillCheckTime {}; // time to kill all the bots ? float m_maintainTime {}; // time to maintain bot creation float m_quotaMaintainTime {}; // time to maintain bot quota - float m_grenadeUpdateTime {}; // time to update active grenades - float m_entityUpdateTime {}; // time to update interesting entities float m_plantSearchUpdateTime {}; // time to update for searching planted bomb float m_lastChatTime {}; // global chat time timestamp - float m_timeBombPlanted {}; // time the bomb were planted int m_lastWinner {}; // the team who won previous round int m_lastDifficulty {}; // last bots difficulty int m_bombSayStatus {}; // some bot is issued whine about bomb int m_numPreviousPlayers {}; // number of players in game im previous player check - bool m_bombPlanted {}; // is bomb planted ? bool m_botsCanPause {}; // bots can do a little pause ? - bool m_roundOver {}; // well, round is over> - bool m_resetHud {}; // reset HUD is called for some one - - Array m_activeGrenades {}; // holds currently active grenades on the map - Array m_interestingEntities {}; // holds currently interesting entities on the map Deque m_saveBotNames {}; // bots names that persist upon changelevel Deque m_addRequests {}; // bot creation tab @@ -81,10 +68,9 @@ public: int getAliveHumansCount (); int getPlayerPriority (edict_t *ent); - float getConnectTime (StringRef name, float original); + float getConnectionTimes (StringRef name, float original); float getAverageTeamKPD (bool calcForBots); - void setBombPlanted (bool isPlanted); void frame (); void createKillerEntity (); void destroyKillerEntity (); @@ -113,8 +99,6 @@ public: void reset (); void initFilters (); void resetFilters (); - void updateActiveGrenade (); - void updateInterestingEntities (); void captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent); void notifyBombDefuse (); void execGameEntity (edict_t *ent); @@ -130,27 +114,10 @@ public: bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); bool balancedKickRandom (bool decQuota); bool hasCustomCSDMSpawnEntities (); - bool isLineBlockedBySmoke (const Vector &from, const Vector &to); bool isFrameSkipDisabled (); public: - const Array &getActiveGrenades () { - return m_activeGrenades; - } - - const Array &getInterestingEntities () { - return m_interestingEntities; - } - - bool hasActiveGrenades () const { - return !m_activeGrenades.empty (); - } - - bool hasInterestingEntities () const { - return !m_interestingEntities.empty (); - } - - bool checkTeamEco (int team) const { + bool getTeamEconomics (int team) const { return m_teamData[team].positiveEco; } @@ -171,30 +138,6 @@ public: addbot ("", -1, -1, -1, -1, manual); } - bool isBombPlanted () const { - return m_bombPlanted; - } - - float getTimeBombPlanted () const { - return m_timeBombPlanted; - } - - float getRoundStartTime () const { - return m_timeRoundStart; - } - - float getRoundMidTime () const { - return m_timeRoundMid; - } - - float getRoundEndTime () const { - return m_timeRoundEnd; - } - - bool isRoundOver () const { - return m_roundOver; - } - bool canPause () const { return m_botsCanPause; } @@ -236,10 +179,6 @@ public: m_teamData[team].lastRadioSlot = radio; } - void setResetHUD (bool resetHud) { - m_resetHud = resetHud; - } - int getLastRadio (const int team) const { return m_teamData[team].lastRadioSlot; } diff --git a/inc/practice.h b/inc/practice.h index fff0bc1..aeb2f45 100644 --- a/inc/practice.h +++ b/inc/practice.h @@ -108,7 +108,7 @@ public: void setDamage (int32_t team, int32_t start, int32_t goal, int32_t value); // interlocked get damage - float plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage); + float getDamageEx (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage); public: void update (); diff --git a/inc/support.h b/inc/support.h index 965c754..0f6b792 100644 --- a/inc/support.h +++ b/inc/support.h @@ -31,36 +31,6 @@ public: // converts weapon id to alias name StringRef weaponIdToAlias (int32_t id); - // check if origin is visible from the entity side - bool isVisible (const Vector &origin, edict_t *ent); - - // check if entity is alive - bool isAlive (edict_t *ent); - - // checks if entity is fakeclient - bool isFakeClient (edict_t *ent); - - // check if entity is a player - bool isPlayer (edict_t *ent); - - // check if entity is a monster - bool isMonster (edict_t *ent); - - // check if entity is a item - bool isItem (edict_t *ent); - - // check if entity is a vip - bool isPlayerVIP (edict_t *ent); - - // check if entity is a hostage entity - bool isHostageEntity (edict_t *ent); - - // check if entity is a door entity - bool isDoorEntity (edict_t *ent); - - // this function is checking that pointed by ent pointer obstacle, can be destroyed - bool isBreakableEntity (edict_t *ent, bool initialSeed = false); - // nearest player search helper bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); @@ -70,8 +40,8 @@ public: // update stats on clients void updateClients (); - // checks if same model omitting the models directory - bool isModel (const edict_t *ent, StringRef model); + // check if origin is visible from the entity side + bool isVisible (const Vector &origin, edict_t *ent); // get the current date and time as string String getCurrentDateTime (); @@ -85,6 +55,9 @@ public: // set custom cvar descriptions void setCustomCvarDescriptions (); + // check if line of sight blocked by a smoke + bool isLineBlockedBySmoke (const Vector &from, const Vector &to); + public: // re-show welcome after changelevel ? diff --git a/inc/vistable.h b/inc/vistable.h index bcc3a84..9f5c288 100644 --- a/inc/vistable.h +++ b/inc/vistable.h @@ -39,7 +39,7 @@ public: ~GraphVistable () = default; public: - bool visible (int srcIndex, int destIndex, VisIndex vis = VisIndex::Any); + bool visible (int srcIndex, int destIndex, VisIndex vis = VisIndex::Any) const; void load (); void save () const; diff --git a/inc/yapb.h b/inc/yapb.h index 99aa05d..8a68bb7 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -400,7 +400,7 @@ private: int numEnemiesNear (const Vector &origin, const float radius) const; int numFriendsNear (const Vector &origin, const float radius) const; - float getBombTimeleft () const; + float getEstimatedNodeReachTime (); float isInFOV (const Vector &dest) const; float getShiftSpeed (); diff --git a/src/analyze.cpp b/src/analyze.cpp index 361eaf5..c366d7d 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -311,7 +311,7 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) { game.testHull (pos, { next.x, next.y, next.z + 19.0f }, TraceIgnore::Monsters, head_hull, nullptr, &tr); // we're can't reach next point - if (!cr::fequal (tr.flFraction, 1.0f) && !util.isBreakableEntity (tr.pHit)) { + if (!cr::fequal (tr.flFraction, 1.0f) && !game.isBreakableEntity (tr.pHit)) { return; } diff --git a/src/botlib.cpp b/src/botlib.cpp index 6010c52..1a36839 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -37,7 +37,7 @@ ConVar cv_pickup_custom_items ("pickup_custom_items", "0", "Allows or disallows ConVar cv_pickup_ammo_and_kits ("pickup_ammo_and_kits", "0", "Allows bots to pick up mod items like ammo, health kits, and suits."); ConVar cv_pickup_best ("pickup_best", "1", "Allows or disallows bots to pick up the best weapons."); ConVar cv_ignore_objectives ("ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and save hostages."); -ConVar cv_smoke_grenade_checks ("smoke_grenade_checks", "2", "Affects the bot's vision by smoke clouds.", true, 0.0f, 2.0f); +ConVar cv_smoke_grenade_checks ("smoke_grenade_checks", "1", "Affects the bot's vision by smoke clouds.", true, 0.0f, 2.0f); // game console variables ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef); @@ -78,10 +78,10 @@ void Bot::avoidGrenades () { m_needAvoidGrenade = 0; } - if (!bots.hasActiveGrenades ()) { + if (!gameState.hasActiveGrenades ()) { return; } - const auto &activeGrenades = bots.getActiveGrenades (); + const auto &activeGrenades = gameState.getActiveGrenades (); // find all grenades on the map for (const auto &pent : activeGrenades) { @@ -105,7 +105,7 @@ void Bot::avoidGrenades () { } } else if (game.isNullEntity (m_avoidGrenade) && model == kExplosiveModelName) { - if (game.getTeam (pent->v.owner) == m_team || pent->v.owner == ent ()) { + if (game.getPlayerTeam (pent->v.owner) == m_team || pent->v.owner == ent ()) { continue; } @@ -209,7 +209,7 @@ void Bot::checkBreakablesAround () { continue; } - if (!util.isBreakableEntity (breakable)) { + if (!game.isBreakableEntity (breakable)) { continue; } @@ -257,7 +257,7 @@ edict_t *Bot::lookupBreakable () { // this function checks if bot is blocked by a shoot able breakable in his moving direction // we're got something already - if (util.isBreakableEntity (m_breakableEntity)) { + if (game.isBreakableEntity (m_breakableEntity)) { return m_breakableEntity; } const float detectBreakableDistance = (usesKnife () || isOnLadder ()) ? 32.0f : rg (72.0f, 256.0f); @@ -270,7 +270,7 @@ edict_t *Bot::lookupBreakable () { auto hit = tr.pHit; // check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap! - if (util.isBreakableEntity (hit)) { + if (game.isBreakableEntity (hit)) { m_breakableOrigin = game.getEntityOrigin (hit); m_breakableEntity = hit; @@ -286,7 +286,7 @@ edict_t *Bot::lookupBreakable () { } // check breakable team, needed for some plugins - if (ent->v.team > 0 && ent->v.team != game.getGameTeam (this->ent ())) { + if (ent->v.team > 0 && ent->v.team != game.getPlayerTeamGame (this->ent ())) { return false; } @@ -362,7 +362,7 @@ void Bot::updatePickups () { } // no interesting entities, how ? - else if (!bots.hasInterestingEntities ()) { + else if (!gameState.hasInterestingEntities ()) { return true; } return false; @@ -376,7 +376,7 @@ void Bot::updatePickups () { return; } - const auto &interesting = bots.getInterestingEntities (); + const auto &interesting = gameState.getInterestingEntities (); const float radiusSq = cr::sqrf (cv_object_pickup_radius.as ()); if (!game.isNullEntity (m_pickupItem)) { @@ -446,7 +446,7 @@ void Bot::updatePickups () { const bool isHostageRescueMap = game.mapIs (MapFlags::HostageRescue); const bool isCSDM = game.is (GameFlags::CSDM); - if (isHostageRescueMap && util.isHostageEntity (ent)) { + if (isHostageRescueMap && game.isHostageEntity (ent)) { allowPickup = true; pickupType = Pickup::Hostage; } @@ -551,7 +551,7 @@ void Bot::updatePickups () { allowPickup = true; pickupType = Pickup::PlantedC4; } - else if (cv_pickup_custom_items && util.isItem (ent) && !classname.startsWith ("item_thighpack")) { + else if (cv_pickup_custom_items && game.isItemEntity (ent) && !classname.startsWith ("item_thighpack")) { allowPickup = true; pickupType = Pickup::Items; } @@ -632,7 +632,7 @@ void Bot::updatePickups () { const auto &path = graph[index]; const float bombTimer = mp_c4timer.as (); - const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + const float timeMidBlowup = gameState.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); if (timeMidBlowup > game.time ()) { clearTask (Task::MoveToPosition); // remove any move tasks @@ -683,7 +683,7 @@ void Bot::updatePickups () { } } else if (pickupType == Pickup::PlantedC4) { - if (util.isAlive (m_enemy)) { + if (game.isAliveEntity (m_enemy)) { return; } @@ -706,7 +706,7 @@ void Bot::updatePickups () { const int index = findDefendNode (origin); const auto &path = graph[index]; - const float timeToExplode = bots.getTimeBombPlanted () + mp_c4timer.as () - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + const float timeToExplode = gameState.getTimeBombPlanted () + mp_c4timer.as () - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); clearTask (Task::MoveToPosition); // remove any move tasks @@ -1294,7 +1294,7 @@ void Bot::buyStuff () { const auto tab = conf.getRawWeapons (); const bool isPistolMode = tab[25].teamStandard == -1 && tab[3].teamStandard == 2; - const bool teamHasGoodEconomics = bots.checkTeamEco (m_team); + const bool teamHasGoodEconomics = bots.getTeamEconomics (m_team); // do this, because xash engine is not capable to run all the features goldsrc, but we have cs 1.6 on it, so buy table must be the same const bool isOldGame = game.is (GameFlags::Legacy); @@ -1696,7 +1696,7 @@ void Bot::overrideConditions () { // check if we need to escape from bomb if ((tid == Task::Normal || tid == Task::MoveToPosition) && game.mapIs (MapFlags::Demolition) - && bots.isBombPlanted () + && gameState.isBombPlanted () && m_isAlive && tid != Task::EscapeFromBomb && tid != Task::Camp @@ -1710,7 +1710,7 @@ void Bot::overrideConditions () { float reachEnemyKnifeDistanceSq = cr::sqrf (128.0f); // special handling, if we have a knife in our hands - if (isKnifeMode () && (util.isPlayer (m_enemy) || (cv_attack_monsters && util.isMonster (m_enemy)))) { + if (isKnifeMode () && (game.isPlayerEntity (m_enemy) || (cv_attack_monsters && game.isMonsterEntity (m_enemy)))) { const auto distanceSq2d = pev->origin.distanceSq2d (m_enemy->v.origin); const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin); @@ -1760,7 +1760,7 @@ void Bot::overrideConditions () { } // special handling for reloading - if (!bots.isRoundOver () + if (!gameState.isRoundOver () && tid == Task::Normal && m_reloadState != Reload::None && m_isReloading @@ -1787,7 +1787,7 @@ void Bot::overrideConditions () { if (game.is (GameFlags::ZombieMod) && !m_isCreature && m_infectedEnemyTeam - && util.isAlive (m_enemy) + && game.isAliveEntity (m_enemy) && m_retreatTime < game.time () && pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (512.0f)) { @@ -1811,7 +1811,7 @@ void Bot::syncUpdatePredictedIndex () { const auto &lastEnemyOrigin = m_lastEnemyOrigin; const auto currentNodeIndex = m_currentNodeIndex; - if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { + if (lastEnemyOrigin.empty () || !vistab.isReady () || !game.isAliveEntity (m_lastEnemy)) { wipePredict (); return; } @@ -1849,7 +1849,7 @@ void Bot::syncUpdatePredictedIndex () { } void Bot::updatePredictedIndex () { - if (!m_isAlive || m_lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { + if (!m_isAlive || m_lastEnemyOrigin.empty () || !vistab.isReady () || !game.isAliveEntity (m_lastEnemy)) { return; // do not run task if no last enemy } @@ -1904,7 +1904,7 @@ void Bot::setConditions () { // did bot just kill an enemy? if (!game.isNullEntity (m_lastVictim)) { - if (game.getTeam (m_lastVictim) != m_team) { + if (game.getPlayerTeam (m_lastVictim) != m_team) { // add some aggression because we just killed somebody m_agressionLevel += 0.1f; @@ -1969,7 +1969,7 @@ void Bot::setConditions () { } // if no more enemies found AND bomb planted, switch to knife to get to bomb place faster - if (m_team == Team::CT && !usesKnife () && m_numEnemiesLeft == 0 && bots.isBombPlanted ()) { + if (m_team == Team::CT && !usesKnife () && m_numEnemiesLeft == 0 && gameState.isBombPlanted ()) { selectWeaponById (Weapon::Knife); m_plantedBombNodeIndex = getNearestToPlantedBomb (); @@ -1990,7 +1990,7 @@ void Bot::setConditions () { // check if our current enemy is still valid if (!game.isNullEntity (m_lastEnemy)) { - if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { + if (!game.isAliveEntity (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { m_lastEnemy = nullptr; } } @@ -2089,7 +2089,7 @@ void Bot::filterTasks () { float &blindedDesire = filter[Task::Blind].desire; // calculate desires to seek cover or hunt - if (util.isPlayer (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { + if (game.isPlayerEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { const float retreatLevel = (100.0f - (m_healthValue > 70.0f ? 100.0f : m_healthValue)) * tempFear; // retreat level depends on bot health if (m_isCreature || @@ -2117,7 +2117,7 @@ void Bot::filterTasks () { if (m_isCreature) { ratio = 0.0f; } - if (bots.isBombPlanted () || m_isStuck || usesKnife ()) { + if (gameState.isBombPlanted () || m_isStuck || usesKnife ()) { ratio /= 3.0f; // reduce the seek cover desire if bomb is planted } else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) { @@ -2139,7 +2139,7 @@ void Bot::filterTasks () { if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && !m_isVIP - && bots.getRoundMidTime () < game.time () + && gameState.getRoundMidTime () < game.time () && !m_hasHostage && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) @@ -2256,10 +2256,11 @@ void Bot::startTask (Task id, float desire, int data, float time, bool resume) { } return; } + else { + clearSearchNodes (); + } } m_tasks.emplace (filter[id].func, id, desire, data, time, resume); - - clearSearchNodes (); ignoreCollision (); const auto tid = getCurrentTaskId (); @@ -2438,7 +2439,7 @@ void Bot::handleChatterTaskChange (Task tid) { } if (rg.chance (25) && tid == Task::Camp) { - if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) { + if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted ()) { pushChatterMessage (Chatter::GuardingPlantedC4); } else { @@ -2471,12 +2472,12 @@ void Bot::executeChatterFrameEvents () { if (!hasFriendNearby && rg.chance (45) && (m_enemy->v.weapons & cr::bit (Weapon::C4))) { pushChatterMessage (Chatter::SpotTheBomber); } - else if (!hasFriendNearby && rg.chance (45) && m_team == Team::Terrorist && util.isPlayerVIP (m_enemy)) { + else if (!hasFriendNearby && rg.chance (45) && m_team == Team::Terrorist && game.isPlayerVIP (m_enemy)) { pushChatterMessage (Chatter::VIPSpotted); } else if (!hasFriendNearby && rg.chance (50) - && game.getTeam (m_enemy) != m_team + && game.getPlayerTeam (m_enemy) != m_team && isGroupOfEnemies (m_enemy->v.origin)) { pushChatterMessage (Chatter::ScaredEmotion); @@ -2495,7 +2496,7 @@ void Bot::executeChatterFrameEvents () { } // if bomb planted warn players ! - if (bots.hasBombSay (BombPlantedSay::Chatter) && bots.isBombPlanted () && m_team == Team::CT) { + if (bots.hasBombSay (BombPlantedSay::Chatter) && gameState.isBombPlanted () && m_team == Team::CT) { pushChatterMessage (Chatter::GottaFindC4); bots.clearBombSay (BombPlantedSay::Chatter); } @@ -2706,7 +2707,7 @@ void Bot::checkRadioQueue () { break; case Radio::ShesGonnaBlow: - if (game.isNullEntity (m_enemy) && distanceSq < cr::sqrf (2048.0f) && bots.isBombPlanted () && m_team == Team::Terrorist) { + if (game.isNullEntity (m_enemy) && distanceSq < cr::sqrf (2048.0f) && gameState.isBombPlanted () && m_team == Team::Terrorist) { pushRadioMessage (Radio::RogerThat); if (getCurrentTaskId () == Task::Camp) { @@ -2722,11 +2723,11 @@ void Bot::checkRadioQueue () { case Radio::RegroupTeam: // if no more enemies found AND bomb planted, switch to knife to get to bombplace faster - if (m_team == Team::CT && !usesKnife () && m_numEnemiesLeft == 0 && bots.isBombPlanted () && getCurrentTaskId () != Task::DefuseBomb) { + if (m_team == Team::CT && !usesKnife () && m_numEnemiesLeft == 0 && gameState.isBombPlanted () && getCurrentTaskId () != Task::DefuseBomb) { selectWeaponById (Weapon::Knife); clearSearchNodes (); - m_position = graph.getBombOrigin (); + m_position = gameState.getBombOrigin (); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); pushRadioMessage (Radio::RogerThat); @@ -2850,7 +2851,7 @@ void Bot::checkRadioQueue () { case Task::Camp: if (rg.chance (m_radioPercent)) { - if (bots.isBombPlanted () && m_team == Team::Terrorist) { + if (gameState.isBombPlanted () && m_team == Team::Terrorist) { pushChatterMessage (Chatter::GuardingPlantedC4); } else if (m_inEscapeZone && m_team == Team::CT) { @@ -2915,14 +2916,14 @@ void Bot::checkRadioQueue () { case Radio::SectorClear: // is bomb planted and it's a ct - if (!bots.isBombPlanted ()) { + if (!gameState.isBombPlanted ()) { break; } // check if it's a ct command - if (game.getTeam (m_radioEntity) == Team::CT + if (game.getPlayerTeam (m_radioEntity) == Team::CT && m_team == Team::CT - && util.isFakeClient (m_radioEntity) + && game.isFakeClientEntity (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.time ()) { float nearestDistanceSq = kInfiniteDistance; @@ -3015,11 +3016,11 @@ void Bot::checkRadioQueue () { void Bot::tryHeadTowardRadioMessage () { const auto tid = getCurrentTaskId (); - if (tid == Task::MoveToPosition || m_headedTime + 15.0f < game.time () || !util.isAlive (m_radioEntity) || m_hasC4) { + if (tid == Task::MoveToPosition || m_headedTime + 15.0f < game.time () || !game.isAliveEntity (m_radioEntity) || m_hasC4) { return; } - if ((util.isFakeClient (m_radioEntity) + if ((game.isFakeClientEntity (m_radioEntity) && rg.chance (m_radioPercent) && m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { @@ -3064,8 +3065,8 @@ void Bot::frame () { return; } - if (bots.isBombPlanted () && m_team == Team::CT && m_isAlive) { - const auto &bombPosition = graph.getBombOrigin (); + if (gameState.isBombPlanted () && m_team == Team::CT && m_isAlive) { + const auto &bombPosition = gameState.getBombOrigin (); if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb @@ -3101,8 +3102,8 @@ void Bot::update () { const auto tid = getCurrentTaskId (); m_canChooseAimDirection = true; - m_isAlive = util.isAlive (ent ()); - m_team = game.getTeam (ent ()); + m_isAlive = game.isAliveEntity (ent ()); + m_team = game.getPlayerTeam (ent ()); m_healthValue = cr::clamp (pev->health, 0.0f, 99999.9f); if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { @@ -3148,7 +3149,7 @@ void Bot::update () { m_lastVoteKick = m_voteKickIndex; // if bot tk punishment is enabled slay the tk - if (cv_tkpunish.as () != 2 || util.isFakeClient (game.entityOfIndex (m_voteKickIndex))) { + if (cv_tkpunish.as () != 2 || game.isFakeClientEntity (game.entityOfIndex (m_voteKickIndex))) { return; } auto killer = game.entityOfIndex (m_lastVoteKick); @@ -3337,7 +3338,7 @@ void Bot::checkSpawnConditions () { void Bot::logic () { // this function gets called each frame and is the core of all bot ai. from here all other subroutines are called - m_movedDistance = kMinMovedDistance; // length of different vector (distance bot moved) + m_movedDistance = kMinMovedDistance + 0.1f; // length of different vector (distance bot moved) resetMovement (); @@ -3369,7 +3370,7 @@ void Bot::logic () { // save current position as previous m_prevOrigin = pev->origin; - m_prevTime = game.time () + (0.2f - m_frameInterval * 2.0f); + m_prevTime = game.time () + (0.15f - m_frameInterval * 2.0f); } // if there's some radio message to respond, check it @@ -3607,7 +3608,7 @@ void Bot::showDebugOverlay () { static hudtextparms_t textParams {}; - textParams.channel = 1; + textParams.channel = 4; textParams.x = -1.0f; textParams.y = 0.0f; textParams.effect = 0; @@ -3669,7 +3670,7 @@ void Bot::takeDamage (edict_t *inflictor, int damage, int armor, int bits) { m_lastDamageType = bits; if (m_isCreature) { - if (util.isPlayer (inflictor) && game.isNullEntity (m_enemy)) { + if (game.isPlayerEntity (inflictor) && game.isNullEntity (m_enemy)) { if (seesEnemy (inflictor)) { m_enemy = inflictor; m_enemyOrigin = inflictor->v.origin; @@ -3683,10 +3684,10 @@ void Bot::takeDamage (edict_t *inflictor, int damage, int armor, int bits) { } m_lastDamageTimestamp = game.time (); - if (util.isPlayer (inflictor) || (cv_attack_monsters && util.isMonster (inflictor))) { - const auto inflictorTeam = game.getTeam (inflictor); + if (game.isPlayerEntity (inflictor) || (cv_attack_monsters && game.isMonsterEntity (inflictor))) { + const auto inflictorTeam = game.getPlayerTeam (inflictor); - if (!util.isMonster (inflictor) && cv_tkpunish && inflictorTeam == m_team && !util.isFakeClient (inflictor)) { + if (!game.isMonsterEntity (inflictor) && cv_tkpunish && inflictorTeam == m_team && !game.isFakeClientEntity (inflictor)) { // alright, die you team killer!!! m_actualReactionTime = 0.0f; m_seeEnemyTime = game.time (); @@ -3750,7 +3751,7 @@ void Bot::takeBlind (int alpha) { m_viewDistance = rg (10.0f, 20.0f); // do not take in effect some unique map effects on round start - if (bots.getRoundStartTime () + 5.0f < game.time ()) { + if (gameState.getRoundStartTime () + 5.0f < game.time ()) { m_viewDistance = m_maxViewDistance; } m_blindTime = game.time () + static_cast (alpha - 200) / 16.0f; @@ -3812,11 +3813,11 @@ void Bot::updatePracticeValue (int damage) const { void Bot::updatePracticeDamage (edict_t *attacker, int damage) { // this function gets called each time a bot gets damaged by some enemy. stores the damage (team-specific) done by victim. - if (!util.isPlayer (attacker)) { + if (!game.isPlayerEntity (attacker)) { return; } - const int attackerTeam = game.getTeam (attacker); + const int attackerTeam = game.getPlayerTeam (attacker); const int victimTeam = m_team; if (attackerTeam == victimTeam) { @@ -3847,7 +3848,7 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) { practice.setDamage (victimIndex, victimIndex, victimIndex, cr::clamp (practice.getDamage (victimTeam, victimIndex, victimIndex), 0, kMaxDamageValue)); } } - const auto updateDamage = util.isFakeClient (attacker) ? 10 : 7; + const auto updateDamage = game.isFakeClientEntity (attacker) ? 10 : 7; // store away the damage done const auto damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, kMaxDamageValue); @@ -3871,7 +3872,7 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { // this function, asks bot to discard his current primary weapon (or c4) to the user that requested it with /drop* // command, very useful, when i'm don't have money to buy anything... ) - if (util.isAlive (user) && m_moneyAmount >= 2000 && hasPrimaryWeapon () && user->v.origin.distanceSq (pev->origin) <= cr::sqrf (450.0f)) { + if (game.isAliveEntity (user) && m_moneyAmount >= 2000 && hasPrimaryWeapon () && user->v.origin.distanceSq (pev->origin) <= cr::sqrf (450.0f)) { m_aimFlags |= AimFlags::Entity; m_lookAt = user->v.origin; @@ -3962,16 +3963,16 @@ void Bot::debugMsgInternal (StringRef str) { Vector Bot::isBombAudible () { // this function checks if bomb is can be heard by the bot, calculations done by manual testing. - if (!bots.isBombPlanted () || getCurrentTaskId () == Task::EscapeFromBomb) { + if (!gameState.isBombPlanted () || getCurrentTaskId () == Task::EscapeFromBomb) { return nullptr; // reliability check } if (m_difficulty > Difficulty::Hard) { - return graph.getBombOrigin (); + return gameState.getBombOrigin (); } - const auto &bombOrigin = graph.getBombOrigin (); + const auto &bombOrigin = gameState.getBombOrigin (); - const float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.as ()) * 100.0f; + const float timeElapsed = ((game.time () - gameState.getTimeBombPlanted ()) / mp_c4timer.as ()) * 100.0f; float desiredRadius = 768.0f; // start the manual calculations @@ -4052,12 +4053,7 @@ void Bot::runMovement () { m_oldButtons = pev->button; } -float Bot::getBombTimeleft () const { - if (!bots.isBombPlanted ()) { - return 0.0f; - } - return cr::max (bots.getTimeBombPlanted () + mp_c4timer.as () - game.time (), 0.0f); -} + bool Bot::isOutOfBombTimer () { if (!game.mapIs (MapFlags::Demolition)) { @@ -4069,13 +4065,13 @@ bool Bot::isOutOfBombTimer () { } // calculate left time - const float timeLeft = getBombTimeleft (); + const float timeLeft = gameState.getBombTimeLeft (); // if time left greater than 13, no need to do other checks if (timeLeft > 13.0f) { return false; } - const auto &bombOrigin = graph.getBombOrigin (); + const auto &bombOrigin = gameState.getBombOrigin (); // for terrorist, if timer is lower than 13 seconds, return true if (timeLeft < 13.0f && m_team == Team::Terrorist && bombOrigin.distanceSq (pev->origin) < cr::sqrf (964.0f)) { @@ -4100,7 +4096,7 @@ bool Bot::isOutOfBombTimer () { return true; } - if (m_hasProgressBar && isOnFloor () && ((m_hasDefuser ? 10.0f : 15.0f) > getBombTimeleft ())) { + if (m_hasProgressBar && isOnFloor () && ((m_hasDefuser ? 10.0f : 15.0f) > gameState.getBombTimeLeft ())) { return true; } return false; // return false otherwise @@ -4114,7 +4110,7 @@ void Bot::updateHearing () { float nearestDistanceSq = kInfiniteDistance; // do not hear to other enemies if just tracked old one - if (m_timeNextTracking < game.time () && m_lastEnemy == m_trackingEdict && util.isAlive (m_lastEnemy)) { + if (m_timeNextTracking < game.time () && m_lastEnemy == m_trackingEdict && game.isAliveEntity (m_lastEnemy)) { m_hearedEnemy = m_lastEnemy; m_lastEnemyOrigin = m_lastEnemy->v.origin; @@ -4153,7 +4149,7 @@ void Bot::updateHearing () { } // did the bot hear someone ? - if (util.isPlayer (m_hearedEnemy)) { + if (game.isPlayerEntity (m_hearedEnemy)) { // change to best weapon if heard something if (m_shootTime < game.time () - 5.0f && isOnFloor () @@ -4257,8 +4253,8 @@ void Bot::enteredBuyZone (int buyState) { if (m_seeEnemyTime + 12.0f < game.time () && m_lastEquipTime + 30.0f < game.time () && m_inBuyZone - && (bots.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as () < game.time ()) - && !bots.isBombPlanted () + && (gameState.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as () < game.time ()) + && !gameState.isBombPlanted () && m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) { m_ignoreBuyDelay = true; @@ -4297,7 +4293,7 @@ void Bot::selectCampButtons (int index) { bool Bot::isBombDefusing (const Vector &bombOrigin) const { // this function finds if somebody currently defusing the bomb. - if (!bots.isBombPlanted ()) { + if (!gameState.isBombPlanted ()) { return false; } bool defusingInProgress = false; @@ -4370,8 +4366,8 @@ void Bot::refreshCreatureStatus (char *infobuffer) { } // if bot is on infected team, and zombie mode is active, assume bot is a creature/zombie - m_isOnInfectedTeam = game.getRealTeam (ent ()) == infectedTeam; - m_infectedEnemyTeam = game.getRealTeam (m_enemy) == infectedTeam; + m_isOnInfectedTeam = game.getRealPlayerTeam (ent ()) == infectedTeam; + m_infectedEnemyTeam = game.getRealPlayerTeam (m_enemy) == infectedTeam; // do not process next if already infected if (m_isOnInfectedTeam || m_infectedEnemyTeam) { @@ -4451,7 +4447,7 @@ void Bot::donateC4ToHuman () { // search world for just dropped bomb game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { - if (util.isModel (ent, "backpack.mdl")) { + if (game.isEntityModelMatches (ent, "backpack.mdl")) { bomb = ent; if (!game.isNullEntity (bomb)) { diff --git a/src/chatlib.cpp b/src/chatlib.cpp index 83e78ba..388bda2 100644 --- a/src/chatlib.cpp +++ b/src/chatlib.cpp @@ -161,7 +161,7 @@ void Bot::prepareChatMessage (StringRef message) { auto humanizedName = [] (int index) -> String { auto ent = game.playerOfIndex (index); - if (!util.isPlayer (ent)) { + if (!game.isPlayerEntity (ent)) { return "unknown"; } String playerName = ent->v.netname.chars (); @@ -193,7 +193,7 @@ void Bot::prepareChatMessage (StringRef message) { // get roundtime auto getRoundTime = [] () -> String { - auto roundTimeSecs = static_cast (bots.getRoundEndTime () - game.time ()); + const auto roundTimeSecs = static_cast (gameState.getRoundEndTime () - game.time ()); String roundTime {}; roundTime.assignf ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59)); @@ -241,8 +241,8 @@ void Bot::prepareChatMessage (StringRef message) { return humanizedName (playerIndex); } else if (!needsEnemy && m_team == client.team) { - if (util.isPlayer (pev->dmg_inflictor) - && game.getRealTeam (pev->dmg_inflictor) == m_team) { + if (game.isPlayerEntity (pev->dmg_inflictor) + && game.getRealPlayerTeam (pev->dmg_inflictor) == m_team) { return humanizedName (game.indexOfPlayer (pev->dmg_inflictor)); } diff --git a/src/combat.cpp b/src/combat.cpp index fa355d4..64d4b38 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -335,7 +335,7 @@ bool Bot::checkBodyPartsWithHitboxes (edict_t *target) { bool Bot::seesEnemy (edict_t *player) { auto isBehindSmokeClouds = [&] (const Vector &pos) { if (cv_smoke_grenade_checks.as () == 2) { - return bots.isLineBlockedBySmoke (getEyesPos (), pos); + return util.isLineBlockedBySmoke (getEyesPos (), pos); } return false; }; @@ -345,7 +345,7 @@ bool Bot::seesEnemy (edict_t *player) { } bool ignoreFieldOfView = false; - if (cv_whose_your_daddy && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) { + if (cv_whose_your_daddy && game.isPlayerEntity (pev->dmg_inflictor) && game.getPlayerTeam (pev->dmg_inflictor) != m_team) { ignoreFieldOfView = true; } @@ -387,7 +387,7 @@ bool Bot::lookupEnemies () { if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) { m_states &= ~Sense::SuspectEnemy; } - else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f > game.time () && util.isAlive (m_lastEnemy)) { + else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f > game.time () && game.isAliveEntity (m_lastEnemy)) { m_states |= Sense::SuspectEnemy; const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f @@ -406,7 +406,7 @@ bool Bot::lookupEnemies () { // is player is alive if (m_enemyUpdateTime > game.time () && player->v.origin.distanceSq (pev->origin) < nearestDistanceSq - && util.isAlive (player) + && game.isAliveEntity (player) && seesEnemy (player)) { newEnemy = player; @@ -427,8 +427,8 @@ bool Bot::lookupEnemies () { if (cv_attack_monsters) { // search the world for monsters... - for (const auto &interesting : bots.getInterestingEntities ()) { - if (!util.isMonster (interesting)) { + for (const auto &interesting : gameState.getInterestingEntities ()) { + if (!game.isMonsterEntity (interesting)) { continue; } @@ -485,7 +485,7 @@ bool Bot::lookupEnemies () { newEnemy = player; // aim VIP first on AS maps... - if (game.is (MapFlags::Assassination) && util.isPlayerVIP (newEnemy)) { + if (game.is (MapFlags::Assassination) && game.isPlayerVIP (newEnemy)) { break; } } @@ -498,7 +498,7 @@ bool Bot::lookupEnemies () { } } - if (newEnemy != nullptr && (util.isPlayer (newEnemy) || (cv_attack_monsters && util.isMonster (newEnemy)))) { + if (newEnemy != nullptr && (game.isPlayerEntity (newEnemy) || (cv_attack_monsters && game.isMonsterEntity (newEnemy)))) { bots.setCanPause (true); m_aimFlags |= AimFlags::Enemy; @@ -587,7 +587,7 @@ bool Bot::lookupEnemies () { newEnemy = m_enemy; m_lastEnemy = newEnemy; - if (!util.isAlive (newEnemy)) { + if (!game.isAliveEntity (newEnemy)) { m_enemy = nullptr; m_enemyBodyPartSet = nullptr; @@ -638,7 +638,7 @@ bool Bot::lookupEnemies () { && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb - && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { + && getCurrentTaskId () != Task::DefuseBomb) || gameState.isRoundOver ()) { if (!m_reloadState) { m_reloadState = Reload::Primary; @@ -719,7 +719,7 @@ Vector Bot::getEnemyBodyOffset () { if (!m_enemyParts && (m_states & Sense::SuspectEnemy)) { spot += getBodyOffsetError (distance); } - else if (util.isPlayer (m_enemy)) { + else if (game.isPlayerEntity (m_enemy)) { // now take in account different parts of enemy body if (m_enemyParts & (Visibility::Head | Visibility::Body)) { auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; @@ -828,11 +828,11 @@ bool Bot::isFriendInLineOfFire (float distance) const { game.testLine (getEyesPos (), getEyesPos () + pev->v_angle.normalize_apx () * distance, TraceIgnore::None, ent (), &tr); // check if we hit something - if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { + if (game.isPlayerEntity (tr.pHit) && tr.pHit != ent ()) { auto hit = tr.pHit; // check valid range - if (game.getTeam (hit) == m_team && util.isAlive (hit)) { + if (game.getPlayerTeam (hit) == m_team && game.isAliveEntity (hit)) { return true; } } @@ -1459,7 +1459,7 @@ void Bot::attackMovement () { if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) { if ((m_states & Sense::SeeingEnemy) && approach < 30 - && !bots.isBombPlanted () + && !gameState.isBombPlanted () && (isEnemyCone || m_isVIP || m_isReloading)) { if (m_retreatTime < game.time ()) { @@ -1973,7 +1973,7 @@ void Bot::decideFollowUser () { continue; } - if (seesEntity (client.origin) && !util.isFakeClient (client.ent)) { + if (seesEntity (client.origin) && !game.isFakeClientEntity (client.ent)) { users.push (client.ent); } } @@ -2248,7 +2248,7 @@ edict_t *Bot::setCorrectGrenadeVelocity (StringRef model) { edict_t *result = nullptr; game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { - if (ent->v.owner == this->ent () && util.isModel (ent, model)) { + if (ent->v.owner == this->ent () && game.isEntityModelMatches (ent, model)) { result = ent; // set the correct velocity for the grenade @@ -2286,7 +2286,7 @@ void Bot::checkGrenadesThrow () { || cv_ignore_enemies || m_isUsingGrenade || m_isReloading - || (isKnifeMode () && !bots.isBombPlanted ()) + || (isKnifeMode () && !gameState.isBombPlanted ()) || m_grenadeCheckTime >= game.time () || m_lastEnemyOrigin.empty ()); @@ -2300,7 +2300,7 @@ void Bot::checkGrenadesThrow () { const auto senseCondition = isGrenadeMode ? false : !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy)); - if (!util.isAlive (m_lastEnemy) || senseCondition) { + if (!game.isAliveEntity (m_lastEnemy) || senseCondition) { clearThrowStates (m_states); return; } @@ -2343,7 +2343,7 @@ void Bot::checkGrenadesThrow () { // special condition if we're have valid current enemy if (!isGrenadeMode && ((m_states & Sense::SeeingEnemy) - && util.isAlive (m_enemy) + && game.isAliveEntity (m_enemy) && ((m_enemy->v.button | m_enemy->v.oldbuttons) & IN_ATTACK) && util.isVisible (pev->origin, m_enemy)) && util.isInViewCone (pev->origin, m_enemy)) { diff --git a/src/config.cpp b/src/config.cpp index 45998f0..1989db9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -352,7 +352,7 @@ void BotConfig::loadChatterConfig () { { "Chatter_Camp", Chatter::Camping, 10.0f }, { "Chatter_OnARoll", Chatter::OnARoll, kMaxChatterRepeatInterval}, }; - Array badFiles {}; + Array missingWaves {}; while (file.getLine (line)) { line.trim (); @@ -394,7 +394,7 @@ void BotConfig::loadChatterConfig () { m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); } else { - badFiles.push (sound); + missingWaves.push (sound); } } sentences.clear (); @@ -404,8 +404,16 @@ void BotConfig::loadChatterConfig () { } file.close (); - if (!badFiles.empty ()) { - game.print ("Warning: Couldn't get duration of next chatter sounds: %s.", String::join (badFiles, ",")); + if (!missingWaves.empty ()) { + constexpr auto kMaxErroredWaves = 10; + + // too much erros bail out + if (missingWaves.length () > kMaxErroredWaves) { + cv_radio_mode.set (1); + + missingWaves.resize (kMaxErroredWaves); + } + game.print ("Warning: Couldn't get duration of next chatter sounds: %s ...", String::join (missingWaves, ",")); } } else { diff --git a/src/control.cpp b/src/control.cpp index 3fd7604..e39cedc 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -183,7 +183,7 @@ int BotControl::cmdMenu () { // reset the current menu closeMenu (); - if (arg (cmd) == "cmd" && util.isAlive (m_ent)) { + if (arg (cmd) == "cmd" && game.isAliveEntity (m_ent)) { showMenu (Menu::Commands); } else { @@ -205,7 +205,7 @@ int BotControl::cmdCvars () { auto match = arg (pattern); // stop printing if executed once more - flushPrintQueue (); + m_printQueue.clear (); // revert all the cvars to their default values if (match == "defaults") { @@ -1115,7 +1115,7 @@ int BotControl::menuFeatures (int item) { break; case 5: - if (util.isAlive (m_ent)) { + if (game.isAliveEntity (m_ent)) { showMenu (Menu::Commands); } else { @@ -1924,7 +1924,7 @@ bool BotControl::executeCommands () { } bool BotControl::executeMenus () { - if (!util.isPlayer (m_ent) || game.isBotCmd ()) { + if (!game.isPlayerEntity (m_ent) || game.isBotCmd ()) { return false; } const auto &issuer = util.getClient (game.indexOfPlayer (m_ent)); @@ -1966,7 +1966,7 @@ void BotControl::showMenu (int id) { menusParsed = true; } - if (!util.isPlayer (m_ent)) { + if (!game.isPlayerEntity (m_ent)) { return; } auto &client = util.getClient (game.indexOfPlayer (m_ent)); @@ -2007,7 +2007,7 @@ void BotControl::showMenu (int id) { } void BotControl::closeMenu () { - if (!util.isPlayer (m_ent)) { + if (!game.isPlayerEntity (m_ent)) { return; } auto &client = util.getClient (game.indexOfPlayer (m_ent)); @@ -2073,7 +2073,7 @@ void BotControl::kickBotByMenu (int page) { } void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) { - if (!game.isDedicated () || util.isFakeClient (ent)) { + if (!game.isDedicated () || game.isFakeClientEntity (ent)) { return; } StringRef key = cv_password_key.as (); @@ -2100,7 +2100,7 @@ void BotControl::maintainAdminRights () { StringRef password = cv_password.as (); for (auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) { + if (!(client.flags & ClientFlags::Used) || game.isFakeClientEntity (client.ent)) { continue; } auto ent = client.ent; diff --git a/src/engine.cpp b/src/engine.cpp index 44002e8..341939b 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -12,6 +12,7 @@ ConVar cv_ignore_map_prefix_game_mode ("ignore_map_prefix_game_mode", "0", "If e ConVar cv_threadpool_workers ("threadpool_workers", "-1", "Maximum number of threads the bot will run to process some tasks. -1 means half of the CPU cores are used.", true, -1.0f, static_cast (plat.hardwareConcurrency ())); ConVar cv_grenadier_mode ("grenadier_mode", "0", "If enabled, bots will not apply throwing conditions on grenades."); ConVar cv_ignore_enemies_after_spawn_time ("ignore_enemies_after_spawn_time", "0", "Makes bots ignore enemies for a specified time in seconds on a new round. Useful for Zombie Plague mods.", false); +ConVar cv_breakable_health_limit ("breakable_health_limit", "500.0", "Specifies the maximum health of a breakable object that the bot will consider destroying.", true, 1.0f, 3000.0); ConVar sv_skycolor_r ("sv_skycolor_r", nullptr, Var::GameRef); ConVar sv_skycolor_g ("sv_skycolor_g", nullptr, Var::GameRef); @@ -77,7 +78,7 @@ void Game::levelInitialize (edict_t *entities, int max) { conf.loadMainConfig (); // ensure the server admin is confident about features he's using - game.ensureHealthyGameEnvironment (); + ensureHealthyGameEnvironment (); // load map-specific config conf.loadMapSpecificConfig (); @@ -138,7 +139,7 @@ void Game::levelInitialize (edict_t *entities, int max) { else if (classname == "func_vip_safetyzone" || classname == "info_vip_safetyzone") { m_mapFlags |= MapFlags::Assassination; // assassination map } - else if (util.isHostageEntity (ent)) { + else if (isHostageEntity (ent)) { m_mapFlags |= MapFlags::HostageRescue; // rescue map } else if (classname == "func_bomb_target" || classname == "info_bomb_target") { @@ -152,13 +153,13 @@ void Game::levelInitialize (edict_t *entities, int max) { m_mapFlags &= ~MapFlags::HostageRescue; } } - else if (util.isDoorEntity (ent)) { + else if (isDoorEntity (ent)) { m_mapFlags |= MapFlags::HasDoors; } else if (classname.startsWith ("func_button")) { m_mapFlags |= MapFlags::HasButtons; } - else if (util.isBreakableEntity (ent, true)) { + else if (isBreakableEntity (ent, true)) { // add breakable for material check m_checkedBreakables[indexOfEntity (ent)] = ent->v.impulse <= 0; @@ -197,12 +198,12 @@ void Game::levelShutdown () { bots.destroyKillerEntity (); // ensure players are off on xash3d - if (game.is (GameFlags::Xash3DLegacy)) { + if (is (GameFlags::Xash3DLegacy)) { bots.kickEveryone (true, false); } // set state to unprecached - game.setUnprecached (); + setUnprecached (); // enable lightstyle animations on level change illum.enableAnimation (true); @@ -211,7 +212,7 @@ void Game::levelShutdown () { util.setNeedForWelcome (false); // clear local entity - game.setLocalEntity (nullptr); + setLocalEntity (nullptr); // reset graph state graph.reset (); @@ -229,7 +230,7 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w // is pointed to by ent, from the vector location start to the vector location end, // which is supposed to last life tenths seconds, and having the color defined by RGB. - if (!util.isPlayer (ent)) { + if (!isPlayerEntity (ent)) { return; // reliability check } @@ -373,7 +374,7 @@ void Game::setPlayerStartDrawModels () { }; models.foreach ([&] (const String &key, const String &val) { - game.searchEntities ("classname", key, [&] (edict_t *ent) { + searchEntities ("classname", key, [&] (edict_t *ent) { m_engineWrap.setModel (ent, val.chars ()); return EntitySearchResult::Continue; }); @@ -426,12 +427,12 @@ void Game::sendClientMessage (bool console, edict_t *ent, StringRef message) { // helper to sending the client message // do not send messages to fake clients - if (!util.isPlayer (ent) || util.isFakeClient (ent)) { + if (!isPlayerEntity (ent) || isFakeClientEntity (ent)) { return; } // if console message and destination is listenserver entity, just print via server message instead of through unreliable channel - if (console && ent == game.getLocalEntity ()) { + if (console && ent == getLocalEntity ()) { sendServerMessage (message); return; } @@ -482,7 +483,7 @@ void Game::sendServerMessage (StringRef message) { void Game::sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message) { constexpr size_t kMaxSendLength = 512; - if (game.isNullEntity (ent)) { + if (isNullEntity (ent)) { return; } MessageWriter msg (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent); @@ -1006,7 +1007,7 @@ bool Game::postload () { // register fake metamod command handler if we not! under mm if (!(is (GameFlags::Metamod))) { - game.registerEngineCommand ("meta", [] () { + registerEngineCommand ("meta", [] () { game.print ("You're launched standalone version of %s. Metamod is not installed or not enabled!", product.name); }); } @@ -1126,7 +1127,7 @@ void Game::slowFrame () { if (m_halfSecondFrame < time ()) { // refresh bomb origin in case some plugin moved it out - graph.setBombOrigin (); + gameState.setBombOrigin (); // ensure the server admin is confident about features he's using ensureHealthyGameEnvironment (); @@ -1209,7 +1210,7 @@ void Game::searchEntities (const Vector &position, float radius, EntitySearch fu } } -bool Game::hasEntityInGame (StringRef classname) { +bool Game::hasEntityInGame (StringRef classname) const { return !isNullEntity (engfuncs.pfnFindEntityByString (nullptr, "classname", classname.chars ())); } @@ -1367,6 +1368,120 @@ bool Game::isDeveloperMode () const { return developer.exists () && developer.value () > 0.0f; } +bool Game::isAliveEntity (edict_t *ent) const { + if (isNullEntity (ent)) { + return false; + } + return ent->v.deadflag == DEAD_NO && ent->v.health > 0.0f && ent->v.movetype != MOVETYPE_NOCLIP; +} + +bool Game::isPlayerEntity (edict_t *ent) const { + if (isNullEntity (ent)) { + return false; + } + + if (ent->v.flags & FL_PROXY) { + return false; + } + + if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || bots[ent] != nullptr) { + return !strings.isEmpty (ent->v.netname.chars ()); + } + return false; +} + +bool Game::isMonsterEntity (edict_t *ent) const { + if (isNullEntity (ent)) { + return false; + } + + if (~ent->v.flags & FL_MONSTER) { + return false; + } + + if (isHostageEntity (ent)) { + return false; + } + return true; +} + +bool Game::isItemEntity (edict_t *ent) const { + return ent && ent->v.classname.str ().contains ("item_"); +} + +bool Game::isPlayerVIP (edict_t *ent) const { + if (!mapIs (MapFlags::Assassination)) { + return false; + } + + if (!isPlayerEntity (ent)) { + return false; + } + return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v'; +} + +bool Game::isDoorEntity (edict_t *ent) const { + if (isNullEntity (ent)) { + return false; + } + const auto classHash = ent->v.classname.str ().hash (); + + constexpr auto kFuncDoor = StringRef::fnv1a32 ("func_door"); + constexpr auto kFuncDoorRotating = StringRef::fnv1a32 ("func_door_rotating"); + + return classHash == kFuncDoor || classHash == kFuncDoorRotating; +} + +bool Game::isHostageEntity (edict_t *ent) const { + if (isNullEntity (ent)) { + return false; + } + const auto classHash = ent->v.classname.str ().hash (); + + constexpr auto kHostageEntity = StringRef::fnv1a32 ("hostage_entity"); + constexpr auto kMonsterScientist = StringRef::fnv1a32 ("monster_scientist"); + + return classHash == kHostageEntity || classHash == kMonsterScientist; +} + +bool Game::isBreakableEntity (edict_t *ent, bool initialSeed) const { + if (!initialSeed) { + if (!hasBreakables ()) { + return false; + } + } + + if (isNullEntity (ent) || ent == getStartEntity () || (!initialSeed && !game.isBreakableValid (ent))) { + return false; + } + const auto limit = cv_breakable_health_limit.as (); + + // not shoot-able + if (ent->v.health >= limit) { + return false; + } + constexpr auto kFuncBreakable = StringRef::fnv1a32 ("func_breakable"); + constexpr auto kFuncPushable = StringRef::fnv1a32 ("func_pushable"); + constexpr auto kFuncWall = StringRef::fnv1a32 ("func_wall"); + + if (ent->v.takedamage > 0.0f && ent->v.impulse <= 0 && !(ent->v.flags & FL_WORLDBRUSH) && !(ent->v.spawnflags & SF_BREAK_TRIGGER_ONLY)) { + const auto classHash = ent->v.classname.str ().hash (); + + if (classHash == kFuncBreakable || (classHash == kFuncPushable && (ent->v.spawnflags & SF_PUSH_BREAKABLE)) || classHash == kFuncWall) { + return ent->v.movetype == MOVETYPE_PUSH || ent->v.movetype == MOVETYPE_PUSHSTEP; + } + } + return false; +} + +bool Game::isFakeClientEntity (edict_t *ent) const { + return bots[ent] != nullptr || (!isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT)); +} + +bool Game::isEntityModelMatches (const edict_t *ent, StringRef model) const { + return model.startsWith (ent->v.model.chars (9)); +} + void LightMeasure::initializeLightstyles () { // this function initializes lighting information... @@ -1588,7 +1703,7 @@ Vector PlayerHitboxEnumerator::get (edict_t *ent, int part, float updateTimestam void PlayerHitboxEnumerator::update (edict_t *ent) { constexpr auto kInvalidHitbox = -1; - if (!util.isAlive (ent)) { + if (!game.isAliveEntity (ent)) { return; } // get info about player @@ -1677,3 +1792,143 @@ void PlayerHitboxEnumerator::reset () { part = {}; } } + +void GameState::setBombOrigin (bool reset, const Vector &pos) { + // this function stores the bomb position as a vector + + if (!game.mapIs (MapFlags::Demolition) || !gameState.isBombPlanted ()) { + return; + } + + if (reset) { + m_bombOrigin.clear (); + setBombPlanted (false); + + return; + } + + if (!pos.empty ()) { + m_bombOrigin = pos; + return; + } + bool wasFound = false; + auto bombModel = conf.getBombModelName (); + + game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { + if (game.isEntityModelMatches (ent, bombModel)) { + m_bombOrigin = game.getEntityOrigin (ent); + wasFound = true; + + return EntitySearchResult::Break; + } + return EntitySearchResult::Continue; + }); + + if (!wasFound) { + m_bombOrigin.clear (); + setBombPlanted (false); + } +} + +void GameState::roundStart () { + m_roundOver = false; + m_timeBombPlanted = 0.0f; + + // tell the bots + bots.initRound (); + setBombOrigin (true); + + // calculate the round mid/end in world time + m_timeRoundStart = game.time () + mp_freezetime.as (); + m_timeRoundMid = m_timeRoundStart + mp_roundtime.as () * 60.0f * 0.5f; + m_timeRoundEnd = m_timeRoundStart + mp_roundtime.as () * 60.0f; + + m_interestingEntities.clear (); + m_activeGrenades.clear (); + + m_activeGrenadesUpdateTime.reset (); + m_interestingEntitiesUpdateTime.reset (); +} + +float GameState::getBombTimeLeft () const { + if (!m_bombPlanted) { + return 0.0f; + } + return cr::max (m_timeBombPlanted + mp_c4timer.as () - game.time (), 0.0f); +} + +void GameState::setBombPlanted (bool isPlanted) { + if (cv_ignore_objectives) { + m_bombPlanted = false; + return; + } + + if (isPlanted) { + m_timeBombPlanted = game.time (); + } + m_bombPlanted = isPlanted; +} + +void GameState::updateActiveGrenade () { + constexpr auto kUpdateTime = 0.25f; + + if (m_activeGrenadesUpdateTime.lessThen (kUpdateTime)) { + return; + } + m_activeGrenades.clear (); // clear previously stored grenades + + // need to ignore bomb model in active grenades... + auto bombModel = conf.getBombModelName (); + + // search the map for any type of grenade + game.searchEntities ("classname", "grenade", [&] (edict_t *e) { + // do not count c4 as a grenade + if (!game.isEntityModelMatches (e, bombModel)) { + m_activeGrenades.push (e); + } + return EntitySearchResult::Continue; // continue iteration + }); + m_activeGrenadesUpdateTime.start (); +} + +void GameState::updateInterestingEntities () { + constexpr auto kUpdateTime = 0.5f; + + if (m_interestingEntitiesUpdateTime.lessThen (kUpdateTime)) { + return; + } + m_interestingEntities.clear (); // clear previously stored entities + + // search the map for any type of grenade + game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) { + auto classname = e->v.classname.str (); + + // search for grenades, weaponboxes, weapons, items and armoury entities + if (classname.startsWith ("weaponbox") || classname.startsWith ("grenade") || game.isItemEntity (e) || classname.startsWith ("armoury")) { + m_interestingEntities.push (e); + } + + // pickup some hostage if on cs_ maps + if (game.mapIs (MapFlags::HostageRescue) && game.isHostageEntity (e)) { + m_interestingEntities.push (e); + } + + // add buttons + if (game.mapIs (MapFlags::HasButtons) && classname.startsWith ("func_button")) { + m_interestingEntities.push (e); + } + + // pickup some csdm stuff if we're running csdm + if (game.is (GameFlags::CSDM) && classname.startsWith ("csdm")) { + m_interestingEntities.push (e); + } + + if (cv_attack_monsters && game.isMonsterEntity (e)) { + m_interestingEntities.push (e); + } + + // continue iteration + return EntitySearchResult::Continue; + }); + m_interestingEntitiesUpdateTime.start (); +} diff --git a/src/entities.cpp b/src/entities.cpp index dbc810b..9a0f8cb 100644 --- a/src/entities.cpp +++ b/src/entities.cpp @@ -9,7 +9,7 @@ // on other than win32/linux platforms i.e. arm we're using xash3d engine to run which exposes // nice interface to handle with linkents. if ever rehlds or hlds engine will ever run on ARM or -// other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS +// other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC // when compiling the bot, to get it supported. #if defined(LINKENT_STATIC) void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) { diff --git a/src/fakeping.cpp b/src/fakeping.cpp index 9cfbe2b..b7cdbc1 100644 --- a/src/fakeping.cpp +++ b/src/fakeping.cpp @@ -24,7 +24,7 @@ void BotFakePingManager::reset (edict_t *to) { } for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) { + if (!(client.flags & ClientFlags::Used) || game.isFakeClientEntity (client.ent)) { continue; } m_pbm.start (client.ent); @@ -46,7 +46,7 @@ void BotFakePingManager::syncCalculate () { int numHumans {}; for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) { + if (!(client.flags & ClientFlags::Used) || game.isFakeClientEntity (client.ent)) { continue; } numHumans++; @@ -101,7 +101,7 @@ void BotFakePingManager::calculate () { } void BotFakePingManager::emit (edict_t *ent) { - if (!util.isPlayer (ent)) { + if (!game.isPlayerEntity (ent)) { return; } diff --git a/src/graph.cpp b/src/graph.cpp index dda415a..3054b5d 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -1310,7 +1310,7 @@ void BotGraph::emitNotify (int32_t sound) const { }; // notify editor - if (util.isPlayer (m_editor) && !m_silenceMessages) { + if (game.isPlayerEntity (m_editor) && !m_silenceMessages) { game.playSound (m_editor, notifySounds[sound].chars ()); } } @@ -1483,7 +1483,7 @@ void BotGraph::calculatePathRadius (int index) { if (tr.flFraction < 1.0f) { game.testLine (radiusStart, radiusEnd, TraceIgnore::Monsters, nullptr, &tr); - if (util.isDoorEntity (tr.pHit)) { + if (game.isDoorEntity (tr.pHit)) { path.radius = 0.0f; wayBlocked = true; @@ -1976,7 +1976,7 @@ bool BotGraph::isNodeReacheableEx (const Vector &src, const Vector &destination, // check if this node is "visible"... game.testLine (src, destination, TraceIgnore::Monsters, m_editor, &tr); - const bool isDoor = util.isDoorEntity (tr.pHit); + const bool isDoor = game.isDoorEntity (tr.pHit); // if node is visible from current position (even behind head)... if (tr.flFraction >= 1.0f || isDoor) { @@ -2059,7 +2059,7 @@ void BotGraph::frame () { } // keep the clipping mode enabled, or it can be turned off after new round has started - if (graph.hasEditFlag (GraphEdit::Noclip) && util.isAlive (m_editor)) { + if (graph.hasEditFlag (GraphEdit::Noclip) && game.isAliveEntity (m_editor)) { m_editor->v.movetype = MOVETYPE_NOCLIP; } @@ -2123,7 +2123,7 @@ void BotGraph::frame () { // check if node is within a distance, and is visible if (distanceSq < cr::sqrf (cv_graph_draw_distance.as ()) && ((util.isVisible (path.origin, m_editor) - && util.isInViewCone (path.origin, m_editor)) || !util.isAlive (m_editor) || distanceSq < cr::sqrf (64.0f))) { + && util.isInViewCone (path.origin, m_editor)) || !game.isAliveEntity (m_editor) || distanceSq < cr::sqrf (64.0f))) { // check the distance if (distanceSq < nearestDistanceSq) { @@ -2766,43 +2766,6 @@ void BotGraph::addBasic () { autoCreateForEntity (NodeAddFlag::Goal, "func_escapezone"); // terrorist escape zone } -void BotGraph::setBombOrigin (bool reset, const Vector &pos) { - // this function stores the bomb position as a vector - - if (!game.mapIs (MapFlags::Demolition) || !bots.isBombPlanted ()) { - return; - } - - if (reset) { - m_bombOrigin.clear (); - bots.setBombPlanted (false); - - return; - } - - if (!pos.empty ()) { - m_bombOrigin = pos; - return; - } - bool wasFound = false; - auto bombModel = conf.getBombModelName (); - - game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { - if (util.isModel (ent, bombModel)) { - m_bombOrigin = game.getEntityOrigin (ent); - wasFound = true; - - return EntitySearchResult::Break; - } - return EntitySearchResult::Continue; - }); - - if (!wasFound) { - m_bombOrigin.clear (); - bots.setBombPlanted (false); - } -} - void BotGraph::startLearnJump () { m_jumpLearnNode = true; } diff --git a/src/hooks.cpp b/src/hooks.cpp index 71e9283..fa6f8ef 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -27,7 +27,7 @@ int32_t ServerQueryHook::sendTo (int socket, const void *message, size_t length, buffer.skip (); // score auto ctime = buffer.read (); // override connection time - buffer.write (bots.getConnectTime (name, ctime)); + buffer.write (bots.getConnectionTimes (name, ctime)); } return send (buffer.data ()); } diff --git a/src/linkage.cpp b/src/linkage.cpp index 139a71c..186f576 100644 --- a/src/linkage.cpp +++ b/src/linkage.cpp @@ -50,7 +50,7 @@ CR_FORCE_STACK_ALIGN void handler_engClientCommand (edict_t *ent, char const *fo // case it's a bot asking for a client command, we handle it like we do for bot commands if (!game.isNullEntity (ent)) { - if (bots[ent] || util.isFakeClient (ent) || (ent->v.flags & FL_DORMANT)) { + if (bots[ent] || game.isFakeClientEntity (ent) || (ent->v.flags & FL_DORMANT)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands } @@ -159,7 +159,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) { auto bot = bots[pentTouched]; - if (bot && util.isBreakableEntity (pentOther)) { + if (bot && game.isBreakableEntity (pentOther)) { bot->checkBreakable (pentOther); } } @@ -389,10 +389,10 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) { if (bots.hasBotsOnline ()) { // keep track of grenades on map - bots.updateActiveGrenade (); + gameState.updateActiveGrenade (); // keep track of interesting entities - bots.updateInterestingEntities (); + gameState.updateInterestingEntities (); } // keep bot number up to date @@ -429,7 +429,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) { auto ent = const_cast (reinterpret_cast (player)); if (fakeping.hasFeature ()) { - if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) { + if (!game.isFakeClientEntity (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) { fakeping.emit (ent); } } @@ -560,7 +560,7 @@ CR_C_LINKAGE int GetEntityAPI_Post (gamefuncs_t *table, int) { auto ent = const_cast (reinterpret_cast (player)); if (fakeping.hasFeature ()) { - if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) { + if (!game.isFakeClientEntity (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) { fakeping.emit (ent); } } @@ -591,7 +591,7 @@ CR_C_LINKAGE int GetEngineFunctions (enginefuncs_t *table, int *) { table->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) CR_FORCE_STACK_ALIGN { // round starts in counter-strike 1.5 if (strcmp (value, "info_map_parameters") == 0) { - bots.initRound (); + gameState.roundStart (); } if (game.is (GameFlags::Metamod)) { @@ -786,7 +786,7 @@ CR_C_LINKAGE int GetEngineFunctions (enginefuncs_t *table, int *) { // as it will crash your server. Why would you, anyway ? bots have no client DLL as far as // we know, right ? But since stupidity rules this world, we do a preventive check :) - if (util.isFakeClient (ent)) { + if (game.isFakeClientEntity (ent)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } diff --git a/src/manager.cpp b/src/manager.cpp index 1295985..9602939 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -469,7 +469,7 @@ void BotManager::maintainLeaders () { } // select leader each team somewhere in round start - if (m_timeRoundStart + rg (1.5f, 3.0f) < game.time ()) { + if (gameState.getRoundStartTime () + rg (1.5f, 3.0f) < game.time ()) { for (int team = 0; team < kGameTeamNum; ++team) { selectLeaders (team, false); } @@ -487,7 +487,7 @@ void BotManager::maintainRoundRestart () { && m_numPreviousPlayers == 0 && totalHumans == 1 && totalBots > 0 - && !m_resetHud) { + && !gameState.isResetHUD ()) { static ConVarRef sv_restartround ("sv_restartround"); @@ -496,13 +496,13 @@ void BotManager::maintainRoundRestart () { } } m_numPreviousPlayers = totalHumans; - m_resetHud = false; + gameState.setResetHUD (false); } void BotManager::maintainAutoKill () { const float killDelay = cv_autokill_delay.as (); - if (killDelay < 1.0f || m_roundOver) { + if (killDelay < 1.0f || gameState.isRoundOver ()) { return; } @@ -516,7 +516,7 @@ void BotManager::maintainAutoKill () { int aliveBots = 0; // do not interrupt bomb-defuse scenario - if (game.mapIs (MapFlags::Demolition) && isBombPlanted ()) { + if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted ()) { return; } const int totalHumans = getHumansCount (true); // we're ignore spectators intentionally @@ -531,7 +531,7 @@ void BotManager::maintainAutoKill () { ++aliveBots; // do not interrupt assassination scenario, if vip is a bot - if (game.is (MapFlags::Assassination) && util.isPlayerVIP (bot->ent ())) { + if (game.is (MapFlags::Assassination) && game.isPlayerVIP (bot->ent ())) { return; } } @@ -545,15 +545,9 @@ void BotManager::maintainAutoKill () { } void BotManager::reset () { - m_grenadeUpdateTime = 0.0f; - m_entityUpdateTime = 0.0f; m_plantSearchUpdateTime = 0.0f; m_lastChatTime = 0.0f; - m_timeBombPlanted = 0.0f; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; - - m_interestingEntities.clear (); - m_activeGrenades.clear (); } void BotManager::initFilters () { @@ -681,7 +675,7 @@ void BotManager::kickFromTeam (Team team, bool removeAll) { } for (const auto &bot : m_bots) { - if (team == game.getRealTeam (bot->ent ())) { + if (team == game.getRealPlayerTeam (bot->ent ())) { bot->kick (removeAll); if (!removeAll) { @@ -696,7 +690,7 @@ void BotManager::killAllBots (int team, bool silent) { // this function kills all bots on server (only this dll controlled bots) for (const auto &bot : m_bots) { - if (team != Team::Invalid && game.getRealTeam (bot->ent ()) != team) { + if (team != Team::Invalid && game.getRealPlayerTeam (bot->ent ()) != team) { continue; } bot->kill (); @@ -732,7 +726,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { if (fromTeam == Team::Unassigned) { return true; } - return game.getRealTeam (bot->ent ()) == fromTeam; + return game.getRealPlayerTeam (bot->ent ()) == fromTeam; }; // first try to kick the bot that is currently dead @@ -830,7 +824,7 @@ bool BotManager::hasCustomCSDMSpawnEntities () { void BotManager::setLastWinner (int winner) { m_lastWinner = winner; - m_roundOver = true; + gameState.setRoundOver (true); if (cv_radio_mode.as () != 2) { return; @@ -839,7 +833,7 @@ void BotManager::setLastWinner (int winner) { if (notify) { if (notify->m_team == winner) { - if (getRoundMidTime () > game.time ()) { + if (gameState.getRoundMidTime () > game.time ()) { notify->pushChatterMessage (Chatter::QuickWonRound); } else { @@ -929,7 +923,7 @@ void BotManager::listBots () { ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s\t%-3.6s\t%-3.5s\t%-3.8s", "index", "name", "personality", "team", "difficulty", "frags", "deaths", "alive", "timeleft"); auto botTeam = [] (edict_t *ent) -> StringRef { - const auto team = game.getRealTeam (ent); + const auto team = game.getRealPlayerTeam (ent); switch (team) { case Team::CT: @@ -967,7 +961,7 @@ void BotManager::listBots () { ctrl.msg ("%d bots", m_bots.length ()); } -float BotManager::getConnectTime (StringRef name, float original) { +float BotManager::getConnectionTimes (StringRef name, float original) { // this function get's fake bot player time. for (const auto &bot : m_bots) { @@ -1020,19 +1014,18 @@ Twin BotManager::countTeamPlayers () { } Bot *BotManager::findHighestFragBot (int team) { - int bestIndex = 0; - float bestScore = -1; + Twin best {}; // search bots in this team for (const auto &bot : bots) { - if (bot->m_isAlive && game.getRealTeam (bot->ent ()) == team) { - if (bot->pev->frags > bestScore) { - bestIndex = bot->index (); - bestScore = bot->pev->frags; + if (bot->m_isAlive && game.getRealPlayerTeam (bot->ent ()) == team) { + if (bot->pev->frags > best.second) { + best.first = bot->index (); + best.second = bot->pev->frags; } } } - return findBotByIndex (bestIndex); + return findBotByIndex (best.first); } void BotManager::updateTeamEconomics (int team, bool setTrue) { @@ -1398,8 +1391,8 @@ void BotManager::disconnectBot (Bot *bot) { } void BotManager::handleDeath (edict_t *killer, edict_t *victim) { - const auto killerTeam = game.getRealTeam (killer); - const auto victimTeam = game.getRealTeam (victim); + const auto killerTeam = game.getRealPlayerTeam (killer); + const auto victimTeam = game.getRealPlayerTeam (victim); if (cv_radio_mode.as () == 2) { // need to send congrats on well placed shot @@ -1512,7 +1505,7 @@ void Bot::newRound () { node = kInvalidNodeIndex; } m_navTimeset = game.time (); - m_team = game.getTeam (ent ()); + m_team = game.getPlayerTeam (ent ()); resetPathSearchType (); @@ -1841,7 +1834,7 @@ void Bot::updateTeamJoin () { if (!m_notStarted) { return; } - const auto botTeam = game.getRealTeam (ent ()); + const auto botTeam = game.getRealPlayerTeam (ent ()); // cs prior beta 7.0 uses hud-based motd, so press fire once if (game.is (GameFlags::Legacy)) { @@ -1948,15 +1941,15 @@ void BotManager::captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent) { } if (cmd.startsWith ("say")) { - const bool alive = util.isAlive (ent); + const bool alive = game.isAliveEntity (ent); int team = -1; if (cmd.endsWith ("team")) { - team = game.getRealTeam (ent); + team = game.getRealPlayerTeam (ent); } for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || (team != -1 && team != client.team2) || alive != util.isAlive (client.ent)) { + if (!(client.flags & ClientFlags::Used) || (team != -1 && team != client.team2) || alive != game.isAliveEntity (client.ent)) { continue; } auto target = bots[client.ent]; @@ -2003,7 +1996,7 @@ void BotManager::captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent) { void BotManager::notifyBombDefuse () { // notify all terrorists that CT is starting bomb defusing - const auto &bombPos = graph.getBombOrigin (); + const auto &bombPos = gameState.getBombOrigin (); for (const auto &bot : bots) { const auto task = bot->getCurrentTaskId (); @@ -2027,68 +2020,6 @@ void BotManager::notifyBombDefuse () { } } -void BotManager::updateActiveGrenade () { - if (m_grenadeUpdateTime > game.time ()) { - return; - } - m_activeGrenades.clear (); // clear previously stored grenades - - // need to ignore bomb model in active grenades... - auto bombModel = conf.getBombModelName (); - - // search the map for any type of grenade - game.searchEntities ("classname", "grenade", [&] (edict_t *e) { - // do not count c4 as a grenade - if (!util.isModel (e, bombModel)) { - m_activeGrenades.push (e); - } - return EntitySearchResult::Continue; // continue iteration - }); - m_grenadeUpdateTime = game.time () + 0.25f; -} - -void BotManager::updateInterestingEntities () { - if (m_entityUpdateTime > game.time ()) { - return; - } - - // clear previously stored entities - m_interestingEntities.clear (); - - // search the map for any type of grenade - game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) { - auto classname = e->v.classname.str (); - - // search for grenades, weaponboxes, weapons, items and armoury entities - if (classname.startsWith ("weaponbox") || classname.startsWith ("grenade") || util.isItem (e) || classname.startsWith ("armoury")) { - m_interestingEntities.push (e); - } - - // pickup some hostage if on cs_ maps - if (game.mapIs (MapFlags::HostageRescue) && util.isHostageEntity (e)) { - m_interestingEntities.push (e); - } - - // add buttons - if (game.mapIs (MapFlags::HasButtons) && classname.startsWith ("func_button")) { - m_interestingEntities.push (e); - } - - // pickup some csdm stuff if we're running csdm - if (game.is (GameFlags::CSDM) && classname.startsWith ("csdm")) { - m_interestingEntities.push (e); - } - - if (cv_attack_monsters && util.isMonster (e)) { - m_interestingEntities.push (e); - } - - // continue iteration - return EntitySearchResult::Continue; - }); - m_entityUpdateTime = game.time () + 0.5f; -} - void BotManager::selectLeaders (int team, bool reset) { auto &leaderChoosen = m_teamData[team].leaderChoosen; @@ -2191,8 +2122,6 @@ void BotManager::selectLeaders (int team, bool reset) { void BotManager::initRound () { // this is called at the start of each round - m_roundOver = false; - // check team economics for (int team = 0; team < kGameTeamNum; ++team) { updateTeamEconomics (team); @@ -2211,35 +2140,15 @@ void BotManager::initRound () { for (auto &client : util.getClients ()) { client.radio = 0; } - - graph.setBombOrigin (true); graph.clearVisited (); m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; - m_timeBombPlanted = 0.0f; m_plantSearchUpdateTime = 0.0f; m_autoKillCheckTime = 0.0f; m_botsCanPause = false; resetFilters (); practice.update (); // update practice data on round start - - // calculate the round mid/end in world time - m_timeRoundStart = game.time () + mp_freezetime.as (); - m_timeRoundMid = m_timeRoundStart + mp_roundtime.as () * 60.0f * 0.5f; - m_timeRoundEnd = m_timeRoundStart + mp_roundtime.as () * 60.0f; -} - -void BotManager::setBombPlanted (bool isPlanted) { - if (cv_ignore_objectives) { - m_bombPlanted = false; - return; - } - - if (isPlanted) { - m_timeBombPlanted = game.time (); - } - m_bombPlanted = isPlanted; } void BotThreadWorker::shutdown () { @@ -2293,121 +2202,6 @@ void BotThreadWorker::startup (int workers) { m_pool->startup (static_cast (requestedThreads)); } -bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) { - if (m_activeGrenades.empty ()) { - return false; - } - constexpr auto kSmokeGrenadeRadius = 115.0f; - - // distance along line of sight covered by smoke - float totalSmokedLength = 0.0f; - - Vector sightDir = to - from; - const float sightLength = sightDir.normalizeInPlace (); - - for (auto pent : m_activeGrenades) { - if (game.isNullEntity (pent)) { - continue; - } - - // need drawn models - if (pent->v.effects & EF_NODRAW) { - continue; - } - - // smoke must be on a ground - if (!(pent->v.flags & FL_ONGROUND)) { - continue; - } - - // must be a smoke grenade - if (!util.isModel (pent, kSmokeModelName)) { - continue; - } - - const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius); - const Vector &smokeOrigin = game.getEntityOrigin (pent); - - Vector toGrenade = smokeOrigin - from; - float alongDist = toGrenade | sightDir; - - // compute closest point to grenade along line of sight ray - Vector close {}; - - // constrain closest point to line segment - if (alongDist < 0.0f) { - close = from; - } - else if (alongDist >= sightLength) { - close = to; - } - else { - close = from + sightDir * alongDist; - } - - // if closest point is within smoke radius, the line overlaps the smoke cloud - Vector toClose = close - smokeOrigin; - float lengthSq = toClose.lengthSq (); - - if (lengthSq < smokeRadiusSq) { - // some portion of the ray intersects the cloud - - const float fromSq = toGrenade.lengthSq (); - const float toSq = (smokeOrigin - to).lengthSq (); - - if (fromSq < smokeRadiusSq) { - if (toSq < smokeRadiusSq) { - // both 'from' and 'to' lie within the cloud - // entire length is smoked - totalSmokedLength += (to - from).length (); - } - else { - // 'from' is inside the cloud, 'to' is outside - // compute half of total smoked length as if ray crosses entire cloud chord - float halfSmokedLength = cr::sqrtf (smokeRadiusSq - lengthSq); - - if (alongDist > 0.0f) { - // ray goes thru 'close' - totalSmokedLength += halfSmokedLength + (close - from).length (); - } - else { - // ray starts after 'close' - totalSmokedLength += halfSmokedLength - (close - from).length (); - } - - } - } - else if (toSq < smokeRadiusSq) { - // 'from' is outside the cloud, 'to' is inside - // compute half of total smoked length as if ray crosses entire cloud chord - const float halfSmokedLength = cr::sqrtf (smokeRadiusSq - lengthSq); - Vector v = to - smokeOrigin; - - if ((v | sightDir) > 0.0f) { - // ray goes thru 'close' - totalSmokedLength += halfSmokedLength + (close - to).length (); - } - else { - // ray ends before 'close' - totalSmokedLength += halfSmokedLength - (close - to).length (); - } - } - else { - // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it - // determine the length of the chord that crosses the cloud - const float smokedLength = 2.0f * cr::sqrtf (smokeRadiusSq - lengthSq); - totalSmokedLength += smokedLength; - } - } - } - - // define how much smoke a bot can see thru - const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius; - - // return true if the total length of smoke-covered line-of-sight is too much - return totalSmokedLength > maxSmokedLength; -} - bool BotManager::isFrameSkipDisabled () { if (game.is (GameFlags::Legacy)) { return true; diff --git a/src/message.cpp b/src/message.cpp index 4df7b1f..602e922 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -26,7 +26,7 @@ void MessageDispatcher::netMsgTextMsg () { // reset bomb position for all the bots const auto resetBombPosition = [] () -> void { if (game.mapIs (MapFlags::Demolition)) { - graph.setBombOrigin (true); + gameState.setBombOrigin (true); } }; @@ -53,8 +53,8 @@ void MessageDispatcher::netMsgTextMsg () { bots.setLastWinner (Team::Terrorist); // update last winner for economics resetBombPosition (); } - else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) { - bots.setBombPlanted (true); + else if ((cached & TextMsgCache::BombPlanted) && !gameState.isBombPlanted ()) { + gameState.setBombPlanted (true); for (const auto ¬ify : bots) { if (notify->m_isAlive) { @@ -68,7 +68,7 @@ void MessageDispatcher::netMsgTextMsg () { } } } - graph.setBombOrigin (); + gameState.setBombOrigin (); } // check for burst fire message @@ -319,7 +319,7 @@ void MessageDispatcher::netMsgHLTV () { // need to start new round ? (we're tracking FOV reset message) if (m_args[players].long_ == 0 && m_args[fov].long_ == 0) { - bots.initRound (); + gameState.roundStart (); } } @@ -389,7 +389,7 @@ void MessageDispatcher::netMsgBarTime () { m_bot->m_hasProgressBar = true; // the progress bar on a hud // notify bots about defusing has started - if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_bot->m_team == Team::CT) { + if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted () && m_bot->m_team == Team::CT) { bots.notifyBombDefuse (); } } @@ -435,7 +435,7 @@ void MessageDispatcher::netMsgResetHUD () { if (m_bot) { m_bot->spawned (); } - bots.setResetHUD (true); + gameState.setResetHUD (true); } MessageDispatcher::MessageDispatcher () { diff --git a/src/navigate.cpp b/src/navigate.cpp index f15b909..ad536ec 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -60,12 +60,12 @@ int Bot::findBestGoal () { bool hasMoreHostagesAround = false; // try to search nearby-unused hostage, and if so, go to next goal - if (bots.hasInterestingEntities ()) { - const auto &interesting = bots.getInterestingEntities (); + if (gameState.hasInterestingEntities ()) { + const auto &interesting = gameState.getInterestingEntities (); // search world for hostages for (const auto &ent : interesting) { - if (!util.isHostageEntity (ent)) { + if (!game.isHostageEntity (ent)) { continue; } bool hostageInUse = false; @@ -134,7 +134,7 @@ int Bot::findBestGoal () { } } else if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT) { - if (bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && !graph.getBombOrigin ().empty ()) { + if (gameState.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && !gameState.getBombOrigin ().empty ()) { if (bots.hasBombSay (BombPlantedSay::ChatSay)) { pushChatMessage (Chat::Plant); @@ -149,10 +149,10 @@ int Bot::findBestGoal () { defensive += 10.0f; } } - else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) { + else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && gameState.getRoundStartTime () + 10.0f < game.time ()) { // send some terrorists to guard planted bomb - if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) { - return m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ()); + if (!m_defendedBomb && gameState.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && gameState.getBombTimeLeft () >= 15.0f) { + return m_chosenGoalIndex = findDefendNode (gameState.getBombOrigin ()); } } else if (game.mapIs (MapFlags::Escape)) { @@ -202,9 +202,9 @@ int Bot::findBestGoal () { int Bot::findBestGoalWhenBombAction () { int result = kInvalidNodeIndex; - if (!bots.isBombPlanted () && !cv_ignore_objectives) { + if (!gameState.isBombPlanted () && !cv_ignore_objectives) { game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { - if (util.isModel (ent, "backpack.mdl")) { + if (game.isEntityModelMatches (ent, "backpack.mdl")) { result = graph.getNearest (game.getEntityOrigin (ent)); if (graph.exists (result)) { @@ -230,7 +230,7 @@ int Bot::findBestGoalWhenBombAction () { } } else if (!m_defendedBomb) { - const auto &bombOrigin = graph.getBombOrigin (); + const auto &bombOrigin = gameState.getBombOrigin (); if (!bombOrigin.empty ()) { m_defendedBomb = true; @@ -239,7 +239,7 @@ int Bot::findBestGoalWhenBombAction () { const auto &path = graph[result]; const float bombTimer = mp_c4timer.as (); - const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + const float timeMidBlowup = gameState.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); if (timeMidBlowup > game.time ()) { clearTask (Task::MoveToPosition); // remove any move tasks @@ -288,14 +288,15 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { else if (tactic == GoalTactic::Goal && !graph.m_goalPoints.empty ()) { // map goal node // force bomber to select closest goal, if round-start goal was reset by something - if (m_isVIP || (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ())) { + if (m_isVIP || (m_hasC4 && gameState.getRoundStartTime () + 20.0f < game.time ())) { float nearestDistanceSq = kInfiniteDistance; int count = 0; for (const auto &point : graph.m_goalPoints) { const float distanceSq = graph[point].origin.distanceSq (pev->origin); - if (distanceSq > cr::sqrf (1024.0f) || isGroupOfEnemies (graph[point].origin)) { + if (distanceSq > cr::sqrf (1024.0f) + || (rg.chance (25) && isGroupOfEnemies (graph[point].origin))) { continue; } if (distanceSq < nearestDistanceSq) { @@ -594,7 +595,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { void Bot::checkTerrain (const Vector &dirNormal) { - // if avoiding someone do not consider stuck + // if avoiding someone do not consider stuckn TraceResult tr {}; m_isStuck = false; @@ -602,7 +603,7 @@ void Bot::checkTerrain (const Vector &dirNormal) { // minimal speed for consider stuck const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4; - const auto randomProbeTime = rg (0.75f, 1.15f); + const auto randomProbeTime = rg (0.50f, 0.75f); // standing still, no need to check? if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed) @@ -900,10 +901,7 @@ void Bot::checkTerrain (const Vector &dirNormal) { } void Bot::checkFall () { - if (isPreviousLadder ()) { - return; - } - else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) { + if (isPreviousLadder () || (m_pathFlags & NodeFlag::Ladder)) { return; } @@ -1105,7 +1103,7 @@ bool Bot::updateNavigation () { if (m_desiredVelocity.length2d () > 0.0f) { pev->velocity = m_desiredVelocity; } - else { + else if (graph.isAnalyzed ()) { auto feet = pev->origin + pev->mins; auto node = Vector { m_pathOrigin.x, m_pathOrigin.y, m_pathOrigin.z - ((m_pathFlags & NodeFlag::Crouch) ? 18.0f : 36.0f) }; @@ -1151,7 +1149,7 @@ bool Bot::updateNavigation () { } if (m_pathFlags & NodeFlag::Ladder) { - constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 36.0f); + constexpr auto kLadderOffset = Vector { 0.0f, 0.0f, 36.0f }; const auto prevNodeIndex = m_previousNodes[0]; const float ladderDistance = pev->origin.distance (m_pathOrigin); @@ -1231,7 +1229,7 @@ bool Bot::updateNavigation () { if (game.mapIs (MapFlags::HasDoors) || (m_pathFlags & NodeFlag::Button)) { game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr); - if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && util.isDoorEntity (tr.pHit)) { + if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && game.isDoorEntity (tr.pHit)) { const auto &origin = game.getEntityOrigin (tr.pHit); const float distanceSq = pev->origin.distanceSq (origin); @@ -1285,7 +1283,7 @@ bool Bot::updateNavigation () { util.findNearestPlayer (reinterpret_cast (&nearest), ent (), 256.0f, false, false, true, true, false); // check if enemy is penetrable - if (util.isAlive (nearest) && isPenetrableObstacle (nearest->v.origin) && !cv_ignore_enemies) { + if (game.isAliveEntity (nearest) && isPenetrableObstacle (nearest->v.origin) && !cv_ignore_enemies) { m_seeEnemyTime = game.time (); m_states |= Sense::SeeingEnemy | Sense::SuspectEnemy; @@ -1324,7 +1322,7 @@ bool Bot::updateNavigation () { } } - float desiredDistanceSq = cr::sqrf (8.0f); + float desiredDistanceSq = cr::sqrf (32.0f); const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); // initialize the radius for a special node type, where the node is considered to be reached @@ -1336,7 +1334,7 @@ bool Bot::updateNavigation () { // on cs_ maps goals are usually hostages, so increase reachability distance for them, they (hostages) picked anyway if (game.mapIs (MapFlags::HostageRescue) && (m_pathFlags & NodeFlag::Goal)) { - desiredDistanceSq = cr::sqrf (128.0f); + desiredDistanceSq = cr::sqrf (96.0f); } } else if (m_pathFlags & NodeFlag::Ladder) { @@ -1374,7 +1372,7 @@ bool Bot::updateNavigation () { } // needs precise placement - check if we get past the point - if (desiredDistanceSq < cr::sqrf (20.0f) && nodeDistanceSq < cr::sqrf (30.0f)) { + if (desiredDistanceSq < cr::sqrf (16.0f) && nodeDistanceSq < cr::sqrf (30.0f)) { const auto predictRangeSq = m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval); if (predictRangeSq >= nodeDistanceSq || predictRangeSq <= desiredDistanceSq) { @@ -1386,7 +1384,10 @@ bool Bot::updateNavigation () { // is sitting there, so the bot is unable to reach the node because of other player on it, and he starts to jumping and so on // here we're clearing task memory data (important!), since task executor may restart goal with one from memory, so this process // will go in cycle, and forcing bot to re-create new route. - if (m_pathWalk.hasNext () && m_pathWalk.next () == m_pathWalk.last () && isOccupiedNode (m_pathWalk.next (), pathHasFlags)) { + if (m_pathWalk.hasNext () + && m_pathWalk.next () == m_pathWalk.last () + && isOccupiedNode (m_pathWalk.next (), pathHasFlags)) { + getTask ()->data = kInvalidNodeIndex; m_currentNodeIndex = kInvalidNodeIndex; @@ -1418,7 +1419,7 @@ bool Bot::updateNavigation () { const int taskTarget = getTask ()->data; if (game.mapIs (MapFlags::Demolition) - && bots.isBombPlanted () + && gameState.isBombPlanted () && m_team == Team::CT && getCurrentTaskId () != Task::EscapeFromBomb && taskTarget != kInvalidNodeIndex) { @@ -1481,7 +1482,7 @@ bool Bot::updateLiftHandling () { game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction < 1.0f - && util.isDoorEntity (tr.pHit) + && game.isDoorEntity (tr.pHit) && (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && pev->groundentity != tr.pHit) { @@ -2117,7 +2118,7 @@ int Bot::findBombNode () { const auto &goals = graph.m_goalPoints; - const auto &bomb = graph.getBombOrigin (); + const auto &bomb = gameState.getBombOrigin (); const auto &audible = isBombAudible (); // take the nearest to bomb nodes instead of goal if close enough @@ -2535,9 +2536,9 @@ bool Bot::advanceMovement () { // only if we in normal task and bomb is not planted if (tid == Task::Normal - && bots.getRoundMidTime () + 5.0f < game.time () + && gameState.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () - && !bots.isBombPlanted () + && !gameState.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex @@ -2549,7 +2550,7 @@ bool Bot::advanceMovement () { auto kills = static_cast (practice.getDamage (m_team, nextIndex, nextIndex)); // if damage done higher than one - if (kills > 1.0f && bots.getRoundMidTime () > game.time ()) { + if (kills > 1.0f && gameState.getRoundMidTime () > game.time ()) { switch (m_personality) { case Personality::Normal: kills *= 0.33f; @@ -2647,7 +2648,7 @@ bool Bot::advanceMovement () { // mark as jump sequence, if the current and next paths are jumps if (isCurrentJump) { - m_jumpSequence = willJump; + m_jumpSequence = willJump && jumpDistanceSq > cr::sqrf (96.0f); } // is there a jump node right ahead and do we need to draw out the light weapon ? @@ -2752,11 +2753,11 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) { if (!game.mapIs (MapFlags::HasDoors)) { return false; } - return result->flFraction < 1.0f && !util.isDoorEntity (result->pHit); + return result->flFraction < 1.0f && !game.isDoorEntity (result->pHit); }; auto checkHostage = [&] (TraceResult *result) { - return result->flFraction < 1.0f && m_team == Team::Terrorist && !util.isHostageEntity (result->pHit); + return result->flFraction < 1.0f && m_team == Team::Terrorist && !game.isHostageEntity (result->pHit); }; // trace from the bot's eyes straight forward... @@ -2764,8 +2765,8 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) { // check if the trace hit something... if (tr->flFraction < 1.0f) { - if ((game.mapIs (MapFlags::HasDoors) && util.isDoorEntity (tr->pHit)) - || (m_team == Team::CT && util.isHostageEntity (tr->pHit))) { + if ((game.mapIs (MapFlags::HasDoors) && game.isDoorEntity (tr->pHit)) + || (m_team == Team::CT && game.isHostageEntity (tr->pHit))) { return false; } return true; // bot's head will hit something @@ -3120,7 +3121,7 @@ bool Bot::isBlockedLeft () { game.testLine (pev->origin, pev->origin - forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... - if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) { + if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !game.isDoorEntity (tr.pHit)) { return true; // bot's body will hit something } return false; @@ -3140,7 +3141,7 @@ bool Bot::isBlockedRight () { game.testLine (pev->origin, pev->origin + forward * direction + right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... - if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) { + if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !game.isDoorEntity (tr.pHit)) { return true; // bot's body will hit something } return false; @@ -3302,7 +3303,7 @@ int Bot::getNearestToPlantedBomb () { // search the bomb on the map game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { - if (util.isModel (ent, bombModel)) { + if (game.isEntityModelMatches (ent, bombModel)) { result = graph.getNearest (game.getEntityOrigin (ent)); if (graph.exists (result)) { diff --git a/src/planner.cpp b/src/planner.cpp index f98eac6..4c7146f 100644 --- a/src/planner.cpp +++ b/src/planner.cpp @@ -17,12 +17,12 @@ float PlannerHeuristic::gfunctionKillsDist (int team, int currentIndex, int pare if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, true); + auto cost = practice.getDamageEx (team, currentIndex, currentIndex, true); const auto ¤t = graph[currentIndex]; for (const auto &neighbour : current.links) { if (neighbour.index != kInvalidNodeIndex) { - cost += practice.plannerGetDamage (team, neighbour.index, neighbour.index, false); + cost += practice.getDamageEx (team, neighbour.index, neighbour.index, false); } } @@ -45,12 +45,12 @@ float PlannerHeuristic::gfunctionKillsDistCTWithHostage (int team, int currentIn } float PlannerHeuristic::gfunctionKills (int team, int currentIndex, int) { - auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, false); + auto cost = practice.getDamageEx (team, currentIndex, currentIndex, false); const auto ¤t = graph[currentIndex]; for (const auto &neighbour : current.links) { if (neighbour.index != kInvalidNodeIndex) { - cost += practice.plannerGetDamage (team, neighbour.index, neighbour.index, false); + cost += practice.getDamageEx (team, neighbour.index, neighbour.index, false); } } @@ -264,7 +264,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder auto rsRandomizer = 1.0f; // randomize path on round start now and then - if (cv_path_randomize_on_round_start && bots.getRoundStartTime () + 2.0f > game.time ()) { + if (cv_path_randomize_on_round_start && gameState.getRoundStartTime () + 2.0f > game.time ()) { rsRandomizer = rg (0.5f, static_cast (botTeam) * 2.0f); } diff --git a/src/practice.cpp b/src/practice.cpp index 4f51797..79bb2ef 100644 --- a/src/practice.cpp +++ b/src/practice.cpp @@ -64,7 +64,7 @@ void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t m_data[{start, goal, team}].damage = static_cast (value); } -float BotPractice::plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage) { +float BotPractice::getDamageEx (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage) { if (!m_damageUpdateLock.tryLock ()) { return 0.0f; } diff --git a/src/support.cpp b/src/support.cpp index 04f0006..ad6e4e3 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -9,7 +9,6 @@ ConVar cv_display_welcome_text ("display_welcome_text", "1", "Enables or disables showing a welcome message to the host entity on game start."); ConVar cv_enable_query_hook ("enable_query_hook", "0", "Enables or disables fake server query responses, which show bots as real players in the server browser."); -ConVar cv_breakable_health_limit ("breakable_health_limit", "500.0", "Specifies the maximum health of a breakable object that the bot will consider destroying.", true, 1.0f, 3000.0); ConVar cv_enable_fake_steamids ("enable_fake_steamids", "0", "Allows or disallows bots to return a fake Steam ID."); BotSupport::BotSupport () { @@ -82,13 +81,6 @@ BotSupport::BotSupport () { m_clients.resize (kGameMaxPlayers + 1); } -bool BotSupport::isAlive (edict_t *ent) { - if (game.isNullEntity (ent)) { - return false; - } - return ent->v.deadflag == DEAD_NO && ent->v.health > 0.0f && ent->v.movetype != MOVETYPE_NOCLIP; -} - bool BotSupport::isVisible (const Vector &origin, edict_t *ent) { if (game.isNullEntity (ent)) { return false; @@ -151,109 +143,6 @@ void BotSupport::decalTrace (TraceResult *trace, int decalIndex) { msg.end (); } -bool BotSupport::isPlayer (edict_t *ent) { - if (game.isNullEntity (ent)) { - return false; - } - - if (ent->v.flags & FL_PROXY) { - return false; - } - - if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || bots[ent] != nullptr) { - return !strings.isEmpty (ent->v.netname.chars ()); - } - return false; -} - -bool BotSupport::isMonster (edict_t *ent) { - if (game.isNullEntity (ent)) { - return false; - } - - if (~ent->v.flags & FL_MONSTER) { - return false; - } - - if (isHostageEntity (ent)) { - return false; - } - return true; -} - -bool BotSupport::isItem (edict_t *ent) { - return ent && ent->v.classname.str ().contains ("item_"); -} - -bool BotSupport::isPlayerVIP (edict_t *ent) { - if (!game.mapIs (MapFlags::Assassination)) { - return false; - } - - if (!isPlayer (ent)) { - return false; - } - return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v'; -} - -bool BotSupport::isDoorEntity (edict_t *ent) { - if (game.isNullEntity (ent)) { - return false; - } - const auto classHash = ent->v.classname.str ().hash (); - - constexpr auto kFuncDoor = StringRef::fnv1a32 ("func_door"); - constexpr auto kFuncDoorRotating = StringRef::fnv1a32 ("func_door_rotating"); - - return classHash == kFuncDoor || classHash == kFuncDoorRotating; -} - -bool BotSupport::isHostageEntity (edict_t *ent) { - if (game.isNullEntity (ent)) { - return false; - } - const auto classHash = ent->v.classname.str ().hash (); - - constexpr auto kHostageEntity = StringRef::fnv1a32 ("hostage_entity"); - constexpr auto kMonsterScientist = StringRef::fnv1a32 ("monster_scientist"); - - return classHash == kHostageEntity || classHash == kMonsterScientist; -} - -bool BotSupport::isBreakableEntity (edict_t *ent, bool initialSeed) { - if (!initialSeed) { - if (!game.hasBreakables ()) { - return false; - } - } - - if (game.isNullEntity (ent) || ent == game.getStartEntity () || (!initialSeed && !game.isBreakableValid (ent))) { - return false; - } - const auto limit = cv_breakable_health_limit.as (); - - // not shoot-able - if (ent->v.health >= limit) { - return false; - } - constexpr auto kFuncBreakable = StringRef::fnv1a32 ("func_breakable"); - constexpr auto kFuncPushable = StringRef::fnv1a32 ("func_pushable"); - constexpr auto kFuncWall = StringRef::fnv1a32 ("func_wall"); - - if (ent->v.takedamage > 0.0f && ent->v.impulse <= 0 && !(ent->v.flags & FL_WORLDBRUSH) && !(ent->v.spawnflags & SF_BREAK_TRIGGER_ONLY)) { - const auto classHash = ent->v.classname.str ().hash (); - - if (classHash == kFuncBreakable || (classHash == kFuncPushable && (ent->v.spawnflags & SF_PUSH_BREAKABLE)) || classHash == kFuncWall) { - return ent->v.movetype == MOVETYPE_PUSH || ent->v.movetype == MOVETYPE_PUSHSTEP; - } - } - return false; -} - -bool BotSupport::isFakeClient (edict_t *ent) { - return bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT)); -} - void BotSupport::checkWelcome () { // the purpose of this function, is to send quick welcome message, to the listenserver entity. @@ -264,7 +153,7 @@ void BotSupport::checkWelcome () { const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true); auto receiveEnt = game.getLocalEntity (); - if (isAlive (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) { + if (game.isAliveEntity (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) { m_welcomeReceiveTime = game.time () + 2.0f + mp_freezetime.as (); // receive welcome message in four seconds after game has commencing } @@ -344,7 +233,7 @@ bool BotSupport::findNearestPlayer (void **pvHolder, edict_t *to, float searchDi continue; } - if ((sameTeam && client.team != game.getTeam (to)) + if ((sameTeam && client.team != game.getPlayerTeam (to)) || (needAlive && !(client.flags & ClientFlags::Alive)) || (needBot && !bots[client.ent]) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) @@ -373,7 +262,7 @@ void BotSupport::updateClients () { client.ent = player; client.flags |= ClientFlags::Used; - if (isAlive (player)) { + if (game.isAliveEntity (player)) { client.flags |= ClientFlags::Alive; } else { @@ -392,10 +281,6 @@ void BotSupport::updateClients () { } } -bool BotSupport::isModel (const edict_t *ent, StringRef model) { - return model.startsWith (ent->v.model.chars (9)); -} - String BotSupport::getCurrentDateTime () { time_t ticks = time (&ticks); tm timeinfo {}; @@ -409,7 +294,7 @@ String BotSupport::getCurrentDateTime () { } StringRef BotSupport::getFakeSteamId (edict_t *ent) { - if (!cv_enable_fake_steamids || !isPlayer (ent)) { + if (!cv_enable_fake_steamids || !game.isPlayerEntity (ent)) { return "BOT"; } auto botNameHash = StringRef::fnv1a32 (ent->v.netname.chars ()); @@ -479,3 +364,118 @@ void BotSupport::setCustomCvarDescriptions () { }); game.setCvarDescription (cv_restricted_weapons, restrictInfo); } + +bool BotSupport::isLineBlockedBySmoke (const Vector &from, const Vector &to) { + if (!gameState.hasActiveGrenades ()) { + return false; + } + constexpr auto kSmokeGrenadeRadius = 115.0f; + + // distance along line of sight covered by smoke + float totalSmokedLength = 0.0f; + + Vector sightDir = to - from; + const float sightLength = sightDir.normalizeInPlace (); + + for (auto pent : gameState.getActiveGrenades ()) { + if (game.isNullEntity (pent)) { + continue; + } + + // need drawn models + if (pent->v.effects & EF_NODRAW) { + continue; + } + + // smoke must be on a ground + if (!(pent->v.flags & FL_ONGROUND)) { + continue; + } + + // must be a smoke grenade + if (!game.isEntityModelMatches (pent, kSmokeModelName)) { + continue; + } + + const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius); + const Vector &smokeOrigin = game.getEntityOrigin (pent); + + Vector toGrenade = smokeOrigin - from; + float alongDist = toGrenade | sightDir; + + // compute closest point to grenade along line of sight ray + Vector close {}; + + // constrain closest point to line segment + if (alongDist < 0.0f) { + close = from; + } + else if (alongDist >= sightLength) { + close = to; + } + else { + close = from + sightDir * alongDist; + } + + // if closest point is within smoke radius, the line overlaps the smoke cloud + Vector toClose = close - smokeOrigin; + float lengthSq = toClose.lengthSq (); + + if (lengthSq < smokeRadiusSq) { + // some portion of the ray intersects the cloud + + const float fromSq = toGrenade.lengthSq (); + const float toSq = (smokeOrigin - to).lengthSq (); + + if (fromSq < smokeRadiusSq) { + if (toSq < smokeRadiusSq) { + // both 'from' and 'to' lie within the cloud + // entire length is smoked + totalSmokedLength += (to - from).length (); + } + else { + // 'from' is inside the cloud, 'to' is outside + // compute half of total smoked length as if ray crosses entire cloud chord + float halfSmokedLength = cr::sqrtf (smokeRadiusSq - lengthSq); + + if (alongDist > 0.0f) { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - from).length (); + } + else { + // ray starts after 'close' + totalSmokedLength += halfSmokedLength - (close - from).length (); + } + + } + } + else if (toSq < smokeRadiusSq) { + // 'from' is outside the cloud, 'to' is inside + // compute half of total smoked length as if ray crosses entire cloud chord + const float halfSmokedLength = cr::sqrtf (smokeRadiusSq - lengthSq); + Vector v = to - smokeOrigin; + + if ((v | sightDir) > 0.0f) { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - to).length (); + } + else { + // ray ends before 'close' + totalSmokedLength += halfSmokedLength - (close - to).length (); + } + } + else { + // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it + // determine the length of the chord that crosses the cloud + const float smokedLength = 2.0f * cr::sqrtf (smokeRadiusSq - lengthSq); + totalSmokedLength += smokedLength; + } + } + } + + // define how much smoke a bot can see thru + const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius; + + // return true if the total length of smoke-covered line-of-sight is too much + return totalSmokedLength > maxSmokedLength; +} diff --git a/src/tasks.cpp b/src/tasks.cpp index 5dbac9f..b264f94 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -28,12 +28,18 @@ void Bot::normal_ () { getTask ()->data = debugGoal; m_chosenGoalIndex = debugGoal; } + const auto &debugOrigin = graph[debugGoal].origin; + const auto distanceToDebugOriginSq = debugOrigin.distanceSq (pev->origin); + + if (!isDucking () && distanceToDebugOriginSq < cr::sqrf (172.0f)) { + m_moveSpeed = pev->maxspeed * 0.4f; + } // stop the bot if precisely reached debug goal if (m_currentNodeIndex == debugGoal) { - const auto &debugOrigin = graph[debugGoal].origin; + if (distanceToDebugOriginSq < cr::sqrf (22.0f) + && util.isVisible (debugOrigin, ent ())) { - if (debugOrigin.distanceSq2d (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) { m_moveToGoal = false; m_checkTerrain = false; @@ -48,7 +54,7 @@ void Bot::normal_ () { // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) if (cv_random_knife_attacks && usesKnife () - && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) + && (game.isNullEntity (m_lastEnemy) || !game.isAliveEntity (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !m_hasHostage @@ -71,7 +77,7 @@ void Bot::normal_ () { // if bomb planted and it's a CT calculate new path to bomb point if he's not already heading for if (!m_bombSearchOverridden - && bots.isBombPlanted () + && gameState.isBombPlanted () && m_team == Team::CT && getTask ()->data != kInvalidNodeIndex && !(graph[getTask ()->data].flags & NodeFlag::Goal) @@ -84,7 +90,7 @@ void Bot::normal_ () { // reached the destination (goal) node? if (updateNavigation ()) { // if we're reached the goal, and there is not enemies, notify the team - if (!bots.isBombPlanted () + if (!gameState.isBombPlanted () && m_currentNodeIndex != kInvalidNodeIndex && (m_pathFlags & NodeFlag::Goal) && rg.chance (15) @@ -106,7 +112,7 @@ void Bot::normal_ () { && m_moveSpeed >= getShiftSpeed () && game.isNullEntity (m_pickupItem)) { - if (!(game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_team == Team::CT)) { + if (!(game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted () && m_team == Team::CT)) { startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.time () + 1.0f, false); } } @@ -134,7 +140,7 @@ void Bot::normal_ () { } // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp - if (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4)) { + if (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !gameState.isBombPlanted () && m_hasC4)) { campingAllowed = false; } @@ -218,7 +224,7 @@ void Bot::normal_ () { } } else if (m_team == Team::CT) { - if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { + if (!gameState.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { const int index = findDefendNode (m_path->origin); float campTime = rg (25.0f, 40.0f); @@ -258,7 +264,7 @@ void Bot::normal_ () { auto pathSearchType = m_pathType; // override with fast path - if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) { + if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted ()) { pathSearchType = rg.chance (80) ? FindPath::Fast : FindPath::Optimal; } ensureCurrentNodeIndex (); @@ -280,7 +286,7 @@ void Bot::normal_ () { && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () - && !bots.isBombPlanted ()) { + && !gameState.isBombPlanted ()) { m_moveSpeed = shiftSpeed; } @@ -288,7 +294,7 @@ void Bot::normal_ () { // bot hasn't seen anything in a long time and is asking his teammates to report in if (cv_radio_mode.as () > 1 && bots.getLastRadio (m_team) != Radio::ReportInTeam - && bots.getRoundStartTime () + 20.0f < game.time () + && gameState.getRoundStartTime () + 20.0f < game.time () && m_askCheckTime < game.time () && rg.chance (15) && m_seeEnemyTime + rg (45.0f, 80.0f) < game.time () && numFriendsNear (pev->origin, 1024.0f) == 0) { @@ -357,7 +363,7 @@ void Bot::huntEnemy_ () { clearTask (Task::Hunt); m_prevGoalIndex = kInvalidNodeIndex; } - else if (game.getTeam (m_lastEnemy) == m_team) { + else if (game.getPlayerTeam (m_lastEnemy) == m_team) { // don't hunt down our teammate... clearTask (Task::Hunt); @@ -413,7 +419,7 @@ void Bot::huntEnemy_ () { void Bot::seekCover_ () { m_aimFlags |= AimFlags::Nav; - if (!util.isAlive (m_lastEnemy)) { + if (!game.isAliveEntity (m_lastEnemy)) { completeTask (); m_prevGoalIndex = kInvalidNodeIndex; } @@ -565,7 +571,7 @@ void Bot::blind_ () { if (rg.chance (50) && m_difficulty >= Difficulty::Normal && !m_lastEnemyOrigin.empty () - && util.isPlayer (m_lastEnemy) + && game.isPlayerEntity (m_lastEnemy) && !usesSniper ()) { auto error = kSprayDistance * m_lastEnemyOrigin.distance (pev->origin) / 2048.0f; @@ -624,7 +630,7 @@ void Bot::camp_ () { m_checkTerrain = false; m_moveToGoal = false; - if (m_team == Team::CT && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (graph.getBombOrigin ()) && !isOutOfBombTimer ()) { + if (m_team == Team::CT && gameState.isBombPlanted () && m_defendedBomb && !isBombDefusing (gameState.getBombOrigin ()) && !isOutOfBombTimer ()) { m_defendedBomb = false; completeTask (); } @@ -644,7 +650,7 @@ void Bot::camp_ () { // random camp dir, or prediction auto useRandomCampDirOrPredictEnemy = [&] () { - if (!m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)) { + if (!m_lastEnemyOrigin.empty () && game.isAliveEntity (m_lastEnemy)) { auto pathLength = m_lastPredictLength; auto predictNode = m_lastPredictIndex; @@ -843,7 +849,7 @@ void Bot::plantBomb_ () { selectWeaponById (Weapon::C4); } - if (util.isAlive (m_enemy) || !m_inBombZone) { + if (game.isAliveEntity (m_enemy) || !m_inBombZone) { completeTask (); } else { @@ -886,7 +892,7 @@ void Bot::plantBomb_ () { void Bot::defuseBomb_ () { const float fullDefuseTime = m_hasDefuser ? 7.0f : 12.0f; - const float timeToBlowUp = getBombTimeleft (); + const float timeToBlowUp = gameState.getBombTimeLeft (); float defuseRemainingTime = fullDefuseTime; @@ -894,7 +900,7 @@ void Bot::defuseBomb_ () { defuseRemainingTime = fullDefuseTime - game.time (); } - const auto &bombPos = graph.getBombOrigin (); + const auto &bombPos = gameState.getBombOrigin (); bool defuseError = false; // exception: bomb has been defused @@ -908,7 +914,7 @@ void Bot::defuseBomb_ () { startTask (Task::MoveToPosition, TaskPri::MoveToPosition, defendPoint, game.time () + rg (3.0f, 6.0f), true); // push move command } } - graph.setBombOrigin (true); + gameState.setBombOrigin (true); if (m_numFriendsLeft != 0 && rg.chance (50)) { if (timeToBlowUp <= 3.0f) { @@ -1064,7 +1070,7 @@ void Bot::defuseBomb_ () { } void Bot::followUser_ () { - if (game.isNullEntity (m_targetEntity) || !util.isAlive (m_targetEntity)) { + if (game.isNullEntity (m_targetEntity) || !game.isAliveEntity (m_targetEntity)) { m_targetEntity = nullptr; completeTask (); @@ -1075,7 +1081,7 @@ void Bot::followUser_ () { TraceResult tr {}; game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, m_targetEntity->v.v_angle.forward () * 500.0f, TraceIgnore::Everything, ent (), &tr); - if (!game.isNullEntity (tr.pHit) && util.isPlayer (tr.pHit) && game.getTeam (tr.pHit) != m_team) { + if (!game.isNullEntity (tr.pHit) && game.isPlayerEntity (tr.pHit) && game.getPlayerTeam (tr.pHit) != m_team) { m_targetEntity = nullptr; m_lastEnemy = tr.pHit; m_lastEnemyOrigin = tr.pHit->v.origin; @@ -1325,7 +1331,7 @@ void Bot::throwSmoke_ () { } void Bot::doublejump_ () { - if (!util.isAlive (m_doubleJumpEntity) + if (!game.isAliveEntity (m_doubleJumpEntity) || (m_aimFlags & AimFlags::Enemy) || (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.time ())) { @@ -1399,7 +1405,7 @@ void Bot::doublejump_ () { void Bot::escapeFromBomb_ () { m_aimFlags |= AimFlags::Nav; - if (!bots.isBombPlanted ()) { + if (!gameState.isBombPlanted ()) { completeTask (); } @@ -1407,7 +1413,7 @@ void Bot::escapeFromBomb_ () { pev->button |= IN_ATTACK2; } - if (!usesKnife () && game.isNullEntity (m_enemy) && !util.isAlive (m_lastEnemy)) { + if (!usesKnife () && game.isNullEntity (m_enemy) && !game.isAliveEntity (m_lastEnemy)) { selectWeaponById (Weapon::Knife); } @@ -1432,7 +1438,7 @@ void Bot::escapeFromBomb_ () { float nearestDistanceSq = kInfiniteDistance; for (const auto &path : graph) { - if (path.origin.distanceSq (graph.getBombOrigin ()) < cr::sqrf (safeRadius) || isOccupiedNode (path.number)) { + if (path.origin.distanceSq (gameState.getBombOrigin ()) < cr::sqrf (safeRadius) || isOccupiedNode (path.number)) { continue; } const float distanceSq = pev->origin.distanceSq (path.origin); @@ -1465,7 +1471,7 @@ void Bot::escapeFromBomb_ () { void Bot::shootBreakable_ () { // breakable destroyed? - if (!util.isBreakableEntity (m_breakableEntity)) { + if (!game.isBreakableEntity (m_breakableEntity)) { completeTask (); return; } @@ -1647,7 +1653,7 @@ void Bot::pickupItem_ () { case Pickup::Hostage: m_aimFlags |= AimFlags::Entity; - if (!util.isAlive (m_pickupItem)) { + if (!game.isAliveEntity (m_pickupItem)) { // don't pickup dead hostages m_pickupItem = nullptr; completeTask (); @@ -1676,7 +1682,7 @@ void Bot::pickupItem_ () { // find the nearest 'unused' hostage within the area game.searchEntities (pev->origin, 1024.0f, [&] (edict_t *ent) { - if (!util.isHostageEntity (ent)) { + if (!game.isHostageEntity (ent)) { return EntitySearchResult::Continue; } diff --git a/src/vision.cpp b/src/vision.cpp index d016392..91b91bd 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -445,8 +445,8 @@ void Bot::setAimDirection () { && m_forgetLastVictimTimer.elapsed () && !m_lastEnemyOrigin.empty () - && util.isPlayer (m_lastEnemy) - && !util.isPlayer (m_enemy)) { + && game.isPlayerEntity (m_lastEnemy) + && !game.isPlayerEntity (m_enemy)) { flags |= AimFlags::LastEnemy; } @@ -503,7 +503,7 @@ void Bot::setAimDirection () { else if (flags & AimFlags::PredictPath) { bool changePredictedEnemy = true; - if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { + if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && game.isAliveEntity (m_lastEnemy)) { changePredictedEnemy = false; } @@ -573,7 +573,9 @@ void Bot::setAimDirection () { const auto &destOrigin = m_destOrigin + pev->view_ofs; m_lookAt = destOrigin; - if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () + const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder (); + + if (!horizontalMovement && m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) @@ -598,7 +600,6 @@ void Bot::setAimDirection () { else { m_lookAt = destOrigin; } - const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder (); if (m_numEnemiesLeft > 0 && m_canChooseAimDirection @@ -625,14 +626,17 @@ void Bot::setAimDirection () { } // try look at next node if on ladder - if (horizontalMovement && m_pathWalk.hasNext ()) { + if (horizontalMovement + && m_pathWalk.hasNext () + && !(m_currentTravelFlags & PathFlag::Jump)) { + const auto &nextPath = graph[m_pathWalk.next ()]; if ((nextPath.flags & NodeFlag::Ladder) - && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (128.0f) - && nextPath.origin.z > m_pathOrigin.z + 26.0f) { + && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (64.0f) + && nextPath.origin.z > m_pathOrigin.z + 30.0f) { - m_lookAt = nextPath.origin + pev->view_ofs; + m_lookAt = nextPath.origin; } } diff --git a/src/vistable.cpp b/src/vistable.cpp index c4e815a..92d6d4f 100644 --- a/src/vistable.cpp +++ b/src/vistable.cpp @@ -125,7 +125,9 @@ void GraphVistable::rebuild () { m_sliceIndex += rg (250, 400); } auto notifyProgress = [] (int value) { - game.print ("Rebuilding vistable... %d%% done.", value); + if (value >= 100 || cv_debug) { + game.print ("Rebuilding vistable... %d%% done.", value); + } }; // notify host about rebuilding @@ -139,6 +141,7 @@ void GraphVistable::rebuild () { m_rebuild = false; m_notifyMsgTimestamp = 0.0f; + m_curIndex = 0; save (); } @@ -149,7 +152,7 @@ void GraphVistable::startRebuild () { m_notifyMsgTimestamp = game.time (); } -bool GraphVistable::visible (int srcIndex, int destIndex, VisIndex vis) { +bool GraphVistable::visible (int srcIndex, int destIndex, VisIndex vis) const { if (!graph.exists (srcIndex) || !graph.exists (destIndex)) { return false; }