nav: more fixes to ladder navigation
refactor: bot difficulty data and add graph refresh command combat: fixes for smoke grenades (ref #743) engine: fixes to spawn management (ref #744)
This commit is contained in:
parent
7b378ba3fa
commit
17ed252b60
26 changed files with 506 additions and 408 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit b5f7ccc23dec2d018048b40085f78255cc232e52
|
Subproject commit f7b1b02a301f900082d2e05ebbbc2d7edc2a4e09
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -419,6 +419,12 @@ CR_DECLARE_SCOPED_ENUM (GoalTactic,
|
||||||
RescueHostage
|
RescueHostage
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ladder move direction
|
||||||
|
CR_DECLARE_SCOPED_ENUM (LadderDir,
|
||||||
|
Up = 0,
|
||||||
|
Down,
|
||||||
|
)
|
||||||
|
|
||||||
// some hard-coded desire defines used to override calculated ones
|
// some hard-coded desire defines used to override calculated ones
|
||||||
namespace TaskPri {
|
namespace TaskPri {
|
||||||
constexpr auto Normal { 35.0f };
|
constexpr auto Normal { 35.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 = 2.5f;
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -59,8 +57,7 @@ public:
|
||||||
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 ();
|
||||||
|
|
|
||||||
|
|
@ -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 ();
|
||||||
|
|
||||||
|
|
@ -333,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
|
||||||
|
|
|
||||||
|
|
@ -227,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 ();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
26
inc/yapb.h
26
inc/yapb.h
|
|
@ -84,8 +84,7 @@ public:
|
||||||
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 {};
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -130,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;
|
||||||
|
|
@ -325,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);
|
||||||
|
|
@ -333,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))) {
|
||||||
|
|
@ -348,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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", "1", "Affects the bot's vision by smoke clouds.", true, 0.0f, 2.0f);
|
|
||||||
|
|
||||||
// game console variables
|
// game console variables
|
||||||
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
||||||
|
|
@ -100,7 +99,7 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +186,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;
|
||||||
}
|
}
|
||||||
|
|
@ -320,15 +320,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 () {
|
||||||
|
|
@ -1644,47 +1643,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;
|
||||||
|
|
@ -1922,7 +1904,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 {
|
||||||
|
|
@ -3101,7 +3082,7 @@ 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 = game.isAliveEntity (ent ());
|
m_isAlive = game.isAliveEntity (ent ());
|
||||||
m_team = game.getPlayerTeam (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);
|
||||||
|
|
@ -3242,7 +3223,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;
|
||||||
}
|
}
|
||||||
|
|
@ -3338,12 +3319,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 + 0.1f; // 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;
|
||||||
|
|
@ -3364,13 +3344,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.15f - 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
|
||||||
|
|
@ -3463,7 +3441,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 ();
|
||||||
}
|
}
|
||||||
|
|
@ -3478,8 +3456,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3643,7 +3619,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3930,9 +3906,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) {
|
||||||
|
|
@ -3943,7 +3916,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) {
|
||||||
|
|
@ -3954,7 +3930,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);
|
||||||
}
|
}
|
||||||
|
|
@ -4125,7 +4101,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 ()) {
|
||||||
|
|
||||||
|
|
@ -4225,7 +4201,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)) {
|
||||||
|
|
||||||
|
|
@ -4244,7 +4220,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 ();
|
||||||
|
|
|
||||||
|
|
@ -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 ();
|
||||||
|
|
@ -671,7 +671,7 @@ Vector Bot::getBodyOffsetError (float distance) {
|
||||||
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);
|
||||||
|
|
@ -722,7 +722,7 @@ Vector Bot::getEnemyBodyOffset () {
|
||||||
else if (game.isPlayerEntity (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 ())) {
|
||||||
|
|
@ -1014,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) {
|
||||||
|
|
@ -1646,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,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 ();
|
||||||
|
|
@ -126,15 +123,11 @@ 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
|
||||||
|
|
@ -187,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 ();
|
||||||
|
|
@ -223,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 {
|
||||||
|
|
@ -791,53 +806,50 @@ 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 {
|
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) {
|
else if (!plat.nix && !plat.win && !plat.macos) {
|
||||||
libSuffix += "_i386";
|
// 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.insert (0, "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.insert (0, "cs" + suffix);
|
||||||
libs.insert (0, { "libcs" });
|
libs.insert (0, "mp" + suffix);
|
||||||
else
|
|
||||||
libs.insert (0, { "mp", "cs" });
|
|
||||||
|
|
||||||
for (auto &lib : libs) {
|
|
||||||
lib += libSuffix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 ("");
|
||||||
|
|
@ -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 ()) {
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -1080,7 +1084,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;
|
||||||
}
|
}
|
||||||
|
|
@ -1089,7 +1093,7 @@ 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);
|
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
|
||||||
|
|
@ -1192,7 +1196,7 @@ 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> ();
|
||||||
|
|
@ -1202,7 +1206,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
|
||||||
if (maxDifficulty > minDifficulty) {
|
if (maxDifficulty > minDifficulty) {
|
||||||
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 ();
|
||||||
|
|
@ -1515,14 +1519,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 ();
|
||||||
|
|
@ -1722,9 +1725,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);
|
||||||
|
|
@ -1828,6 +1828,16 @@ void Bot::markStale () {
|
||||||
pev->flags |= FL_DORMANT;
|
pev->flags |= FL_DORMANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bot::setNewDifficulty (int32_t newDifficulty) {
|
||||||
|
if (newDifficulty < Difficulty::Noob || newDifficulty > Difficulty::Expert) {
|
||||||
|
m_difficulty = Difficulty::Hard;
|
||||||
|
m_difficultyData = conf.getDifficultyTweaks (Difficulty::Hard);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
||||||
191
src/navigate.cpp
191
src/navigate.cpp
|
|
@ -470,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -602,11 +602,11 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
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.50f, 0.75f);
|
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) {
|
||||||
|
|
@ -617,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;
|
||||||
|
|
||||||
|
|
@ -653,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -683,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] {};
|
||||||
|
|
@ -696,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;
|
||||||
|
|
||||||
|
|
@ -745,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;
|
||||||
|
|
@ -813,6 +815,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bits & CollisionProbe::Duck) {
|
if (bits & CollisionProbe::Duck) {
|
||||||
state[i] = 0;
|
state[i] = 0;
|
||||||
|
|
||||||
|
|
@ -901,7 +904,7 @@ void Bot::checkTerrain (const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::checkFall () {
|
void Bot::checkFall () {
|
||||||
if (isPreviousLadder () || (m_pathFlags & NodeFlag::Ladder)) {
|
if (isPreviousLadder () || (m_pathFlags & NodeFlag::Ladder) || isOnLadder ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -945,28 +948,30 @@ 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) {
|
||||||
|
if (graph.exists (m_currentNodeIndex) && !isReachableNode (m_currentNodeIndex)) {
|
||||||
m_currentNodeIndex = kInvalidNodeIndex;
|
m_currentNodeIndex = kInvalidNodeIndex;
|
||||||
findValidNode ();
|
findValidNode ();
|
||||||
|
|
||||||
m_fixFallTimer.start (1.0f);
|
m_fixFallTimer.start (1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Bot::moveToGoal () {
|
void Bot::moveToGoal () {
|
||||||
findValidNode ();
|
findValidNode ();
|
||||||
|
|
@ -1000,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
|
||||||
|
|
@ -1135,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1149,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;
|
||||||
|
|
@ -1171,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)
|
||||||
|
|
@ -1238,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;
|
||||||
|
|
@ -1252,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 ()) {
|
||||||
|
|
@ -1322,26 +1316,35 @@ bool Bot::updateNavigation () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float desiredDistanceSq = cr::sqrf (32.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)
|
||||||
|
&& (m_pathFlags & NodeFlag::Goal)) {
|
||||||
|
|
||||||
desiredDistanceSq = cr::sqrf (96.0f);
|
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;
|
||||||
|
|
@ -1368,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 (16.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1410,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;
|
||||||
}
|
}
|
||||||
|
|
@ -1870,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];
|
||||||
|
|
@ -2460,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2590,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;
|
||||||
|
|
||||||
|
|
@ -2603,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);
|
||||||
|
|
@ -2666,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);
|
||||||
|
|
@ -2725,21 +2733,30 @@ void Bot::setPathOrigin () {
|
||||||
else if (radius > 0.0f) {
|
else if (radius > 0.0f) {
|
||||||
setNonZeroPathOrigin ();
|
setNonZeroPathOrigin ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOnLadder ()) {
|
if (isOnLadder ()) {
|
||||||
|
edict_t *ladder = nullptr;
|
||||||
|
|
||||||
|
game.searchEntities (m_pathOrigin, 96.0f, [&] (edict_t *e) {
|
||||||
|
if (e->v.classname.str () == "func_ladder") {
|
||||||
|
ladder = e;
|
||||||
|
return EntitySearchResult::Break;
|
||||||
|
}
|
||||||
|
return EntitySearchResult::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!game.isNullEntity (ladder)) {
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
game.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_pathOrigin, TraceIgnore::Everything, ent (), &tr);
|
game.testLine ({ pev->origin.x, pev->origin.y, ladder->v.absmin.z }, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
m_pathOrigin = m_pathOrigin + (pev->origin - m_pathOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.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)
|
||||||
|
|
||||||
|
|
@ -2774,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);
|
||||||
|
|
||||||
|
|
@ -2790,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);
|
||||||
|
|
||||||
|
|
@ -2826,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);
|
||||||
|
|
@ -2838,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);
|
||||||
|
|
||||||
|
|
@ -2918,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);
|
||||||
|
|
@ -2928,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...
|
||||||
|
|
@ -2943,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...
|
||||||
|
|
@ -2951,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...
|
||||||
|
|
@ -2966,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...
|
||||||
|
|
@ -2974,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...
|
||||||
|
|
@ -3082,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...
|
||||||
|
|
@ -3097,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...
|
||||||
|
|
@ -3114,11 +3131,11 @@ 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 && !game.isDoorEntity (tr.pHit)) {
|
if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !game.isDoorEntity (tr.pHit)) {
|
||||||
|
|
@ -3131,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);
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,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;
|
||||||
|
|
@ -372,7 +372,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 };
|
||||||
|
|
|
||||||
|
|
@ -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,12 +85,8 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
|
||||||
if (tryReload ()) {
|
if (tryReload ()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.isDeveloperMode ()) {
|
|
||||||
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
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
|
||||||
auto unlinkIfGraph = [&] () {
|
auto unlinkIfGraph = [&] () {
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ ConVar cv_display_welcome_text ("display_welcome_text", "1", "Enables or disable
|
||||||
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_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;
|
||||||
|
|
@ -369,7 +372,6 @@ bool BotSupport::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
|
||||||
if (!gameState.hasActiveGrenades ()) {
|
if (!gameState.hasActiveGrenades ()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
constexpr auto kSmokeGrenadeRadius = 115.0f;
|
|
||||||
|
|
||||||
// distance along line of sight covered by smoke
|
// distance along line of sight covered by smoke
|
||||||
float totalSmokedLength = 0.0f;
|
float totalSmokedLength = 0.0f;
|
||||||
|
|
@ -397,7 +399,7 @@ bool BotSupport::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius);
|
const float smokeRadiusSq = cr::sqrf (cv_smoke_greande_checks_radius.as <float> ());
|
||||||
const Vector &smokeOrigin = game.getEntityOrigin (pent);
|
const Vector &smokeOrigin = game.getEntityOrigin (pent);
|
||||||
|
|
||||||
Vector toGrenade = smokeOrigin - from;
|
Vector toGrenade = smokeOrigin - from;
|
||||||
|
|
@ -474,7 +476,7 @@ bool BotSupport::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// define how much smoke a bot can see thru
|
// define how much smoke a bot can see thru
|
||||||
const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius;
|
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 true if the total length of smoke-covered line-of-sight is too much
|
||||||
return totalSmokedLength > maxSmokedLength;
|
return totalSmokedLength > maxSmokedLength;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ 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 ();
|
||||||
|
|
||||||
|
|
|
||||||
100
src/vision.cpp
100
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)) {
|
||||||
|
|
@ -436,7 +440,7 @@ 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
|
||||||
|
|
@ -532,7 +536,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 +547,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,47 +579,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;
|
||||||
|
|
||||||
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder ();
|
const bool verticalMove = (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isPreviousLadder () || pev->velocity.z > 16.0f;
|
||||||
|
|
||||||
if (!horizontalMovement && m_moveToGoal && m_seeEnemyTime + 4.0f < game.time ()
|
|
||||||
&& !m_isStuck && !(pev->button & IN_DUCK)
|
|
||||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
|
||||||
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
|
|
||||||
&& 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -624,24 +604,56 @@ 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
|
if (verticalMove && m_pathWalk.hasNext ()) {
|
||||||
&& m_pathWalk.hasNext ()
|
|
||||||
&& !(m_currentTravelFlags & PathFlag::Jump)) {
|
|
||||||
|
|
||||||
const auto &nextPath = graph[m_pathWalk.next ()];
|
const auto &nextPath = graph[m_pathWalk.next ()];
|
||||||
|
|
||||||
if ((nextPath.flags & NodeFlag::Ladder)
|
if ((nextPath.flags & NodeFlag::Ladder)
|
||||||
&& m_destOrigin.distanceSq (pev->origin) < cr::sqrf (64.0f)
|
&& m_destOrigin.distanceSq (pev->origin) < cr::sqrf (96.0f)
|
||||||
&& nextPath.origin.z > m_pathOrigin.z + 30.0f) {
|
&& nextPath.origin.z > m_pathOrigin.z + 26.0f) {
|
||||||
|
|
||||||
m_lookAt = nextPath.origin;
|
m_lookAt = nextPath.origin + pev->view_ofs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue