nav: various fixes to movement code

refactor: move some things into new game state class
This commit is contained in:
jeefo 2025-10-08 20:12:46 +03:00
commit 7b378ba3fa
No known key found for this signature in database
GPG key ID: D696786B81B667C8
29 changed files with 805 additions and 745 deletions

View file

@ -447,7 +447,7 @@ constexpr auto kSprayDistanceX2 = kSprayDistance * 2;
constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kMaxChatterRepeatInterval = 99.0f;
constexpr auto kViewFrameUpdate = 1.0f / 25.0f; constexpr auto kViewFrameUpdate = 1.0f / 25.0f;
constexpr auto kGrenadeDamageRadius = 385.0f; constexpr auto kGrenadeDamageRadius = 385.0f;
constexpr auto kMinMovedDistance = 3.0f; constexpr auto kMinMovedDistance = 2.5f;
constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance); constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
constexpr auto kMaxWeapons = 32; constexpr auto kMaxWeapons = 32;

View file

@ -265,7 +265,7 @@ public:
void searchEntities (const Vector &position, float radius, EntitySearch functor) const; void searchEntities (const Vector &position, float radius, EntitySearch functor) const;
// check if map has entity // check if map has entity
bool hasEntityInGame (StringRef classname); bool hasEntityInGame (StringRef classname) const;
// print the version to server console on startup // print the version to server console on startup
void printBotVersion () const; void printBotVersion () const;
@ -282,6 +282,38 @@ public:
// is developer mode ? // is developer mode ?
bool isDeveloperMode () const; 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 inlines
public: public:
// get the current time on server // get the current time on server
@ -343,7 +375,7 @@ public:
} }
// get the wroldspawn entity // get the wroldspawn entity
edict_t *getStartEntity () { edict_t *getStartEntity () const {
return m_startEntity; return m_startEntity;
} }
@ -353,7 +385,7 @@ public:
} }
// gets the player team // gets the player team
int getTeam (edict_t *ent) const { int getPlayerTeam (edict_t *ent) const {
if (isNullEntity (ent)) { if (isNullEntity (ent)) {
return Team::Unassigned; return Team::Unassigned;
} }
@ -361,7 +393,7 @@ public:
} }
// gets the player team (real in ffa) // gets the player team (real in ffa)
int getRealTeam (edict_t *ent) const { int getRealPlayerTeam (edict_t *ent) const {
if (isNullEntity (ent)) { if (isNullEntity (ent)) {
return Team::Unassigned; return Team::Unassigned;
} }
@ -369,8 +401,8 @@ public:
} }
// get real gamedll team (matches gamedll indices) // get real gamedll team (matches gamedll indices)
int getGameTeam (edict_t *ent) const { int getPlayerTeamGame (edict_t *ent) const {
return getRealTeam (ent) + 1; return getRealPlayerTeam (ent) + 1;
} }
// sets the precache to uninitialized // 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 <GameState> {
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 <edict_t *> m_activeGrenades {}; // holds currently active grenades on the map
Array <edict_t *> 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 <edict_t *> &getActiveGrenades () {
return m_activeGrenades;
}
const Array <edict_t *> &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 // expose globals
CR_EXPOSE_GLOBAL_SINGLETON (Game, game); CR_EXPOSE_GLOBAL_SINGLETON (Game, game);
CR_EXPOSE_GLOBAL_SINGLETON (GameState, gameState);
CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum); CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum);

View file

@ -173,7 +173,6 @@ private:
Vector m_learnVelocity {}; Vector m_learnVelocity {};
Vector m_learnPosition {}; Vector m_learnPosition {};
Vector m_bombOrigin {};
Vector m_lastNode {}; Vector m_lastNode {};
IntArray m_terrorPoints {}; IntArray m_terrorPoints {};
@ -253,7 +252,6 @@ public:
void clearVisited (); void clearVisited ();
void eraseFromBucket (const Vector &pos, int index); void eraseFromBucket (const Vector &pos, int index);
void setBombOrigin (bool reset = false, const Vector &pos = nullptr);
void unassignPath (int from, int to); void unassignPath (int from, int to);
void convertFromPOD (Path &path, const PODPath &pod) const; void convertFromPOD (Path &path, const PODPath &pod) const;
void convertToPOD (const Path &path, PODPath &pod); void convertToPOD (const Path &path, PODPath &pod);
@ -292,10 +290,6 @@ public:
m_editFlags &= ~flag; m_editFlags &= ~flag;
} }
const Vector &getBombOrigin () const {
return m_bombOrigin;
}
// access paths // access paths
Path &operator [] (int index) { Path &operator [] (int index) {
return m_paths[index]; return m_paths[index];

View file

@ -24,32 +24,19 @@ public:
using UniqueBot = UniquePtr <Bot>; using UniqueBot = UniquePtr <Bot>;
private: 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_difficultyBalanceTime {}; // time to balance difficulties ?
float m_autoKillCheckTime {}; // time to kill all the bots ? float m_autoKillCheckTime {}; // time to kill all the bots ?
float m_maintainTime {}; // time to maintain bot creation float m_maintainTime {}; // time to maintain bot creation
float m_quotaMaintainTime {}; // time to maintain bot quota 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_plantSearchUpdateTime {}; // time to update for searching planted bomb
float m_lastChatTime {}; // global chat time timestamp 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_lastWinner {}; // the team who won previous round
int m_lastDifficulty {}; // last bots difficulty int m_lastDifficulty {}; // last bots difficulty
int m_bombSayStatus {}; // some bot is issued whine about bomb int m_bombSayStatus {}; // some bot is issued whine about bomb
int m_numPreviousPlayers {}; // number of players in game im previous player check 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_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 <edict_t *> m_activeGrenades {}; // holds currently active grenades on the map
Array <edict_t *> m_interestingEntities {}; // holds currently interesting entities on the map
Deque <String> m_saveBotNames {}; // bots names that persist upon changelevel Deque <String> m_saveBotNames {}; // bots names that persist upon changelevel
Deque <BotRequest> m_addRequests {}; // bot creation tab Deque <BotRequest> m_addRequests {}; // bot creation tab
@ -81,10 +68,9 @@ public:
int getAliveHumansCount (); int getAliveHumansCount ();
int getPlayerPriority (edict_t *ent); int getPlayerPriority (edict_t *ent);
float getConnectTime (StringRef name, float original); float getConnectionTimes (StringRef name, float original);
float getAverageTeamKPD (bool calcForBots); float getAverageTeamKPD (bool calcForBots);
void setBombPlanted (bool isPlanted);
void frame (); void frame ();
void createKillerEntity (); void createKillerEntity ();
void destroyKillerEntity (); void destroyKillerEntity ();
@ -113,8 +99,6 @@ public:
void reset (); void reset ();
void initFilters (); void initFilters ();
void resetFilters (); void resetFilters ();
void updateActiveGrenade ();
void updateInterestingEntities ();
void captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent); void captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent);
void notifyBombDefuse (); void notifyBombDefuse ();
void execGameEntity (edict_t *ent); void execGameEntity (edict_t *ent);
@ -130,27 +114,10 @@ public:
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
bool balancedKickRandom (bool decQuota); bool balancedKickRandom (bool decQuota);
bool hasCustomCSDMSpawnEntities (); bool hasCustomCSDMSpawnEntities ();
bool isLineBlockedBySmoke (const Vector &from, const Vector &to);
bool isFrameSkipDisabled (); bool isFrameSkipDisabled ();
public: public:
const Array <edict_t *> &getActiveGrenades () { bool getTeamEconomics (int team) const {
return m_activeGrenades;
}
const Array <edict_t *> &getInterestingEntities () {
return m_interestingEntities;
}
bool hasActiveGrenades () const {
return !m_activeGrenades.empty ();
}
bool hasInterestingEntities () const {
return !m_interestingEntities.empty ();
}
bool checkTeamEco (int team) const {
return m_teamData[team].positiveEco; return m_teamData[team].positiveEco;
} }
@ -171,30 +138,6 @@ public:
addbot ("", -1, -1, -1, -1, manual); 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 { bool canPause () const {
return m_botsCanPause; return m_botsCanPause;
} }
@ -236,10 +179,6 @@ public:
m_teamData[team].lastRadioSlot = radio; m_teamData[team].lastRadioSlot = radio;
} }
void setResetHUD (bool resetHud) {
m_resetHud = resetHud;
}
int getLastRadio (const int team) const { int getLastRadio (const int team) const {
return m_teamData[team].lastRadioSlot; return m_teamData[team].lastRadioSlot;
} }

View file

@ -108,7 +108,7 @@ public:
void setDamage (int32_t team, int32_t start, int32_t goal, int32_t value); void setDamage (int32_t team, int32_t start, int32_t goal, int32_t value);
// interlocked get damage // 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: public:
void update (); void update ();

View file

@ -31,36 +31,6 @@ public:
// converts weapon id to alias name // converts weapon id to alias name
StringRef weaponIdToAlias (int32_t id); 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 // 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); 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 // update stats on clients
void updateClients (); void updateClients ();
// checks if same model omitting the models directory // check if origin is visible from the entity side
bool isModel (const edict_t *ent, StringRef model); bool isVisible (const Vector &origin, edict_t *ent);
// get the current date and time as string // get the current date and time as string
String getCurrentDateTime (); String getCurrentDateTime ();
@ -85,6 +55,9 @@ public:
// set custom cvar descriptions // set custom cvar descriptions
void setCustomCvarDescriptions (); void setCustomCvarDescriptions ();
// check if line of sight blocked by a smoke
bool isLineBlockedBySmoke (const Vector &from, const Vector &to);
public: public:
// re-show welcome after changelevel ? // re-show welcome after changelevel ?

View file

@ -39,7 +39,7 @@ public:
~GraphVistable () = default; ~GraphVistable () = default;
public: 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 load ();
void save () const; void save () const;

View file

@ -400,7 +400,7 @@ private:
int numEnemiesNear (const Vector &origin, const float radius) const; int numEnemiesNear (const Vector &origin, const float radius) const;
int numFriendsNear (const Vector &origin, const float radius) const; int numFriendsNear (const Vector &origin, const float radius) const;
float getBombTimeleft () const;
float getEstimatedNodeReachTime (); float getEstimatedNodeReachTime ();
float isInFOV (const Vector &dest) const; float isInFOV (const Vector &dest) const;
float getShiftSpeed (); float getShiftSpeed ();

View file

@ -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); game.testHull (pos, { next.x, next.y, next.z + 19.0f }, TraceIgnore::Monsters, head_hull, nullptr, &tr);
// we're can't reach next point // 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; return;
} }

View file

@ -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_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_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_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 // game console variables
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef); ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
@ -78,10 +78,10 @@ void Bot::avoidGrenades () {
m_needAvoidGrenade = 0; m_needAvoidGrenade = 0;
} }
if (!bots.hasActiveGrenades ()) { if (!gameState.hasActiveGrenades ()) {
return; return;
} }
const auto &activeGrenades = bots.getActiveGrenades (); const auto &activeGrenades = gameState.getActiveGrenades ();
// find all grenades on the map // find all grenades on the map
for (const auto &pent : activeGrenades) { for (const auto &pent : activeGrenades) {
@ -105,7 +105,7 @@ void Bot::avoidGrenades () {
} }
} }
else if (game.isNullEntity (m_avoidGrenade) && model == kExplosiveModelName) { 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; continue;
} }
@ -209,7 +209,7 @@ void Bot::checkBreakablesAround () {
continue; continue;
} }
if (!util.isBreakableEntity (breakable)) { if (!game.isBreakableEntity (breakable)) {
continue; 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 // this function checks if bot is blocked by a shoot able breakable in his moving direction
// we're got something already // we're got something already
if (util.isBreakableEntity (m_breakableEntity)) { if (game.isBreakableEntity (m_breakableEntity)) {
return m_breakableEntity; return m_breakableEntity;
} }
const float detectBreakableDistance = (usesKnife () || isOnLadder ()) ? 32.0f : rg (72.0f, 256.0f); const float detectBreakableDistance = (usesKnife () || isOnLadder ()) ? 32.0f : rg (72.0f, 256.0f);
@ -270,7 +270,7 @@ edict_t *Bot::lookupBreakable () {
auto hit = tr.pHit; auto hit = tr.pHit;
// check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap! // 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_breakableOrigin = game.getEntityOrigin (hit);
m_breakableEntity = hit; m_breakableEntity = hit;
@ -286,7 +286,7 @@ edict_t *Bot::lookupBreakable () {
} }
// check breakable team, needed for some plugins // 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; return false;
} }
@ -362,7 +362,7 @@ void Bot::updatePickups () {
} }
// no interesting entities, how ? // no interesting entities, how ?
else if (!bots.hasInterestingEntities ()) { else if (!gameState.hasInterestingEntities ()) {
return true; return true;
} }
return false; return false;
@ -376,7 +376,7 @@ void Bot::updatePickups () {
return; return;
} }
const auto &interesting = bots.getInterestingEntities (); const auto &interesting = gameState.getInterestingEntities ();
const float radiusSq = cr::sqrf (cv_object_pickup_radius.as <float> ()); const float radiusSq = cr::sqrf (cv_object_pickup_radius.as <float> ());
if (!game.isNullEntity (m_pickupItem)) { if (!game.isNullEntity (m_pickupItem)) {
@ -446,7 +446,7 @@ void Bot::updatePickups () {
const bool isHostageRescueMap = game.mapIs (MapFlags::HostageRescue); const bool isHostageRescueMap = game.mapIs (MapFlags::HostageRescue);
const bool isCSDM = game.is (GameFlags::CSDM); const bool isCSDM = game.is (GameFlags::CSDM);
if (isHostageRescueMap && util.isHostageEntity (ent)) { if (isHostageRescueMap && game.isHostageEntity (ent)) {
allowPickup = true; allowPickup = true;
pickupType = Pickup::Hostage; pickupType = Pickup::Hostage;
} }
@ -551,7 +551,7 @@ void Bot::updatePickups () {
allowPickup = true; allowPickup = true;
pickupType = Pickup::PlantedC4; 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; allowPickup = true;
pickupType = Pickup::Items; pickupType = Pickup::Items;
} }
@ -632,7 +632,7 @@ void Bot::updatePickups () {
const auto &path = graph[index]; const auto &path = graph[index];
const float bombTimer = mp_c4timer.as <float> (); const float bombTimer = mp_c4timer.as <float> ();
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 ()) { if (timeMidBlowup > game.time ()) {
clearTask (Task::MoveToPosition); // remove any move tasks clearTask (Task::MoveToPosition); // remove any move tasks
@ -683,7 +683,7 @@ void Bot::updatePickups () {
} }
} }
else if (pickupType == Pickup::PlantedC4) { else if (pickupType == Pickup::PlantedC4) {
if (util.isAlive (m_enemy)) { if (game.isAliveEntity (m_enemy)) {
return; return;
} }
@ -706,7 +706,7 @@ void Bot::updatePickups () {
const int index = findDefendNode (origin); const int index = findDefendNode (origin);
const auto &path = graph[index]; const auto &path = graph[index];
const float timeToExplode = bots.getTimeBombPlanted () + mp_c4timer.as <float> () - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); const float timeToExplode = gameState.getTimeBombPlanted () + mp_c4timer.as <float> () - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
clearTask (Task::MoveToPosition); // remove any move tasks clearTask (Task::MoveToPosition); // remove any move tasks
@ -1294,7 +1294,7 @@ void Bot::buyStuff () {
const auto tab = conf.getRawWeapons (); const auto tab = conf.getRawWeapons ();
const bool isPistolMode = tab[25].teamStandard == -1 && tab[3].teamStandard == 2; 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 // 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); const bool isOldGame = game.is (GameFlags::Legacy);
@ -1696,7 +1696,7 @@ void Bot::overrideConditions () {
// check if we need to escape from bomb // check if we need to escape from bomb
if ((tid == Task::Normal || tid == Task::MoveToPosition) if ((tid == Task::Normal || tid == Task::MoveToPosition)
&& game.mapIs (MapFlags::Demolition) && game.mapIs (MapFlags::Demolition)
&& bots.isBombPlanted () && gameState.isBombPlanted ()
&& m_isAlive && m_isAlive
&& tid != Task::EscapeFromBomb && tid != Task::EscapeFromBomb
&& tid != Task::Camp && tid != Task::Camp
@ -1710,7 +1710,7 @@ void Bot::overrideConditions () {
float reachEnemyKnifeDistanceSq = cr::sqrf (128.0f); float reachEnemyKnifeDistanceSq = cr::sqrf (128.0f);
// special handling, if we have a knife in our hands // 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 distanceSq2d = pev->origin.distanceSq2d (m_enemy->v.origin);
const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin); const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
@ -1760,7 +1760,7 @@ void Bot::overrideConditions () {
} }
// special handling for reloading // special handling for reloading
if (!bots.isRoundOver () if (!gameState.isRoundOver ()
&& tid == Task::Normal && tid == Task::Normal
&& m_reloadState != Reload::None && m_reloadState != Reload::None
&& m_isReloading && m_isReloading
@ -1787,7 +1787,7 @@ void Bot::overrideConditions () {
if (game.is (GameFlags::ZombieMod) if (game.is (GameFlags::ZombieMod)
&& !m_isCreature && !m_isCreature
&& m_infectedEnemyTeam && m_infectedEnemyTeam
&& util.isAlive (m_enemy) && game.isAliveEntity (m_enemy)
&& m_retreatTime < game.time () && m_retreatTime < game.time ()
&& pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (512.0f)) { && 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 &lastEnemyOrigin = m_lastEnemyOrigin;
const auto currentNodeIndex = m_currentNodeIndex; const auto currentNodeIndex = m_currentNodeIndex;
if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { if (lastEnemyOrigin.empty () || !vistab.isReady () || !game.isAliveEntity (m_lastEnemy)) {
wipePredict (); wipePredict ();
return; return;
} }
@ -1849,7 +1849,7 @@ void Bot::syncUpdatePredictedIndex () {
} }
void Bot::updatePredictedIndex () { 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 return; // do not run task if no last enemy
} }
@ -1904,7 +1904,7 @@ void Bot::setConditions () {
// did bot just kill an enemy? // did bot just kill an enemy?
if (!game.isNullEntity (m_lastVictim)) { 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 // add some aggression because we just killed somebody
m_agressionLevel += 0.1f; 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 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); selectWeaponById (Weapon::Knife);
m_plantedBombNodeIndex = getNearestToPlantedBomb (); m_plantedBombNodeIndex = getNearestToPlantedBomb ();
@ -1990,7 +1990,7 @@ void Bot::setConditions () {
// check if our current enemy is still valid // check if our current enemy is still valid
if (!game.isNullEntity (m_lastEnemy)) { 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; m_lastEnemy = nullptr;
} }
} }
@ -2089,7 +2089,7 @@ void Bot::filterTasks () {
float &blindedDesire = filter[Task::Blind].desire; float &blindedDesire = filter[Task::Blind].desire;
// calculate desires to seek cover or hunt // 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 const float retreatLevel = (100.0f - (m_healthValue > 70.0f ? 100.0f : m_healthValue)) * tempFear; // retreat level depends on bot health
if (m_isCreature || if (m_isCreature ||
@ -2117,7 +2117,7 @@ void Bot::filterTasks () {
if (m_isCreature) { if (m_isCreature) {
ratio = 0.0f; 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 ratio /= 3.0f; // reduce the seek cover desire if bomb is planted
} }
else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) { else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) {
@ -2139,7 +2139,7 @@ void Bot::filterTasks () {
if (getCurrentTaskId () != Task::EscapeFromBomb if (getCurrentTaskId () != Task::EscapeFromBomb
&& game.isNullEntity (m_enemy) && game.isNullEntity (m_enemy)
&& !m_isVIP && !m_isVIP
&& bots.getRoundMidTime () < game.time () && gameState.getRoundMidTime () < game.time ()
&& !m_hasHostage && !m_hasHostage
&& !m_isUsingGrenade && !m_isUsingGrenade
&& m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin)
@ -2256,10 +2256,11 @@ void Bot::startTask (Task id, float desire, int data, float time, bool resume) {
} }
return; return;
} }
else {
clearSearchNodes ();
}
} }
m_tasks.emplace (filter[id].func, id, desire, data, time, resume); m_tasks.emplace (filter[id].func, id, desire, data, time, resume);
clearSearchNodes ();
ignoreCollision (); ignoreCollision ();
const auto tid = getCurrentTaskId (); const auto tid = getCurrentTaskId ();
@ -2438,7 +2439,7 @@ void Bot::handleChatterTaskChange (Task tid) {
} }
if (rg.chance (25) && tid == Task::Camp) { if (rg.chance (25) && tid == Task::Camp) {
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) { if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted ()) {
pushChatterMessage (Chatter::GuardingPlantedC4); pushChatterMessage (Chatter::GuardingPlantedC4);
} }
else { else {
@ -2471,12 +2472,12 @@ void Bot::executeChatterFrameEvents () {
if (!hasFriendNearby && rg.chance (45) && (m_enemy->v.weapons & cr::bit (Weapon::C4))) { if (!hasFriendNearby && rg.chance (45) && (m_enemy->v.weapons & cr::bit (Weapon::C4))) {
pushChatterMessage (Chatter::SpotTheBomber); 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); pushChatterMessage (Chatter::VIPSpotted);
} }
else if (!hasFriendNearby else if (!hasFriendNearby
&& rg.chance (50) && rg.chance (50)
&& game.getTeam (m_enemy) != m_team && game.getPlayerTeam (m_enemy) != m_team
&& isGroupOfEnemies (m_enemy->v.origin)) { && isGroupOfEnemies (m_enemy->v.origin)) {
pushChatterMessage (Chatter::ScaredEmotion); pushChatterMessage (Chatter::ScaredEmotion);
@ -2495,7 +2496,7 @@ void Bot::executeChatterFrameEvents () {
} }
// if bomb planted warn players ! // 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); pushChatterMessage (Chatter::GottaFindC4);
bots.clearBombSay (BombPlantedSay::Chatter); bots.clearBombSay (BombPlantedSay::Chatter);
} }
@ -2706,7 +2707,7 @@ void Bot::checkRadioQueue () {
break; break;
case Radio::ShesGonnaBlow: 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); pushRadioMessage (Radio::RogerThat);
if (getCurrentTaskId () == Task::Camp) { if (getCurrentTaskId () == Task::Camp) {
@ -2722,11 +2723,11 @@ void Bot::checkRadioQueue () {
case Radio::RegroupTeam: case Radio::RegroupTeam:
// if no more enemies found AND bomb planted, switch to knife to get to bombplace faster // 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); selectWeaponById (Weapon::Knife);
clearSearchNodes (); clearSearchNodes ();
m_position = graph.getBombOrigin (); m_position = gameState.getBombOrigin ();
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
pushRadioMessage (Radio::RogerThat); pushRadioMessage (Radio::RogerThat);
@ -2850,7 +2851,7 @@ void Bot::checkRadioQueue () {
case Task::Camp: case Task::Camp:
if (rg.chance (m_radioPercent)) { if (rg.chance (m_radioPercent)) {
if (bots.isBombPlanted () && m_team == Team::Terrorist) { if (gameState.isBombPlanted () && m_team == Team::Terrorist) {
pushChatterMessage (Chatter::GuardingPlantedC4); pushChatterMessage (Chatter::GuardingPlantedC4);
} }
else if (m_inEscapeZone && m_team == Team::CT) { else if (m_inEscapeZone && m_team == Team::CT) {
@ -2915,14 +2916,14 @@ void Bot::checkRadioQueue () {
case Radio::SectorClear: case Radio::SectorClear:
// is bomb planted and it's a ct // is bomb planted and it's a ct
if (!bots.isBombPlanted ()) { if (!gameState.isBombPlanted ()) {
break; break;
} }
// check if it's a ct command // 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 && m_team == Team::CT
&& util.isFakeClient (m_radioEntity) && game.isFakeClientEntity (m_radioEntity)
&& bots.getPlantedBombSearchTimestamp () < game.time ()) { && bots.getPlantedBombSearchTimestamp () < game.time ()) {
float nearestDistanceSq = kInfiniteDistance; float nearestDistanceSq = kInfiniteDistance;
@ -3015,11 +3016,11 @@ void Bot::checkRadioQueue () {
void Bot::tryHeadTowardRadioMessage () { void Bot::tryHeadTowardRadioMessage () {
const auto tid = getCurrentTaskId (); 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; return;
} }
if ((util.isFakeClient (m_radioEntity) if ((game.isFakeClientEntity (m_radioEntity)
&& rg.chance (m_radioPercent) && rg.chance (m_radioPercent)
&& m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { && m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) {
@ -3064,8 +3065,8 @@ void Bot::frame () {
return; return;
} }
if (bots.isBombPlanted () && m_team == Team::CT && m_isAlive) { if (gameState.isBombPlanted () && m_team == Team::CT && m_isAlive) {
const auto &bombPosition = graph.getBombOrigin (); const auto &bombPosition = gameState.getBombOrigin ();
if (!m_hasProgressBar if (!m_hasProgressBar
&& getCurrentTaskId () != Task::EscapeFromBomb && getCurrentTaskId () != Task::EscapeFromBomb
@ -3101,8 +3102,8 @@ void Bot::update () {
const auto tid = getCurrentTaskId (); const auto tid = getCurrentTaskId ();
m_canChooseAimDirection = true; m_canChooseAimDirection = true;
m_isAlive = util.isAlive (ent ()); m_isAlive = game.isAliveEntity (ent ());
m_team = game.getTeam (ent ()); m_team = game.getPlayerTeam (ent ());
m_healthValue = cr::clamp (pev->health, 0.0f, 99999.9f); m_healthValue = cr::clamp (pev->health, 0.0f, 99999.9f);
if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) {
@ -3148,7 +3149,7 @@ void Bot::update () {
m_lastVoteKick = m_voteKickIndex; m_lastVoteKick = m_voteKickIndex;
// if bot tk punishment is enabled slay the tk // if bot tk punishment is enabled slay the tk
if (cv_tkpunish.as <int> () != 2 || util.isFakeClient (game.entityOfIndex (m_voteKickIndex))) { if (cv_tkpunish.as <int> () != 2 || game.isFakeClientEntity (game.entityOfIndex (m_voteKickIndex))) {
return; return;
} }
auto killer = game.entityOfIndex (m_lastVoteKick); auto killer = game.entityOfIndex (m_lastVoteKick);
@ -3337,7 +3338,7 @@ void Bot::checkSpawnConditions () {
void Bot::logic () { void Bot::logic () {
// this function gets called each frame and is the core of all bot ai. from here all other subroutines are called // 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 (); resetMovement ();
@ -3369,7 +3370,7 @@ void Bot::logic () {
// save current position as previous // save current position as previous
m_prevOrigin = pev->origin; 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 // if there's some radio message to respond, check it
@ -3607,7 +3608,7 @@ void Bot::showDebugOverlay () {
static hudtextparms_t textParams {}; static hudtextparms_t textParams {};
textParams.channel = 1; textParams.channel = 4;
textParams.x = -1.0f; textParams.x = -1.0f;
textParams.y = 0.0f; textParams.y = 0.0f;
textParams.effect = 0; textParams.effect = 0;
@ -3669,7 +3670,7 @@ void Bot::takeDamage (edict_t *inflictor, int damage, int armor, int bits) {
m_lastDamageType = bits; m_lastDamageType = bits;
if (m_isCreature) { if (m_isCreature) {
if (util.isPlayer (inflictor) && game.isNullEntity (m_enemy)) { if (game.isPlayerEntity (inflictor) && game.isNullEntity (m_enemy)) {
if (seesEnemy (inflictor)) { if (seesEnemy (inflictor)) {
m_enemy = inflictor; m_enemy = inflictor;
m_enemyOrigin = inflictor->v.origin; 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 (); m_lastDamageTimestamp = game.time ();
if (util.isPlayer (inflictor) || (cv_attack_monsters && util.isMonster (inflictor))) { if (game.isPlayerEntity (inflictor) || (cv_attack_monsters && game.isMonsterEntity (inflictor))) {
const auto inflictorTeam = game.getTeam (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!!! // alright, die you team killer!!!
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
m_seeEnemyTime = game.time (); m_seeEnemyTime = game.time ();
@ -3750,7 +3751,7 @@ void Bot::takeBlind (int alpha) {
m_viewDistance = rg (10.0f, 20.0f); m_viewDistance = rg (10.0f, 20.0f);
// do not take in effect some unique map effects on round start // 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_viewDistance = m_maxViewDistance;
} }
m_blindTime = game.time () + static_cast <float> (alpha - 200) / 16.0f; m_blindTime = game.time () + static_cast <float> (alpha - 200) / 16.0f;
@ -3812,11 +3813,11 @@ void Bot::updatePracticeValue (int damage) const {
void Bot::updatePracticeDamage (edict_t *attacker, int damage) { 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. // 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; return;
} }
const int attackerTeam = game.getTeam (attacker); const int attackerTeam = game.getPlayerTeam (attacker);
const int victimTeam = m_team; const int victimTeam = m_team;
if (attackerTeam == victimTeam) { 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)); 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 // store away the damage done
const auto damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, kMaxDamageValue); 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* // 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... ) // 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_aimFlags |= AimFlags::Entity;
m_lookAt = user->v.origin; m_lookAt = user->v.origin;
@ -3962,16 +3963,16 @@ void Bot::debugMsgInternal (StringRef str) {
Vector Bot::isBombAudible () { Vector Bot::isBombAudible () {
// this function checks if bomb is can be heard by the bot, calculations done by manual testing. // 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 return nullptr; // reliability check
} }
if (m_difficulty > Difficulty::Hard) { 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 <float> ()) * 100.0f; const float timeElapsed = ((game.time () - gameState.getTimeBombPlanted ()) / mp_c4timer.as <float> ()) * 100.0f;
float desiredRadius = 768.0f; float desiredRadius = 768.0f;
// start the manual calculations // start the manual calculations
@ -4052,12 +4053,7 @@ void Bot::runMovement () {
m_oldButtons = pev->button; m_oldButtons = pev->button;
} }
float Bot::getBombTimeleft () const {
if (!bots.isBombPlanted ()) {
return 0.0f;
}
return cr::max (bots.getTimeBombPlanted () + mp_c4timer.as <float> () - game.time (), 0.0f);
}
bool Bot::isOutOfBombTimer () { bool Bot::isOutOfBombTimer () {
if (!game.mapIs (MapFlags::Demolition)) { if (!game.mapIs (MapFlags::Demolition)) {
@ -4069,13 +4065,13 @@ bool Bot::isOutOfBombTimer () {
} }
// calculate left time // 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 time left greater than 13, no need to do other checks
if (timeLeft > 13.0f) { if (timeLeft > 13.0f) {
return false; return false;
} }
const auto &bombOrigin = graph.getBombOrigin (); const auto &bombOrigin = gameState.getBombOrigin ();
// for terrorist, if timer is lower than 13 seconds, return true // 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)) { 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; 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 true;
} }
return false; // return false otherwise return false; // return false otherwise
@ -4114,7 +4110,7 @@ void Bot::updateHearing () {
float nearestDistanceSq = kInfiniteDistance; float nearestDistanceSq = kInfiniteDistance;
// do not hear to other enemies if just tracked old one // 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_hearedEnemy = m_lastEnemy;
m_lastEnemyOrigin = m_lastEnemy->v.origin; m_lastEnemyOrigin = m_lastEnemy->v.origin;
@ -4153,7 +4149,7 @@ void Bot::updateHearing () {
} }
// did the bot hear someone ? // did the bot hear someone ?
if (util.isPlayer (m_hearedEnemy)) { if (game.isPlayerEntity (m_hearedEnemy)) {
// change to best weapon if heard something // change to best weapon if heard something
if (m_shootTime < game.time () - 5.0f if (m_shootTime < game.time () - 5.0f
&& isOnFloor () && isOnFloor ()
@ -4257,8 +4253,8 @@ void Bot::enteredBuyZone (int buyState) {
if (m_seeEnemyTime + 12.0f < game.time () if (m_seeEnemyTime + 12.0f < game.time ()
&& m_lastEquipTime + 30.0f < game.time () && m_lastEquipTime + 30.0f < game.time ()
&& m_inBuyZone && m_inBuyZone
&& (bots.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as <float> () < game.time ()) && (gameState.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as <float> () < game.time ())
&& !bots.isBombPlanted () && !gameState.isBombPlanted ()
&& m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) { && m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) {
m_ignoreBuyDelay = true; m_ignoreBuyDelay = true;
@ -4297,7 +4293,7 @@ void Bot::selectCampButtons (int index) {
bool Bot::isBombDefusing (const Vector &bombOrigin) const { bool Bot::isBombDefusing (const Vector &bombOrigin) const {
// this function finds if somebody currently defusing the bomb. // this function finds if somebody currently defusing the bomb.
if (!bots.isBombPlanted ()) { if (!gameState.isBombPlanted ()) {
return false; return false;
} }
bool defusingInProgress = 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 // if bot is on infected team, and zombie mode is active, assume bot is a creature/zombie
m_isOnInfectedTeam = game.getRealTeam (ent ()) == infectedTeam; m_isOnInfectedTeam = game.getRealPlayerTeam (ent ()) == infectedTeam;
m_infectedEnemyTeam = game.getRealTeam (m_enemy) == infectedTeam; m_infectedEnemyTeam = game.getRealPlayerTeam (m_enemy) == infectedTeam;
// do not process next if already infected // do not process next if already infected
if (m_isOnInfectedTeam || m_infectedEnemyTeam) { if (m_isOnInfectedTeam || m_infectedEnemyTeam) {
@ -4451,7 +4447,7 @@ void Bot::donateC4ToHuman () {
// search world for just dropped bomb // search world for just dropped bomb
game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) {
if (util.isModel (ent, "backpack.mdl")) { if (game.isEntityModelMatches (ent, "backpack.mdl")) {
bomb = ent; bomb = ent;
if (!game.isNullEntity (bomb)) { if (!game.isNullEntity (bomb)) {

View file

@ -161,7 +161,7 @@ void Bot::prepareChatMessage (StringRef message) {
auto humanizedName = [] (int index) -> String { auto humanizedName = [] (int index) -> String {
auto ent = game.playerOfIndex (index); auto ent = game.playerOfIndex (index);
if (!util.isPlayer (ent)) { if (!game.isPlayerEntity (ent)) {
return "unknown"; return "unknown";
} }
String playerName = ent->v.netname.chars (); String playerName = ent->v.netname.chars ();
@ -193,7 +193,7 @@ void Bot::prepareChatMessage (StringRef message) {
// get roundtime // get roundtime
auto getRoundTime = [] () -> String { auto getRoundTime = [] () -> String {
auto roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.time ()); const auto roundTimeSecs = static_cast <int> (gameState.getRoundEndTime () - game.time ());
String roundTime {}; String roundTime {};
roundTime.assignf ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59)); 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); return humanizedName (playerIndex);
} }
else if (!needsEnemy && m_team == client.team) { else if (!needsEnemy && m_team == client.team) {
if (util.isPlayer (pev->dmg_inflictor) if (game.isPlayerEntity (pev->dmg_inflictor)
&& game.getRealTeam (pev->dmg_inflictor) == m_team) { && game.getRealPlayerTeam (pev->dmg_inflictor) == m_team) {
return humanizedName (game.indexOfPlayer (pev->dmg_inflictor)); return humanizedName (game.indexOfPlayer (pev->dmg_inflictor));
} }

View file

@ -335,7 +335,7 @@ bool Bot::checkBodyPartsWithHitboxes (edict_t *target) {
bool Bot::seesEnemy (edict_t *player) { bool Bot::seesEnemy (edict_t *player) {
auto isBehindSmokeClouds = [&] (const Vector &pos) { auto isBehindSmokeClouds = [&] (const Vector &pos) {
if (cv_smoke_grenade_checks.as <int> () == 2) { if (cv_smoke_grenade_checks.as <int> () == 2) {
return bots.isLineBlockedBySmoke (getEyesPos (), pos); return util.isLineBlockedBySmoke (getEyesPos (), pos);
} }
return false; return false;
}; };
@ -345,7 +345,7 @@ bool Bot::seesEnemy (edict_t *player) {
} }
bool ignoreFieldOfView = false; 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; ignoreFieldOfView = true;
} }
@ -387,7 +387,7 @@ bool Bot::lookupEnemies () {
if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) { if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) {
m_states &= ~Sense::SuspectEnemy; 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; m_states |= Sense::SuspectEnemy;
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f
@ -406,7 +406,7 @@ bool Bot::lookupEnemies () {
// is player is alive // is player is alive
if (m_enemyUpdateTime > game.time () if (m_enemyUpdateTime > game.time ()
&& player->v.origin.distanceSq (pev->origin) < nearestDistanceSq && player->v.origin.distanceSq (pev->origin) < nearestDistanceSq
&& util.isAlive (player) && game.isAliveEntity (player)
&& seesEnemy (player)) { && seesEnemy (player)) {
newEnemy = player; newEnemy = player;
@ -427,8 +427,8 @@ bool Bot::lookupEnemies () {
if (cv_attack_monsters) { if (cv_attack_monsters) {
// search the world for monsters... // search the world for monsters...
for (const auto &interesting : bots.getInterestingEntities ()) { for (const auto &interesting : gameState.getInterestingEntities ()) {
if (!util.isMonster (interesting)) { if (!game.isMonsterEntity (interesting)) {
continue; continue;
} }
@ -485,7 +485,7 @@ bool Bot::lookupEnemies () {
newEnemy = player; newEnemy = player;
// aim VIP first on AS maps... // aim VIP first on AS maps...
if (game.is (MapFlags::Assassination) && util.isPlayerVIP (newEnemy)) { if (game.is (MapFlags::Assassination) && game.isPlayerVIP (newEnemy)) {
break; 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); bots.setCanPause (true);
m_aimFlags |= AimFlags::Enemy; m_aimFlags |= AimFlags::Enemy;
@ -587,7 +587,7 @@ bool Bot::lookupEnemies () {
newEnemy = m_enemy; newEnemy = m_enemy;
m_lastEnemy = newEnemy; m_lastEnemy = newEnemy;
if (!util.isAlive (newEnemy)) { if (!game.isAliveEntity (newEnemy)) {
m_enemy = nullptr; m_enemy = nullptr;
m_enemyBodyPartSet = nullptr; m_enemyBodyPartSet = nullptr;
@ -638,7 +638,7 @@ bool Bot::lookupEnemies () {
&& game.isNullEntity (m_enemy) && game.isNullEntity (m_enemy)
&& getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::ShootBreakable
&& getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::PlantBomb
&& getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { && getCurrentTaskId () != Task::DefuseBomb) || gameState.isRoundOver ()) {
if (!m_reloadState) { if (!m_reloadState) {
m_reloadState = Reload::Primary; m_reloadState = Reload::Primary;
@ -719,7 +719,7 @@ Vector Bot::getEnemyBodyOffset () {
if (!m_enemyParts && (m_states & Sense::SuspectEnemy)) { if (!m_enemyParts && (m_states & Sense::SuspectEnemy)) {
spot += getBodyOffsetError (distance); spot += getBodyOffsetError (distance);
} }
else if (util.isPlayer (m_enemy)) { else if (game.isPlayerEntity (m_enemy)) {
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) { if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; 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); game.testLine (getEyesPos (), getEyesPos () + pev->v_angle.normalize_apx () * distance, TraceIgnore::None, ent (), &tr);
// check if we hit something // 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; auto hit = tr.pHit;
// check valid range // check valid range
if (game.getTeam (hit) == m_team && util.isAlive (hit)) { if (game.getPlayerTeam (hit) == m_team && game.isAliveEntity (hit)) {
return true; return true;
} }
} }
@ -1459,7 +1459,7 @@ void Bot::attackMovement () {
if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) { if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) {
if ((m_states & Sense::SeeingEnemy) if ((m_states & Sense::SeeingEnemy)
&& approach < 30 && approach < 30
&& !bots.isBombPlanted () && !gameState.isBombPlanted ()
&& (isEnemyCone || m_isVIP || m_isReloading)) { && (isEnemyCone || m_isVIP || m_isReloading)) {
if (m_retreatTime < game.time ()) { if (m_retreatTime < game.time ()) {
@ -1973,7 +1973,7 @@ void Bot::decideFollowUser () {
continue; continue;
} }
if (seesEntity (client.origin) && !util.isFakeClient (client.ent)) { if (seesEntity (client.origin) && !game.isFakeClientEntity (client.ent)) {
users.push (client.ent); users.push (client.ent);
} }
} }
@ -2248,7 +2248,7 @@ edict_t *Bot::setCorrectGrenadeVelocity (StringRef model) {
edict_t *result = nullptr; edict_t *result = nullptr;
game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { 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; result = ent;
// set the correct velocity for the grenade // set the correct velocity for the grenade
@ -2286,7 +2286,7 @@ void Bot::checkGrenadesThrow () {
|| cv_ignore_enemies || cv_ignore_enemies
|| m_isUsingGrenade || m_isUsingGrenade
|| m_isReloading || m_isReloading
|| (isKnifeMode () && !bots.isBombPlanted ()) || (isKnifeMode () && !gameState.isBombPlanted ())
|| m_grenadeCheckTime >= game.time () || m_grenadeCheckTime >= game.time ()
|| m_lastEnemyOrigin.empty ()); || m_lastEnemyOrigin.empty ());
@ -2300,7 +2300,7 @@ void Bot::checkGrenadesThrow () {
const auto senseCondition = isGrenadeMode ? false : !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy)); 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); clearThrowStates (m_states);
return; return;
} }
@ -2343,7 +2343,7 @@ void Bot::checkGrenadesThrow () {
// special condition if we're have valid current enemy // special condition if we're have valid current enemy
if (!isGrenadeMode && ((m_states & Sense::SeeingEnemy) if (!isGrenadeMode && ((m_states & Sense::SeeingEnemy)
&& util.isAlive (m_enemy) && game.isAliveEntity (m_enemy)
&& ((m_enemy->v.button | m_enemy->v.oldbuttons) & IN_ATTACK) && ((m_enemy->v.button | m_enemy->v.oldbuttons) & IN_ATTACK)
&& util.isVisible (pev->origin, m_enemy)) && util.isVisible (pev->origin, m_enemy))
&& util.isInViewCone (pev->origin, m_enemy)) { && util.isInViewCone (pev->origin, m_enemy)) {

View file

@ -352,7 +352,7 @@ void BotConfig::loadChatterConfig () {
{ "Chatter_Camp", Chatter::Camping, 10.0f }, { "Chatter_Camp", Chatter::Camping, 10.0f },
{ "Chatter_OnARoll", Chatter::OnARoll, kMaxChatterRepeatInterval}, { "Chatter_OnARoll", Chatter::OnARoll, kMaxChatterRepeatInterval},
}; };
Array <String> badFiles {}; Array <String> missingWaves {};
while (file.getLine (line)) { while (file.getLine (line)) {
line.trim (); line.trim ();
@ -394,7 +394,7 @@ void BotConfig::loadChatterConfig () {
m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration);
} }
else { else {
badFiles.push (sound); missingWaves.push (sound);
} }
} }
sentences.clear (); sentences.clear ();
@ -404,8 +404,16 @@ void BotConfig::loadChatterConfig () {
} }
file.close (); file.close ();
if (!badFiles.empty ()) { if (!missingWaves.empty ()) {
game.print ("Warning: Couldn't get duration of next chatter sounds: %s.", String::join (badFiles, ",")); 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 { else {

View file

@ -183,7 +183,7 @@ int BotControl::cmdMenu () {
// reset the current menu // reset the current menu
closeMenu (); closeMenu ();
if (arg <StringRef> (cmd) == "cmd" && util.isAlive (m_ent)) { if (arg <StringRef> (cmd) == "cmd" && game.isAliveEntity (m_ent)) {
showMenu (Menu::Commands); showMenu (Menu::Commands);
} }
else { else {
@ -205,7 +205,7 @@ int BotControl::cmdCvars () {
auto match = arg <StringRef> (pattern); auto match = arg <StringRef> (pattern);
// stop printing if executed once more // stop printing if executed once more
flushPrintQueue (); m_printQueue.clear ();
// revert all the cvars to their default values // revert all the cvars to their default values
if (match == "defaults") { if (match == "defaults") {
@ -1115,7 +1115,7 @@ int BotControl::menuFeatures (int item) {
break; break;
case 5: case 5:
if (util.isAlive (m_ent)) { if (game.isAliveEntity (m_ent)) {
showMenu (Menu::Commands); showMenu (Menu::Commands);
} }
else { else {
@ -1924,7 +1924,7 @@ bool BotControl::executeCommands () {
} }
bool BotControl::executeMenus () { bool BotControl::executeMenus () {
if (!util.isPlayer (m_ent) || game.isBotCmd ()) { if (!game.isPlayerEntity (m_ent) || game.isBotCmd ()) {
return false; return false;
} }
const auto &issuer = util.getClient (game.indexOfPlayer (m_ent)); const auto &issuer = util.getClient (game.indexOfPlayer (m_ent));
@ -1966,7 +1966,7 @@ void BotControl::showMenu (int id) {
menusParsed = true; menusParsed = true;
} }
if (!util.isPlayer (m_ent)) { if (!game.isPlayerEntity (m_ent)) {
return; return;
} }
auto &client = util.getClient (game.indexOfPlayer (m_ent)); auto &client = util.getClient (game.indexOfPlayer (m_ent));
@ -2007,7 +2007,7 @@ void BotControl::showMenu (int id) {
} }
void BotControl::closeMenu () { void BotControl::closeMenu () {
if (!util.isPlayer (m_ent)) { if (!game.isPlayerEntity (m_ent)) {
return; return;
} }
auto &client = util.getClient (game.indexOfPlayer (m_ent)); 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) { void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) {
if (!game.isDedicated () || util.isFakeClient (ent)) { if (!game.isDedicated () || game.isFakeClientEntity (ent)) {
return; return;
} }
StringRef key = cv_password_key.as <StringRef> (); StringRef key = cv_password_key.as <StringRef> ();
@ -2100,7 +2100,7 @@ void BotControl::maintainAdminRights () {
StringRef password = cv_password.as <StringRef> (); StringRef password = cv_password.as <StringRef> ();
for (auto &client : util.getClients ()) { for (auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) { if (!(client.flags & ClientFlags::Used) || game.isFakeClientEntity (client.ent)) {
continue; continue;
} }
auto ent = client.ent; auto ent = client.ent;

View file

@ -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 <float> (plat.hardwareConcurrency ())); 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 <float> (plat.hardwareConcurrency ()));
ConVar cv_grenadier_mode ("grenadier_mode", "0", "If enabled, bots will not apply throwing conditions on grenades."); 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_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_r ("sv_skycolor_r", nullptr, Var::GameRef);
ConVar sv_skycolor_g ("sv_skycolor_g", 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 (); conf.loadMainConfig ();
// ensure the server admin is confident about features he's using // ensure the server admin is confident about features he's using
game.ensureHealthyGameEnvironment (); ensureHealthyGameEnvironment ();
// load map-specific config // load map-specific config
conf.loadMapSpecificConfig (); conf.loadMapSpecificConfig ();
@ -138,7 +139,7 @@ void Game::levelInitialize (edict_t *entities, int max) {
else if (classname == "func_vip_safetyzone" || classname == "info_vip_safetyzone") { else if (classname == "func_vip_safetyzone" || classname == "info_vip_safetyzone") {
m_mapFlags |= MapFlags::Assassination; // assassination map m_mapFlags |= MapFlags::Assassination; // assassination map
} }
else if (util.isHostageEntity (ent)) { else if (isHostageEntity (ent)) {
m_mapFlags |= MapFlags::HostageRescue; // rescue map m_mapFlags |= MapFlags::HostageRescue; // rescue map
} }
else if (classname == "func_bomb_target" || classname == "info_bomb_target") { 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; m_mapFlags &= ~MapFlags::HostageRescue;
} }
} }
else if (util.isDoorEntity (ent)) { else if (isDoorEntity (ent)) {
m_mapFlags |= MapFlags::HasDoors; m_mapFlags |= MapFlags::HasDoors;
} }
else if (classname.startsWith ("func_button")) { else if (classname.startsWith ("func_button")) {
m_mapFlags |= MapFlags::HasButtons; m_mapFlags |= MapFlags::HasButtons;
} }
else if (util.isBreakableEntity (ent, true)) { else if (isBreakableEntity (ent, true)) {
// add breakable for material check // add breakable for material check
m_checkedBreakables[indexOfEntity (ent)] = ent->v.impulse <= 0; m_checkedBreakables[indexOfEntity (ent)] = ent->v.impulse <= 0;
@ -197,12 +198,12 @@ void Game::levelShutdown () {
bots.destroyKillerEntity (); bots.destroyKillerEntity ();
// ensure players are off on xash3d // ensure players are off on xash3d
if (game.is (GameFlags::Xash3DLegacy)) { if (is (GameFlags::Xash3DLegacy)) {
bots.kickEveryone (true, false); bots.kickEveryone (true, false);
} }
// set state to unprecached // set state to unprecached
game.setUnprecached (); setUnprecached ();
// enable lightstyle animations on level change // enable lightstyle animations on level change
illum.enableAnimation (true); illum.enableAnimation (true);
@ -211,7 +212,7 @@ void Game::levelShutdown () {
util.setNeedForWelcome (false); util.setNeedForWelcome (false);
// clear local entity // clear local entity
game.setLocalEntity (nullptr); setLocalEntity (nullptr);
// reset graph state // reset graph state
graph.reset (); 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, // 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. // 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 return; // reliability check
} }
@ -373,7 +374,7 @@ void Game::setPlayerStartDrawModels () {
}; };
models.foreach ([&] (const String &key, const String &val) { 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 ()); m_engineWrap.setModel (ent, val.chars ());
return EntitySearchResult::Continue; return EntitySearchResult::Continue;
}); });
@ -426,12 +427,12 @@ void Game::sendClientMessage (bool console, edict_t *ent, StringRef message) {
// helper to sending the client message // helper to sending the client message
// do not send messages to fake clients // do not send messages to fake clients
if (!util.isPlayer (ent) || util.isFakeClient (ent)) { if (!isPlayerEntity (ent) || isFakeClientEntity (ent)) {
return; return;
} }
// if console message and destination is listenserver entity, just print via server message instead of through unreliable channel // 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); sendServerMessage (message);
return; return;
} }
@ -482,7 +483,7 @@ void Game::sendServerMessage (StringRef message) {
void Game::sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message) { void Game::sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message) {
constexpr size_t kMaxSendLength = 512; constexpr size_t kMaxSendLength = 512;
if (game.isNullEntity (ent)) { if (isNullEntity (ent)) {
return; return;
} }
MessageWriter msg (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent); 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 // register fake metamod command handler if we not! under mm
if (!(is (GameFlags::Metamod))) { 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); 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 ()) { if (m_halfSecondFrame < time ()) {
// refresh bomb origin in case some plugin moved it out // 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 // ensure the server admin is confident about features he's using
ensureHealthyGameEnvironment (); 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 ())); return !isNullEntity (engfuncs.pfnFindEntityByString (nullptr, "classname", classname.chars ()));
} }
@ -1367,6 +1368,120 @@ bool Game::isDeveloperMode () const {
return developer.exists () && developer.value () > 0.0f; 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 <float> ();
// 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 () { void LightMeasure::initializeLightstyles () {
// this function initializes lighting information... // 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) { void PlayerHitboxEnumerator::update (edict_t *ent) {
constexpr auto kInvalidHitbox = -1; constexpr auto kInvalidHitbox = -1;
if (!util.isAlive (ent)) { if (!game.isAliveEntity (ent)) {
return; return;
} }
// get info about player // get info about player
@ -1677,3 +1792,143 @@ void PlayerHitboxEnumerator::reset () {
part = {}; 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 <float> ();
m_timeRoundMid = m_timeRoundStart + mp_roundtime.as <float> () * 60.0f * 0.5f;
m_timeRoundEnd = m_timeRoundStart + mp_roundtime.as <float> () * 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 <float> () - 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 ();
}

View file

@ -9,7 +9,7 @@
// on other than win32/linux platforms i.e. arm we're using xash3d engine to run which exposes // 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 // 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. // when compiling the bot, to get it supported.
#if defined(LINKENT_STATIC) #if defined(LINKENT_STATIC)
void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) { void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) {

View file

@ -24,7 +24,7 @@ void BotFakePingManager::reset (edict_t *to) {
} }
for (const auto &client : util.getClients ()) { 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; continue;
} }
m_pbm.start (client.ent); m_pbm.start (client.ent);
@ -46,7 +46,7 @@ void BotFakePingManager::syncCalculate () {
int numHumans {}; int numHumans {};
for (const auto &client : util.getClients ()) { 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; continue;
} }
numHumans++; numHumans++;
@ -101,7 +101,7 @@ void BotFakePingManager::calculate () {
} }
void BotFakePingManager::emit (edict_t *ent) { void BotFakePingManager::emit (edict_t *ent) {
if (!util.isPlayer (ent)) { if (!game.isPlayerEntity (ent)) {
return; return;
} }

View file

@ -1310,7 +1310,7 @@ void BotGraph::emitNotify (int32_t sound) const {
}; };
// notify editor // notify editor
if (util.isPlayer (m_editor) && !m_silenceMessages) { if (game.isPlayerEntity (m_editor) && !m_silenceMessages) {
game.playSound (m_editor, notifySounds[sound].chars ()); game.playSound (m_editor, notifySounds[sound].chars ());
} }
} }
@ -1483,7 +1483,7 @@ void BotGraph::calculatePathRadius (int index) {
if (tr.flFraction < 1.0f) { if (tr.flFraction < 1.0f) {
game.testLine (radiusStart, radiusEnd, TraceIgnore::Monsters, nullptr, &tr); game.testLine (radiusStart, radiusEnd, TraceIgnore::Monsters, nullptr, &tr);
if (util.isDoorEntity (tr.pHit)) { if (game.isDoorEntity (tr.pHit)) {
path.radius = 0.0f; path.radius = 0.0f;
wayBlocked = true; wayBlocked = true;
@ -1976,7 +1976,7 @@ bool BotGraph::isNodeReacheableEx (const Vector &src, const Vector &destination,
// check if this node is "visible"... // check if this node is "visible"...
game.testLine (src, destination, TraceIgnore::Monsters, m_editor, &tr); 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 node is visible from current position (even behind head)...
if (tr.flFraction >= 1.0f || isDoor) { 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 // 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; m_editor->v.movetype = MOVETYPE_NOCLIP;
} }
@ -2123,7 +2123,7 @@ void BotGraph::frame () {
// check if node is within a distance, and is visible // check if node is within a distance, and is visible
if (distanceSq < cr::sqrf (cv_graph_draw_distance.as <float> ()) if (distanceSq < cr::sqrf (cv_graph_draw_distance.as <float> ())
&& ((util.isVisible (path.origin, m_editor) && ((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 // check the distance
if (distanceSq < nearestDistanceSq) { if (distanceSq < nearestDistanceSq) {
@ -2766,43 +2766,6 @@ void BotGraph::addBasic () {
autoCreateForEntity (NodeAddFlag::Goal, "func_escapezone"); // terrorist escape zone 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 () { void BotGraph::startLearnJump () {
m_jumpLearnNode = true; m_jumpLearnNode = true;
} }

View file

@ -27,7 +27,7 @@ int32_t ServerQueryHook::sendTo (int socket, const void *message, size_t length,
buffer.skip <int32_t> (); // score buffer.skip <int32_t> (); // score
auto ctime = buffer.read <float> (); // override connection time auto ctime = buffer.read <float> (); // override connection time
buffer.write <float> (bots.getConnectTime (name, ctime)); buffer.write <float> (bots.getConnectionTimes (name, ctime));
} }
return send (buffer.data ()); return send (buffer.data ());
} }

View file

@ -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 // case it's a bot asking for a client command, we handle it like we do for bot commands
if (!game.isNullEntity (ent)) { 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)) { if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands 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]; auto bot = bots[pentTouched];
if (bot && util.isBreakableEntity (pentOther)) { if (bot && game.isBreakableEntity (pentOther)) {
bot->checkBreakable (pentOther); bot->checkBreakable (pentOther);
} }
} }
@ -389,10 +389,10 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
if (bots.hasBotsOnline ()) { if (bots.hasBotsOnline ()) {
// keep track of grenades on map // keep track of grenades on map
bots.updateActiveGrenade (); gameState.updateActiveGrenade ();
// keep track of interesting entities // keep track of interesting entities
bots.updateInterestingEntities (); gameState.updateInterestingEntities ();
} }
// keep bot number up to date // keep bot number up to date
@ -429,7 +429,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player)); auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player));
if (fakeping.hasFeature ()) { 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); fakeping.emit (ent);
} }
} }
@ -560,7 +560,7 @@ CR_C_LINKAGE int GetEntityAPI_Post (gamefuncs_t *table, int) {
auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player)); auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player));
if (fakeping.hasFeature ()) { 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); 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 { table->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) CR_FORCE_STACK_ALIGN {
// round starts in counter-strike 1.5 // round starts in counter-strike 1.5
if (strcmp (value, "info_map_parameters") == 0) { if (strcmp (value, "info_map_parameters") == 0) {
bots.initRound (); gameState.roundStart ();
} }
if (game.is (GameFlags::Metamod)) { 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 // 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 :) // 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)) { if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE); RETURN_META (MRES_SUPERCEDE);
} }

View file

@ -469,7 +469,7 @@ void BotManager::maintainLeaders () {
} }
// select leader each team somewhere in round start // 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) { for (int team = 0; team < kGameTeamNum; ++team) {
selectLeaders (team, false); selectLeaders (team, false);
} }
@ -487,7 +487,7 @@ void BotManager::maintainRoundRestart () {
&& m_numPreviousPlayers == 0 && m_numPreviousPlayers == 0
&& totalHumans == 1 && totalHumans == 1
&& totalBots > 0 && totalBots > 0
&& !m_resetHud) { && !gameState.isResetHUD ()) {
static ConVarRef sv_restartround ("sv_restartround"); static ConVarRef sv_restartround ("sv_restartround");
@ -496,13 +496,13 @@ void BotManager::maintainRoundRestart () {
} }
} }
m_numPreviousPlayers = totalHumans; m_numPreviousPlayers = totalHumans;
m_resetHud = false; gameState.setResetHUD (false);
} }
void BotManager::maintainAutoKill () { void BotManager::maintainAutoKill () {
const float killDelay = cv_autokill_delay.as <float> (); const float killDelay = cv_autokill_delay.as <float> ();
if (killDelay < 1.0f || m_roundOver) { if (killDelay < 1.0f || gameState.isRoundOver ()) {
return; return;
} }
@ -516,7 +516,7 @@ void BotManager::maintainAutoKill () {
int aliveBots = 0; int aliveBots = 0;
// do not interrupt bomb-defuse scenario // do not interrupt bomb-defuse scenario
if (game.mapIs (MapFlags::Demolition) && isBombPlanted ()) { if (game.mapIs (MapFlags::Demolition) && gameState.isBombPlanted ()) {
return; return;
} }
const int totalHumans = getHumansCount (true); // we're ignore spectators intentionally const int totalHumans = getHumansCount (true); // we're ignore spectators intentionally
@ -531,7 +531,7 @@ void BotManager::maintainAutoKill () {
++aliveBots; ++aliveBots;
// do not interrupt assassination scenario, if vip is a bot // 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; return;
} }
} }
@ -545,15 +545,9 @@ void BotManager::maintainAutoKill () {
} }
void BotManager::reset () { void BotManager::reset () {
m_grenadeUpdateTime = 0.0f;
m_entityUpdateTime = 0.0f;
m_plantSearchUpdateTime = 0.0f; m_plantSearchUpdateTime = 0.0f;
m_lastChatTime = 0.0f; m_lastChatTime = 0.0f;
m_timeBombPlanted = 0.0f;
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
m_interestingEntities.clear ();
m_activeGrenades.clear ();
} }
void BotManager::initFilters () { void BotManager::initFilters () {
@ -681,7 +675,7 @@ void BotManager::kickFromTeam (Team team, bool removeAll) {
} }
for (const auto &bot : m_bots) { for (const auto &bot : m_bots) {
if (team == game.getRealTeam (bot->ent ())) { if (team == game.getRealPlayerTeam (bot->ent ())) {
bot->kick (removeAll); bot->kick (removeAll);
if (!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) // this function kills all bots on server (only this dll controlled bots)
for (const auto &bot : m_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; continue;
} }
bot->kill (); bot->kill ();
@ -732,7 +726,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
if (fromTeam == Team::Unassigned) { if (fromTeam == Team::Unassigned) {
return true; return true;
} }
return game.getRealTeam (bot->ent ()) == fromTeam; return game.getRealPlayerTeam (bot->ent ()) == fromTeam;
}; };
// first try to kick the bot that is currently dead // first try to kick the bot that is currently dead
@ -830,7 +824,7 @@ bool BotManager::hasCustomCSDMSpawnEntities () {
void BotManager::setLastWinner (int winner) { void BotManager::setLastWinner (int winner) {
m_lastWinner = winner; m_lastWinner = winner;
m_roundOver = true; gameState.setRoundOver (true);
if (cv_radio_mode.as <int> () != 2) { if (cv_radio_mode.as <int> () != 2) {
return; return;
@ -839,7 +833,7 @@ void BotManager::setLastWinner (int winner) {
if (notify) { if (notify) {
if (notify->m_team == winner) { if (notify->m_team == winner) {
if (getRoundMidTime () > game.time ()) { if (gameState.getRoundMidTime () > game.time ()) {
notify->pushChatterMessage (Chatter::QuickWonRound); notify->pushChatterMessage (Chatter::QuickWonRound);
} }
else { 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"); 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 { auto botTeam = [] (edict_t *ent) -> StringRef {
const auto team = game.getRealTeam (ent); const auto team = game.getRealPlayerTeam (ent);
switch (team) { switch (team) {
case Team::CT: case Team::CT:
@ -967,7 +961,7 @@ void BotManager::listBots () {
ctrl.msg ("%d bots", m_bots.length ()); 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. // this function get's fake bot player time.
for (const auto &bot : m_bots) { for (const auto &bot : m_bots) {
@ -1020,19 +1014,18 @@ Twin <int, int> BotManager::countTeamPlayers () {
} }
Bot *BotManager::findHighestFragBot (int team) { Bot *BotManager::findHighestFragBot (int team) {
int bestIndex = 0; Twin <int32_t, float> best {};
float bestScore = -1;
// search bots in this team // search bots in this team
for (const auto &bot : bots) { for (const auto &bot : bots) {
if (bot->m_isAlive && game.getRealTeam (bot->ent ()) == team) { if (bot->m_isAlive && game.getRealPlayerTeam (bot->ent ()) == team) {
if (bot->pev->frags > bestScore) { if (bot->pev->frags > best.second) {
bestIndex = bot->index (); best.first = bot->index ();
bestScore = bot->pev->frags; best.second = bot->pev->frags;
} }
} }
} }
return findBotByIndex (bestIndex); return findBotByIndex (best.first);
} }
void BotManager::updateTeamEconomics (int team, bool setTrue) { 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) { void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
const auto killerTeam = game.getRealTeam (killer); const auto killerTeam = game.getRealPlayerTeam (killer);
const auto victimTeam = game.getRealTeam (victim); const auto victimTeam = game.getRealPlayerTeam (victim);
if (cv_radio_mode.as <int> () == 2) { if (cv_radio_mode.as <int> () == 2) {
// need to send congrats on well placed shot // need to send congrats on well placed shot
@ -1512,7 +1505,7 @@ void Bot::newRound () {
node = kInvalidNodeIndex; node = kInvalidNodeIndex;
} }
m_navTimeset = game.time (); m_navTimeset = game.time ();
m_team = game.getTeam (ent ()); m_team = game.getPlayerTeam (ent ());
resetPathSearchType (); resetPathSearchType ();
@ -1841,7 +1834,7 @@ void Bot::updateTeamJoin () {
if (!m_notStarted) { if (!m_notStarted) {
return; 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 // cs prior beta 7.0 uses hud-based motd, so press fire once
if (game.is (GameFlags::Legacy)) { if (game.is (GameFlags::Legacy)) {
@ -1948,15 +1941,15 @@ void BotManager::captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent) {
} }
if (cmd.startsWith ("say")) { if (cmd.startsWith ("say")) {
const bool alive = util.isAlive (ent); const bool alive = game.isAliveEntity (ent);
int team = -1; int team = -1;
if (cmd.endsWith ("team")) { if (cmd.endsWith ("team")) {
team = game.getRealTeam (ent); team = game.getRealPlayerTeam (ent);
} }
for (const auto &client : util.getClients ()) { 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; continue;
} }
auto target = bots[client.ent]; auto target = bots[client.ent];
@ -2003,7 +1996,7 @@ void BotManager::captureChatRadio (StringRef cmd, StringRef arg, edict_t *ent) {
void BotManager::notifyBombDefuse () { void BotManager::notifyBombDefuse () {
// notify all terrorists that CT is starting bomb defusing // notify all terrorists that CT is starting bomb defusing
const auto &bombPos = graph.getBombOrigin (); const auto &bombPos = gameState.getBombOrigin ();
for (const auto &bot : bots) { for (const auto &bot : bots) {
const auto task = bot->getCurrentTaskId (); 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) { void BotManager::selectLeaders (int team, bool reset) {
auto &leaderChoosen = m_teamData[team].leaderChoosen; auto &leaderChoosen = m_teamData[team].leaderChoosen;
@ -2191,8 +2122,6 @@ void BotManager::selectLeaders (int team, bool reset) {
void BotManager::initRound () { void BotManager::initRound () {
// this is called at the start of each round // this is called at the start of each round
m_roundOver = false;
// check team economics // check team economics
for (int team = 0; team < kGameTeamNum; ++team) { for (int team = 0; team < kGameTeamNum; ++team) {
updateTeamEconomics (team); updateTeamEconomics (team);
@ -2211,35 +2140,15 @@ void BotManager::initRound () {
for (auto &client : util.getClients ()) { for (auto &client : util.getClients ()) {
client.radio = 0; client.radio = 0;
} }
graph.setBombOrigin (true);
graph.clearVisited (); graph.clearVisited ();
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
m_timeBombPlanted = 0.0f;
m_plantSearchUpdateTime = 0.0f; m_plantSearchUpdateTime = 0.0f;
m_autoKillCheckTime = 0.0f; m_autoKillCheckTime = 0.0f;
m_botsCanPause = false; m_botsCanPause = false;
resetFilters (); resetFilters ();
practice.update (); // update practice data on round start practice.update (); // update practice data on round start
// calculate the round mid/end in world time
m_timeRoundStart = game.time () + mp_freezetime.as <float> ();
m_timeRoundMid = m_timeRoundStart + mp_roundtime.as <float> () * 60.0f * 0.5f;
m_timeRoundEnd = m_timeRoundStart + mp_roundtime.as <float> () * 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 () { void BotThreadWorker::shutdown () {
@ -2293,121 +2202,6 @@ void BotThreadWorker::startup (int workers) {
m_pool->startup (static_cast <size_t> (requestedThreads)); m_pool->startup (static_cast <size_t> (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 () { bool BotManager::isFrameSkipDisabled () {
if (game.is (GameFlags::Legacy)) { if (game.is (GameFlags::Legacy)) {
return true; return true;

View file

@ -26,7 +26,7 @@ void MessageDispatcher::netMsgTextMsg () {
// reset bomb position for all the bots // reset bomb position for all the bots
const auto resetBombPosition = [] () -> void { const auto resetBombPosition = [] () -> void {
if (game.mapIs (MapFlags::Demolition)) { 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 bots.setLastWinner (Team::Terrorist); // update last winner for economics
resetBombPosition (); resetBombPosition ();
} }
else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) { else if ((cached & TextMsgCache::BombPlanted) && !gameState.isBombPlanted ()) {
bots.setBombPlanted (true); gameState.setBombPlanted (true);
for (const auto &notify : bots) { for (const auto &notify : bots) {
if (notify->m_isAlive) { if (notify->m_isAlive) {
@ -68,7 +68,7 @@ void MessageDispatcher::netMsgTextMsg () {
} }
} }
} }
graph.setBombOrigin (); gameState.setBombOrigin ();
} }
// check for burst fire message // check for burst fire message
@ -319,7 +319,7 @@ void MessageDispatcher::netMsgHLTV () {
// need to start new round ? (we're tracking FOV reset message) // need to start new round ? (we're tracking FOV reset message)
if (m_args[players].long_ == 0 && m_args[fov].long_ == 0) { 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 m_bot->m_hasProgressBar = true; // the progress bar on a hud
// notify bots about defusing has started // 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 (); bots.notifyBombDefuse ();
} }
} }
@ -435,7 +435,7 @@ void MessageDispatcher::netMsgResetHUD () {
if (m_bot) { if (m_bot) {
m_bot->spawned (); m_bot->spawned ();
} }
bots.setResetHUD (true); gameState.setResetHUD (true);
} }
MessageDispatcher::MessageDispatcher () { MessageDispatcher::MessageDispatcher () {

View file

@ -60,12 +60,12 @@ int Bot::findBestGoal () {
bool hasMoreHostagesAround = false; bool hasMoreHostagesAround = false;
// try to search nearby-unused hostage, and if so, go to next goal // try to search nearby-unused hostage, and if so, go to next goal
if (bots.hasInterestingEntities ()) { if (gameState.hasInterestingEntities ()) {
const auto &interesting = bots.getInterestingEntities (); const auto &interesting = gameState.getInterestingEntities ();
// search world for hostages // search world for hostages
for (const auto &ent : interesting) { for (const auto &ent : interesting) {
if (!util.isHostageEntity (ent)) { if (!game.isHostageEntity (ent)) {
continue; continue;
} }
bool hostageInUse = false; bool hostageInUse = false;
@ -134,7 +134,7 @@ int Bot::findBestGoal () {
} }
} }
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT) { 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)) { if (bots.hasBombSay (BombPlantedSay::ChatSay)) {
pushChatMessage (Chat::Plant); pushChatMessage (Chat::Plant);
@ -149,10 +149,10 @@ int Bot::findBestGoal () {
defensive += 10.0f; 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 // send some terrorists to guard planted bomb
if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) { if (!m_defendedBomb && gameState.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && gameState.getBombTimeLeft () >= 15.0f) {
return m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ()); return m_chosenGoalIndex = findDefendNode (gameState.getBombOrigin ());
} }
} }
else if (game.mapIs (MapFlags::Escape)) { else if (game.mapIs (MapFlags::Escape)) {
@ -202,9 +202,9 @@ int Bot::findBestGoal () {
int Bot::findBestGoalWhenBombAction () { int Bot::findBestGoalWhenBombAction () {
int result = kInvalidNodeIndex; int result = kInvalidNodeIndex;
if (!bots.isBombPlanted () && !cv_ignore_objectives) { if (!gameState.isBombPlanted () && !cv_ignore_objectives) {
game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { 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)); result = graph.getNearest (game.getEntityOrigin (ent));
if (graph.exists (result)) { if (graph.exists (result)) {
@ -230,7 +230,7 @@ int Bot::findBestGoalWhenBombAction () {
} }
} }
else if (!m_defendedBomb) { else if (!m_defendedBomb) {
const auto &bombOrigin = graph.getBombOrigin (); const auto &bombOrigin = gameState.getBombOrigin ();
if (!bombOrigin.empty ()) { if (!bombOrigin.empty ()) {
m_defendedBomb = true; m_defendedBomb = true;
@ -239,7 +239,7 @@ int Bot::findBestGoalWhenBombAction () {
const auto &path = graph[result]; const auto &path = graph[result];
const float bombTimer = mp_c4timer.as <float> (); const float bombTimer = mp_c4timer.as <float> ();
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 ()) { if (timeMidBlowup > game.time ()) {
clearTask (Task::MoveToPosition); // remove any move tasks 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 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 // 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; float nearestDistanceSq = kInfiniteDistance;
int count = 0; int count = 0;
for (const auto &point : graph.m_goalPoints) { for (const auto &point : graph.m_goalPoints) {
const float distanceSq = graph[point].origin.distanceSq (pev->origin); 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; continue;
} }
if (distanceSq < nearestDistanceSq) { if (distanceSq < nearestDistanceSq) {
@ -594,7 +595,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
void Bot::checkTerrain (const Vector &dirNormal) { void Bot::checkTerrain (const Vector &dirNormal) {
// if avoiding someone do not consider stuck // if avoiding someone do not consider stuckn
TraceResult tr {}; TraceResult tr {};
m_isStuck = false; m_isStuck = false;
@ -602,7 +603,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
// minimal speed for consider stuck // minimal speed for consider stuck
const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4; 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? // standing still, no need to check?
if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed) if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed)
@ -900,10 +901,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
} }
void Bot::checkFall () { void Bot::checkFall () {
if (isPreviousLadder ()) { if (isPreviousLadder () || (m_pathFlags & NodeFlag::Ladder)) {
return;
}
else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) {
return; return;
} }
@ -1105,7 +1103,7 @@ bool Bot::updateNavigation () {
if (m_desiredVelocity.length2d () > 0.0f) { if (m_desiredVelocity.length2d () > 0.0f) {
pev->velocity = m_desiredVelocity; pev->velocity = m_desiredVelocity;
} }
else { else if (graph.isAnalyzed ()) {
auto feet = pev->origin + pev->mins; 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) }; 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) { 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 auto prevNodeIndex = m_previousNodes[0];
const float ladderDistance = pev->origin.distance (m_pathOrigin); const float ladderDistance = pev->origin.distance (m_pathOrigin);
@ -1231,7 +1229,7 @@ bool Bot::updateNavigation () {
if (game.mapIs (MapFlags::HasDoors) || (m_pathFlags & NodeFlag::Button)) { if (game.mapIs (MapFlags::HasDoors) || (m_pathFlags & NodeFlag::Button)) {
game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr); 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 auto &origin = game.getEntityOrigin (tr.pHit);
const float distanceSq = pev->origin.distanceSq (origin); const float distanceSq = pev->origin.distanceSq (origin);
@ -1285,7 +1283,7 @@ bool Bot::updateNavigation () {
util.findNearestPlayer (reinterpret_cast <void **> (&nearest), ent (), 256.0f, false, false, true, true, false); util.findNearestPlayer (reinterpret_cast <void **> (&nearest), ent (), 256.0f, false, false, true, true, false);
// check if enemy is penetrable // 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_seeEnemyTime = game.time ();
m_states |= Sense::SeeingEnemy | Sense::SuspectEnemy; 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); const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin);
// initialize the radius for a special node type, where the node is considered to be reached // 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 // 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)) { 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) { else if (m_pathFlags & NodeFlag::Ladder) {
@ -1374,7 +1372,7 @@ bool Bot::updateNavigation () {
} }
// needs precise placement - check if we get past the point // 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); const auto predictRangeSq = m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval);
if (predictRangeSq >= nodeDistanceSq || predictRangeSq <= desiredDistanceSq) { 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 // 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 // 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. // 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; getTask ()->data = kInvalidNodeIndex;
m_currentNodeIndex = kInvalidNodeIndex; m_currentNodeIndex = kInvalidNodeIndex;
@ -1418,7 +1419,7 @@ bool Bot::updateNavigation () {
const int taskTarget = getTask ()->data; const int taskTarget = getTask ()->data;
if (game.mapIs (MapFlags::Demolition) if (game.mapIs (MapFlags::Demolition)
&& bots.isBombPlanted () && gameState.isBombPlanted ()
&& m_team == Team::CT && m_team == Team::CT
&& getCurrentTaskId () != Task::EscapeFromBomb && getCurrentTaskId () != Task::EscapeFromBomb
&& taskTarget != kInvalidNodeIndex) { && taskTarget != kInvalidNodeIndex) {
@ -1481,7 +1482,7 @@ bool Bot::updateLiftHandling () {
game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Everything, ent (), &tr); game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Everything, ent (), &tr);
if (tr.flFraction < 1.0f 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) && (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside)
&& pev->groundentity != tr.pHit) { && pev->groundentity != tr.pHit) {
@ -2117,7 +2118,7 @@ int Bot::findBombNode () {
const auto &goals = graph.m_goalPoints; const auto &goals = graph.m_goalPoints;
const auto &bomb = graph.getBombOrigin (); const auto &bomb = gameState.getBombOrigin ();
const auto &audible = isBombAudible (); const auto &audible = isBombAudible ();
// take the nearest to bomb nodes instead of goal if close enough // 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 // only if we in normal task and bomb is not planted
if (tid == Task::Normal if (tid == Task::Normal
&& bots.getRoundMidTime () + 5.0f < game.time () && gameState.getRoundMidTime () + 5.0f < game.time ()
&& m_timeCamping + 5.0f < game.time () && m_timeCamping + 5.0f < game.time ()
&& !bots.isBombPlanted () && !gameState.isBombPlanted ()
&& m_personality != Personality::Rusher && m_personality != Personality::Rusher
&& !m_hasC4 && !m_isVIP && !m_hasC4 && !m_isVIP
&& m_loosedBombNodeIndex == kInvalidNodeIndex && m_loosedBombNodeIndex == kInvalidNodeIndex
@ -2549,7 +2550,7 @@ bool Bot::advanceMovement () {
auto kills = static_cast <float> (practice.getDamage (m_team, nextIndex, nextIndex)); auto kills = static_cast <float> (practice.getDamage (m_team, nextIndex, nextIndex));
// if damage done higher than one // 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) { switch (m_personality) {
case Personality::Normal: case Personality::Normal:
kills *= 0.33f; kills *= 0.33f;
@ -2647,7 +2648,7 @@ bool Bot::advanceMovement () {
// mark as jump sequence, if the current and next paths are jumps // mark as jump sequence, if the current and next paths are jumps
if (isCurrentJump) { 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 ? // 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)) { if (!game.mapIs (MapFlags::HasDoors)) {
return false; return false;
} }
return result->flFraction < 1.0f && !util.isDoorEntity (result->pHit); return result->flFraction < 1.0f && !game.isDoorEntity (result->pHit);
}; };
auto checkHostage = [&] (TraceResult *result) { 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... // 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... // check if the trace hit something...
if (tr->flFraction < 1.0f) { if (tr->flFraction < 1.0f) {
if ((game.mapIs (MapFlags::HasDoors) && util.isDoorEntity (tr->pHit)) if ((game.mapIs (MapFlags::HasDoors) && game.isDoorEntity (tr->pHit))
|| (m_team == Team::CT && util.isHostageEntity (tr->pHit))) { || (m_team == Team::CT && game.isHostageEntity (tr->pHit))) {
return false; return false;
} }
return true; // bot's head will hit something 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); game.testLine (pev->origin, pev->origin - forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something... // 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 true; // bot's body will hit something
} }
return false; return false;
@ -3140,7 +3141,7 @@ bool Bot::isBlockedRight () {
game.testLine (pev->origin, pev->origin + forward * direction + right * 48.0f, TraceIgnore::Monsters, ent (), &tr); game.testLine (pev->origin, pev->origin + forward * direction + right * 48.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something... // 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 true; // bot's body will hit something
} }
return false; return false;
@ -3302,7 +3303,7 @@ int Bot::getNearestToPlantedBomb () {
// search the bomb on the map // search the bomb on the map
game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { game.searchEntities ("classname", "grenade", [&] (edict_t *ent) {
if (util.isModel (ent, bombModel)) { if (game.isEntityModelMatches (ent, bombModel)) {
result = graph.getNearest (game.getEntityOrigin (ent)); result = graph.getNearest (game.getEntityOrigin (ent));
if (graph.exists (result)) { if (graph.exists (result)) {

View file

@ -17,12 +17,12 @@ float PlannerHeuristic::gfunctionKillsDist (int team, int currentIndex, int pare
if (parentIndex == kInvalidNodeIndex) { if (parentIndex == kInvalidNodeIndex) {
return 0.0f; return 0.0f;
} }
auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, true); auto cost = practice.getDamageEx (team, currentIndex, currentIndex, true);
const auto &current = graph[currentIndex]; const auto &current = graph[currentIndex];
for (const auto &neighbour : current.links) { for (const auto &neighbour : current.links) {
if (neighbour.index != kInvalidNodeIndex) { 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) { 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 &current = graph[currentIndex]; const auto &current = graph[currentIndex];
for (const auto &neighbour : current.links) { for (const auto &neighbour : current.links) {
if (neighbour.index != kInvalidNodeIndex) { 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; auto rsRandomizer = 1.0f;
// randomize path on round start now and then // 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 <float> (botTeam) * 2.0f); rsRandomizer = rg (0.5f, static_cast <float> (botTeam) * 2.0f);
} }

View file

@ -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 <int16_t> (value); m_data[{start, goal, team}].damage = static_cast <int16_t> (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 ()) { if (!m_damageUpdateLock.tryLock ()) {
return 0.0f; return 0.0f;
} }

View file

@ -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_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_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."); ConVar cv_enable_fake_steamids ("enable_fake_steamids", "0", "Allows or disallows bots to return a fake Steam ID.");
BotSupport::BotSupport () { BotSupport::BotSupport () {
@ -82,13 +81,6 @@ BotSupport::BotSupport () {
m_clients.resize (kGameMaxPlayers + 1); 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) { bool BotSupport::isVisible (const Vector &origin, edict_t *ent) {
if (game.isNullEntity (ent)) { if (game.isNullEntity (ent)) {
return false; return false;
@ -151,109 +143,6 @@ void BotSupport::decalTrace (TraceResult *trace, int decalIndex) {
msg.end (); 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 <float> ();
// 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 () { void BotSupport::checkWelcome () {
// the purpose of this function, is to send quick welcome message, to the listenserver entity. // 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); const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true);
auto receiveEnt = game.getLocalEntity (); 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 <float> (); // receive welcome message in four seconds after game has commencing m_welcomeReceiveTime = game.time () + 2.0f + mp_freezetime.as <float> (); // 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; continue;
} }
if ((sameTeam && client.team != game.getTeam (to)) if ((sameTeam && client.team != game.getPlayerTeam (to))
|| (needAlive && !(client.flags & ClientFlags::Alive)) || (needAlive && !(client.flags & ClientFlags::Alive))
|| (needBot && !bots[client.ent]) || (needBot && !bots[client.ent])
|| (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needDrawn && (client.ent->v.effects & EF_NODRAW))
@ -373,7 +262,7 @@ void BotSupport::updateClients () {
client.ent = player; client.ent = player;
client.flags |= ClientFlags::Used; client.flags |= ClientFlags::Used;
if (isAlive (player)) { if (game.isAliveEntity (player)) {
client.flags |= ClientFlags::Alive; client.flags |= ClientFlags::Alive;
} }
else { 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 () { String BotSupport::getCurrentDateTime () {
time_t ticks = time (&ticks); time_t ticks = time (&ticks);
tm timeinfo {}; tm timeinfo {};
@ -409,7 +294,7 @@ String BotSupport::getCurrentDateTime () {
} }
StringRef BotSupport::getFakeSteamId (edict_t *ent) { StringRef BotSupport::getFakeSteamId (edict_t *ent) {
if (!cv_enable_fake_steamids || !isPlayer (ent)) { if (!cv_enable_fake_steamids || !game.isPlayerEntity (ent)) {
return "BOT"; return "BOT";
} }
auto botNameHash = StringRef::fnv1a32 (ent->v.netname.chars ()); auto botNameHash = StringRef::fnv1a32 (ent->v.netname.chars ());
@ -479,3 +364,118 @@ void BotSupport::setCustomCvarDescriptions () {
}); });
game.setCvarDescription (cv_restricted_weapons, restrictInfo); 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;
}

View file

@ -28,12 +28,18 @@ void Bot::normal_ () {
getTask ()->data = debugGoal; getTask ()->data = debugGoal;
m_chosenGoalIndex = 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 // stop the bot if precisely reached debug goal
if (m_currentNodeIndex == debugGoal) { 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_moveToGoal = false;
m_checkTerrain = 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) // bots rushing with knife, when have no enemy (thanks for idea to nicebot project)
if (cv_random_knife_attacks if (cv_random_knife_attacks
&& usesKnife () && usesKnife ()
&& (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && (game.isNullEntity (m_lastEnemy) || !game.isAliveEntity (m_lastEnemy))
&& game.isNullEntity (m_enemy) && game.isNullEntity (m_enemy)
&& m_knifeAttackTime < game.time () && m_knifeAttackTime < game.time ()
&& !m_hasHostage && !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 bomb planted and it's a CT calculate new path to bomb point if he's not already heading for
if (!m_bombSearchOverridden if (!m_bombSearchOverridden
&& bots.isBombPlanted () && gameState.isBombPlanted ()
&& m_team == Team::CT && m_team == Team::CT
&& getTask ()->data != kInvalidNodeIndex && getTask ()->data != kInvalidNodeIndex
&& !(graph[getTask ()->data].flags & NodeFlag::Goal) && !(graph[getTask ()->data].flags & NodeFlag::Goal)
@ -84,7 +90,7 @@ void Bot::normal_ () {
// reached the destination (goal) node? // reached the destination (goal) node?
if (updateNavigation ()) { if (updateNavigation ()) {
// if we're reached the goal, and there is not enemies, notify the team // if we're reached the goal, and there is not enemies, notify the team
if (!bots.isBombPlanted () if (!gameState.isBombPlanted ()
&& m_currentNodeIndex != kInvalidNodeIndex && m_currentNodeIndex != kInvalidNodeIndex
&& (m_pathFlags & NodeFlag::Goal) && (m_pathFlags & NodeFlag::Goal)
&& rg.chance (15) && rg.chance (15)
@ -106,7 +112,7 @@ void Bot::normal_ () {
&& m_moveSpeed >= getShiftSpeed () && m_moveSpeed >= getShiftSpeed ()
&& game.isNullEntity (m_pickupItem)) { && 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); 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 // 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; campingAllowed = false;
} }
@ -218,7 +224,7 @@ void Bot::normal_ () {
} }
} }
else if (m_team == Team::CT) { 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); const int index = findDefendNode (m_path->origin);
float campTime = rg (25.0f, 40.0f); float campTime = rg (25.0f, 40.0f);
@ -258,7 +264,7 @@ void Bot::normal_ () {
auto pathSearchType = m_pathType; auto pathSearchType = m_pathType;
// override with fast path // 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; pathSearchType = rg.chance (80) ? FindPath::Fast : FindPath::Optimal;
} }
ensureCurrentNodeIndex (); ensureCurrentNodeIndex ();
@ -280,7 +286,7 @@ void Bot::normal_ () {
&& (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy)) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy))
&& numEnemiesNear (pev->origin, 768.0f) >= 1 && numEnemiesNear (pev->origin, 768.0f) >= 1
&& !isKnifeMode () && !isKnifeMode ()
&& !bots.isBombPlanted ()) { && !gameState.isBombPlanted ()) {
m_moveSpeed = shiftSpeed; 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 // bot hasn't seen anything in a long time and is asking his teammates to report in
if (cv_radio_mode.as <int> () > 1 if (cv_radio_mode.as <int> () > 1
&& bots.getLastRadio (m_team) != Radio::ReportInTeam && 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_askCheckTime < game.time () && rg.chance (15)
&& m_seeEnemyTime + rg (45.0f, 80.0f) < game.time () && m_seeEnemyTime + rg (45.0f, 80.0f) < game.time ()
&& numFriendsNear (pev->origin, 1024.0f) == 0) { && numFriendsNear (pev->origin, 1024.0f) == 0) {
@ -357,7 +363,7 @@ void Bot::huntEnemy_ () {
clearTask (Task::Hunt); clearTask (Task::Hunt);
m_prevGoalIndex = kInvalidNodeIndex; 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... // don't hunt down our teammate...
clearTask (Task::Hunt); clearTask (Task::Hunt);
@ -413,7 +419,7 @@ void Bot::huntEnemy_ () {
void Bot::seekCover_ () { void Bot::seekCover_ () {
m_aimFlags |= AimFlags::Nav; m_aimFlags |= AimFlags::Nav;
if (!util.isAlive (m_lastEnemy)) { if (!game.isAliveEntity (m_lastEnemy)) {
completeTask (); completeTask ();
m_prevGoalIndex = kInvalidNodeIndex; m_prevGoalIndex = kInvalidNodeIndex;
} }
@ -565,7 +571,7 @@ void Bot::blind_ () {
if (rg.chance (50) if (rg.chance (50)
&& m_difficulty >= Difficulty::Normal && m_difficulty >= Difficulty::Normal
&& !m_lastEnemyOrigin.empty () && !m_lastEnemyOrigin.empty ()
&& util.isPlayer (m_lastEnemy) && game.isPlayerEntity (m_lastEnemy)
&& !usesSniper ()) { && !usesSniper ()) {
auto error = kSprayDistance * m_lastEnemyOrigin.distance (pev->origin) / 2048.0f; auto error = kSprayDistance * m_lastEnemyOrigin.distance (pev->origin) / 2048.0f;
@ -624,7 +630,7 @@ void Bot::camp_ () {
m_checkTerrain = false; m_checkTerrain = false;
m_moveToGoal = 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; m_defendedBomb = false;
completeTask (); completeTask ();
} }
@ -644,7 +650,7 @@ void Bot::camp_ () {
// random camp dir, or prediction // random camp dir, or prediction
auto useRandomCampDirOrPredictEnemy = [&] () { auto useRandomCampDirOrPredictEnemy = [&] () {
if (!m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)) { if (!m_lastEnemyOrigin.empty () && game.isAliveEntity (m_lastEnemy)) {
auto pathLength = m_lastPredictLength; auto pathLength = m_lastPredictLength;
auto predictNode = m_lastPredictIndex; auto predictNode = m_lastPredictIndex;
@ -843,7 +849,7 @@ void Bot::plantBomb_ () {
selectWeaponById (Weapon::C4); selectWeaponById (Weapon::C4);
} }
if (util.isAlive (m_enemy) || !m_inBombZone) { if (game.isAliveEntity (m_enemy) || !m_inBombZone) {
completeTask (); completeTask ();
} }
else { else {
@ -886,7 +892,7 @@ void Bot::plantBomb_ () {
void Bot::defuseBomb_ () { void Bot::defuseBomb_ () {
const float fullDefuseTime = m_hasDefuser ? 7.0f : 12.0f; const float fullDefuseTime = m_hasDefuser ? 7.0f : 12.0f;
const float timeToBlowUp = getBombTimeleft (); const float timeToBlowUp = gameState.getBombTimeLeft ();
float defuseRemainingTime = fullDefuseTime; float defuseRemainingTime = fullDefuseTime;
@ -894,7 +900,7 @@ void Bot::defuseBomb_ () {
defuseRemainingTime = fullDefuseTime - game.time (); defuseRemainingTime = fullDefuseTime - game.time ();
} }
const auto &bombPos = graph.getBombOrigin (); const auto &bombPos = gameState.getBombOrigin ();
bool defuseError = false; bool defuseError = false;
// exception: bomb has been defused // 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 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 (m_numFriendsLeft != 0 && rg.chance (50)) {
if (timeToBlowUp <= 3.0f) { if (timeToBlowUp <= 3.0f) {
@ -1064,7 +1070,7 @@ void Bot::defuseBomb_ () {
} }
void Bot::followUser_ () { void Bot::followUser_ () {
if (game.isNullEntity (m_targetEntity) || !util.isAlive (m_targetEntity)) { if (game.isNullEntity (m_targetEntity) || !game.isAliveEntity (m_targetEntity)) {
m_targetEntity = nullptr; m_targetEntity = nullptr;
completeTask (); completeTask ();
@ -1075,7 +1081,7 @@ void Bot::followUser_ () {
TraceResult tr {}; 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); 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_targetEntity = nullptr;
m_lastEnemy = tr.pHit; m_lastEnemy = tr.pHit;
m_lastEnemyOrigin = tr.pHit->v.origin; m_lastEnemyOrigin = tr.pHit->v.origin;
@ -1325,7 +1331,7 @@ void Bot::throwSmoke_ () {
} }
void Bot::doublejump_ () { void Bot::doublejump_ () {
if (!util.isAlive (m_doubleJumpEntity) if (!game.isAliveEntity (m_doubleJumpEntity)
|| (m_aimFlags & AimFlags::Enemy) || (m_aimFlags & AimFlags::Enemy)
|| (m_travelStartIndex != kInvalidNodeIndex || (m_travelStartIndex != kInvalidNodeIndex
&& getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.time ())) { && 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_ () { void Bot::escapeFromBomb_ () {
m_aimFlags |= AimFlags::Nav; m_aimFlags |= AimFlags::Nav;
if (!bots.isBombPlanted ()) { if (!gameState.isBombPlanted ()) {
completeTask (); completeTask ();
} }
@ -1407,7 +1413,7 @@ void Bot::escapeFromBomb_ () {
pev->button |= IN_ATTACK2; 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); selectWeaponById (Weapon::Knife);
} }
@ -1432,7 +1438,7 @@ void Bot::escapeFromBomb_ () {
float nearestDistanceSq = kInfiniteDistance; float nearestDistanceSq = kInfiniteDistance;
for (const auto &path : graph) { 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; continue;
} }
const float distanceSq = pev->origin.distanceSq (path.origin); const float distanceSq = pev->origin.distanceSq (path.origin);
@ -1465,7 +1471,7 @@ void Bot::escapeFromBomb_ () {
void Bot::shootBreakable_ () { void Bot::shootBreakable_ () {
// breakable destroyed? // breakable destroyed?
if (!util.isBreakableEntity (m_breakableEntity)) { if (!game.isBreakableEntity (m_breakableEntity)) {
completeTask (); completeTask ();
return; return;
} }
@ -1647,7 +1653,7 @@ void Bot::pickupItem_ () {
case Pickup::Hostage: case Pickup::Hostage:
m_aimFlags |= AimFlags::Entity; m_aimFlags |= AimFlags::Entity;
if (!util.isAlive (m_pickupItem)) { if (!game.isAliveEntity (m_pickupItem)) {
// don't pickup dead hostages // don't pickup dead hostages
m_pickupItem = nullptr; m_pickupItem = nullptr;
completeTask (); completeTask ();
@ -1676,7 +1682,7 @@ void Bot::pickupItem_ () {
// find the nearest 'unused' hostage within the area // find the nearest 'unused' hostage within the area
game.searchEntities (pev->origin, 1024.0f, [&] (edict_t *ent) { game.searchEntities (pev->origin, 1024.0f, [&] (edict_t *ent) {
if (!util.isHostageEntity (ent)) { if (!game.isHostageEntity (ent)) {
return EntitySearchResult::Continue; return EntitySearchResult::Continue;
} }

View file

@ -445,8 +445,8 @@ void Bot::setAimDirection () {
&& m_forgetLastVictimTimer.elapsed () && m_forgetLastVictimTimer.elapsed ()
&& !m_lastEnemyOrigin.empty () && !m_lastEnemyOrigin.empty ()
&& util.isPlayer (m_lastEnemy) && game.isPlayerEntity (m_lastEnemy)
&& !util.isPlayer (m_enemy)) { && !game.isPlayerEntity (m_enemy)) {
flags |= AimFlags::LastEnemy; flags |= AimFlags::LastEnemy;
} }
@ -503,7 +503,7 @@ void Bot::setAimDirection () {
else if (flags & AimFlags::PredictPath) { else if (flags & AimFlags::PredictPath) {
bool changePredictedEnemy = true; 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; changePredictedEnemy = false;
} }
@ -573,7 +573,9 @@ void Bot::setAimDirection () {
const auto &destOrigin = m_destOrigin + pev->view_ofs; const auto &destOrigin = m_destOrigin + pev->view_ofs;
m_lookAt = destOrigin; 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_isStuck && !(pev->button & IN_DUCK)
&& m_currentNodeIndex != kInvalidNodeIndex && m_currentNodeIndex != kInvalidNodeIndex
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
@ -598,7 +600,6 @@ void Bot::setAimDirection () {
else { else {
m_lookAt = destOrigin; m_lookAt = destOrigin;
} }
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder ();
if (m_numEnemiesLeft > 0 if (m_numEnemiesLeft > 0
&& m_canChooseAimDirection && m_canChooseAimDirection
@ -625,14 +626,17 @@ void Bot::setAimDirection () {
} }
// try look at next node if on ladder // 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 ()]; const auto &nextPath = graph[m_pathWalk.next ()];
if ((nextPath.flags & NodeFlag::Ladder) if ((nextPath.flags & NodeFlag::Ladder)
&& m_destOrigin.distanceSq (pev->origin) < cr::sqrf (128.0f) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (64.0f)
&& nextPath.origin.z > m_pathOrigin.z + 26.0f) { && nextPath.origin.z > m_pathOrigin.z + 30.0f) {
m_lookAt = nextPath.origin + pev->view_ofs; m_lookAt = nextPath.origin;
} }
} }

View file

@ -125,7 +125,9 @@ void GraphVistable::rebuild () {
m_sliceIndex += rg (250, 400); m_sliceIndex += rg (250, 400);
} }
auto notifyProgress = [] (int value) { auto notifyProgress = [] (int value) {
if (value >= 100 || cv_debug) {
game.print ("Rebuilding vistable... %d%% done.", value); game.print ("Rebuilding vistable... %d%% done.", value);
}
}; };
// notify host about rebuilding // notify host about rebuilding
@ -139,6 +141,7 @@ void GraphVistable::rebuild () {
m_rebuild = false; m_rebuild = false;
m_notifyMsgTimestamp = 0.0f; m_notifyMsgTimestamp = 0.0f;
m_curIndex = 0;
save (); save ();
} }
@ -149,7 +152,7 @@ void GraphVistable::startRebuild () {
m_notifyMsgTimestamp = game.time (); 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)) { if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
return false; return false;
} }