Compare commits
10 commits
b083e872e0
...
95f1261dda
| Author | SHA1 | Date | |
|---|---|---|---|
| 95f1261dda | |||
| 3ed7a71dd5 | |||
|
|
4015f8de06 |
||
|
|
113cb5e916 |
||
|
|
cd02ca3a23 |
||
|
|
6decb2bfb3 |
||
|
|
bc67453b6c |
||
|
|
17ed252b60 |
||
|
|
7b378ba3fa |
||
|
|
6604145481 |
40 changed files with 1519 additions and 1186 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: ['linux-x86', 'linux-amd64', 'linux-x86-gcc', 'linux-x86-nosimd', 'linux-arm64', 'apple-x86', 'windows-x86-clang', 'windows-amd64', 'windows-x86-gcc']
|
arch: ['linux-x86', 'linux-amd64', 'linux-x86-gcc', 'linux-x86-nosimd', 'linux-arm64', 'linux-riscv64', 'apple-x86', 'windows-x86-clang', 'windows-amd64', 'windows-x86-gcc']
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
|
|
@ -69,3 +69,9 @@ EnableFakeBotFeatures = no
|
||||||
; sent to the server console. This replaces yb_logger_disable_logfile cvar.
|
; sent to the server console. This replaces yb_logger_disable_logfile cvar.
|
||||||
;
|
;
|
||||||
DisableLogFile = no
|
DisableLogFile = no
|
||||||
|
|
||||||
|
;
|
||||||
|
; Disables enforcement of player spawn limits during bot creation and quota management,
|
||||||
|
; allowing the use of custom spawn editors.
|
||||||
|
;
|
||||||
|
DisableSpawnControl = no
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit b5f7ccc23dec2d018048b40085f78255cc232e52
|
Subproject commit 173f387022310148e1f9ef65d61164579ba509a9
|
||||||
17
inc/config.h
17
inc/config.h
|
|
@ -29,16 +29,6 @@ public:
|
||||||
|
|
||||||
// mostly config stuff, and some stuff dealing with menus
|
// mostly config stuff, and some stuff dealing with menus
|
||||||
class BotConfig final : public Singleton <BotConfig> {
|
class BotConfig final : public Singleton <BotConfig> {
|
||||||
public:
|
|
||||||
struct DifficultyData {
|
|
||||||
float reaction[2] {};
|
|
||||||
int32_t headshotPct {};
|
|
||||||
int32_t seenThruPct {};
|
|
||||||
int32_t hearThruPct {};
|
|
||||||
int32_t maxRecoil {};
|
|
||||||
Vector aimError {};
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Array <StringArray> m_chat {};
|
Array <StringArray> m_chat {};
|
||||||
Array <Array <ChatterItem>> m_chatter {};
|
Array <Array <ChatterItem>> m_chatter {};
|
||||||
|
|
@ -52,7 +42,7 @@ private:
|
||||||
StringArray m_avatars {};
|
StringArray m_avatars {};
|
||||||
|
|
||||||
HashMap <uint32_t, String, Hash <int32_t>> m_language {};
|
HashMap <uint32_t, String, Hash <int32_t>> m_language {};
|
||||||
HashMap <int32_t, DifficultyData> m_difficulty {};
|
HashMap <int32_t, BotDifficultyData> m_difficulty {};
|
||||||
HashMap <String, String> m_custom {};
|
HashMap <String, String> m_custom {};
|
||||||
|
|
||||||
// default tables for personality weapon preferences, overridden by weapon.cfg
|
// default tables for personality weapon preferences, overridden by weapon.cfg
|
||||||
|
|
@ -218,10 +208,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// get's the difficulty level tweaks
|
// get's the difficulty level tweaks
|
||||||
DifficultyData *getDifficultyTweaks (int32_t level) {
|
BotDifficultyData *getDifficultyTweaks (int32_t level) {
|
||||||
if (level < Difficulty::Noob || level > Difficulty::Expert) {
|
|
||||||
return &m_difficulty[Difficulty::Expert];
|
|
||||||
}
|
|
||||||
return &m_difficulty[level];
|
return &m_difficulty[level];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ CR_DECLARE_SCOPED_ENUM (Menu,
|
||||||
|
|
||||||
// bomb say string
|
// bomb say string
|
||||||
CR_DECLARE_SCOPED_ENUM (BombPlantedSay,
|
CR_DECLARE_SCOPED_ENUM (BombPlantedSay,
|
||||||
ChatSay = cr::bit (1),
|
ChatSay = cr::bit (1),
|
||||||
Chatter = cr::bit (2)
|
Chatter = cr::bit (2)
|
||||||
)
|
)
|
||||||
|
|
||||||
// chat types id's
|
// chat types id's
|
||||||
|
|
@ -419,7 +419,13 @@ CR_DECLARE_SCOPED_ENUM (GoalTactic,
|
||||||
RescueHostage
|
RescueHostage
|
||||||
)
|
)
|
||||||
|
|
||||||
// some hard-coded desire defines used to override calculated ones
|
// ladder move direction
|
||||||
|
CR_DECLARE_SCOPED_ENUM (LadderDir,
|
||||||
|
Up = 0,
|
||||||
|
Down,
|
||||||
|
)
|
||||||
|
|
||||||
|
// some hard-coded desire defines used to override calculated ones
|
||||||
namespace TaskPri {
|
namespace TaskPri {
|
||||||
constexpr auto Normal { 35.0f };
|
constexpr auto Normal { 35.0f };
|
||||||
constexpr auto Pause { 36.0f };
|
constexpr auto Pause { 36.0f };
|
||||||
|
|
@ -447,7 +453,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 = cr::sqrf (2.0f);
|
||||||
|
|
||||||
constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
|
constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
|
||||||
constexpr auto kMaxWeapons = 32;
|
constexpr auto kMaxWeapons = 32;
|
||||||
|
|
@ -462,35 +468,35 @@ constexpr auto kConfigExtension = "cfg";
|
||||||
|
|
||||||
// weapon masks
|
// weapon masks
|
||||||
constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) |
|
constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) |
|
||||||
cr::bit (Weapon::M3) |
|
cr::bit (Weapon::M3) |
|
||||||
cr::bit (Weapon::MAC10) |
|
cr::bit (Weapon::MAC10) |
|
||||||
cr::bit (Weapon::UMP45) |
|
cr::bit (Weapon::UMP45) |
|
||||||
cr::bit (Weapon::MP5) |
|
cr::bit (Weapon::MP5) |
|
||||||
cr::bit (Weapon::TMP) |
|
cr::bit (Weapon::TMP) |
|
||||||
cr::bit (Weapon::P90) |
|
cr::bit (Weapon::P90) |
|
||||||
cr::bit (Weapon::AUG) |
|
cr::bit (Weapon::AUG) |
|
||||||
cr::bit (Weapon::M4A1) |
|
cr::bit (Weapon::M4A1) |
|
||||||
cr::bit (Weapon::SG552) |
|
cr::bit (Weapon::SG552) |
|
||||||
cr::bit (Weapon::AK47) |
|
cr::bit (Weapon::AK47) |
|
||||||
cr::bit (Weapon::Scout) |
|
cr::bit (Weapon::Scout) |
|
||||||
cr::bit (Weapon::SG550) |
|
cr::bit (Weapon::SG550) |
|
||||||
cr::bit (Weapon::AWP) |
|
cr::bit (Weapon::AWP) |
|
||||||
cr::bit (Weapon::G3SG1) |
|
cr::bit (Weapon::G3SG1) |
|
||||||
cr::bit (Weapon::M249) |
|
cr::bit (Weapon::M249) |
|
||||||
cr::bit (Weapon::Famas) |
|
cr::bit (Weapon::Famas) |
|
||||||
cr::bit (Weapon::Galil));
|
cr::bit (Weapon::Galil));
|
||||||
|
|
||||||
constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228)
|
constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228)
|
||||||
| cr::bit (Weapon::Elite)
|
| cr::bit (Weapon::Elite)
|
||||||
| cr::bit (Weapon::USP)
|
| cr::bit (Weapon::USP)
|
||||||
| cr::bit (Weapon::Glock18)
|
| cr::bit (Weapon::Glock18)
|
||||||
| cr::bit (Weapon::Deagle)
|
| cr::bit (Weapon::Deagle)
|
||||||
| cr::bit (Weapon::FiveSeven));
|
| cr::bit (Weapon::FiveSeven));
|
||||||
|
|
||||||
constexpr auto kSniperWeaponMask = (cr::bit (Weapon::Scout)
|
constexpr auto kSniperWeaponMask = (cr::bit (Weapon::Scout)
|
||||||
| cr::bit (Weapon::SG550)
|
| cr::bit (Weapon::SG550)
|
||||||
| cr::bit (Weapon::AWP)
|
| cr::bit (Weapon::AWP)
|
||||||
| cr::bit (Weapon::G3SG1));
|
| cr::bit (Weapon::G3SG1));
|
||||||
|
|
||||||
// weapons < 7 are secondary
|
// weapons < 7 are secondary
|
||||||
constexpr auto kPrimaryWeaponMinIndex = 7;
|
constexpr auto kPrimaryWeaponMinIndex = 7;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ CR_DECLARE_SCOPED_ENUM (PrintQueueDestination,
|
||||||
// bot command manager
|
// bot command manager
|
||||||
class BotControl final : public Singleton <BotControl> {
|
class BotControl final : public Singleton <BotControl> {
|
||||||
public:
|
public:
|
||||||
using Handler = int (BotControl::*) ();
|
using Handler = int (BotControl:: *) ();
|
||||||
using MenuHandler = int (BotControl::*) (int);
|
using MenuHandler = int (BotControl:: *) (int);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// generic bot command
|
// generic bot command
|
||||||
|
|
@ -36,8 +36,7 @@ public:
|
||||||
public:
|
public:
|
||||||
explicit BotCmd () = default;
|
explicit BotCmd () = default;
|
||||||
|
|
||||||
BotCmd (StringRef name, StringRef format, StringRef help, Handler handler, bool visible = true) : name (name), format (format), help (help), handler (cr::move (handler)), visible (visible)
|
BotCmd (StringRef name, StringRef format, StringRef help, Handler handler, bool visible = true) : name (name), format (format), help (help), handler (cr::move (handler)), visible (visible) {}
|
||||||
{ }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// single bot menu
|
// single bot menu
|
||||||
|
|
@ -47,8 +46,7 @@ public:
|
||||||
MenuHandler handler {};
|
MenuHandler handler {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BotMenu (int ident, int slots, StringRef text, MenuHandler handler) : ident (ident), slots (slots), text (text), handler (cr::move (handler))
|
explicit BotMenu (int ident, int slots, StringRef text, MenuHandler handler) : ident (ident), slots (slots), text (text), handler (cr::move (handler)) {}
|
||||||
{ }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// queued text message to prevent overflow with rapid output
|
// queued text message to prevent overflow with rapid output
|
||||||
|
|
@ -57,10 +55,9 @@ public:
|
||||||
String text {};
|
String text {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PrintQueue () = default;
|
explicit PrintQueue () = default;
|
||||||
|
|
||||||
PrintQueue (int32_t destination, StringRef text) : destination (destination), text (text)
|
PrintQueue (int32_t destination, StringRef text) : destination (destination), text (text) {}
|
||||||
{ }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// save old values of changed cvars to revert them back when editing turned off
|
// save old values of changed cvars to revert them back when editing turned off
|
||||||
|
|
@ -118,6 +115,7 @@ private:
|
||||||
int cmdNodeSave ();
|
int cmdNodeSave ();
|
||||||
int cmdNodeLoad ();
|
int cmdNodeLoad ();
|
||||||
int cmdNodeErase ();
|
int cmdNodeErase ();
|
||||||
|
int cmdNodeRefresh ();
|
||||||
int cmdNodeEraseTraining ();
|
int cmdNodeEraseTraining ();
|
||||||
int cmdNodeDelete ();
|
int cmdNodeDelete ();
|
||||||
int cmdNodeCheck ();
|
int cmdNodeCheck ();
|
||||||
|
|
@ -255,7 +253,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edict_t *getIssuer() {
|
edict_t *getIssuer () {
|
||||||
return m_ent;
|
return m_ent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
148
inc/engine.h
148
inc/engine.h
|
|
@ -60,7 +60,7 @@ CR_DECLARE_SCOPED_ENUM (MapFlags,
|
||||||
Escape = cr::bit (3),
|
Escape = cr::bit (3),
|
||||||
KnifeArena = cr::bit (4),
|
KnifeArena = cr::bit (4),
|
||||||
FightYard = cr::bit (5),
|
FightYard = cr::bit (5),
|
||||||
GrenadeWar = cr::bit(6),
|
GrenadeWar = cr::bit (6),
|
||||||
HasDoors = cr::bit (10), // additional flags
|
HasDoors = cr::bit (10), // additional flags
|
||||||
HasButtons = cr::bit (11) // map has buttons
|
HasButtons = cr::bit (11) // map has buttons
|
||||||
)
|
)
|
||||||
|
|
@ -190,6 +190,9 @@ public:
|
||||||
// initialize levels
|
// initialize levels
|
||||||
void levelInitialize (edict_t *entities, int max);
|
void levelInitialize (edict_t *entities, int max);
|
||||||
|
|
||||||
|
// when entity spawns
|
||||||
|
void onSpawnEntity (edict_t *ent);
|
||||||
|
|
||||||
// shutdown levels
|
// shutdown levels
|
||||||
void levelShutdown ();
|
void levelShutdown ();
|
||||||
|
|
||||||
|
|
@ -265,7 +268,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 +285,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
|
||||||
|
|
@ -301,7 +336,10 @@ public:
|
||||||
|
|
||||||
// gets custom engine args for client command
|
// gets custom engine args for client command
|
||||||
const char *botArgs () const {
|
const char *botArgs () const {
|
||||||
return strings.format (String::join (m_botArgs, " ", m_botArgs[0].startsWith ("say") ? 1 : 0).chars ());
|
auto result = strings.chars ();
|
||||||
|
strings.copy (result, String::join (m_botArgs, " ", m_botArgs[0].startsWith ("say") ? 1 : 0).chars (), cr::StringBuffer::StaticBufferSize);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets custom engine argv for client command
|
// gets custom engine argv for client command
|
||||||
|
|
@ -343,7 +381,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 +391,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 +399,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 +407,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
|
||||||
|
|
@ -454,7 +492,7 @@ public:
|
||||||
|
|
||||||
// helper to sending the client message
|
// helper to sending the client message
|
||||||
void sendClientMessage (bool console, edict_t *ent, StringRef message);
|
void sendClientMessage (bool console, edict_t *ent, StringRef message);
|
||||||
|
|
||||||
// helper to sending the server message
|
// helper to sending the server message
|
||||||
void sendServerMessage (StringRef message);
|
void sendServerMessage (StringRef message);
|
||||||
|
|
||||||
|
|
@ -723,6 +761,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);
|
||||||
|
|
|
||||||
|
|
@ -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 {};
|
||||||
|
|
@ -228,6 +227,7 @@ public:
|
||||||
bool loadGraphData ();
|
bool loadGraphData ();
|
||||||
bool canDownload ();
|
bool canDownload ();
|
||||||
bool isAnalyzed () const;
|
bool isAnalyzed () const;
|
||||||
|
bool isConverted () const;
|
||||||
|
|
||||||
void saveOldFormat ();
|
void saveOldFormat ();
|
||||||
void reset ();
|
void reset ();
|
||||||
|
|
@ -253,7 +253,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 +291,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];
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ CR_DECLARE_SCOPED_ENUM (StatusIconCache,
|
||||||
|
|
||||||
class MessageDispatcher final : public Singleton <MessageDispatcher> {
|
class MessageDispatcher final : public Singleton <MessageDispatcher> {
|
||||||
private:
|
private:
|
||||||
using MsgFunc = void (MessageDispatcher::*) ();
|
using MsgFunc = void (MessageDispatcher:: *) ();
|
||||||
using MsgHash = Hash <int32_t>;
|
using MsgHash = Hash <int32_t>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -81,9 +81,9 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Args (float value) : float_ (value) { }
|
Args (float value) : float_ (value) {}
|
||||||
Args (int32_t value) : long_ (value) { }
|
Args (int32_t value) : long_ (value) {}
|
||||||
Args (const char *value) : chars_ (value) { }
|
Args (const char *value) : chars_ (value) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ CR_DECLARE_SCOPED_ENUM (AStarResult,
|
||||||
Success = 0,
|
Success = 0,
|
||||||
Failed,
|
Failed,
|
||||||
InternalError,
|
InternalError,
|
||||||
)
|
)
|
||||||
|
|
||||||
// node added
|
// node added
|
||||||
using NodeAdderFn = const Lambda <bool (int)> &;
|
using NodeAdderFn = const Lambda <bool (int)> &;
|
||||||
|
|
||||||
// route twin node
|
// route twin node
|
||||||
template <typename HT> struct RouteTwin final {
|
template <typename HT> struct RouteTwin final {
|
||||||
|
|
@ -58,7 +58,7 @@ public:
|
||||||
static float gfunctionKillsDist (int team, int currentIndex, int parentIndex);
|
static float gfunctionKillsDist (int team, int currentIndex, int parentIndex);
|
||||||
|
|
||||||
// least kills and number of nodes to goal for a team (when with hostage)
|
// least kills and number of nodes to goal for a team (when with hostage)
|
||||||
static float gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex);
|
static float gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex);
|
||||||
|
|
||||||
// least kills to goal for a team
|
// least kills to goal for a team
|
||||||
static float gfunctionKills (int team, int currentIndex, int);
|
static float gfunctionKills (int team, int currentIndex, int);
|
||||||
|
|
|
||||||
|
|
@ -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 ();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ CR_DECLARE_SCOPED_ENUM (StorageOption,
|
||||||
Official = cr::bit (4), // this is additional flag for graph indicates graph are official
|
Official = cr::bit (4), // this is additional flag for graph indicates graph are official
|
||||||
Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad
|
Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad
|
||||||
Exten = cr::bit (6), // this is additional flag indicates that there's extension info
|
Exten = cr::bit (6), // this is additional flag indicates that there's extension info
|
||||||
Analyzed = cr::bit (7) // this graph has been analyzed
|
Analyzed = cr::bit (7), // this graph has been analyzed
|
||||||
|
Converted = cr::bit (8) // converted from a pwf format
|
||||||
)
|
)
|
||||||
|
|
||||||
// storage header versions
|
// storage header versions
|
||||||
|
|
|
||||||
|
|
@ -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 ?
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -54,6 +54,11 @@ public:
|
||||||
bool isReady () const {
|
bool isReady () const {
|
||||||
return !m_rebuild;
|
return !m_rebuild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is visible fromr both points ?
|
||||||
|
bool visibleBothSides (int srcIndex, int destIndex, VisIndex vis = VisIndex::Any) const {
|
||||||
|
return visible (srcIndex, destIndex, vis) && visible (destIndex, srcIndex, vis);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// expose global
|
// expose global
|
||||||
|
|
|
||||||
42
inc/yapb.h
42
inc/yapb.h
|
|
@ -23,7 +23,7 @@ using namespace cr;
|
||||||
|
|
||||||
// tasks definition
|
// tasks definition
|
||||||
struct BotTask {
|
struct BotTask {
|
||||||
using Function = void (Bot::*) ();
|
using Function = void (Bot:: *) ();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Function func {}; // corresponding exec function in bot class
|
Function func {}; // corresponding exec function in bot class
|
||||||
|
|
@ -34,7 +34,7 @@ public:
|
||||||
bool resume {}; // if task can be continued if interrupted
|
bool resume {}; // if task can be continued if interrupted
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BotTask (Function func, Task id, float desire, int data, float time, bool resume) : func (func), id (id), desire (desire), data (data), time (time), resume (resume) { }
|
BotTask (Function func, Task id, float desire, int data, float time, bool resume) : func (func), id (id), desire (desire), data (data), time (time), resume (resume) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// weapon properties structure
|
// weapon properties structure
|
||||||
|
|
@ -68,7 +68,7 @@ struct WeaponInfo {
|
||||||
bool primaryFireHold {}; // hold down primary fire button to use?
|
bool primaryFireHold {}; // hold down primary fire button to use?
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WeaponInfo (int id,
|
WeaponInfo (int id,
|
||||||
StringRef name,
|
StringRef name,
|
||||||
StringRef model,
|
StringRef model,
|
||||||
int price,
|
int price,
|
||||||
|
|
@ -78,14 +78,13 @@ public:
|
||||||
int buyGroup,
|
int buyGroup,
|
||||||
int buySelect,
|
int buySelect,
|
||||||
int buySelectT,
|
int buySelectT,
|
||||||
int buySelectCT,
|
int buySelectCT,
|
||||||
int penetratePower,
|
int penetratePower,
|
||||||
int maxClip,
|
int maxClip,
|
||||||
int type,
|
int type,
|
||||||
bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd),
|
bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd),
|
||||||
teamAS (teamAs), buyGroup (buyGroup), buySelect (buySelect), buySelectT (buySelectT), buySelectCT (buySelectCT),
|
teamAS (teamAs), buyGroup (buyGroup), buySelect (buySelect), buySelectT (buySelectT), buySelectCT (buySelectCT),
|
||||||
penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold)
|
penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold) {}
|
||||||
{ }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// clients noise
|
// clients noise
|
||||||
|
|
@ -123,6 +122,16 @@ struct BotTeamData {
|
||||||
int32_t lastRadioSlot = { kInvalidRadioSlot }; // last radio message for team
|
int32_t lastRadioSlot = { kInvalidRadioSlot }; // last radio message for team
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// bot difficulty data
|
||||||
|
struct BotDifficultyData {
|
||||||
|
float reaction[2] {};
|
||||||
|
int32_t headshotPct {};
|
||||||
|
int32_t seenThruPct {};
|
||||||
|
int32_t hearThruPct {};
|
||||||
|
int32_t maxRecoil {};
|
||||||
|
Vector aimError {};
|
||||||
|
};
|
||||||
|
|
||||||
// include bot graph stuff
|
// include bot graph stuff
|
||||||
#include <graph.h>
|
#include <graph.h>
|
||||||
#include <vision.h>
|
#include <vision.h>
|
||||||
|
|
@ -210,6 +219,8 @@ private:
|
||||||
mutable Mutex m_pathFindLock {};
|
mutable Mutex m_pathFindLock {};
|
||||||
mutable Mutex m_predictLock {};
|
mutable Mutex m_predictLock {};
|
||||||
|
|
||||||
|
float f_wpt_tim_str_chg;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_states {}; // sensing bitstates
|
uint32_t m_states {}; // sensing bitstates
|
||||||
uint32_t m_collideMoves[kMaxCollideMoves] {}; // sorted array of movements
|
uint32_t m_collideMoves[kMaxCollideMoves] {}; // sorted array of movements
|
||||||
|
|
@ -240,11 +251,10 @@ private:
|
||||||
int m_lastPredictLength {}; // last predicted path length
|
int m_lastPredictLength {}; // last predicted path length
|
||||||
int m_pickupType {}; // type of entity which needs to be used/picked up
|
int m_pickupType {}; // type of entity which needs to be used/picked up
|
||||||
|
|
||||||
float m_headedTime {};
|
float m_headedTime {}; // last time followed by radio entity
|
||||||
float m_prevTime {}; // time previously checked movement speed
|
float m_prevTime {}; // time previously checked movement speed
|
||||||
float m_heavyTimestamp {}; // is it time to execute heavy-weight functions
|
float m_heavyTimestamp {}; // is it time to execute heavy-weight functions
|
||||||
float m_prevSpeed {}; // speed some frames before
|
float m_prevSpeed {}; // speed some frames before
|
||||||
float m_prevVelocity {}; // velocity some frames before
|
|
||||||
float m_timeDoorOpen {}; // time to next door open check
|
float m_timeDoorOpen {}; // time to next door open check
|
||||||
float m_timeHitDoor {}; // specific time after hitting the door
|
float m_timeHitDoor {}; // specific time after hitting the door
|
||||||
float m_lastChatTime {}; // time bot last chatted
|
float m_lastChatTime {}; // time bot last chatted
|
||||||
|
|
@ -361,7 +371,6 @@ private:
|
||||||
Vector m_lookAtPredict {}; // aiming vector when predicting
|
Vector m_lookAtPredict {}; // aiming vector when predicting
|
||||||
Vector m_desiredVelocity {}; // desired velocity for jump nodes
|
Vector m_desiredVelocity {}; // desired velocity for jump nodes
|
||||||
Vector m_breakableOrigin {}; // origin of breakable
|
Vector m_breakableOrigin {}; // origin of breakable
|
||||||
Vector m_rightRef {}; // right referential vector
|
|
||||||
Vector m_checkFallPoint[2] {}; // check fall point
|
Vector m_checkFallPoint[2] {}; // check fall point
|
||||||
|
|
||||||
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
|
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
|
||||||
|
|
@ -370,6 +379,7 @@ private:
|
||||||
|
|
||||||
UniquePtr <class PlayerHitboxEnumerator> m_hitboxEnumerator {};
|
UniquePtr <class PlayerHitboxEnumerator> m_hitboxEnumerator {};
|
||||||
|
|
||||||
|
BotDifficultyData *m_difficultyData {};
|
||||||
Path *m_path {}; // pointer to the current path node
|
Path *m_path {}; // pointer to the current path node
|
||||||
String m_chatBuffer {}; // space for strings (say text...)
|
String m_chatBuffer {}; // space for strings (say text...)
|
||||||
Frustum::Planes m_viewFrustum {};
|
Frustum::Planes m_viewFrustum {};
|
||||||
|
|
@ -400,7 +410,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 ();
|
||||||
|
|
@ -520,7 +530,7 @@ private:
|
||||||
void setPathOrigin ();
|
void setPathOrigin ();
|
||||||
void fireWeapons ();
|
void fireWeapons ();
|
||||||
void doFireWeapons ();
|
void doFireWeapons ();
|
||||||
void selectWeapons (float distance, int index, int id, int choosen);
|
void handleWeapons (float distance, int index, int id, int choosen);
|
||||||
void focusEnemy ();
|
void focusEnemy ();
|
||||||
void selectBestWeapon ();
|
void selectBestWeapon ();
|
||||||
void selectSecondary ();
|
void selectSecondary ();
|
||||||
|
|
@ -529,7 +539,6 @@ private:
|
||||||
void syncUpdatePredictedIndex ();
|
void syncUpdatePredictedIndex ();
|
||||||
void updatePredictedIndex ();
|
void updatePredictedIndex ();
|
||||||
void refreshCreatureStatus (char *infobuffer);
|
void refreshCreatureStatus (char *infobuffer);
|
||||||
void updateRightRef ();
|
|
||||||
void donateC4ToHuman ();
|
void donateC4ToHuman ();
|
||||||
void clearAmmoInfo ();
|
void clearAmmoInfo ();
|
||||||
void handleChatterTaskChange (Task tid);
|
void handleChatterTaskChange (Task tid);
|
||||||
|
|
@ -681,6 +690,7 @@ public:
|
||||||
int m_ammoInClip[kMaxWeapons] {}; // ammo in clip for each weapons
|
int m_ammoInClip[kMaxWeapons] {}; // ammo in clip for each weapons
|
||||||
int m_ammo[MAX_AMMO_SLOTS] {}; // total ammo amounts
|
int m_ammo[MAX_AMMO_SLOTS] {}; // total ammo amounts
|
||||||
int m_deathCount {}; // number of bot deaths
|
int m_deathCount {}; // number of bot deaths
|
||||||
|
int m_ladderDir {}; // ladder move direction
|
||||||
|
|
||||||
bool m_isVIP {}; // bot is vip?
|
bool m_isVIP {}; // bot is vip?
|
||||||
bool m_isAlive {}; // has the player been killed or has he just respawned
|
bool m_isAlive {}; // has the player been killed or has he just respawned
|
||||||
|
|
@ -700,7 +710,7 @@ public:
|
||||||
bool m_hasHostage {}; // does bot owns some hostages
|
bool m_hasHostage {}; // does bot owns some hostages
|
||||||
bool m_hasProgressBar {}; // has progress bar on a HUD
|
bool m_hasProgressBar {}; // has progress bar on a HUD
|
||||||
bool m_jumpReady {}; // is double jump ready
|
bool m_jumpReady {}; // is double jump ready
|
||||||
bool m_canChooseAimDirection {}; // can choose aiming direction
|
bool m_canSetAimDirection {}; // can choose aiming direction
|
||||||
bool m_isEnemyReachable {}; // direct line to enemy
|
bool m_isEnemyReachable {}; // direct line to enemy
|
||||||
bool m_kickedByRotation {}; // is bot kicked due to rotation ?
|
bool m_kickedByRotation {}; // is bot kicked due to rotation ?
|
||||||
bool m_kickMeFromServer {}; // kick the bot off the server?
|
bool m_kickMeFromServer {}; // kick the bot off the server?
|
||||||
|
|
@ -768,6 +778,7 @@ public:
|
||||||
void startDoubleJump (edict_t *ent);
|
void startDoubleJump (edict_t *ent);
|
||||||
void sendBotToOrigin (const Vector &origin);
|
void sendBotToOrigin (const Vector &origin);
|
||||||
void markStale ();
|
void markStale ();
|
||||||
|
void setNewDifficulty (int32_t newDifficulty);
|
||||||
bool hasHostage ();
|
bool hasHostage ();
|
||||||
bool hasPrimaryWeapon () const;
|
bool hasPrimaryWeapon () const;
|
||||||
bool hasSecondaryWeapon () const;
|
bool hasSecondaryWeapon () const;
|
||||||
|
|
@ -792,7 +803,7 @@ public:
|
||||||
bool isDucking () const {
|
bool isDucking () const {
|
||||||
return !!(pev->flags & FL_DUCKING);
|
return !!(pev->flags & FL_DUCKING);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector getCenter () const {
|
Vector getCenter () const {
|
||||||
return (pev->absmax + pev->absmin) * 0.5;
|
return (pev->absmax + pev->absmin) * 0.5;
|
||||||
};
|
};
|
||||||
|
|
@ -942,6 +953,7 @@ extern ConVar cv_ignore_enemies_after_spawn_time;
|
||||||
extern ConVar cv_camping_time_min;
|
extern ConVar cv_camping_time_min;
|
||||||
extern ConVar cv_camping_time_max;
|
extern ConVar cv_camping_time_max;
|
||||||
extern ConVar cv_smoke_grenade_checks;
|
extern ConVar cv_smoke_grenade_checks;
|
||||||
|
extern ConVar cv_smoke_greande_checks_radius;
|
||||||
extern ConVar cv_check_darkness;
|
extern ConVar cv_check_darkness;
|
||||||
extern ConVar cv_use_hitbox_enemy_targeting;
|
extern ConVar cv_use_hitbox_enemy_targeting;
|
||||||
extern ConVar cv_restricted_weapons;
|
extern ConVar cv_restricted_weapons;
|
||||||
|
|
|
||||||
11
meson.build
11
meson.build
|
|
@ -37,6 +37,7 @@ os = host_machine.system()
|
||||||
cpu = host_machine.cpu_family()
|
cpu = host_machine.cpu_family()
|
||||||
cxx = compiler.get_id()
|
cxx = compiler.get_id()
|
||||||
build_type = get_option ('buildtype')
|
build_type = get_option ('buildtype')
|
||||||
|
cpu_non_x86 = cpu == 'arm' or cpu.startswith('ppc') or cpu.startswith('riscv')
|
||||||
|
|
||||||
opt_64bit = get_option('64bit')
|
opt_64bit = get_option('64bit')
|
||||||
opt_native = get_option('native')
|
opt_native = get_option('native')
|
||||||
|
|
@ -111,7 +112,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
||||||
'-pthread'
|
'-pthread'
|
||||||
]
|
]
|
||||||
|
|
||||||
if not opt_native and cpu != 'arm' and not cpu.startswith('ppc')
|
if not opt_native and not cpu_non_x86
|
||||||
cxxflags += '-mtune=generic'
|
cxxflags += '-mtune=generic'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
@ -119,7 +120,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
||||||
cxxflags += [
|
cxxflags += [
|
||||||
'-march=armv8-a+fp+simd',
|
'-march=armv8-a+fp+simd',
|
||||||
]
|
]
|
||||||
elif cpu != 'arm' and not cpu.startswith('ppc')
|
elif not cpu_non_x86
|
||||||
if not opt_nosimd
|
if not opt_nosimd
|
||||||
cxxflags += [
|
cxxflags += [
|
||||||
'-msse', '-msse2', '-msse3', '-msse3', '-mfpmath=sse'
|
'-msse', '-msse2', '-msse3', '-msse3', '-mfpmath=sse'
|
||||||
|
|
@ -143,7 +144,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
||||||
'-funroll-loops', '-fomit-frame-pointer', '-fno-stack-protector', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-fno-math-errno'
|
'-funroll-loops', '-fomit-frame-pointer', '-fno-stack-protector', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-fno-math-errno'
|
||||||
]
|
]
|
||||||
|
|
||||||
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
|
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and not cpu_non_x86
|
||||||
if not opt_static_linkent
|
if not opt_static_linkent
|
||||||
cxxflags += [
|
cxxflags += [
|
||||||
'-fdata-sections',
|
'-fdata-sections',
|
||||||
|
|
@ -203,7 +204,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# by default we buid 32bit binaries
|
# by default we buid 32bit binaries
|
||||||
if not opt_64bit and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
|
if not opt_64bit and cpu != 'aarch64' and not cpu_non_x86
|
||||||
cxxflags += '-m32'
|
cxxflags += '-m32'
|
||||||
ldflags += '-m32'
|
ldflags += '-m32'
|
||||||
|
|
||||||
|
|
@ -319,6 +320,8 @@ target_name = meson.project_name()
|
||||||
# xash specific postfix for binaries
|
# xash specific postfix for binaries
|
||||||
if cpu == 'aarch64'
|
if cpu == 'aarch64'
|
||||||
target_name += '_arm64'
|
target_name += '_arm64'
|
||||||
|
elif cpu.startswith('riscv')
|
||||||
|
target_name += '_riscv64d'
|
||||||
elif opt_64bit
|
elif opt_64bit
|
||||||
target_name += '_amd64'
|
target_name += '_amd64'
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
15
package.py
15
package.py
|
|
@ -134,13 +134,14 @@ class BotRelease(object):
|
||||||
self.pkg_matrix.append (BotPackage('linux', 'tar.xz', {'linux-x86': 'so'}))
|
self.pkg_matrix.append (BotPackage('linux', 'tar.xz', {'linux-x86': 'so'}))
|
||||||
self.pkg_matrix.append (BotPackage('extras', 'zip',
|
self.pkg_matrix.append (BotPackage('extras', 'zip',
|
||||||
{'linux-arm64': 'so',
|
{'linux-arm64': 'so',
|
||||||
'linux-amd64': 'so',
|
'linux-amd64': 'so',
|
||||||
'linux-x86-gcc': 'so',
|
'linux-riscv64': 'so',
|
||||||
'linux-x86-nosimd': 'so',
|
'linux-x86-gcc': 'so',
|
||||||
'windows-x86-gcc': 'dll',
|
'linux-x86-nosimd': 'so',
|
||||||
|
'windows-x86-gcc': 'dll',
|
||||||
'windows-x86-clang': 'dll',
|
'windows-x86-clang': 'dll',
|
||||||
'windows-x86-msvc-xp': 'dll',
|
'windows-x86-msvc-xp': 'dll',
|
||||||
'windows-amd64': 'dll',
|
'windows-amd64': 'dll',
|
||||||
'apple-x86': 'dylib',
|
'apple-x86': 'dylib',
|
||||||
'apple-arm64': 'dylib',
|
'apple-arm64': 'dylib',
|
||||||
}, extra=True))
|
}, extra=True))
|
||||||
|
|
@ -263,7 +264,9 @@ class BotRelease(object):
|
||||||
binary_name = binary_name + '_arm64'
|
binary_name = binary_name + '_arm64'
|
||||||
elif artifact.endswith('amd64'):
|
elif artifact.endswith('amd64'):
|
||||||
binary_name = binary_name + '_amd64'
|
binary_name = binary_name + '_amd64'
|
||||||
|
elif artifact.endswith('riscv64'):
|
||||||
|
binary_name = binary_name + '_riscv64d'
|
||||||
|
|
||||||
binary = os.path.join(self.artifacts, artifact, f'{binary_name}.{pkg.artifact[artifact]}')
|
binary = os.path.join(self.artifacts, artifact, f'{binary_name}.{pkg.artifact[artifact]}')
|
||||||
binary_base = os.path.basename(binary)
|
binary_base = os.path.basename(binary)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ ConVar cv_graph_analyze_optimize_nodes_on_finish ("graph_analyze_optimize_nodes_
|
||||||
ConVar cv_graph_analyze_mark_goals_on_finish ("graph_analyze_mark_goals_on_finish", "1", "Specifies if the analyzer should mark nodes as map goals automatically upon finishing.");
|
ConVar cv_graph_analyze_mark_goals_on_finish ("graph_analyze_mark_goals_on_finish", "1", "Specifies if the analyzer should mark nodes as map goals automatically upon finishing.");
|
||||||
|
|
||||||
void GraphAnalyze::start () {
|
void GraphAnalyze::start () {
|
||||||
|
if (m_isAnalyzing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// start analyzer in few seconds after level initialized
|
// start analyzer in few seconds after level initialized
|
||||||
if (cv_graph_analyze_auto_start) {
|
if (cv_graph_analyze_auto_start) {
|
||||||
m_updateInterval = game.time () + 3.0f;
|
m_updateInterval = game.time () + 3.0f;
|
||||||
|
|
@ -126,7 +130,7 @@ void GraphAnalyze::update () {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphAnalyze::suspend () {
|
void GraphAnalyze::suspend () {
|
||||||
m_updateInterval = 0.0f;
|
m_updateInterval = kInfiniteDistance;
|
||||||
m_isAnalyzing = false;
|
m_isAnalyzing = false;
|
||||||
m_isAnalyzed = false;
|
m_isAnalyzed = false;
|
||||||
m_basicsCreated = false;
|
m_basicsCreated = false;
|
||||||
|
|
@ -307,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,7 +325,7 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
||||||
if (cr::fequal (tr.flFraction, 1.0f)) {
|
if (cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Vector nextPos = { tr.vecEndPos.x, tr.vecEndPos.y, tr.vecEndPos.z + 19.0f };
|
const Vector &nextPos = { tr.vecEndPos.x, tr.vecEndPos.y, tr.vecEndPos.z + 19.0f };
|
||||||
|
|
||||||
const int endIndex = graph.getForAnalyzer (nextPos, range);
|
const int endIndex = graph.getForAnalyzer (nextPos, range);
|
||||||
const int targetIndex = graph.getNearestNoBuckets (nextPos, 250.0f);
|
const int targetIndex = graph.getNearestNoBuckets (nextPos, 250.0f);
|
||||||
|
|
@ -329,7 +333,7 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
||||||
if (graph.exists (endIndex) || !graph.exists (targetIndex)) {
|
if (graph.exists (endIndex) || !graph.exists (targetIndex)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto targetPos = graph[targetIndex].origin;
|
const auto &targetPos = graph[targetIndex].origin;
|
||||||
|
|
||||||
// re-check there's nothing nearby, and add something we're want
|
// re-check there's nothing nearby, and add something we're want
|
||||||
if (!graph.exists (graph.getNearestNoBuckets (nextPos, range))) {
|
if (!graph.exists (graph.getNearestNoBuckets (nextPos, range))) {
|
||||||
|
|
@ -344,6 +348,7 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
||||||
if ((graph.isNodeReacheable (targetPos, testPos)
|
if ((graph.isNodeReacheable (targetPos, testPos)
|
||||||
&& graph.isNodeReacheable (testPos, targetPos)) || (graph.isNodeReacheableWithJump (testPos, targetPos)
|
&& graph.isNodeReacheable (testPos, targetPos)) || (graph.isNodeReacheableWithJump (testPos, targetPos)
|
||||||
&& graph.isNodeReacheableWithJump (targetPos, testPos))) {
|
&& graph.isNodeReacheableWithJump (targetPos, testPos))) {
|
||||||
|
|
||||||
graph.add (NodeAddFlag::Normal, m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 9.0f } : nextPos);
|
graph.add (NodeAddFlag::Normal, m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 9.0f } : nextPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
266
src/botlib.cpp
266
src/botlib.cpp
|
|
@ -37,7 +37,6 @@ 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);
|
|
||||||
|
|
||||||
// game console variables
|
// game console variables
|
||||||
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
||||||
|
|
@ -78,10 +77,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) {
|
||||||
|
|
@ -100,12 +99,12 @@ void Bot::avoidGrenades () {
|
||||||
if (!(m_states & Sense::SeeingEnemy)) {
|
if (!(m_states & Sense::SeeingEnemy)) {
|
||||||
m_lookAt.y = cr::wrapAngle ((game.getEntityOrigin (pent) - getEyesPos ()).angles ().y + 180.0f);
|
m_lookAt.y = cr::wrapAngle ((game.getEntityOrigin (pent) - getEyesPos ()).angles ().y + 180.0f);
|
||||||
|
|
||||||
m_canChooseAimDirection = false;
|
m_canSetAimDirection = false;
|
||||||
m_preventFlashing = game.time () + rg (1.0f, 2.0f);
|
m_preventFlashing = game.time () + rg (1.0f, 2.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,6 +154,12 @@ void Bot::checkBreakable (edict_t *touch) {
|
||||||
if (!game.hasBreakables ()) {
|
if (!game.hasBreakables ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bool hasEnemy = !game.isNullEntity (m_enemy);
|
||||||
|
|
||||||
|
// do n ot track for breakables if has some enemies
|
||||||
|
if (hasEnemy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (game.isNullEntity (touch)) {
|
if (game.isNullEntity (touch)) {
|
||||||
auto breakable = lookupBreakable ();
|
auto breakable = lookupBreakable ();
|
||||||
|
|
@ -187,6 +192,7 @@ void Bot::checkBreakablesAround () {
|
||||||
|| !game.hasBreakables ()
|
|| !game.hasBreakables ()
|
||||||
|| m_seeEnemyTime + 4.0f > game.time ()
|
|| m_seeEnemyTime + 4.0f > game.time ()
|
||||||
|| !game.isNullEntity (m_enemy)
|
|| !game.isNullEntity (m_enemy)
|
||||||
|
|| (m_aimFlags & (AimFlags::PredictPath | AimFlags::Danger))
|
||||||
|| !hasPrimaryWeapon ()) {
|
|| !hasPrimaryWeapon ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +215,7 @@ void Bot::checkBreakablesAround () {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!util.isBreakableEntity (breakable)) {
|
if (!game.isBreakableEntity (breakable)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,7 +263,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 +276,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 +292,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,15 +326,14 @@ void Bot::setIdealReactionTimers (bool actual) {
|
||||||
|
|
||||||
return; // zero out reaction times for extreme mode
|
return; // zero out reaction times for extreme mode
|
||||||
}
|
}
|
||||||
const auto tweak = conf.getDifficultyTweaks (m_difficulty);
|
|
||||||
|
|
||||||
if (actual) {
|
if (actual) {
|
||||||
m_idealReactionTime = tweak->reaction[0];
|
m_idealReactionTime = m_difficultyData->reaction[0];
|
||||||
m_actualReactionTime = tweak->reaction[0];
|
m_actualReactionTime = m_difficultyData->reaction[0];
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_idealReactionTime = rg (tweak->reaction[0], tweak->reaction[1]);
|
m_idealReactionTime = rg (m_difficultyData->reaction[0], m_difficultyData->reaction[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::updatePickups () {
|
void Bot::updatePickups () {
|
||||||
|
|
@ -362,7 +367,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 +381,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 +451,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 +556,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 +637,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 +688,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 +711,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
|
||||||
|
|
||||||
|
|
@ -881,7 +886,9 @@ void Bot::showChatterIcon (bool show, bool disconnect) const {
|
||||||
|
|
||||||
// do not respect timers while disconnecting bot
|
// do not respect timers while disconnecting bot
|
||||||
for (auto &client : util.getClients ()) {
|
for (auto &client : util.getClients ()) {
|
||||||
if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) {
|
if (!(client.flags & ClientFlags::Used)
|
||||||
|
|| (client.ent->v.flags & FL_FAKECLIENT)
|
||||||
|
|| (client.team != m_team && !disconnect)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -891,7 +898,7 @@ void Bot::showChatterIcon (bool show, bool disconnect) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not respect timers while disconnecting bot
|
// do not respect timers while disconnecting bot
|
||||||
if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && (disconnect || client.iconTimestamp[ownIndex] < game.time ())) {
|
if (!show && (disconnect || (client.iconFlags[ownIndex] & ClientFlags::Icon)) && (disconnect || client.iconTimestamp[ownIndex] < game.time ())) {
|
||||||
sendBotVoice (false, client.ent, entindex ());
|
sendBotVoice (false, client.ent, entindex ());
|
||||||
|
|
||||||
client.iconTimestamp[ownIndex] = 0.0f;
|
client.iconTimestamp[ownIndex] = 0.0f;
|
||||||
|
|
@ -1292,7 +1299,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);
|
||||||
|
|
@ -1642,47 +1649,30 @@ void Bot::buyStuff () {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::updateEmotions () {
|
void Bot::updateEmotions () {
|
||||||
// slowly increase/decrease dynamic emotions back to their base level
|
constexpr auto kEmotionUpdateStep = 0.05f;
|
||||||
|
|
||||||
if (m_nextEmotionUpdate > game.time ()) {
|
if (m_nextEmotionUpdate > game.time ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_seeEnemyTime + 1.0f > game.time ()) {
|
if (m_seeEnemyTime + 1.0f > game.time ()) {
|
||||||
m_agressionLevel += 0.05f;
|
m_agressionLevel = cr::min (m_agressionLevel + kEmotionUpdateStep, 1.0f);
|
||||||
|
|
||||||
if (m_agressionLevel > 1.0f) {
|
|
||||||
m_agressionLevel = 1.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (m_seeEnemyTime + 5.0f < game.time ()) {
|
else if (m_seeEnemyTime + 5.0f < game.time ()) {
|
||||||
|
// smoothly return aggression to base level
|
||||||
if (m_agressionLevel > m_baseAgressionLevel) {
|
if (m_agressionLevel > m_baseAgressionLevel) {
|
||||||
m_agressionLevel -= 0.05f;
|
m_agressionLevel = cr::max (m_agressionLevel - kEmotionUpdateStep, m_baseAgressionLevel);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_agressionLevel += 0.05f;
|
m_agressionLevel = cr::min (m_agressionLevel + kEmotionUpdateStep, m_baseAgressionLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// smoothly return fear to base level
|
||||||
if (m_fearLevel > m_baseFearLevel) {
|
if (m_fearLevel > m_baseFearLevel) {
|
||||||
m_fearLevel -= 0.05f;
|
m_fearLevel = cr::max (m_fearLevel - kEmotionUpdateStep, m_baseFearLevel);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_fearLevel += 0.05f;
|
m_fearLevel = cr::min (m_fearLevel + kEmotionUpdateStep, m_baseFearLevel);
|
||||||
}
|
|
||||||
|
|
||||||
if (m_agressionLevel > 1.0f) {
|
|
||||||
m_agressionLevel = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_fearLevel > 1.0f) {
|
|
||||||
m_fearLevel = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_agressionLevel < 0.0f) {
|
|
||||||
m_agressionLevel = 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_fearLevel < 0.0f) {
|
|
||||||
m_fearLevel = 0.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_nextEmotionUpdate = game.time () + 0.5f;
|
m_nextEmotionUpdate = game.time () + 0.5f;
|
||||||
|
|
@ -1694,7 +1684,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
|
||||||
|
|
@ -1708,7 +1698,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);
|
||||||
|
|
||||||
|
|
@ -1758,7 +1748,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
|
||||||
|
|
@ -1785,7 +1775,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)) {
|
||||||
|
|
||||||
|
|
@ -1809,7 +1799,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;
|
||||||
}
|
}
|
||||||
|
|
@ -1828,7 +1818,9 @@ void Bot::syncUpdatePredictedIndex () {
|
||||||
|
|
||||||
const float distToBotSq = botOrigin.distanceSq (graph[index].origin);
|
const float distToBotSq = botOrigin.distanceSq (graph[index].origin);
|
||||||
|
|
||||||
if (vistab.visible (currentNodeIndex, index) && distToBotSq < cr::sqrf (2048.0f)) {
|
if (vistab.visible (currentNodeIndex, index)
|
||||||
|
&& distToBotSq < cr::sqrf (2048.0f)
|
||||||
|
&& distToBotSq > cr::sqrf (128.0f)) {
|
||||||
bestIndex = index;
|
bestIndex = index;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1845,7 +1837,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1900,7 +1892,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;
|
||||||
|
|
||||||
|
|
@ -1918,7 +1910,6 @@ void Bot::setConditions () {
|
||||||
}
|
}
|
||||||
else if (rg.chance (60)) {
|
else if (rg.chance (60)) {
|
||||||
if (m_lastVictim->v.weapons & kSniperWeaponMask) {
|
if (m_lastVictim->v.weapons & kSniperWeaponMask) {
|
||||||
|
|
||||||
pushChatterMessage (Chatter::SniperKilled);
|
pushChatterMessage (Chatter::SniperKilled);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -1950,7 +1941,7 @@ void Bot::setConditions () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_killsInterval = m_lastVictimTime - game.time ();
|
m_killsInterval = game.time () - m_lastVictimTime;
|
||||||
|
|
||||||
if (m_killsInterval <= 5.0f) {
|
if (m_killsInterval <= 5.0f) {
|
||||||
++m_killsCount;
|
++m_killsCount;
|
||||||
|
|
@ -1965,7 +1956,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 ();
|
||||||
|
|
||||||
|
|
@ -1986,7 +1977,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2085,7 +2076,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 ||
|
||||||
|
|
@ -2113,7 +2104,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 ())) {
|
||||||
|
|
@ -2135,7 +2126,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)
|
||||||
|
|
@ -2252,10 +2243,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 ();
|
||||||
|
|
@ -2434,7 +2426,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 {
|
||||||
|
|
@ -2467,12 +2459,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);
|
||||||
|
|
@ -2491,11 +2483,11 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::checkRadioQueue () {
|
void Bot::checkRadioQueue () {
|
||||||
|
|
@ -2702,7 +2694,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) {
|
||||||
|
|
@ -2718,11 +2710,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);
|
||||||
|
|
@ -2846,7 +2838,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) {
|
||||||
|
|
@ -2911,14 +2903,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;
|
||||||
|
|
@ -3011,11 +3003,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)) {
|
||||||
|
|
||||||
|
|
@ -3060,8 +3052,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
|
||||||
|
|
@ -3096,9 +3088,9 @@ void Bot::frame () {
|
||||||
void Bot::update () {
|
void Bot::update () {
|
||||||
const auto tid = getCurrentTaskId ();
|
const auto tid = getCurrentTaskId ();
|
||||||
|
|
||||||
m_canChooseAimDirection = true;
|
m_canSetAimDirection = 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)) {
|
||||||
|
|
@ -3144,7 +3136,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);
|
||||||
|
|
@ -3237,7 +3229,7 @@ void Bot::logicDuringFreezetime () {
|
||||||
if (ent) {
|
if (ent) {
|
||||||
m_lookAt = ent->v.origin + ent->v.view_ofs;
|
m_lookAt = ent->v.origin + ent->v.view_ofs;
|
||||||
|
|
||||||
if (m_buyingFinished) {
|
if (m_buyingFinished && game.getPlayerTeam (ent) != m_team) {
|
||||||
m_enemy = ent;
|
m_enemy = ent;
|
||||||
m_enemyOrigin = ent->v.origin;
|
m_enemyOrigin = ent->v.origin;
|
||||||
}
|
}
|
||||||
|
|
@ -3283,7 +3275,15 @@ void Bot::checkSpawnConditions () {
|
||||||
dropCurrentWeapon ();
|
dropCurrentWeapon ();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selectWeaponById (Weapon::Knife);
|
bool switchToKnife = true;
|
||||||
|
|
||||||
|
if (m_currentWeapon == Weapon::Scout) {
|
||||||
|
switchToKnife = rg.chance (25);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switchToKnife) {
|
||||||
|
selectWeaponById (Weapon::Knife);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_checkKnifeSwitch = false;
|
m_checkKnifeSwitch = false;
|
||||||
|
|
@ -3325,12 +3325,11 @@ 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)
|
|
||||||
|
|
||||||
resetMovement ();
|
resetMovement ();
|
||||||
|
|
||||||
// increase reaction time
|
// increase reaction time
|
||||||
m_actualReactionTime += 0.3f;
|
m_actualReactionTime += 0.3f;
|
||||||
|
m_movedDistance = kMinMovedDistance + 0.1f; // length of different vector (distance bot moved)
|
||||||
|
|
||||||
if (m_actualReactionTime > m_idealReactionTime) {
|
if (m_actualReactionTime > m_idealReactionTime) {
|
||||||
m_actualReactionTime = m_idealReactionTime;
|
m_actualReactionTime = m_idealReactionTime;
|
||||||
|
|
@ -3351,13 +3350,11 @@ void Bot::logic () {
|
||||||
if (m_prevTime <= game.time ()) {
|
if (m_prevTime <= game.time ()) {
|
||||||
|
|
||||||
// see how far bot has moved since the previous position...
|
// see how far bot has moved since the previous position...
|
||||||
if (m_checkTerrain) {
|
m_movedDistance = m_prevOrigin.distanceSq (pev->origin);
|
||||||
m_movedDistance = m_prevOrigin.distance (pev->origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.2f - m_frameInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's some radio message to respond, check it
|
// if there's some radio message to respond, check it
|
||||||
|
|
@ -3450,7 +3447,7 @@ void Bot::logic () {
|
||||||
|
|
||||||
// ensure we're not stuck picking something
|
// ensure we're not stuck picking something
|
||||||
if (m_moveToGoal && m_moveSpeed > 0.0f
|
if (m_moveToGoal && m_moveSpeed > 0.0f
|
||||||
&& rg (2.5f, 3.5f) + m_navTimeset + m_destOrigin.distanceSq2d (pev->origin) / cr::sqrf (m_moveSpeed) < game.time ()
|
&& rg (2.5f, 3.5f) + m_navTimeset + m_destOrigin.distanceSq2d (pev->origin) / cr::sqrf (cr::max (1.0f, m_moveSpeed)) < game.time ()
|
||||||
&& !(m_states & Sense::SeeingEnemy)) {
|
&& !(m_states & Sense::SeeingEnemy)) {
|
||||||
ensurePickupEntitiesClear ();
|
ensurePickupEntitiesClear ();
|
||||||
}
|
}
|
||||||
|
|
@ -3465,8 +3462,6 @@ void Bot::logic () {
|
||||||
|
|
||||||
// save the previous speed (for checking if stuck)
|
// save the previous speed (for checking if stuck)
|
||||||
m_prevSpeed = cr::abs (m_moveSpeed);
|
m_prevSpeed = cr::abs (m_moveSpeed);
|
||||||
m_prevVelocity = cr::abs (pev->velocity.length2d ());
|
|
||||||
|
|
||||||
m_lastDamageType = -1; // reset damage
|
m_lastDamageType = -1; // reset damage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3474,6 +3469,8 @@ void Bot::spawned () {
|
||||||
if (game.is (GameFlags::CSDM | GameFlags::ZombieMod)) {
|
if (game.is (GameFlags::CSDM | GameFlags::ZombieMod)) {
|
||||||
newRound ();
|
newRound ();
|
||||||
clearTasks ();
|
clearTasks ();
|
||||||
|
|
||||||
|
m_buyingFinished = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3593,7 +3590,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;
|
||||||
|
|
@ -3628,7 +3625,7 @@ void Bot::showDebugOverlay () {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::hasHostage () {
|
bool Bot::hasHostage () {
|
||||||
if (cv_ignore_objectives || game.mapIs (MapFlags::Demolition)) {
|
if (cv_ignore_objectives || !game.mapIs (MapFlags::HostageRescue)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3655,7 +3652,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;
|
||||||
|
|
@ -3669,10 +3666,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 ();
|
||||||
|
|
@ -3736,7 +3733,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;
|
||||||
|
|
@ -3798,11 +3795,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) {
|
||||||
|
|
@ -3833,7 +3830,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);
|
||||||
|
|
@ -3857,7 +3854,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;
|
||||||
|
|
||||||
|
|
@ -3915,9 +3912,6 @@ void Bot::resetDoubleJump () {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::debugMsgInternal (StringRef str) {
|
void Bot::debugMsgInternal (StringRef str) {
|
||||||
if (game.isDedicated ()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const int level = cv_debug.as <int> ();
|
const int level = cv_debug.as <int> ();
|
||||||
|
|
||||||
if (level <= 2) {
|
if (level <= 2) {
|
||||||
|
|
@ -3928,7 +3922,10 @@ void Bot::debugMsgInternal (StringRef str) {
|
||||||
|
|
||||||
bool playMessage = false;
|
bool playMessage = false;
|
||||||
|
|
||||||
if (level == 3 && !game.isNullEntity (game.getLocalEntity ()) && game.getLocalEntity ()->v.iuser2 == entindex ()) {
|
if (level == 3
|
||||||
|
&& !game.isNullEntity (game.getLocalEntity ())
|
||||||
|
&& game.getLocalEntity ()->v.iuser2 == entindex ()) {
|
||||||
|
|
||||||
playMessage = true;
|
playMessage = true;
|
||||||
}
|
}
|
||||||
else if (level != 3) {
|
else if (level != 3) {
|
||||||
|
|
@ -3939,7 +3936,7 @@ void Bot::debugMsgInternal (StringRef str) {
|
||||||
logger.message (printBuf.chars ());
|
logger.message (printBuf.chars ());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playMessage) {
|
if (playMessage && !game.isDedicated ()) {
|
||||||
ctrl.msg (printBuf.chars ());
|
ctrl.msg (printBuf.chars ());
|
||||||
sendToChat (printBuf, false);
|
sendToChat (printBuf, false);
|
||||||
}
|
}
|
||||||
|
|
@ -3948,16 +3945,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
|
||||||
|
|
@ -4038,12 +4035,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)) {
|
||||||
|
|
@ -4055,13 +4047,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)) {
|
||||||
|
|
@ -4086,7 +4078,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
|
||||||
|
|
@ -4100,7 +4092,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;
|
||||||
|
|
||||||
|
|
@ -4115,7 +4107,7 @@ void Bot::updateHearing () {
|
||||||
if (!(client.flags & ClientFlags::Used)
|
if (!(client.flags & ClientFlags::Used)
|
||||||
|| !(client.flags & ClientFlags::Alive)
|
|| !(client.flags & ClientFlags::Alive)
|
||||||
|| client.ent == ent ()
|
|| client.ent == ent ()
|
||||||
|| client.team == m_team
|
|| client.team2 == m_team
|
||||||
|| !client.ent
|
|| !client.ent
|
||||||
|| client.noise.last < game.time ()) {
|
|| client.noise.last < game.time ()) {
|
||||||
|
|
||||||
|
|
@ -4139,7 +4131,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 ()
|
||||||
|
|
@ -4215,7 +4207,7 @@ void Bot::updateHearing () {
|
||||||
else {
|
else {
|
||||||
if (cv_shoots_thru_walls
|
if (cv_shoots_thru_walls
|
||||||
&& m_lastEnemy == m_hearedEnemy
|
&& m_lastEnemy == m_hearedEnemy
|
||||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
|
&& rg.chance (m_difficultyData->hearThruPct)
|
||||||
&& m_seeEnemyTime + 3.0f > game.time ()
|
&& m_seeEnemyTime + 3.0f > game.time ()
|
||||||
&& isPenetrableObstacle (m_hearedEnemy->v.origin)) {
|
&& isPenetrableObstacle (m_hearedEnemy->v.origin)) {
|
||||||
|
|
||||||
|
|
@ -4234,7 +4226,7 @@ void Bot::updateHearing () {
|
||||||
void Bot::enteredBuyZone (int buyState) {
|
void Bot::enteredBuyZone (int buyState) {
|
||||||
// this function is gets called when bot enters a buyzone, to allow bot to buy some stuff
|
// this function is gets called when bot enters a buyzone, to allow bot to buy some stuff
|
||||||
|
|
||||||
if (m_isCreature) {
|
if (m_isCreature || hasHostage ()) {
|
||||||
return; // creatures can't buy anything
|
return; // creatures can't buy anything
|
||||||
}
|
}
|
||||||
const int *econLimit = conf.getEconLimit ();
|
const int *econLimit = conf.getEconLimit ();
|
||||||
|
|
@ -4243,8 +4235,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;
|
||||||
|
|
@ -4283,7 +4275,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;
|
||||||
|
|
@ -4356,8 +4348,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) {
|
||||||
|
|
@ -4437,7 +4429,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)) {
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -614,7 +614,7 @@ bool Bot::lookupEnemies () {
|
||||||
|
|
||||||
// if no enemy visible check if last one shoot able through wall
|
// if no enemy visible check if last one shoot able through wall
|
||||||
if (cv_shoots_thru_walls
|
if (cv_shoots_thru_walls
|
||||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
|
&& rg.chance (m_difficultyData->seenThruPct)
|
||||||
&& isPenetrableObstacle (newEnemy->v.origin)) {
|
&& isPenetrableObstacle (newEnemy->v.origin)) {
|
||||||
|
|
||||||
m_seeEnemyTime = game.time ();
|
m_seeEnemyTime = game.time ();
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -667,11 +667,11 @@ Vector Bot::getBodyOffsetError (float distance) {
|
||||||
const auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins;
|
const auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins;
|
||||||
|
|
||||||
m_aimLastError = Vector (
|
m_aimLastError = Vector (
|
||||||
rg (mins.x * hitError, maxs.x * hitError),
|
rg (mins.x * hitError, maxs.x * hitError),
|
||||||
rg (mins.y * hitError, maxs.y * hitError),
|
rg (mins.y * hitError, maxs.y * hitError),
|
||||||
rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f));
|
rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f));
|
||||||
|
|
||||||
const auto &aimError = conf.getDifficultyTweaks (m_difficulty)->aimError;
|
const auto &aimError = m_difficultyData->aimError;
|
||||||
m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z));
|
m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z));
|
||||||
|
|
||||||
m_aimErrorTime = game.time () + rg (0.4f, 0.8f);
|
m_aimErrorTime = game.time () + rg (0.4f, 0.8f);
|
||||||
|
|
@ -710,14 +710,19 @@ Vector Bot::getEnemyBodyOffset () {
|
||||||
compensation.clear ();
|
compensation.clear ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the correct head origin
|
||||||
|
const auto &headOrigin = [&] (edict_t *e, const float distance) -> Vector {
|
||||||
|
return Vector { e->v.origin.x, e->v.origin.y, e->v.absmin.z + e->v.size.z * 0.81f } + getCustomHeight (distance);
|
||||||
|
};
|
||||||
|
|
||||||
// if we only suspect an enemy behind a wall take the worst skill
|
// if we only suspect an enemy behind a wall take the worst skill
|
||||||
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 = m_difficultyData->headshotPct;
|
||||||
|
|
||||||
// with to much recoil or using specific weapons choice to aim to the chest
|
// with to much recoil or using specific weapons choice to aim to the chest
|
||||||
if (distance > kSprayDistance && (isRecoilHigh () || usesShotgun ())) {
|
if (distance > kSprayDistance && (isRecoilHigh () || usesShotgun ())) {
|
||||||
|
|
@ -728,11 +733,13 @@ Vector Bot::getEnemyBodyOffset () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check is our skill match to aim at head, else aim at enemy body
|
// now check is our skill match to aim at head, else aim at enemy body
|
||||||
if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) {
|
if (m_enemyBodyPartSet == m_enemy
|
||||||
spot = m_enemyOrigin + getCustomHeight (distance);
|
|| ((m_enemyBodyPartSet != m_enemy) && rg.chance (headshotPct))) {
|
||||||
|
|
||||||
|
spot = headOrigin (m_enemy, distance);
|
||||||
|
|
||||||
if (usesSniper ()) {
|
if (usesSniper ()) {
|
||||||
spot.z -= pev->view_ofs.z * 0.5f;
|
spot.z -= pev->view_ofs.z * 0.35f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set's the enemy shooting spot to head, if headshot pct allows, and use head for that
|
// set's the enemy shooting spot to head, if headshot pct allows, and use head for that
|
||||||
|
|
@ -741,6 +748,10 @@ Vector Bot::getEnemyBodyOffset () {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
spot = m_enemy->v.origin;
|
spot = m_enemy->v.origin;
|
||||||
|
|
||||||
|
if (m_difficulty == Difficulty::Expert) {
|
||||||
|
spot.z += pev->view_ofs.z * 0.35f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_enemyParts & Visibility::Body) {
|
else if (m_enemyParts & Visibility::Body) {
|
||||||
|
|
@ -750,10 +761,10 @@ Vector Bot::getEnemyBodyOffset () {
|
||||||
spot = m_enemyOrigin;
|
spot = m_enemyOrigin;
|
||||||
}
|
}
|
||||||
else if (m_enemyParts & Visibility::Head) {
|
else if (m_enemyParts & Visibility::Head) {
|
||||||
spot = m_enemyOrigin + getCustomHeight (distance);
|
spot = headOrigin (m_enemy, distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto idealSpot = m_enemyOrigin;
|
auto idealSpot = spot;
|
||||||
|
|
||||||
if (m_difficulty < Difficulty::Hard && isEnemyInSight (idealSpot)) {
|
if (m_difficulty < Difficulty::Hard && isEnemyInSight (idealSpot)) {
|
||||||
spot = idealSpot + ((spot - idealSpot) * 0.005f); // gradually adjust the aiming direction
|
spot = idealSpot + ((spot - idealSpot) * 0.005f); // gradually adjust the aiming direction
|
||||||
|
|
@ -778,10 +789,10 @@ Vector Bot::getCustomHeight (float distance) const {
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr float kOffsetRanges[9][3] = {
|
constexpr float kOffsetRanges[9][3] = {
|
||||||
{ 0.0f, 0.0f, 0.0f }, // none
|
{ 0.0f, 0.0f, 0.0f }, // none
|
||||||
{ 0.0f, 0.0f, 0.0f }, // melee
|
{ 0.0f, 0.0f, 0.0f }, // melee
|
||||||
{ 0.5f, -0.1f, -1.5f }, // pistol
|
{ 0.5f, -0.1f, -1.5f }, // pistol
|
||||||
{ 6.5f, 6.0f, -2.0f }, // shotgun
|
{ 6.5f, 6.0f, -2.0f }, // shotgun
|
||||||
{ 0.5f, -7.5f, -9.5f }, // zoomrifle
|
{ 0.5f, -7.5f, -9.5f }, // zoomrifle
|
||||||
{ 0.5f, -7.5f, -9.5f }, // rifle
|
{ 0.5f, -7.5f, -9.5f }, // rifle
|
||||||
{ 0.5f, -7.5f, -9.5f }, // smg
|
{ 0.5f, -7.5f, -9.5f }, // smg
|
||||||
|
|
@ -817,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1003,7 +1014,7 @@ bool Bot::needToPauseFiring (float distance) {
|
||||||
|
|
||||||
const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f;
|
const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f;
|
||||||
const float baseTime = distance > kSprayDistance ? 0.55f : 0.38f;
|
const float baseTime = distance > kSprayDistance ? 0.55f : 0.38f;
|
||||||
const float maxRecoil = static_cast <float> (conf.getDifficultyTweaks (m_difficulty)->maxRecoil);
|
const float maxRecoil = static_cast <float> (m_difficultyData->maxRecoil);
|
||||||
|
|
||||||
// check if we need to compensate recoil
|
// check if we need to compensate recoil
|
||||||
if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) {
|
if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) {
|
||||||
|
|
@ -1079,7 +1090,7 @@ bool Bot::checkZoom (float distance) {
|
||||||
return zoomChange;
|
return zoomChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::selectWeapons (float distance, int, int id, int choosen) {
|
void Bot::handleWeapons (float distance, int, int id, int choosen) {
|
||||||
const auto tab = conf.getRawWeapons ();
|
const auto tab = conf.getRawWeapons ();
|
||||||
|
|
||||||
// we want to fire weapon, don't reload now
|
// we want to fire weapon, don't reload now
|
||||||
|
|
@ -1249,7 +1260,7 @@ void Bot::fireWeapons () {
|
||||||
|
|
||||||
// if knife mode use knife only
|
// if knife mode use knife only
|
||||||
if (isKnifeMode ()) {
|
if (isKnifeMode ()) {
|
||||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
handleWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1263,7 +1274,7 @@ void Bot::fireWeapons () {
|
||||||
&& !isGroupOfEnemies (pev->origin)
|
&& !isGroupOfEnemies (pev->origin)
|
||||||
&& getCurrentTaskId () != Task::Camp) {
|
&& getCurrentTaskId () != Task::Camp) {
|
||||||
|
|
||||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
handleWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1316,7 +1327,7 @@ void Bot::fireWeapons () {
|
||||||
}
|
}
|
||||||
selectId = Weapon::Knife; // no available ammo, use knife!
|
selectId = Weapon::Knife; // no available ammo, use knife!
|
||||||
}
|
}
|
||||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
handleWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
|
bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
|
||||||
|
|
@ -1448,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 ()) {
|
||||||
|
|
@ -1615,9 +1626,9 @@ void Bot::attackMovement () {
|
||||||
if (m_difficulty >= Difficulty::Normal
|
if (m_difficulty >= Difficulty::Normal
|
||||||
&& distanceSq < cr::sqrf (kSprayDistance)
|
&& distanceSq < cr::sqrf (kSprayDistance)
|
||||||
&& (m_jumpTime + 5.0f < game.time ()
|
&& (m_jumpTime + 5.0f < game.time ()
|
||||||
&& isOnFloor ()
|
&& isOnFloor ()
|
||||||
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
|
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
|
||||||
&& pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) {
|
&& pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) {
|
||||||
|
|
||||||
pev->button |= IN_JUMP;
|
pev->button |= IN_JUMP;
|
||||||
}
|
}
|
||||||
|
|
@ -1635,8 +1646,7 @@ void Bot::attackMovement () {
|
||||||
|
|
||||||
const int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
const int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
||||||
|
|
||||||
if (vistab.visible (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch)
|
if (vistab.visibleBothSides (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch)) {
|
||||||
&& vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) {
|
|
||||||
m_duckTime = game.time () + m_frameInterval * 3.0f;
|
m_duckTime = game.time () + m_frameInterval * 3.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1962,7 +1972,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2237,7 +2247,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
|
||||||
|
|
@ -2275,7 +2285,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 ());
|
||||||
|
|
||||||
|
|
@ -2289,7 +2299,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;
|
||||||
}
|
}
|
||||||
|
|
@ -2332,7 +2342,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)) {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ void BotConfig::loadMainConfig (bool isFirstLoad) {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto storeVarValue = [] (cvar_t *c, StringRef value) {
|
auto storeVarValue = [] (cvar_t *c, StringRef value) {
|
||||||
auto &cvars = game.getCvars ();
|
auto &cvars = game.getCvars ();
|
||||||
|
|
||||||
for (auto &var : cvars) {
|
for (auto &var : cvars) {
|
||||||
|
|
@ -303,9 +303,9 @@ void BotConfig::loadChatterConfig () {
|
||||||
{ "Chatter_GuardingEscapeZone", Chatter::GuardingEscapeZone, kMaxChatterRepeatInterval },
|
{ "Chatter_GuardingEscapeZone", Chatter::GuardingEscapeZone, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_GuardingVipSafety", Chatter::GuardingVIPSafety, kMaxChatterRepeatInterval },
|
{ "Chatter_GuardingVipSafety", Chatter::GuardingVIPSafety, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_PlantingC4", Chatter::PlantingBomb, 10.0f },
|
{ "Chatter_PlantingC4", Chatter::PlantingBomb, 10.0f },
|
||||||
{ "Chatter_InCombat", Chatter::InCombat, kMaxChatterRepeatInterval },
|
{ "Chatter_InCombat", Chatter::InCombat, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_SeeksEnemy", Chatter::SeekingEnemies, kMaxChatterRepeatInterval },
|
{ "Chatter_SeeksEnemy", Chatter::SeekingEnemies, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_Nothing", Chatter::Nothing, kMaxChatterRepeatInterval },
|
{ "Chatter_Nothing", Chatter::Nothing, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_EnemyDown", Chatter::EnemyDown, 10.0f },
|
{ "Chatter_EnemyDown", Chatter::EnemyDown, 10.0f },
|
||||||
{ "Chatter_UseHostage", Chatter::UsingHostages, kMaxChatterRepeatInterval },
|
{ "Chatter_UseHostage", Chatter::UsingHostages, kMaxChatterRepeatInterval },
|
||||||
{ "Chatter_WonTheRound", Chatter::WonTheRound, kMaxChatterRepeatInterval },
|
{ "Chatter_WonTheRound", Chatter::WonTheRound, kMaxChatterRepeatInterval },
|
||||||
|
|
@ -350,9 +350,9 @@ void BotConfig::loadChatterConfig () {
|
||||||
{ "Chatter_BombSiteSecured", Chatter::BombsiteSecured, 3.5f },
|
{ "Chatter_BombSiteSecured", Chatter::BombsiteSecured, 3.5f },
|
||||||
{ "Chatter_GoingToCamp", Chatter::GoingToCamp, 30.0f },
|
{ "Chatter_GoingToCamp", Chatter::GoingToCamp, 30.0f },
|
||||||
{ "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 {
|
||||||
|
|
@ -624,7 +632,7 @@ void BotConfig::loadDifficultyConfig () {
|
||||||
};
|
};
|
||||||
|
|
||||||
m_difficulty[Difficulty::Expert] = {
|
m_difficulty[Difficulty::Expert] = {
|
||||||
{ 0.1f, 0.2f }, 100, 90, 90, 21, { 0.0f, 0.0f, 0.0f }
|
{ 0.1f, 0.2f }, 100, 90, 90, 21, { 0.0f, 0.0f, 0.0f }
|
||||||
};
|
};
|
||||||
|
|
||||||
// currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob, recoil, aim_error {x,y,z}
|
// currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob, recoil, aim_error {x,y,z}
|
||||||
|
|
@ -713,16 +721,17 @@ void BotConfig::loadCustomConfig () {
|
||||||
|
|
||||||
auto setDefaults = [&] () {
|
auto setDefaults = [&] () {
|
||||||
m_custom = {
|
m_custom = {
|
||||||
{ "C4ModelName", "c4.mdl" },
|
{ "C4ModelName", "c4.mdl" },
|
||||||
{ "AMXParachuteCvar", "sv_parachute" },
|
{ "AMXParachuteCvar", "sv_parachute" },
|
||||||
{ "CustomCSDMSpawnPoint", "view_spawn" },
|
{ "CustomCSDMSpawnPoint", "view_spawn" },
|
||||||
{ "CSDMDetectCvar", "csdm_active" },
|
{ "CSDMDetectCvar", "csdm_active" },
|
||||||
{ "ZMDetectCvar", "zp_delay" },
|
{ "ZMDetectCvar", "zp_delay" },
|
||||||
{ "ZMDelayCvar", "zp_delay" },
|
{ "ZMDelayCvar", "zp_delay" },
|
||||||
{ "ZMInfectedTeam", "T" },
|
{ "ZMInfectedTeam", "T" },
|
||||||
{ "EnableFakeBotFeatures", "no" },
|
{ "EnableFakeBotFeatures", "no" },
|
||||||
{ "DisableLogFile", "no" },
|
{ "DisableLogFile", "no" },
|
||||||
{ "CheckConnectivityHost", "yapb.jeefo.net" }
|
{ "CheckConnectivityHost", "yapb.jeefo.net" },
|
||||||
|
{ "DisableSpawnControl", "no" }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
setDefaults ();
|
setDefaults ();
|
||||||
|
|
|
||||||
181
src/control.cpp
181
src/control.cpp
|
|
@ -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") {
|
||||||
|
|
@ -423,6 +423,7 @@ int BotControl::cmdNode () {
|
||||||
addGraphCmd ("stats", "stats [noarguments]", "Shows the stats about node types on the map.", &BotControl::cmdNodeShowStats);
|
addGraphCmd ("stats", "stats [noarguments]", "Shows the stats about node types on the map.", &BotControl::cmdNodeShowStats);
|
||||||
addGraphCmd ("fileinfo", "fileinfo [noarguments]", "Shows basic information about graph file.", &BotControl::cmdNodeFileInfo);
|
addGraphCmd ("fileinfo", "fileinfo [noarguments]", "Shows basic information about graph file.", &BotControl::cmdNodeFileInfo);
|
||||||
addGraphCmd ("adjust_height", "adjust_height [height offset]", "Modifies all the graph nodes height (z-component) with specified offset.", &BotControl::cmdNodeAdjustHeight);
|
addGraphCmd ("adjust_height", "adjust_height [height offset]", "Modifies all the graph nodes height (z-component) with specified offset.", &BotControl::cmdNodeAdjustHeight);
|
||||||
|
addGraphCmd ("refresh", "refresh [noarguments]", "Deletes a current graph and downloads one from graph database.", &BotControl::cmdNodeRefresh);
|
||||||
|
|
||||||
// add path commands
|
// add path commands
|
||||||
addGraphCmd ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
|
addGraphCmd ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
|
||||||
|
|
@ -598,6 +599,13 @@ int BotControl::cmdNodeAddBasic () {
|
||||||
int BotControl::cmdNodeSave () {
|
int BotControl::cmdNodeSave () {
|
||||||
enum args { graph_cmd = 1, cmd, option };
|
enum args { graph_cmd = 1, cmd, option };
|
||||||
|
|
||||||
|
// prevent some commands while analyzing graph
|
||||||
|
if (analyzer.isAnalyzing ()) {
|
||||||
|
msg ("This command is unavailable while map analysis is ongoing.");
|
||||||
|
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
// if no check is set save anyway
|
// if no check is set save anyway
|
||||||
if (arg <StringRef> (option) == "nocheck") {
|
if (arg <StringRef> (option) == "nocheck") {
|
||||||
graph.saveGraphData ();
|
graph.saveGraphData ();
|
||||||
|
|
@ -629,6 +637,13 @@ int BotControl::cmdNodeSave () {
|
||||||
int BotControl::cmdNodeLoad () {
|
int BotControl::cmdNodeLoad () {
|
||||||
enum args { graph_cmd = 1, cmd };
|
enum args { graph_cmd = 1, cmd };
|
||||||
|
|
||||||
|
// prevent some commands while analyzing graph
|
||||||
|
if (analyzer.isAnalyzing ()) {
|
||||||
|
msg ("This command is unavailable while map analysis is ongoing.");
|
||||||
|
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
// just save graph on request
|
// just save graph on request
|
||||||
if (graph.loadGraphData ()) {
|
if (graph.loadGraphData ()) {
|
||||||
msg ("Graph successfully loaded.");
|
msg ("Graph successfully loaded.");
|
||||||
|
|
@ -642,6 +657,13 @@ int BotControl::cmdNodeLoad () {
|
||||||
int BotControl::cmdNodeErase () {
|
int BotControl::cmdNodeErase () {
|
||||||
enum args { graph_cmd = 1, cmd, iamsure };
|
enum args { graph_cmd = 1, cmd, iamsure };
|
||||||
|
|
||||||
|
// prevent some commands while analyzing graph
|
||||||
|
if (analyzer.isAnalyzing ()) {
|
||||||
|
msg ("This command is unavailable while map analysis is ongoing.");
|
||||||
|
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
// prevent accidents when graph are deleted unintentionally
|
// prevent accidents when graph are deleted unintentionally
|
||||||
if (arg <StringRef> (iamsure) == "iamsure") {
|
if (arg <StringRef> (iamsure) == "iamsure") {
|
||||||
bstor.unlinkFromDisk (false, false);
|
bstor.unlinkFromDisk (false, false);
|
||||||
|
|
@ -652,6 +674,26 @@ int BotControl::cmdNodeErase () {
|
||||||
return BotCommandResult::Handled;
|
return BotCommandResult::Handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int BotControl::cmdNodeRefresh () {
|
||||||
|
enum args { graph_cmd = 1, cmd, iamsure };
|
||||||
|
|
||||||
|
if (!graph.canDownload ()) {
|
||||||
|
msg ("Can't sync graph with database while graph url is not set.");
|
||||||
|
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent accidents when graph are deleted unintentionally
|
||||||
|
if (arg <StringRef> (iamsure) == "iamsure") {
|
||||||
|
bstor.unlinkFromDisk (false, false);
|
||||||
|
graph.loadGraphData ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg ("Please, append \"iamsure\" as parameter to get graph refreshed from the graph database.");
|
||||||
|
}
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
int BotControl::cmdNodeEraseTraining () {
|
int BotControl::cmdNodeEraseTraining () {
|
||||||
enum args { graph_cmd = 1, cmd };
|
enum args { graph_cmd = 1, cmd };
|
||||||
|
|
||||||
|
|
@ -1115,7 +1157,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 +1966,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 +2008,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 +2049,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 +2115,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 +2142,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;
|
||||||
|
|
@ -2151,21 +2193,114 @@ BotControl::BotControl () {
|
||||||
m_menuServerFillTeam = 5;
|
m_menuServerFillTeam = 5;
|
||||||
m_printQueueFlushTimestamp = 0.0f;
|
m_printQueueFlushTimestamp = 0.0f;
|
||||||
|
|
||||||
m_cmds.emplace ("add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct", "add [difficulty] [personality] [team] [model] [name]", "Adding specific bot into the game.", &BotControl::cmdAddBot);
|
m_cmds = {
|
||||||
m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot);
|
{
|
||||||
m_cmds.emplace ("removebots/kickbots/kickall/kickall_ct/kickall_t", "removebots [instant] [team]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
|
"add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct",
|
||||||
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team] [silent]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
|
"add [difficulty] [personality] [team] [model] [name]",
|
||||||
m_cmds.emplace ("fill/fillserver", "fill [team] [count] [difficulty] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
|
"Adding specific bot into the game.",
|
||||||
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
|
|
||||||
m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use.", &BotControl::cmdWeaponMode);
|
&BotControl::cmdAddBot
|
||||||
m_cmds.emplace ("menu/botmenu", "menu [cmd]", "Opens the main bot menu, or command menu if specified.", &BotControl::cmdMenu);
|
},
|
||||||
m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion);
|
{
|
||||||
m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph editor.", &BotControl::cmdNodeMenu);
|
"kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t",
|
||||||
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
|
"kick [team]",
|
||||||
m_cmds.emplace ("graph/g/w/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
|
"Kicks off the random bot from the game.",
|
||||||
m_cmds.emplace ("cvars", "cvars [save|save_map|cvar|defaults]", "Display all the cvars with their descriptions.", &BotControl::cmdCvars);
|
|
||||||
m_cmds.emplace ("show_custom", "show_custom [noarguments]", "Shows the current values from custom.cfg.", &BotControl::cmdShowCustom, false);
|
&BotControl::cmdKickBot
|
||||||
m_cmds.emplace ("exec", "exec [user_id] [command]", "Executes a client command on bot entity.", &BotControl::cmdExec);
|
},
|
||||||
|
{
|
||||||
|
"removebots/kickbots/kickall/kickall_ct/kickall_t",
|
||||||
|
"removebots [instant] [team]",
|
||||||
|
"Kicks all the bots from the game.",
|
||||||
|
|
||||||
|
&BotControl::cmdKickBots
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kill/killbots/killall/kill_ct/kill_t",
|
||||||
|
"kill [team] [silent]",
|
||||||
|
"Kills the specified team / all the bots.",
|
||||||
|
|
||||||
|
&BotControl::cmdKillBots
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill/fillserver",
|
||||||
|
"fill [team] [count] [difficulty] [personality]",
|
||||||
|
"Fill the server (add bots) with specified parameters.",
|
||||||
|
|
||||||
|
&BotControl::cmdFill
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vote/votemap",
|
||||||
|
"vote [map_id]",
|
||||||
|
"Forces all the bots to vote for the specified map.",
|
||||||
|
|
||||||
|
&BotControl::cmdVote
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weapons/weaponmode",
|
||||||
|
"weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]",
|
||||||
|
"Sets the bots' weapon mode to use.",
|
||||||
|
|
||||||
|
&BotControl::cmdWeaponMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu/botmenu",
|
||||||
|
"menu [cmd]",
|
||||||
|
"Opens the main bot menu, or command menu if specified.",
|
||||||
|
|
||||||
|
&BotControl::cmdMenu
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version/ver/about",
|
||||||
|
"version [no arguments]",
|
||||||
|
"Displays version information about bot build.",
|
||||||
|
|
||||||
|
&BotControl::cmdVersion
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"graphmenu/wpmenu/wptmenu",
|
||||||
|
"graphmenu [noarguments]",
|
||||||
|
"Opens and displays bots graph editor.",
|
||||||
|
|
||||||
|
&BotControl::cmdNodeMenu
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"list/listbots",
|
||||||
|
"list [noarguments]",
|
||||||
|
"Lists the bots currently playing on server.",
|
||||||
|
|
||||||
|
&BotControl::cmdList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"graph/g/w/wp/wpt/waypoint",
|
||||||
|
"graph [help]",
|
||||||
|
"Handles graph operations.",
|
||||||
|
|
||||||
|
&BotControl::cmdNode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cvars",
|
||||||
|
"cvars [save|save_map|cvar|defaults]",
|
||||||
|
"Display all the cvars with their descriptions.",
|
||||||
|
|
||||||
|
&BotControl::cmdCvars
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"show_custom",
|
||||||
|
"show_custom [noarguments]",
|
||||||
|
"Shows the current values from custom.cfg.",
|
||||||
|
|
||||||
|
&BotControl::cmdShowCustom,
|
||||||
|
false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exec",
|
||||||
|
"exec [user_id] [command]",
|
||||||
|
"Executes a client command on bot entity.",
|
||||||
|
|
||||||
|
&BotControl::cmdExec
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// declare the menus
|
// declare the menus
|
||||||
createMenus ();
|
createMenus ();
|
||||||
|
|
|
||||||
392
src/engine.cpp
392
src/engine.cpp
|
|
@ -12,6 +12,7 @@ ConVar cv_ignore_map_prefix_game_mode ("ignore_map_prefix_game_mode", "0", "If e
|
||||||
ConVar cv_threadpool_workers ("threadpool_workers", "-1", "Maximum number of threads the bot will run to process some tasks. -1 means half of the CPU cores are used.", true, -1.0f, static_cast <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);
|
||||||
|
|
@ -60,9 +61,6 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
||||||
// startup threaded worker
|
// startup threaded worker
|
||||||
worker.startup (cv_threadpool_workers.as <int> ());
|
worker.startup (cv_threadpool_workers.as <int> ());
|
||||||
|
|
||||||
m_spawnCount[Team::CT] = 0;
|
|
||||||
m_spawnCount[Team::Terrorist] = 0;
|
|
||||||
|
|
||||||
// clear all breakables before initialization
|
// clear all breakables before initialization
|
||||||
m_breakables.clear ();
|
m_breakables.clear ();
|
||||||
m_checkedBreakables.clear ();
|
m_checkedBreakables.clear ();
|
||||||
|
|
@ -77,7 +75,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 ();
|
||||||
|
|
@ -125,20 +123,16 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
||||||
ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
|
ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
|
||||||
ent->v.renderamt = 127; // set its transparency amount
|
ent->v.renderamt = 127; // set its transparency amount
|
||||||
ent->v.effects |= EF_NODRAW;
|
ent->v.effects |= EF_NODRAW;
|
||||||
|
|
||||||
++m_spawnCount[Team::CT];
|
|
||||||
}
|
}
|
||||||
else if (classname == "info_player_deathmatch") {
|
else if (classname == "info_player_deathmatch") {
|
||||||
ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
|
ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
|
||||||
ent->v.renderamt = 127; // set its transparency amount
|
ent->v.renderamt = 127; // set its transparency amount
|
||||||
ent->v.effects |= EF_NODRAW;
|
ent->v.effects |= EF_NODRAW;
|
||||||
|
|
||||||
++m_spawnCount[Team::Terrorist];
|
|
||||||
}
|
}
|
||||||
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 +146,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;
|
||||||
|
|
@ -186,6 +180,24 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
||||||
m_halfSecondFrame = 0.0f;
|
m_halfSecondFrame = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::onSpawnEntity (edict_t *ent) {
|
||||||
|
constexpr auto kEntityInfoPlayerStart = StringRef::fnv1a32 ("info_player_start");
|
||||||
|
constexpr auto kEntityInfoVIPStart = StringRef::fnv1a32 ("info_vip_start");
|
||||||
|
constexpr auto kEntityInfoPlayerDeathmatch = StringRef::fnv1a32 ("info_player_deathmatch");
|
||||||
|
|
||||||
|
if (game.isNullEntity (ent) || ent->v.classname == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto classNameHash = ent->v.classname.str ().hash ();
|
||||||
|
|
||||||
|
if (classNameHash == kEntityInfoPlayerStart || classNameHash == kEntityInfoVIPStart) {
|
||||||
|
++m_spawnCount[Team::CT];
|
||||||
|
}
|
||||||
|
else if (classNameHash == kEntityInfoPlayerDeathmatch) {
|
||||||
|
++m_spawnCount[Team::Terrorist];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Game::levelShutdown () {
|
void Game::levelShutdown () {
|
||||||
// save collected practice on shutdown
|
// save collected practice on shutdown
|
||||||
practice.save ();
|
practice.save ();
|
||||||
|
|
@ -197,12 +209,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 +223,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 ();
|
||||||
|
|
@ -222,6 +234,10 @@ void Game::levelShutdown () {
|
||||||
// disable command handling
|
// disable command handling
|
||||||
ctrl.setDenyCommands (true);
|
ctrl.setDenyCommands (true);
|
||||||
|
|
||||||
|
// reset spawn counts
|
||||||
|
for (auto &sc : m_spawnCount) {
|
||||||
|
sc = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type) const {
|
void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type) const {
|
||||||
|
|
@ -229,7 +245,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,13 +383,13 @@ void Game::playSound (edict_t *ent, const char *sound) {
|
||||||
|
|
||||||
void Game::setPlayerStartDrawModels () {
|
void Game::setPlayerStartDrawModels () {
|
||||||
static HashMap <String, String> models {
|
static HashMap <String, String> models {
|
||||||
{"info_player_start", "models/player/urban/urban.mdl"},
|
{ "info_player_start", "models/player/urban/urban.mdl" },
|
||||||
{"info_player_deathmatch", "models/player/terror/terror.mdl"},
|
{ "info_player_deathmatch", "models/player/terror/terror.mdl" },
|
||||||
{"info_vip_start", "models/player/vip/vip.mdl"}
|
{ "info_vip_start", "models/player/vip/vip.mdl" }
|
||||||
};
|
};
|
||||||
|
|
||||||
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 +442,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 +498,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);
|
||||||
|
|
@ -790,53 +806,53 @@ void Game::registerCvars (bool gameVars) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::constructCSBinaryName (StringArray &libs) {
|
void Game::constructCSBinaryName (StringArray &libs) {
|
||||||
String libSuffix {}; // construct library suffix
|
String suffix {};
|
||||||
|
|
||||||
if (plat.android) {
|
if (plat.android) {
|
||||||
libSuffix += "_android";
|
suffix = "_android";
|
||||||
|
if (plat.x64) {
|
||||||
|
suffix += "_arm64";
|
||||||
|
}
|
||||||
|
else if (plat.arm) {
|
||||||
|
suffix += "_armv7l";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (plat.psvita) {
|
else if (plat.psvita) {
|
||||||
libSuffix += "_psvita";
|
suffix = "_psvita";
|
||||||
}
|
}
|
||||||
|
else if (plat.x64) {
|
||||||
if (plat.x64) {
|
|
||||||
if (plat.arm) {
|
if (plat.arm) {
|
||||||
libSuffix += "_arm64";
|
suffix = "_arm64";
|
||||||
}
|
}
|
||||||
else if (plat.ppc) {
|
else if (plat.ppc) {
|
||||||
libSuffix += "_ppc64le";
|
suffix = "_ppc64le";
|
||||||
|
}
|
||||||
|
else if (plat.riscv) {
|
||||||
|
suffix = "_riscv64d";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
libSuffix += "_amd64";
|
suffix = "_amd64";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (plat.arm) {
|
||||||
if (plat.arm) {
|
// non-android arm32
|
||||||
// don't want to put whole build.h logic from xash3d, just set whatever is supported by the YaPB
|
suffix = "_armv7hf";
|
||||||
if (plat.android) {
|
|
||||||
libSuffix += "_armv7l";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
libSuffix += "_armv7hf";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!plat.nix && !plat.win && !plat.macos) {
|
|
||||||
libSuffix += "_i386";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (!plat.nix && !plat.win && !plat.macos) {
|
||||||
|
// fallback for unknown 32-bit x86 (e.g., legacy linux/bsd)
|
||||||
|
suffix = "_i386";
|
||||||
|
}
|
||||||
|
// else: suffix remains empty (e.g., x86 linux/windows/macos)
|
||||||
|
|
||||||
if (libSuffix.empty ())
|
// build base names
|
||||||
libs.insert (0, { "mp", "cs", "cs_i386" });
|
if (plat.android) {
|
||||||
|
// only "libcs" with suffix (no "mp", and must have "lib" prefix)
|
||||||
|
libs.push ("libcs" + suffix);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// on Android, it's important to have `lib` prefix, otherwise package manager won't unpack the libraries
|
// Standard: "mp" and "cs" with suffix
|
||||||
if (plat.android)
|
libs.push ("cs" + suffix);
|
||||||
libs.insert (0, { "libcs" });
|
libs.push ("mp" + suffix);
|
||||||
else
|
|
||||||
libs.insert (0, { "mp", "cs" });
|
|
||||||
|
|
||||||
for (auto &lib : libs) {
|
|
||||||
lib += libSuffix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -880,10 +896,10 @@ bool Game::loadCSBinary () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plat.emscripten) {
|
if (plat.emscripten) {
|
||||||
path = String(plat.env ("XASH3D_GAMELIBPATH")); // defined by launcher
|
path = String (plat.env ("XASH3D_GAMELIBPATH")); // defined by launcher
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.empty()) {
|
if (path.empty ()) {
|
||||||
path = strings.joinPath (modname, "dlls", lib) + kLibrarySuffix;
|
path = strings.joinPath (modname, "dlls", lib) + kLibrarySuffix;
|
||||||
|
|
||||||
// if we can't read file, skip it
|
// if we can't read file, skip it
|
||||||
|
|
@ -939,7 +955,7 @@ bool Game::loadCSBinary () {
|
||||||
|
|
||||||
// no fake pings on xash3d
|
// no fake pings on xash3d
|
||||||
if (!(m_gameFlags & (GameFlags::Xash3D | GameFlags::Xash3DLegacy))) {
|
if (!(m_gameFlags & (GameFlags::Xash3D | GameFlags::Xash3DLegacy))) {
|
||||||
m_gameFlags |= GameFlags::HasFakePings;
|
m_gameFlags |= GameFlags::HasFakePings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -1006,7 +1022,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 +1142,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 +1225,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 +1383,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 < 5 || 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 +1718,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 +1807,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 ();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -603,6 +603,10 @@ bool BotGraph::isAnalyzed () const {
|
||||||
return (m_info.header.options & StorageOption::Analyzed);
|
return (m_info.header.options & StorageOption::Analyzed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BotGraph::isConverted () const {
|
||||||
|
return (m_info.header.options & StorageOption::Converted);
|
||||||
|
}
|
||||||
|
|
||||||
void BotGraph::add (int type, const Vector &pos) {
|
void BotGraph::add (int type, const Vector &pos) {
|
||||||
if (!hasEditor () && !analyzer.isAnalyzing ()) {
|
if (!hasEditor () && !analyzer.isAnalyzing ()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1292,6 +1296,7 @@ void BotGraph::showFileInfo () {
|
||||||
msg (" uncompressed_size: %dkB", info.uncompressed / 1024);
|
msg (" uncompressed_size: %dkB", info.uncompressed / 1024);
|
||||||
msg (" options: %d", info.options); // display as string ?
|
msg (" options: %d", info.options); // display as string ?
|
||||||
msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
|
msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
|
||||||
|
msg (" converted: %s", isConverted () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
|
||||||
msg (" pathfinder: %s", planner.isPathsCheckFailed () ? "floyd" : "astar");
|
msg (" pathfinder: %s", planner.isPathsCheckFailed () ? "floyd" : "astar");
|
||||||
|
|
||||||
msg ("");
|
msg ("");
|
||||||
|
|
@ -1310,7 +1315,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 +1488,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;
|
||||||
|
|
||||||
|
|
@ -1755,6 +1760,8 @@ bool BotGraph::convertOldFormat () {
|
||||||
|
|
||||||
m_info.author = header.author;
|
m_info.author = header.author;
|
||||||
|
|
||||||
|
m_info.header.options = StorageOption::Converted;
|
||||||
|
|
||||||
// clean editor so graph will be saved with header's author
|
// clean editor so graph will be saved with header's author
|
||||||
auto editor = m_editor;
|
auto editor = m_editor;
|
||||||
m_editor = nullptr;
|
m_editor = nullptr;
|
||||||
|
|
@ -1813,9 +1820,10 @@ bool BotGraph::loadGraphData () {
|
||||||
if (!modified.empty () && !modified.contains ("(none)")) {
|
if (!modified.empty () && !modified.contains ("(none)")) {
|
||||||
m_info.modified.assign (exten.modified);
|
m_info.modified.assign (exten.modified);
|
||||||
}
|
}
|
||||||
|
vistab.load (); // load/initialize visibility
|
||||||
|
|
||||||
planner.init (); // initialize our little path planner
|
planner.init (); // initialize our little path planner
|
||||||
practice.load (); // load bots practice
|
practice.load (); // load bots practice
|
||||||
vistab.load (); // load/initialize visibility
|
|
||||||
|
|
||||||
populateNodes ();
|
populateNodes ();
|
||||||
|
|
||||||
|
|
@ -1851,7 +1859,7 @@ bool BotGraph::canDownload () {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BotGraph::saveGraphData () {
|
bool BotGraph::saveGraphData () {
|
||||||
auto options = StorageOption::Graph | StorageOption::Exten;
|
auto options = m_info.header.options | StorageOption::Graph | StorageOption::Exten;
|
||||||
String editorName {};
|
String editorName {};
|
||||||
|
|
||||||
if (!hasEditor () && !m_info.author.empty ()) {
|
if (!hasEditor () && !m_info.author.empty ()) {
|
||||||
|
|
@ -1907,7 +1915,7 @@ void BotGraph::saveOldFormat () {
|
||||||
|
|
||||||
String editorName {};
|
String editorName {};
|
||||||
|
|
||||||
if (!hasEditor () && !m_info.author.empty ()) {
|
if (!hasEditor () && !m_info.author.empty ()) {
|
||||||
editorName = m_info.author;
|
editorName = m_info.author;
|
||||||
}
|
}
|
||||||
else if (!game.isNullEntity (m_editor)) {
|
else if (!game.isNullEntity (m_editor)) {
|
||||||
|
|
@ -1976,7 +1984,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 +2067,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 +2131,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) {
|
||||||
|
|
@ -2296,17 +2304,20 @@ void BotGraph::frame () {
|
||||||
if (path.radius > 0.0f) {
|
if (path.radius > 0.0f) {
|
||||||
const float sqr = cr::sqrtf (cr::sqrf (path.radius) * 0.5f);
|
const float sqr = cr::sqrtf (cr::sqrf (path.radius) * 0.5f);
|
||||||
|
|
||||||
game.drawLine (m_editor, origin + Vector (path.radius, 0.0f, 0.0f), origin + Vector (sqr, -sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
const Vector points[] = {
|
||||||
game.drawLine (m_editor, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (0.0f, -path.radius, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
{ path.radius, 0.0f, 0.0f },
|
||||||
|
{ sqr, -sqr, 0.0f },
|
||||||
|
{ 0.0f, -path.radius, 0.0f },
|
||||||
|
{ -sqr, -sqr, 0.0f },
|
||||||
|
{ -path.radius, 0.0f, 0.0f },
|
||||||
|
{ -sqr, sqr, 0.0f },
|
||||||
|
{ 0.0f, path.radius, 0.0f },
|
||||||
|
{ sqr, sqr, 0.0f }
|
||||||
|
};
|
||||||
|
|
||||||
game.drawLine (m_editor, origin + Vector (0.0f, -path.radius, 0.0f), origin + Vector (-sqr, -sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
for (auto i = 0; i < kMaxNodeLinks; ++i) {
|
||||||
game.drawLine (m_editor, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (-path.radius, 0.0f, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
game.drawLine (m_editor, origin + points[i], origin + points[(i + 1) % 8], 5, 0, radiusColor, 200, 0, 10);
|
||||||
|
}
|
||||||
game.drawLine (m_editor, origin + Vector (-path.radius, 0.0f, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
|
||||||
game.drawLine (m_editor, origin + Vector (-sqr, sqr, 0.0f), origin + Vector (0.0f, path.radius, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
|
||||||
|
|
||||||
game.drawLine (m_editor, origin + Vector (0.0f, path.radius, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
|
||||||
game.drawLine (m_editor, origin + Vector (sqr, sqr, 0.0f), origin + Vector (path.radius, 0.0f, 0.0f), 5, 0, radiusColor, 200, 0, 10);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const float sqr = cr::sqrtf (32.0f);
|
const float sqr = cr::sqrtf (32.0f);
|
||||||
|
|
@ -2390,8 +2401,8 @@ void BotGraph::frame () {
|
||||||
message.assignf (" %s node:\n"
|
message.assignf (" %s node:\n"
|
||||||
" Node %d of %d, Radius: %.1f, Light: %s\n"
|
" Node %d of %d, Radius: %.1f, Light: %s\n"
|
||||||
" Flags: %s\n"
|
" Flags: %s\n"
|
||||||
" Origin: (%.1f, %.1f, %.1f)\n",
|
" Origin: (%.1f, %.1f, %.1f)\n",
|
||||||
type, node, m_paths.length () - 1, p.radius,
|
type, node, m_paths.length () - 1, p.radius,
|
||||||
cr::fequal (p.light, kInvalidLightLevel) ? "Invalid" : strings.format ("%1.f", p.light),
|
cr::fequal (p.light, kInvalidLightLevel) ? "Invalid" : strings.format ("%1.f", p.light),
|
||||||
flags, p.origin.x, p.origin.y, p.origin.z
|
flags, p.origin.x, p.origin.y, p.origin.z
|
||||||
);
|
);
|
||||||
|
|
@ -2766,43 +2777,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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +130,9 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
||||||
// precache everything
|
// precache everything
|
||||||
game.precache ();
|
game.precache ();
|
||||||
|
|
||||||
|
// notify about entity spawn
|
||||||
|
game.onSpawnEntity (ent);
|
||||||
|
|
||||||
if (game.is (GameFlags::Metamod)) {
|
if (game.is (GameFlags::Metamod)) {
|
||||||
RETURN_META_VALUE (MRES_IGNORED, 0);
|
RETURN_META_VALUE (MRES_IGNORED, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +162,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 +392,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 +432,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -483,6 +486,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.is (GameFlags::Metamod)) {
|
if (game.is (GameFlags::Metamod)) {
|
||||||
RETURN_META (MRES_IGNORED);
|
RETURN_META (MRES_IGNORED);
|
||||||
}
|
}
|
||||||
|
|
@ -560,7 +564,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 +595,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 +790,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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
348
src/manager.cpp
348
src/manager.cpp
|
|
@ -186,9 +186,9 @@ BotCreateResult BotManager::create (StringRef name, int difficulty, int personal
|
||||||
|
|
||||||
// try to set proffered personality
|
// try to set proffered personality
|
||||||
static HashMap <String, Personality> personalityMap {
|
static HashMap <String, Personality> personalityMap {
|
||||||
{"normal", Personality::Normal },
|
{ "normal", Personality::Normal },
|
||||||
{"careful", Personality::Careful },
|
{ "careful", Personality::Careful },
|
||||||
{"rusher", Personality::Rusher },
|
{ "rusher", Personality::Rusher },
|
||||||
};
|
};
|
||||||
|
|
||||||
// set personality if requested
|
// set personality if requested
|
||||||
|
|
@ -332,13 +332,17 @@ void BotManager::addbot (StringRef name, StringRef difficulty, StringRef persona
|
||||||
// this function is same as the function above, but accept as parameters string instead of integers
|
// this function is same as the function above, but accept as parameters string instead of integers
|
||||||
|
|
||||||
BotRequest request {};
|
BotRequest request {};
|
||||||
static StringRef any = "*";
|
constexpr StringRef ANY = "*";
|
||||||
|
|
||||||
request.name = (name.empty () || name == any) ? StringRef ("\0") : name;
|
auto handleParam = [&ANY] (StringRef value) {
|
||||||
request.difficulty = (difficulty.empty () || difficulty == any) ? -1 : difficulty.as <int> ();
|
return value.empty () || value == ANY ? -1 : value.as <int> ();
|
||||||
request.team = (team.empty () || team == any) ? -1 : team.as <int> ();
|
};
|
||||||
request.skin = (skin.empty () || skin == any) ? -1 : skin.as <int> ();
|
|
||||||
request.personality = (personality.empty () || personality == any) ? -1 : personality.as <int> ();
|
request.name = name.empty () || name == ANY ? StringRef ("\0") : name;
|
||||||
|
request.difficulty = handleParam (difficulty);
|
||||||
|
request.team = handleParam (team);
|
||||||
|
request.skin = handleParam (skin);
|
||||||
|
request.personality = handleParam (personality);
|
||||||
request.manual = manual;
|
request.manual = manual;
|
||||||
|
|
||||||
addbot (request.name, request.difficulty, request.personality, request.team, request.skin, request.manual);
|
addbot (request.name, request.difficulty, request.personality, request.team, request.skin, request.manual);
|
||||||
|
|
@ -444,6 +448,11 @@ void BotManager::maintainQuota () {
|
||||||
maxSpawnCount = maxClients + 1;
|
maxSpawnCount = maxClients + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disable spawn control
|
||||||
|
if (conf.fetchCustom ("DisableSpawnControl").startsWith ("yes")) {
|
||||||
|
maxSpawnCount = game.maxClients () + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// sent message only to console from here
|
// sent message only to console from here
|
||||||
ctrl.setFromConsole (true);
|
ctrl.setFromConsole (true);
|
||||||
|
|
||||||
|
|
@ -469,7 +478,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 +496,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 +505,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 +525,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 +540,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 +554,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 () {
|
||||||
|
|
@ -623,7 +626,7 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int
|
||||||
}
|
}
|
||||||
const auto maxToAdd = maxClients - (getHumansCount () + getBotCount ());
|
const auto maxToAdd = maxClients - (getHumansCount () + getBotCount ());
|
||||||
|
|
||||||
constexpr char kTeams[6][12] = { "", {"Terrorists"}, {"CTs"}, "", "", {"Random"}, };
|
constexpr char kTeams[6][12] = { "", { "Terrorists" }, { "CTs" }, "", "", { "Random" }, };
|
||||||
auto toAdd = numToAdd == -1 ? maxToAdd : numToAdd;
|
auto toAdd = numToAdd == -1 ? maxToAdd : numToAdd;
|
||||||
|
|
||||||
// limit manually added count as well
|
// limit manually added count as well
|
||||||
|
|
@ -681,7 +684,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 +699,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 +735,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 +833,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 +842,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 {
|
||||||
|
|
@ -890,25 +893,25 @@ void BotManager::setWeaponMode (int selection) {
|
||||||
selection--;
|
selection--;
|
||||||
|
|
||||||
constexpr int kStdMaps[7][kNumWeapons] = {
|
constexpr int kStdMaps[7][kNumWeapons] = {
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Knife only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Knife only
|
||||||
{-1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Pistols only
|
{ -1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Pistols only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Shotgun only
|
{ -1, -1, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Shotgun only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 2, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1}, // Machine Guns only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 2, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1 }, // Machine Guns only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 1, 0, 1, 1, -1, -1, -1, -1, -1, -1}, // Rifles only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 1, 0, 1, 1, -1, -1, -1, -1, -1, -1 }, // Rifles only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 0, 1, -1, -1}, // Snipers only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 0, 1, -1, -1 }, // Snipers only
|
||||||
{-1, -1, -1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1} // Standard
|
{ -1, -1, -1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1 } // Standard
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr int kAsMaps[7][kNumWeapons] = {
|
constexpr int kAsMaps[7][kNumWeapons] = {
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Knife only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Knife only
|
||||||
{-1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Pistols only
|
{ -1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Pistols only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Shotgun only
|
{ -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // Shotgun only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1}, // Machine Guns only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1 }, // Machine Guns only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, 1, 0, 1, 1, -1, -1, -1, -1, -1, -1}, // Rifles only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, 1, 0, 1, 1, -1, -1, -1, -1, -1, -1 }, // Rifles only
|
||||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 1, -1, -1}, // Snipers only
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 1, -1, -1 }, // Snipers only
|
||||||
{-1, -1, -1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 2, 0, -1, 1, 0, 1, 1, 0, 0, -1, 1, 1, 1} // Standard
|
{ -1, -1, -1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 2, 0, -1, 1, 0, 1, 1, 0, 0, -1, 1, 1, 1 } // Standard
|
||||||
};
|
};
|
||||||
constexpr char kModes[7][12] = { {"Knife"}, {"Pistol"}, {"Shotgun"}, {"Machine Gun"}, {"Rifle"}, {"Sniper"}, {"Standard"} };
|
constexpr char kModes[7][12] = { { "Knife" }, { "Pistol" }, { "Shotgun" }, { "Machine Gun" }, { "Rifle" }, { "Sniper" }, { "Standard" } };
|
||||||
|
|
||||||
// get the raw weapons array
|
// get the raw weapons array
|
||||||
auto tab = conf.getRawWeapons ();
|
auto tab = conf.getRawWeapons ();
|
||||||
|
|
@ -929,7 +932,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 +970,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 +1023,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) {
|
||||||
|
|
@ -1087,7 +1089,7 @@ void BotManager::updateBotDifficulties () {
|
||||||
|
|
||||||
// sets new difficulty for all bots
|
// sets new difficulty for all bots
|
||||||
for (const auto &bot : m_bots) {
|
for (const auto &bot : m_bots) {
|
||||||
bot->m_difficulty = difficulty;
|
bot->setNewDifficulty (difficulty);
|
||||||
}
|
}
|
||||||
m_lastDifficulty = difficulty;
|
m_lastDifficulty = difficulty;
|
||||||
}
|
}
|
||||||
|
|
@ -1096,7 +1098,8 @@ void BotManager::updateBotDifficulties () {
|
||||||
void BotManager::balanceBotDifficulties () {
|
void BotManager::balanceBotDifficulties () {
|
||||||
// difficulty changing once per round (time)
|
// difficulty changing once per round (time)
|
||||||
auto updateDifficulty = [] (Bot *bot, int32_t offset) {
|
auto updateDifficulty = [] (Bot *bot, int32_t offset) {
|
||||||
bot->m_difficulty = cr::clamp (static_cast <Difficulty> (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert);
|
game.print ("offset = %d", offset);
|
||||||
|
bot->setNewDifficulty (cr::clamp (static_cast <Difficulty> (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert));
|
||||||
};
|
};
|
||||||
|
|
||||||
// with nightmare difficulty, there is no balance
|
// with nightmare difficulty, there is no balance
|
||||||
|
|
@ -1199,17 +1202,17 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
|
||||||
|
|
||||||
m_isAlive = false;
|
m_isAlive = false;
|
||||||
m_weaponBurstMode = BurstMode::Off;
|
m_weaponBurstMode = BurstMode::Off;
|
||||||
m_difficulty = cr::clamp (static_cast <Difficulty> (difficulty), Difficulty::Noob, Difficulty::Expert);
|
setNewDifficulty (cr::clamp (static_cast <Difficulty> (difficulty), Difficulty::Noob, Difficulty::Expert));
|
||||||
|
|
||||||
auto minDifficulty = cv_difficulty_min.as <int> ();
|
auto minDifficulty = cv_difficulty_min.as <int> ();
|
||||||
auto maxDifficulty = cv_difficulty_max.as <int> ();
|
auto maxDifficulty = cv_difficulty_max.as <int> ();
|
||||||
|
|
||||||
// if we're have min/max difficulty specified, choose value from they
|
// if we're have min/max difficulty specified, choose value from they
|
||||||
if (minDifficulty != Difficulty::Invalid && maxDifficulty != Difficulty::Invalid) {
|
if (minDifficulty != Difficulty::Invalid && maxDifficulty != Difficulty::Invalid) {
|
||||||
if (maxDifficulty > minDifficulty) {
|
if (minDifficulty > maxDifficulty) {
|
||||||
cr::swap (maxDifficulty, minDifficulty);
|
cr::swap (maxDifficulty, minDifficulty);
|
||||||
}
|
}
|
||||||
m_difficulty = rg (minDifficulty, maxDifficulty);
|
setNewDifficulty (rg (minDifficulty, maxDifficulty));
|
||||||
}
|
}
|
||||||
m_pingBase = fakeping.randomBase ();
|
m_pingBase = fakeping.randomBase ();
|
||||||
m_ping = fakeping.randomBase ();
|
m_ping = fakeping.randomBase ();
|
||||||
|
|
@ -1398,8 +1401,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 +1515,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 ();
|
||||||
|
|
||||||
|
|
@ -1522,14 +1525,13 @@ void Bot::newRound () {
|
||||||
|
|
||||||
m_isLeader = false;
|
m_isLeader = false;
|
||||||
m_hasProgressBar = false;
|
m_hasProgressBar = false;
|
||||||
m_canChooseAimDirection = true;
|
m_canSetAimDirection = true;
|
||||||
m_preventFlashing = 0.0f;
|
m_preventFlashing = 0.0f;
|
||||||
|
|
||||||
m_timeTeamOrder = 0.0f;
|
m_timeTeamOrder = 0.0f;
|
||||||
m_askCheckTime = rg (30.0f, 90.0f);
|
m_askCheckTime = rg (30.0f, 90.0f);
|
||||||
m_minSpeed = 260.0f;
|
m_minSpeed = 260.0f;
|
||||||
m_prevSpeed = 0.0f;
|
m_prevSpeed = 0.0f;
|
||||||
m_prevVelocity = 0.0f;
|
|
||||||
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
|
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
|
||||||
m_prevTime = game.time ();
|
m_prevTime = game.time ();
|
||||||
m_lookUpdateTime = game.time ();
|
m_lookUpdateTime = game.time ();
|
||||||
|
|
@ -1729,9 +1731,6 @@ void Bot::newRound () {
|
||||||
m_enemyIgnoreTimer = 0.0f;
|
m_enemyIgnoreTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update refvec for blocked movement
|
|
||||||
updateRightRef ();
|
|
||||||
|
|
||||||
// and put buying into its message queue
|
// and put buying into its message queue
|
||||||
pushMsgQueue (BotMsg::Buy);
|
pushMsgQueue (BotMsg::Buy);
|
||||||
startTask (Task::Normal, TaskPri::Normal, kInvalidNodeIndex, 0.0f, true);
|
startTask (Task::Normal, TaskPri::Normal, kInvalidNodeIndex, 0.0f, true);
|
||||||
|
|
@ -1835,13 +1834,25 @@ void Bot::markStale () {
|
||||||
pev->flags |= FL_DORMANT;
|
pev->flags |= FL_DORMANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bot::setNewDifficulty (int32_t newDifficulty) {
|
||||||
|
if (newDifficulty < Difficulty::Noob || newDifficulty > Difficulty::Expert) {
|
||||||
|
const auto difficlutyDefault = Difficulty::Hard;;
|
||||||
|
|
||||||
|
m_difficulty = difficlutyDefault;
|
||||||
|
m_difficultyData = conf.getDifficultyTweaks (difficlutyDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_difficulty = newDifficulty;
|
||||||
|
m_difficultyData = conf.getDifficultyTweaks (newDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
void Bot::updateTeamJoin () {
|
void Bot::updateTeamJoin () {
|
||||||
// this function handles the selection of teams & class
|
// this function handles the selection of teams & class
|
||||||
|
|
||||||
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 +1959,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 +2014,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 +2038,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 +2140,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 +2158,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 +2220,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;
|
||||||
|
|
|
||||||
|
|
@ -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 ¬ify : bots) {
|
for (const auto ¬ify : 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 () {
|
||||||
|
|
|
||||||
278
src/navigate.cpp
278
src/navigate.cpp
|
|
@ -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) {
|
||||||
|
|
@ -469,7 +470,7 @@ void Bot::resetCollision () {
|
||||||
void Bot::ignoreCollision () {
|
void Bot::ignoreCollision () {
|
||||||
resetCollision ();
|
resetCollision ();
|
||||||
|
|
||||||
m_lastCollTime = game.time () + 0.5f;
|
m_lastCollTime = game.time () + 0.65f;
|
||||||
m_checkTerrain = false;
|
m_checkTerrain = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -594,18 +595,18 @@ 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;
|
||||||
|
|
||||||
const auto tid = getCurrentTaskId ();
|
const auto tid = getCurrentTaskId ();
|
||||||
|
|
||||||
// minimal speed for consider stuck
|
// minimal speed for consider stuck
|
||||||
const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4;
|
const float minimalSpeed = isDucking () ? 7.0f : 10.0f;
|
||||||
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 ((m_moveSpeed >= minimalSpeed || m_strafeSpeed >= minimalSpeed)
|
||||||
&& m_lastCollTime < game.time ()
|
&& m_lastCollTime < game.time ()
|
||||||
&& tid != Task::Attack
|
&& tid != Task::Attack
|
||||||
&& tid != Task::Camp) {
|
&& tid != Task::Camp) {
|
||||||
|
|
@ -616,7 +617,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
m_firstCollideTime = 0.0f;
|
m_firstCollideTime = 0.0f;
|
||||||
}
|
}
|
||||||
// didn't we move enough previously?
|
// didn't we move enough previously?
|
||||||
else if (m_movedDistance < kMinMovedDistance && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
|
else if (m_movedDistance < kMinMovedDistance && m_prevSpeed > 20.0f) {
|
||||||
m_prevTime = game.time (); // then consider being stuck
|
m_prevTime = game.time (); // then consider being stuck
|
||||||
m_isStuck = true;
|
m_isStuck = true;
|
||||||
|
|
||||||
|
|
@ -652,14 +653,16 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
resetCollision (); // reset collision memory if not being stuck for 0.5 secs
|
resetCollision (); // reset collision memory if not being stuck for 0.5 secs
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const auto state = m_collideMoves[m_collStateIndex];
|
||||||
|
|
||||||
// remember to keep pressing stuff if it was necessary ago
|
// remember to keep pressing stuff if it was necessary ago
|
||||||
if (m_collideMoves[m_collStateIndex] == CollisionState::Duck && (isOnFloor () || isInWater ())) {
|
if (state == CollisionState::Duck && (isOnFloor () || isInWater ())) {
|
||||||
pev->button |= IN_DUCK;
|
pev->button |= IN_DUCK;
|
||||||
}
|
}
|
||||||
else if (m_collideMoves[m_collStateIndex] == CollisionState::StrafeLeft) {
|
else if (state == CollisionState::StrafeLeft) {
|
||||||
setStrafeSpeed (dirNormal, -pev->maxspeed);
|
setStrafeSpeed (dirNormal, -pev->maxspeed);
|
||||||
}
|
}
|
||||||
else if (m_collideMoves[m_collStateIndex] == CollisionState::StrafeRight) {
|
else if (state == CollisionState::StrafeRight) {
|
||||||
setStrafeSpeed (dirNormal, pev->maxspeed);
|
setStrafeSpeed (dirNormal, pev->maxspeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -682,6 +685,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
bits |= CollisionProbe::Duck;
|
bits |= CollisionProbe::Duck;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// collision check allowed if not flying through the air
|
// collision check allowed if not flying through the air
|
||||||
if (isOnFloor () || isOnLadder () || isInWater ()) {
|
if (isOnFloor () || isOnLadder () || isInWater ()) {
|
||||||
uint32_t state[kMaxCollideMoves * 2 + 1] {};
|
uint32_t state[kMaxCollideMoves * 2 + 1] {};
|
||||||
|
|
@ -695,7 +699,6 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
state[i++] = CollisionState::StrafeRight;
|
state[i++] = CollisionState::StrafeRight;
|
||||||
state[i++] = CollisionState::Duck;
|
state[i++] = CollisionState::Duck;
|
||||||
|
|
||||||
// now weight all possible states
|
|
||||||
if (bits & CollisionProbe::Jump) {
|
if (bits & CollisionProbe::Jump) {
|
||||||
state[i] = 0;
|
state[i] = 0;
|
||||||
|
|
||||||
|
|
@ -744,7 +747,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
|
||||||
|
// now weight all possible states
|
||||||
if (bits & CollisionProbe::Strafe) {
|
if (bits & CollisionProbe::Strafe) {
|
||||||
state[i] = 0;
|
state[i] = 0;
|
||||||
state[i + 1] = 0;
|
state[i + 1] = 0;
|
||||||
|
|
@ -812,6 +815,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bits & CollisionProbe::Duck) {
|
if (bits & CollisionProbe::Duck) {
|
||||||
state[i] = 0;
|
state[i] = 0;
|
||||||
|
|
||||||
|
|
@ -900,10 +904,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::checkFall () {
|
void Bot::checkFall () {
|
||||||
if (isPreviousLadder ()) {
|
if (isPreviousLadder () || (m_pathFlags & NodeFlag::Ladder) || isOnLadder ()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -947,26 +948,28 @@ void Bot::checkFall () {
|
||||||
const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]);
|
const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]);
|
||||||
|
|
||||||
if (nowDistanceSq > baseDistanceSq
|
if (nowDistanceSq > baseDistanceSq
|
||||||
&& (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
|
&& (nowDistanceSq > baseDistanceSq * 1.8f || nowDistanceSq > baseDistanceSq + 260.0f)
|
||||||
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
|
&& baseDistanceSq >= cr::sqrf (124.0f) && nowDistanceSq >= cr::sqrf (146.0f)) {
|
||||||
fixFall = true;
|
fixFall = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f
|
else if (m_checkFallPoint[1].z > pev->origin.z + 138.0f
|
||||||
&& m_checkFallPoint[0].z > pev->origin.z + 128.0f) {
|
|| m_checkFallPoint[0].z > pev->origin.z + 138.0f) {
|
||||||
fixFall = true;
|
fixFall = true;
|
||||||
}
|
}
|
||||||
else if (m_currentNodeIndex != kInvalidNodeIndex
|
else if (m_currentNodeIndex != kInvalidNodeIndex
|
||||||
&& nowDistanceSq > cr::sqrf (16.0f)
|
&& nowDistanceSq > cr::sqrf (32.0f)
|
||||||
&& m_checkFallPoint[1].z > pev->origin.z + 62.0f) {
|
&& m_checkFallPoint[1].z > pev->origin.z + 72.0f) {
|
||||||
fixFall = true;
|
fixFall = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixFall) {
|
if (fixFall) {
|
||||||
m_currentNodeIndex = kInvalidNodeIndex;
|
if (graph.exists (m_currentNodeIndex) && !isReachableNode (m_currentNodeIndex)) {
|
||||||
findValidNode ();
|
m_currentNodeIndex = kInvalidNodeIndex;
|
||||||
|
findValidNode ();
|
||||||
|
|
||||||
m_fixFallTimer.start (1.0f);
|
m_fixFallTimer.start (1.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1002,22 +1005,22 @@ void Bot::moveToGoal () {
|
||||||
// press jump button if we need to leave the ladder
|
// press jump button if we need to leave the ladder
|
||||||
if (!(m_pathFlags & NodeFlag::Ladder)
|
if (!(m_pathFlags & NodeFlag::Ladder)
|
||||||
&& isPreviousLadder ()
|
&& isPreviousLadder ()
|
||||||
&& isOnFloor ()
|
|
||||||
&& isOnLadder ()
|
&& isOnLadder ()
|
||||||
&& m_moveSpeed > 50.0f
|
&& m_moveSpeed > 50.0f
|
||||||
&& pev->velocity.lengthSq () < 50.0f) {
|
&& pev->velocity.lengthSq () < 50.0f
|
||||||
|
&& m_ladderDir == LadderDir::Down) {
|
||||||
|
|
||||||
pev->button |= IN_JUMP;
|
pev->button |= IN_JUMP;
|
||||||
m_jumpTime = game.time () + 1.0f;
|
m_jumpTime = game.time () + 1.0f;
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
const auto distanceSq2d = m_destOrigin.distanceSq2d (pev->origin + pev->velocity * m_frameInterval);
|
const auto distanceSq2d = m_destOrigin.distanceSq2d (pev->origin + pev->velocity * m_frameInterval);
|
||||||
|
|
||||||
if (distanceSq2d < cr::sqrf (m_moveSpeed) * m_frameInterval && getTask ()->data != kInvalidNodeIndex) {
|
if (distanceSq2d < cr::sqrf (m_moveSpeed * m_frameInterval) && getTask ()->data != kInvalidNodeIndex) {
|
||||||
m_moveSpeed = distanceSq2d;
|
m_moveSpeed = distanceSq2d * 2.0f;
|
||||||
}
|
|
||||||
if (m_moveSpeed > pev->maxspeed) {
|
|
||||||
m_moveSpeed = pev->maxspeed;
|
|
||||||
}
|
}
|
||||||
|
m_moveSpeed = cr::clamp (m_moveSpeed, 4.0f, pev->maxspeed);
|
||||||
|
#endif
|
||||||
m_lastUsedNodesTime = game.time ();
|
m_lastUsedNodesTime = game.time ();
|
||||||
|
|
||||||
// special movement for swimming here
|
// special movement for swimming here
|
||||||
|
|
@ -1105,7 +1108,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) };
|
||||||
|
|
||||||
|
|
@ -1137,10 +1140,11 @@ bool Bot::updateNavigation () {
|
||||||
m_jumpFinished = true;
|
m_jumpFinished = true;
|
||||||
m_checkTerrain = false;
|
m_checkTerrain = false;
|
||||||
m_desiredVelocity.clear ();
|
m_desiredVelocity.clear ();
|
||||||
|
m_currentTravelFlags &= ~PathFlag::Jump;
|
||||||
|
|
||||||
// cool down a little if next path after current will be jump
|
// cool down a little if next path after current will be jump
|
||||||
if (m_jumpSequence) {
|
if (m_jumpSequence) {
|
||||||
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + rg (0.75f, 1.2f) + m_frameInterval, false);
|
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + rg (0.75f, 1.25f) + m_frameInterval, false);
|
||||||
m_jumpSequence = false;
|
m_jumpSequence = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1151,19 +1155,13 @@ bool Bot::updateNavigation () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pathFlags & NodeFlag::Ladder) {
|
if (m_pathFlags & NodeFlag::Ladder) {
|
||||||
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 auto ladderDistanceSq = pev->origin.distanceSq (m_pathOrigin);
|
||||||
|
|
||||||
// do a precise movement when very near
|
// do a precise movement when very near
|
||||||
if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
|
if (graph.exists (prevNodeIndex)
|
||||||
if (m_pathOrigin.z >= pev->origin.z + 16.0f) {
|
&& !(graph[prevNodeIndex].flags & NodeFlag::Ladder)
|
||||||
m_pathOrigin = m_path->origin + kLadderOffset;
|
&& ladderDistanceSq < cr::sqrf (64.0f)) {
|
||||||
}
|
|
||||||
else if (m_pathOrigin.z < pev->origin.z - 16.0f) {
|
|
||||||
m_pathOrigin = m_path->origin - kLadderOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDucking ()) {
|
if (!isDucking ()) {
|
||||||
m_moveSpeed = pev->maxspeed * 0.4f;
|
m_moveSpeed = pev->maxspeed * 0.4f;
|
||||||
|
|
@ -1173,20 +1171,14 @@ bool Bot::updateNavigation () {
|
||||||
if (!isOnLadder ()) {
|
if (!isOnLadder ()) {
|
||||||
pev->button &= ~IN_DUCK;
|
pev->button &= ~IN_DUCK;
|
||||||
}
|
}
|
||||||
m_approachingLadderTimer.start (m_frameInterval * 6.0f);
|
m_approachingLadderTimer.start (2.0f * m_frameInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isOnLadder () && isOnFloor () && !isDucking ()) {
|
if (!isOnLadder () && isOnFloor () && !isDucking ()) {
|
||||||
if (!isPreviousLadder ()) {
|
if (!isPreviousLadder ()) {
|
||||||
m_moveSpeed = ladderDistance;
|
m_moveSpeed = cr::sqrtf (ladderDistanceSq);
|
||||||
}
|
|
||||||
|
|
||||||
if (m_moveSpeed < 150.0f) {
|
|
||||||
m_moveSpeed = 150.0f;
|
|
||||||
}
|
|
||||||
else if (m_moveSpeed > pev->maxspeed) {
|
|
||||||
m_moveSpeed = pev->maxspeed;
|
|
||||||
}
|
}
|
||||||
|
m_moveSpeed = cr::clamp (m_moveSpeed, 160.0f, pev->maxspeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
|
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
|
||||||
|
|
@ -1231,7 +1223,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);
|
||||||
|
|
||||||
|
|
@ -1240,7 +1232,7 @@ bool Bot::updateNavigation () {
|
||||||
ignoreCollision (); // don't consider being stuck
|
ignoreCollision (); // don't consider being stuck
|
||||||
|
|
||||||
// also 'use' the door randomly
|
// also 'use' the door randomly
|
||||||
if (rg.chance (50)) {
|
if (m_buttonPushTime < game.time () && rg.chance (50)) {
|
||||||
// do not use door directly under xash, or we will get failed assert in gamedll code
|
// do not use door directly under xash, or we will get failed assert in gamedll code
|
||||||
if (game.is (GameFlags::Xash3D)) {
|
if (game.is (GameFlags::Xash3D)) {
|
||||||
pev->button |= IN_USE;
|
pev->button |= IN_USE;
|
||||||
|
|
@ -1254,7 +1246,7 @@ bool Bot::updateNavigation () {
|
||||||
|
|
||||||
// make sure we are always facing the door when going through it
|
// make sure we are always facing the door when going through it
|
||||||
m_aimFlags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
|
m_aimFlags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
|
||||||
m_canChooseAimDirection = false;
|
m_canSetAimDirection = false;
|
||||||
|
|
||||||
// delay task
|
// delay task
|
||||||
if (m_buttonPushTime < game.time ()) {
|
if (m_buttonPushTime < game.time ()) {
|
||||||
|
|
@ -1285,7 +1277,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,26 +1316,35 @@ bool Bot::updateNavigation () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float desiredDistanceSq = cr::sqrf (8.0f);
|
float desiredDistanceSq = cr::sqrf (48.0f);
|
||||||
const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin);
|
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
|
||||||
if (m_pathFlags & NodeFlag::Lift) {
|
if (m_pathFlags & NodeFlag::Lift) {
|
||||||
desiredDistanceSq = cr::sqrf (50.0f);
|
desiredDistanceSq = cr::sqrf (50.0f);
|
||||||
}
|
}
|
||||||
else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) {
|
else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) {
|
||||||
desiredDistanceSq = cr::sqrf (9.0f);
|
desiredDistanceSq = cr::sqrf (25.0f);
|
||||||
|
|
||||||
// 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)
|
||||||
desiredDistanceSq = cr::sqrf (128.0f);
|
&& (m_pathFlags & NodeFlag::Goal)) {
|
||||||
|
|
||||||
|
desiredDistanceSq = cr::sqrf (96.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_pathFlags & NodeFlag::Ladder) {
|
else if (isOnLadder () || (m_pathFlags & NodeFlag::Ladder)) {
|
||||||
desiredDistanceSq = cr::sqrf (16.0f);
|
desiredDistanceSq = cr::sqrf (15.0f);
|
||||||
}
|
}
|
||||||
else if (m_currentTravelFlags & PathFlag::Jump) {
|
else if (m_currentTravelFlags & PathFlag::Jump) {
|
||||||
desiredDistanceSq = 0.0f;
|
desiredDistanceSq = 0.0f;
|
||||||
|
|
||||||
|
if (pev->velocity.z > 16.0f) {
|
||||||
|
desiredDistanceSq = cr::sqrf (8.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_pathFlags & NodeFlag::Crouch) {
|
||||||
|
desiredDistanceSq = cr::sqrf (6.0f);
|
||||||
}
|
}
|
||||||
else if (m_path->number == cv_debug_goal.as <int> ()) {
|
else if (m_path->number == cv_debug_goal.as <int> ()) {
|
||||||
desiredDistanceSq = 0.0f;
|
desiredDistanceSq = 0.0f;
|
||||||
|
|
@ -1370,14 +1371,14 @@ bool Bot::updateNavigation () {
|
||||||
|
|
||||||
// if just recalculated path, assume reached current node
|
// if just recalculated path, assume reached current node
|
||||||
if (!m_repathTimer.elapsed () && !pathHasFlags) {
|
if (!m_repathTimer.elapsed () && !pathHasFlags) {
|
||||||
desiredDistanceSq = cr::sqrf (72.0f);
|
desiredDistanceSq = cr::sqrf (48.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
desiredDistanceSq = nodeDistanceSq + 1.0f;
|
desiredDistanceSq = nodeDistanceSq + 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1386,7 +1387,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;
|
||||||
|
|
@ -1409,6 +1413,9 @@ bool Bot::updateNavigation () {
|
||||||
|
|
||||||
// update the practice for team
|
// update the practice for team
|
||||||
practice.setValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue);
|
practice.setValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue);
|
||||||
|
|
||||||
|
// ignore collision
|
||||||
|
ignoreCollision ();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1418,7 +1425,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 +1488,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) {
|
||||||
|
|
||||||
|
|
@ -1869,7 +1876,7 @@ bool Bot::findNextBestNodeEx (const IntArray &data, bool handleFails) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case low node density do not skip previous ones, in case of fail reduce max nodes to skip
|
// in case low node density do not skip previous ones, in case of fail reduce max nodes to skip
|
||||||
const auto &numToSkip = graph.length () < 512 ? 0 : (handleFails ? 1 : rg (1, 4));
|
const auto &numToSkip = graph.length () < 512 || m_isStuck ? 0 : (handleFails ? 1 : rg (1, 4));
|
||||||
|
|
||||||
for (const auto &i : data) {
|
for (const auto &i : data) {
|
||||||
const auto &path = graph[i];
|
const auto &path = graph[i];
|
||||||
|
|
@ -2117,7 +2124,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
|
||||||
|
|
@ -2459,7 +2466,7 @@ bool Bot::selectBestNextNode () {
|
||||||
const auto currentNodeIndex = m_pathWalk.first ();
|
const auto currentNodeIndex = m_pathWalk.first ();
|
||||||
const auto prevNodeIndex = m_currentNodeIndex;
|
const auto prevNodeIndex = m_currentNodeIndex;
|
||||||
|
|
||||||
if (!isOccupiedNode (currentNodeIndex)) {
|
if (isOnLadder () || !isOccupiedNode (currentNodeIndex)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2535,9 +2542,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 +2556,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;
|
||||||
|
|
@ -2589,10 +2596,11 @@ bool Bot::advanceMovement () {
|
||||||
bool isCurrentJump = false;
|
bool isCurrentJump = false;
|
||||||
|
|
||||||
// find out about connection flags
|
// find out about connection flags
|
||||||
if (destIndex != kInvalidNodeIndex && m_currentNodeIndex != kInvalidNodeIndex) {
|
if (graph.exists (destIndex) && m_path) {
|
||||||
for (const auto &link : m_path->links) {
|
for (const auto &link : m_path->links) {
|
||||||
if (link.index == destIndex) {
|
if (link.index == destIndex) {
|
||||||
m_currentTravelFlags = link.flags;
|
m_currentTravelFlags = link.flags;
|
||||||
|
|
||||||
m_desiredVelocity = link.velocity;
|
m_desiredVelocity = link.velocity;
|
||||||
m_jumpFinished = false;
|
m_jumpFinished = false;
|
||||||
|
|
||||||
|
|
@ -2602,7 +2610,7 @@ bool Bot::advanceMovement () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if graph is analyzed try our special jumps
|
// if graph is analyzed try our special jumps
|
||||||
if (graph.isAnalyzed () || analyzer.isAnalyzed ()) {
|
if ((graph.isAnalyzed () || analyzer.isAnalyzed ()) && !(m_path->flags & NodeFlag::Ladder)) {
|
||||||
for (const auto &link : m_path->links) {
|
for (const auto &link : m_path->links) {
|
||||||
if (link.index == destIndex) {
|
if (link.index == destIndex) {
|
||||||
const float diff = cr::abs (m_path->origin.z - graph[destIndex].origin.z);
|
const float diff = cr::abs (m_path->origin.z - graph[destIndex].origin.z);
|
||||||
|
|
@ -2647,7 +2655,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 ?
|
||||||
|
|
@ -2665,6 +2673,7 @@ bool Bot::advanceMovement () {
|
||||||
|
|
||||||
// get ladder nodes used by other (first moving) bots
|
// get ladder nodes used by other (first moving) bots
|
||||||
for (const auto &other : bots) {
|
for (const auto &other : bots) {
|
||||||
|
|
||||||
// if another bot uses this ladder, wait 3 secs
|
// if another bot uses this ladder, wait 3 secs
|
||||||
if (other.get () != this && other->m_isAlive && other->m_currentNodeIndex == destIndex && other->isOnLadder ()) {
|
if (other.get () != this && other->m_isAlive && other->m_currentNodeIndex == destIndex && other->isOnLadder ()) {
|
||||||
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 3.0f, false);
|
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 3.0f, false);
|
||||||
|
|
@ -2724,21 +2733,30 @@ void Bot::setPathOrigin () {
|
||||||
else if (radius > 0.0f) {
|
else if (radius > 0.0f) {
|
||||||
setNonZeroPathOrigin ();
|
setNonZeroPathOrigin ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOnLadder ()) {
|
if (isOnLadder ()) {
|
||||||
TraceResult tr {};
|
edict_t *ladder = nullptr;
|
||||||
game.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_pathOrigin, TraceIgnore::Everything, ent (), &tr);
|
|
||||||
|
|
||||||
if (tr.flFraction < 1.0f) {
|
game.searchEntities (m_pathOrigin, 96.0f, [&] (edict_t *e) {
|
||||||
m_pathOrigin = m_pathOrigin + (pev->origin - m_pathOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f);
|
if (e->v.classname.str () == "func_ladder") {
|
||||||
|
ladder = e;
|
||||||
|
return EntitySearchResult::Break;
|
||||||
|
}
|
||||||
|
return EntitySearchResult::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!game.isNullEntity (ladder)) {
|
||||||
|
TraceResult tr {};
|
||||||
|
game.testLine ({ pev->origin.x, pev->origin.y, ladder->v.absmin.z }, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
||||||
|
if (tr.flFraction < 1.0f) {
|
||||||
|
m_pathOrigin = graph[m_currentNodeIndex].origin + (pev->origin - m_pathOrigin) * 0.5 + Vector (0.0f, 0.0f, 32.0f);
|
||||||
|
}
|
||||||
|
m_ladderDir = m_pathOrigin.z < pev->origin.z ? LadderDir::Down : LadderDir::Up;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::updateRightRef () {
|
|
||||||
m_rightRef = Vector { 0.0f, pev->angles.y, 0.0f }.right (); // convert current view angle to vectors for traceline math...
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
||||||
// checks if bot is blocked in his movement direction (excluding doors)
|
// checks if bot is blocked in his movement direction (excluding doors)
|
||||||
|
|
||||||
|
|
@ -2752,11 +2770,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 +2782,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
|
||||||
|
|
@ -2773,12 +2791,12 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
||||||
constexpr auto kVec00N16 = Vector (0.0f, 0.0f, -16.0f);
|
constexpr auto kVec00N16 = Vector (0.0f, 0.0f, -16.0f);
|
||||||
|
|
||||||
// right referential vector
|
// right referential vector
|
||||||
updateRightRef ();
|
auto right = Vector { 0.0f, pev->angles.y, 0.0f }.right ();
|
||||||
|
|
||||||
// bot's head is clear, check at shoulder level...
|
// bot's head is clear, check at shoulder level...
|
||||||
// trace from the bot's shoulder left diagonal forward to the right shoulder...
|
// trace from the bot's shoulder left diagonal forward to the right shoulder...
|
||||||
src = getEyesPos () + kVec00N16 - m_rightRef * -16.0f;
|
src = getEyesPos () + kVec00N16 - right * -16.0f;
|
||||||
forward = getEyesPos () + kVec00N16 + m_rightRef * 16.0f + normal * 24.0f;
|
forward = getEyesPos () + kVec00N16 + right * 16.0f + normal * 24.0f;
|
||||||
|
|
||||||
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
||||||
|
|
||||||
|
|
@ -2789,8 +2807,8 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
||||||
|
|
||||||
// bot's head is clear, check at shoulder level...
|
// bot's head is clear, check at shoulder level...
|
||||||
// trace from the bot's shoulder right diagonal forward to the left shoulder...
|
// trace from the bot's shoulder right diagonal forward to the left shoulder...
|
||||||
src = getEyesPos () + kVec00N16 + m_rightRef * 16.0f;
|
src = getEyesPos () + kVec00N16 + right * 16.0f;
|
||||||
forward = getEyesPos () + kVec00N16 - m_rightRef * -16.0f + normal * 24.0f;
|
forward = getEyesPos () + kVec00N16 - right * -16.0f + normal * 24.0f;
|
||||||
|
|
||||||
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
||||||
|
|
||||||
|
|
@ -2825,8 +2843,8 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
||||||
constexpr auto kVec00N24 = Vector (0.0f, 0.0f, -24.0f);
|
constexpr auto kVec00N24 = Vector (0.0f, 0.0f, -24.0f);
|
||||||
|
|
||||||
// trace from the left waist to the right forward waist pos
|
// trace from the left waist to the right forward waist pos
|
||||||
src = pev->origin + kVec00N17 - m_rightRef * -16.0f;
|
src = pev->origin + kVec00N17 - right * -16.0f;
|
||||||
forward = pev->origin + kVec00N17 + m_rightRef * 16.0f + normal * 24.0f;
|
forward = pev->origin + kVec00N17 + right * 16.0f + normal * 24.0f;
|
||||||
|
|
||||||
// trace from the bot's waist straight forward...
|
// trace from the bot's waist straight forward...
|
||||||
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
||||||
|
|
@ -2837,8 +2855,8 @@ bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// trace from the left waist to the right forward waist pos
|
// trace from the left waist to the right forward waist pos
|
||||||
src = pev->origin + kVec00N24 + m_rightRef * 16.0f;
|
src = pev->origin + kVec00N24 + right * 16.0f;
|
||||||
forward = pev->origin + kVec00N24 - m_rightRef * -16.0f + normal * 24.0f;
|
forward = pev->origin + kVec00N24 - right * -16.0f + normal * 24.0f;
|
||||||
|
|
||||||
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
|
||||||
|
|
||||||
|
|
@ -2917,7 +2935,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
if (!isOnFloor () && (isOnLadder () || !isInWater ())) {
|
if (!isOnFloor () && (isOnLadder () || !isInWater ())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
updateRightRef ();
|
auto right = Vector { 0.0f, pev->angles.y, 0.0f }.right (); // convert current view angle to vectors for traceline math...
|
||||||
|
|
||||||
// check for normal jump height first...
|
// check for normal jump height first...
|
||||||
auto src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
auto src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
||||||
|
|
@ -2927,7 +2945,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr);
|
game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
return doneCanJumpUp (normal, m_rightRef);
|
return doneCanJumpUp (normal, right);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// now trace from jump height upward to check for obstructions...
|
// now trace from jump height upward to check for obstructions...
|
||||||
|
|
@ -2942,7 +2960,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check same height to one side of the bot...
|
// now check same height to one side of the bot...
|
||||||
src = pev->origin + m_rightRef * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
src = pev->origin + right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
||||||
dest = src + normal * 32.0f;
|
dest = src + normal * 32.0f;
|
||||||
|
|
||||||
// trace a line forward at maximum jump height...
|
// trace a line forward at maximum jump height...
|
||||||
|
|
@ -2950,7 +2968,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
|
|
||||||
// if trace hit something, return false
|
// if trace hit something, return false
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
return doneCanJumpUp (normal, m_rightRef);
|
return doneCanJumpUp (normal, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now trace from jump height upward to check for obstructions...
|
// now trace from jump height upward to check for obstructions...
|
||||||
|
|
@ -2965,7 +2983,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check same height on the other side of the bot...
|
// now check same height on the other side of the bot...
|
||||||
src = pev->origin + (-m_rightRef * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
src = pev->origin + (-right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
||||||
dest = src + normal * 32.0f;
|
dest = src + normal * 32.0f;
|
||||||
|
|
||||||
// trace a line forward at maximum jump height...
|
// trace a line forward at maximum jump height...
|
||||||
|
|
@ -2973,7 +2991,7 @@ bool Bot::canJumpUp (const Vector &normal) {
|
||||||
|
|
||||||
// if trace hit something, return false
|
// if trace hit something, return false
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
return doneCanJumpUp (normal, m_rightRef);
|
return doneCanJumpUp (normal, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now trace from jump height upward to check for obstructions...
|
// now trace from jump height upward to check for obstructions...
|
||||||
|
|
@ -3081,10 +3099,10 @@ bool Bot::canDuckUnder (const Vector &normal) {
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
updateRightRef ();
|
auto right = Vector { 0.0f, pev->angles.y, 0.0f }.right ();
|
||||||
|
|
||||||
// now check same height to one side of the bot...
|
// now check same height to one side of the bot...
|
||||||
src = baseHeight + m_rightRef * 16.0f;
|
src = baseHeight + right * 16.0f;
|
||||||
dest = src + normal * 32.0f;
|
dest = src + normal * 32.0f;
|
||||||
|
|
||||||
// trace a line forward at duck height...
|
// trace a line forward at duck height...
|
||||||
|
|
@ -3096,7 +3114,7 @@ bool Bot::canDuckUnder (const Vector &normal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check same height on the other side of the bot...
|
// now check same height on the other side of the bot...
|
||||||
src = baseHeight + (-m_rightRef * 16.0f);
|
src = baseHeight + (-right * 16.0f);
|
||||||
dest = src + normal * 32.0f;
|
dest = src + normal * 32.0f;
|
||||||
|
|
||||||
// trace a line forward at duck height...
|
// trace a line forward at duck height...
|
||||||
|
|
@ -3113,14 +3131,14 @@ bool Bot::isBlockedLeft () {
|
||||||
if (m_moveSpeed < 0.0f) {
|
if (m_moveSpeed < 0.0f) {
|
||||||
direction = -48.0f;
|
direction = -48.0f;
|
||||||
}
|
}
|
||||||
Vector right {}, forward {};
|
Vector left {}, forward {};
|
||||||
pev->angles.angleVectors (&forward, &right, nullptr);
|
pev->angles.angleVectors (&forward, &left, nullptr);
|
||||||
|
|
||||||
// do a trace to the left...
|
// do a trace to the left...
|
||||||
game.testLine (pev->origin, pev->origin - forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr);
|
game.testLine (pev->origin, pev->origin + forward * direction - left * -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;
|
||||||
|
|
@ -3130,8 +3148,8 @@ bool Bot::isBlockedRight () {
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
float direction = 48.0f;
|
float direction = 48.0f;
|
||||||
|
|
||||||
if (m_moveSpeed > 0.0f) {
|
if (m_moveSpeed < 0.0f) {
|
||||||
direction = 48.0f;
|
direction = -48.0f;
|
||||||
}
|
}
|
||||||
Vector right {}, forward {};
|
Vector right {}, forward {};
|
||||||
pev->angles.angleVectors (&forward, &right, nullptr);
|
pev->angles.angleVectors (&forward, &right, nullptr);
|
||||||
|
|
@ -3140,7 +3158,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 +3320,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)) {
|
||||||
|
|
|
||||||
|
|
@ -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 ¤t = graph[currentIndex];
|
const auto ¤t = 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,19 +38,23 @@ float PlannerHeuristic::gfunctionKillsDistCTWithHostage (int team, int currentIn
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
return kInfiniteHeuristic;
|
return kInfiniteHeuristic;
|
||||||
}
|
}
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
else if (current.flags & NodeFlag::Ladder) {
|
||||||
return gfunctionKillsDist (team, currentIndex, parentIndex) * 500.0f;
|
return gfunctionKillsDist (team, currentIndex, parentIndex) * 6.0f;
|
||||||
}
|
}
|
||||||
|
else if (current.flags & NodeFlag::Crouch) {
|
||||||
|
return gfunctionKillsDist (team, currentIndex, parentIndex) * 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return gfunctionKillsDist (team, currentIndex, parentIndex);
|
return gfunctionKillsDist (team, currentIndex, parentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ¤t = graph[currentIndex];
|
const auto ¤t = 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,9 +73,13 @@ auto PlannerHeuristic::gfunctionKillsCTWithHostage (int team, int currentIndex,
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
return kInfiniteHeuristic;
|
return kInfiniteHeuristic;
|
||||||
}
|
}
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
else if (current.flags & NodeFlag::Ladder) {
|
||||||
return gfunctionKills (team, currentIndex, parentIndex) * 500.0f;
|
return gfunctionKills (team, currentIndex, parentIndex) * 6.0f;
|
||||||
}
|
}
|
||||||
|
else if (current.flags & NodeFlag::Crouch) {
|
||||||
|
return gfunctionKills (team, currentIndex, parentIndex) * 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return gfunctionKills (team, currentIndex, parentIndex);
|
return gfunctionKills (team, currentIndex, parentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,9 +111,13 @@ float PlannerHeuristic::gfunctionPathDistWithHostage (int, int currentIndex, int
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
return kInfiniteHeuristic;
|
return kInfiniteHeuristic;
|
||||||
}
|
}
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
else if (current.flags & NodeFlag::Ladder) {
|
||||||
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 500.0f;
|
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 6.0f;
|
||||||
}
|
}
|
||||||
|
else if (current.flags & NodeFlag::Crouch) {
|
||||||
|
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex);
|
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +196,7 @@ bool AStarAlgo::cantSkipNode (const int a, const int b, bool skipVisCheck) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipVisCheck) {
|
if (!skipVisCheck) {
|
||||||
const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number);
|
const bool notVisible = !vistab.visibleBothSides (ag.number, bg.number);
|
||||||
|
|
||||||
if (notVisible) {
|
if (notVisible) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -203,7 +215,7 @@ bool AStarAlgo::cantSkipNode (const int a, const int b, bool skipVisCheck) {
|
||||||
const float distanceSq = ag.origin.distanceSq (bg.origin);
|
const float distanceSq = ag.origin.distanceSq (bg.origin);
|
||||||
|
|
||||||
const bool tooFar = distanceSq > cr::sqrf (400.0f);
|
const bool tooFar = distanceSq > cr::sqrf (400.0f);
|
||||||
const bool tooClose = distanceSq < cr::sqrtf (40.0f);
|
const bool tooClose = distanceSq < cr::sqrf (40.0f);
|
||||||
|
|
||||||
if (tooFar || tooClose) {
|
if (tooFar || tooClose) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -264,7 +276,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +384,7 @@ void FloydWarshallAlgo::syncRebuild () {
|
||||||
for (int k = 0; k < m_length; ++k) {
|
for (int k = 0; k < m_length; ++k) {
|
||||||
for (int i = 0; i < m_length; ++i) {
|
for (int i = 0; i < m_length; ++i) {
|
||||||
for (int j = 0; j < m_length; ++j) {
|
for (int j = 0; j < m_length; ++j) {
|
||||||
int distance = (matrix + (i * m_length) + k)->dist + (matrix + (k * m_length) + j)->dist;
|
const auto distance = (matrix + (i * m_length) + k)->dist + (matrix + (k * m_length) + j)->dist;
|
||||||
|
|
||||||
if (distance < (matrix + (i * m_length) + j)->dist) {
|
if (distance < (matrix + (i * m_length) + j)->dist) {
|
||||||
*(matrix + (i * m_length) + j) = { (matrix + (i * m_length) + k)->index, distance };
|
*(matrix + (i * m_length) + j) = { (matrix + (i * m_length) + k)->index, distance };
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
|
||||||
|
|
||||||
// graphs can be downloaded...
|
// graphs can be downloaded...
|
||||||
const bool isGraph = !!(type.option & StorageOption::Graph);
|
const bool isGraph = !!(type.option & StorageOption::Graph);
|
||||||
const bool isDebug = cv_debug;
|
const bool isDebug = cv_debug || game.isDeveloperMode ();
|
||||||
|
|
||||||
MemFile file (filename); // open the file
|
MemFile file (filename); // open the file
|
||||||
data.clear ();
|
data.clear ();
|
||||||
|
|
@ -85,11 +85,7 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
|
||||||
if (tryReload ()) {
|
if (tryReload ()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
||||||
if (game.isDeveloperMode ()) {
|
|
||||||
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// erase the current graph just in case
|
// erase the current graph just in case
|
||||||
|
|
@ -178,7 +174,7 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
|
||||||
if (isGraph) {
|
if (isGraph) {
|
||||||
resetRetries ();
|
resetRetries ();
|
||||||
|
|
||||||
ExtenHeader extenHeader;
|
ExtenHeader extenHeader {};
|
||||||
strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author));
|
strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author));
|
||||||
|
|
||||||
if (extenSize <= actuallyRead) {
|
if (extenSize <= actuallyRead) {
|
||||||
|
|
|
||||||
240
src/support.cpp
240
src/support.cpp
|
|
@ -9,9 +9,11 @@
|
||||||
|
|
||||||
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.");
|
||||||
|
|
||||||
|
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_greande_checks_radius ("greande_checks_radius", "220", "Radius to check for smoke clouds around a detonated grenade.", true, 32.0f, 320.0f);
|
||||||
|
|
||||||
BotSupport::BotSupport () {
|
BotSupport::BotSupport () {
|
||||||
m_needToSendWelcome = false;
|
m_needToSendWelcome = false;
|
||||||
m_welcomeReceiveTime = 0.0f;
|
m_welcomeReceiveTime = 0.0f;
|
||||||
|
|
@ -82,13 +84,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 +146,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 +156,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 +236,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 +265,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 +284,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 +297,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 +367,117 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (cv_smoke_greande_checks_radius.as <float> ());
|
||||||
|
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 * cv_smoke_greande_checks_radius.as <float> ();
|
||||||
|
|
||||||
|
// return true if the total length of smoke-covered line-of-sight is too much
|
||||||
|
return totalSmokedLength > maxSmokedLength;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,25 @@ void Bot::normal_ () {
|
||||||
const int debugGoal = cv_debug_goal.as <int> ();
|
const int debugGoal = cv_debug_goal.as <int> ();
|
||||||
|
|
||||||
// user forced a node as a goal?
|
// user forced a node as a goal?
|
||||||
if (debugGoal != kInvalidNodeIndex) {
|
if (graph.exists (debugGoal)) {
|
||||||
if (getTask ()->data != debugGoal) {
|
if (getTask ()->data != debugGoal) {
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -99,14 +105,14 @@ void Bot::normal_ () {
|
||||||
// spray logo sometimes if allowed to do so
|
// spray logo sometimes if allowed to do so
|
||||||
if (!(m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))
|
if (!(m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))
|
||||||
&& m_seeEnemyTime + 5.0f < game.time ()
|
&& m_seeEnemyTime + 5.0f < game.time ()
|
||||||
&& m_reloadState == Reload::None
|
&& m_reloadState == Reload::None
|
||||||
&& m_timeLogoSpray < game.time ()
|
&& m_timeLogoSpray < game.time ()
|
||||||
&& cv_spraypaints
|
&& cv_spraypaints
|
||||||
&& pev->groundentity == game.getStartEntity ()
|
&& pev->groundentity == game.getStartEntity ()
|
||||||
&& 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,18 +650,23 @@ 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;
|
||||||
|
|
||||||
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
|
if (isNodeValidForPredict (predictNode)
|
||||||
|
&& pathLength > 1
|
||||||
|
&& vistab.visible (predictNode, m_currentNodeIndex)) {
|
||||||
|
|
||||||
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pathLength = 0;
|
pathLength = 0;
|
||||||
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
|
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||||
|
|
||||||
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
|
if (isNodeValidForPredict (predictNode) && pathLength > 1
|
||||||
|
&& vistab.visible (predictNode, m_currentNodeIndex)) {
|
||||||
|
|
||||||
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -838,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 {
|
||||||
|
|
@ -881,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;
|
||||||
|
|
||||||
|
|
@ -889,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
|
||||||
|
|
@ -903,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) {
|
||||||
|
|
@ -1059,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 ();
|
||||||
|
|
||||||
|
|
@ -1070,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;
|
||||||
|
|
@ -1320,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 ())) {
|
||||||
|
|
@ -1394,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 ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1402,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1427,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);
|
||||||
|
|
@ -1458,15 +1469,16 @@ void Bot::escapeFromBomb_ () {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::shootBreakable_ () {
|
void Bot::shootBreakable_ () {
|
||||||
|
const bool hasEnemy = !game.isNullEntity (m_enemy);
|
||||||
|
|
||||||
// breakable destroyed?
|
// breakable destroyed?
|
||||||
if (!util.isBreakableEntity (m_breakableEntity)) {
|
if (hasEnemy || !game.isBreakableEntity (m_breakableEntity)) {
|
||||||
completeTask ();
|
completeTask ();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
game.testLine (pev->origin, m_breakableOrigin, TraceIgnore::Monsters , ent (), &tr);
|
game.testLine (pev->origin, m_breakableOrigin, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
||||||
if (tr.pHit != m_breakableEntity && !cr::fequal (tr.flFraction, 1.0f)) {
|
if (tr.pHit != m_breakableEntity && !cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
m_ignoredBreakable.push (tr.pHit);
|
m_ignoredBreakable.push (tr.pHit);
|
||||||
|
|
@ -1496,7 +1508,7 @@ void Bot::shootBreakable_ () {
|
||||||
m_shootTime = game.time ();
|
m_shootTime = game.time ();
|
||||||
|
|
||||||
// enforce shooting
|
// enforce shooting
|
||||||
if (!usesKnife () && !m_isReloading && !(pev->button & IN_RELOAD) && getAmmoInClip () > 0) {
|
if (!usesKnife () && !m_isReloading && !(pev->button & IN_RELOAD) && getAmmoInClip () > 0) {
|
||||||
if (!(m_oldButtons & IN_ATTACK)) {
|
if (!(m_oldButtons & IN_ATTACK)) {
|
||||||
pev->button |= IN_ATTACK;
|
pev->button |= IN_ATTACK;
|
||||||
}
|
}
|
||||||
|
|
@ -1642,7 +1654,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 ();
|
||||||
|
|
@ -1671,7 +1683,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
115
src/vision.cpp
115
src/vision.cpp
|
|
@ -230,7 +230,11 @@ void Bot::updateLookAngles () {
|
||||||
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
|
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
|
||||||
|
|
||||||
// prevent reverse facing angles when navigating normally
|
// prevent reverse facing angles when navigating normally
|
||||||
if (m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) {
|
if (m_moveToGoal
|
||||||
|
&& !importantAimFlags
|
||||||
|
&& !m_pathOrigin.empty ()
|
||||||
|
&& !isOnLadder ()) {
|
||||||
|
|
||||||
const float forward = (m_lookAt - pev->origin).yaw ();
|
const float forward = (m_lookAt - pev->origin).yaw ();
|
||||||
|
|
||||||
if (!cr::fzero (forward)) {
|
if (!cr::fzero (forward)) {
|
||||||
|
|
@ -274,8 +278,8 @@ void Bot::updateLookAngles () {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) {
|
void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) {
|
||||||
Vector spring { 13.0f, 13.0f, 0.0f };
|
Vector spring { 13.0f, 9.0f, 0.0f };
|
||||||
Vector damperCoefficient { 0.22f, 0.22f, 0.0f };
|
Vector damperCoefficient { 0.28f, 0.26f, 0.0f };
|
||||||
|
|
||||||
const float offset = cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 25.0f;
|
const float offset = cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 25.0f;
|
||||||
|
|
||||||
|
|
@ -292,10 +296,14 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) {
|
||||||
|
|
||||||
if (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity)) {
|
if (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity)) {
|
||||||
m_playerTargetTime = game.time ();
|
m_playerTargetTime = game.time ();
|
||||||
m_randomizedIdealAngles = m_idealAngles;
|
|
||||||
|
|
||||||
stiffness = spring * (0.2f + offset / 125.0f);
|
// bias aim slightly below exact target (chest > head)
|
||||||
|
m_randomizedIdealAngles = m_idealAngles;
|
||||||
|
m_randomizedIdealAngles.x += rg(2.0f, 6.0f); // pitch down
|
||||||
|
|
||||||
|
stiffness = spring * (0.18f + offset / 140.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
// is it time for bot to randomize the aim direction again (more often where moving) ?
|
// is it time for bot to randomize the aim direction again (more often where moving) ?
|
||||||
if (m_randomizeAnglesTime < game.time ()
|
if (m_randomizeAnglesTime < game.time ()
|
||||||
|
|
@ -436,17 +444,17 @@ void Bot::setAimDirection () {
|
||||||
|| (m_currentTravelFlags & PathFlag::Jump)) {
|
|| (m_currentTravelFlags & PathFlag::Jump)) {
|
||||||
|
|
||||||
flags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
|
flags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
|
||||||
m_canChooseAimDirection = false;
|
m_canSetAimDirection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't switch view right away after loosing focus with current enemy
|
// don't switch view right away after loosing focus with current enemy
|
||||||
if ((m_shootTime + rg (0.75f, 1.25f) > game.time ()
|
if ((m_shootTime + rg (0.75f, 1.25f) > game.time ()
|
||||||
|| m_seeEnemyTime + 1.5f > game.time ())
|
|| m_seeEnemyTime + rg (1.0f, 1.25f) > game.time ())
|
||||||
|
|
||||||
&& m_forgetLastVictimTimer.elapsed ()
|
&& m_forgetLastVictimTimer.elapsed ()
|
||||||
&& !m_lastEnemyOrigin.empty ()
|
&& !m_lastEnemyOrigin.empty ()
|
||||||
&& util.isAlive (m_lastEnemy)
|
&& game.isPlayerEntity (m_lastEnemy)
|
||||||
&& game.isNullEntity (m_enemy)) {
|
&& !game.isPlayerEntity (m_enemy)) {
|
||||||
|
|
||||||
flags |= AimFlags::LastEnemy;
|
flags |= AimFlags::LastEnemy;
|
||||||
}
|
}
|
||||||
|
|
@ -503,7 +511,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,7 +540,8 @@ void Bot::setAimDirection () {
|
||||||
}
|
}
|
||||||
const float distToPredictNodeSq = graph[predictNode].origin.distanceSq (pev->origin);
|
const float distToPredictNodeSq = graph[predictNode].origin.distanceSq (pev->origin);
|
||||||
|
|
||||||
if (distToPredictNodeSq >= cr::sqrf (2048.0f)) {
|
if (distToPredictNodeSq >= cr::sqrf (2048.0f) ||
|
||||||
|
distToPredictNodeSq <= cr::sqrf (256.0f)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,7 +551,8 @@ void Bot::setAimDirection () {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return isNodeValidForPredict (predictNode) && pathLength < cv_max_nodes_for_predict.as <int> ();
|
return isNodeValidForPredict (predictNode) && pathLength < cv_max_nodes_for_predict.as <int> ()
|
||||||
|
&& numEnemiesNear (graph[predictNode].origin, 1024.0f) > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (changePredictedEnemy) {
|
if (changePredictedEnemy) {
|
||||||
|
|
@ -573,46 +583,21 @@ 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 verticalMove = (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isPreviousLadder () || pev->velocity.z > 16.0f;
|
||||||
&& !m_isStuck && !(pev->button & IN_DUCK)
|
|
||||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
|
||||||
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
|
|
||||||
&& m_pathWalk.hasNext () && !isOnLadder ()
|
|
||||||
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
|
|
||||||
|
|
||||||
const auto nextPathIndex = m_pathWalk.next ();
|
|
||||||
const auto nextPathX2 = m_pathWalk.nextX2 ();
|
|
||||||
|
|
||||||
if (vistab.visible (m_currentNodeIndex, nextPathX2)) {
|
|
||||||
const auto &gn = graph[nextPathX2];
|
|
||||||
m_lookAt = gn.origin + pev->view_ofs;
|
|
||||||
}
|
|
||||||
else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
|
|
||||||
const auto &gn = graph[nextPathIndex];
|
|
||||||
m_lookAt = gn.origin + pev->view_ofs;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_lookAt = pev->origin + pev->view_ofs + pev->v_angle.forward () * 300.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_lookAt = destOrigin;
|
|
||||||
}
|
|
||||||
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder ();
|
|
||||||
|
|
||||||
if (m_numEnemiesLeft > 0
|
if (m_numEnemiesLeft > 0
|
||||||
&& m_canChooseAimDirection
|
&& m_canSetAimDirection
|
||||||
&& m_seeEnemyTime + 4.0f < game.time ()
|
&& m_seeEnemyTime + 4.0f < game.time ()
|
||||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
&& graph.exists (m_currentNodeIndex)
|
||||||
&& !horizontalMovement) {
|
&& !(m_aimFlags & AimFlags::PredictPath)) {
|
||||||
|
|
||||||
const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||||
|
|
||||||
if (graph.exists (dangerIndex)
|
if (graph.exists (dangerIndex)
|
||||||
&& vistab.visible (m_currentNodeIndex, dangerIndex)
|
&& vistab.visibleBothSides (m_currentNodeIndex, dangerIndex)
|
||||||
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
||||||
|
|
||||||
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (512.0f)) {
|
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (240.0f)) {
|
||||||
m_lookAt = destOrigin;
|
m_lookAt = destOrigin;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -623,13 +608,48 @@ void Bot::setAimDirection () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!verticalMove
|
||||||
|
&& m_moveToGoal
|
||||||
|
&& m_canSetAimDirection
|
||||||
|
&& isOnFloor ()
|
||||||
|
&& !isDucking ()
|
||||||
|
&& graph.exists (m_currentNodeIndex)
|
||||||
|
&& m_pathWalk.hasNext ()
|
||||||
|
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (384.0f)
|
||||||
|
&& m_path->radius >= 16.0f
|
||||||
|
&& m_path->flags == 0 && graph[m_pathWalk.next ()].flags == 0) {
|
||||||
|
|
||||||
|
const auto nextPathIndex = m_pathWalk.next ();
|
||||||
|
const auto isNarrowPlace = isInNarrowPlace ();
|
||||||
|
|
||||||
|
if (graph.exists (nextPathIndex)
|
||||||
|
&& cr::abs (graph[nextPathIndex].origin.z - m_pathOrigin.z) < 8.0f) {
|
||||||
|
|
||||||
|
if (m_pathWalk.length () > 2 && !isNarrowPlace) {
|
||||||
|
const auto nextPathIndexX2 = m_pathWalk.nextX2 ();
|
||||||
|
|
||||||
|
if (vistab.visibleBothSides (m_currentNodeIndex, nextPathIndexX2)) {
|
||||||
|
m_lookAt = graph[nextPathIndexX2].origin + pev->view_ofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isNarrowPlace && vistab.visibleBothSides (m_currentNodeIndex, nextPathIndex)) {
|
||||||
|
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_lookAt = destOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_lookAt = destOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// try look at next node if on ladder
|
// try look at next node if on ladder
|
||||||
if (horizontalMovement && m_pathWalk.hasNext ()) {
|
if (verticalMove && m_pathWalk.hasNext ()) {
|
||||||
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 (96.0f)
|
||||||
&& nextPath.origin.z > m_pathOrigin.z + 26.0f) {
|
&& nextPath.origin.z > m_pathOrigin.z + 26.0f) {
|
||||||
|
|
||||||
m_lookAt = nextPath.origin + pev->view_ofs;
|
m_lookAt = nextPath.origin + pev->view_ofs;
|
||||||
|
|
@ -637,7 +657,7 @@ void Bot::setAimDirection () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't look at bottom of node, if reached it
|
// don't look at bottom of node, if reached it
|
||||||
if (m_lookAt == destOrigin && !horizontalMovement) {
|
if (m_lookAt == destOrigin && !verticalMove) {
|
||||||
m_lookAt.z = getEyesPos ().z;
|
m_lookAt.z = getEyesPos ().z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -654,3 +674,4 @@ void Bot::setAimDirection () {
|
||||||
m_lookAt = m_destOrigin;
|
m_lookAt = m_destOrigin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
game.print ("Rebuilding vistable... %d%% done.", value);
|
if (value >= 100 || cv_debug) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue