backport: nodes flooder (analyzer) from cs-ebot
analyze: allow to disable goal marking analyze: add cvars descriptions and bounds nav: added optional post path smoothing for astar algorithm nav: now bots will use Dijkstra algo instead of floyd-warshall if memory usage too high (controlled via yb_path_floyd_memory_limit cvar) (fixes #434) nav: vistable are now calculated every frame to prevent game-freeze during loading the game (fixes #434) graph: pracrice reworked to hash table so memory footprint is as low as possible (at cost 5-10% performance loss on practice) (fixes #434) control: bots commands now is case-insensitive bot: major refactoring of bot's code nav: issue warnings about path fail only with debug practice: check for visibility when updating danger index analyzer: suspend any analyzing on change level control: add kickall_ct/kickall_t nav: increase blocked distance in stuck check
This commit is contained in:
parent
bb2e93a539
commit
e7712a551a
31 changed files with 3114 additions and 1722 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6d716494737f740ebea58cfc6e6b6b30039cf7e9
|
Subproject commit 4d370ec500675ad23e9507831bbbbd6bb166ce3a
|
||||||
|
|
@ -27,10 +27,16 @@ typedef unsigned char byte;
|
||||||
#include <crlib/vector.h>
|
#include <crlib/vector.h>
|
||||||
#include <crlib/string.h>
|
#include <crlib/string.h>
|
||||||
|
|
||||||
|
#if defined (CR_ARCH_X64)
|
||||||
|
using estring_t = int32_t;
|
||||||
|
#else
|
||||||
|
using estring_t = uint32_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
// idea from regamedll
|
// idea from regamedll
|
||||||
class string_t final : public cr::DenyCopying {
|
class string_t final : public cr::DenyCopying {
|
||||||
private:
|
private:
|
||||||
int offset;
|
estring_t offset;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit string_t () : offset (0)
|
explicit string_t () : offset (0)
|
||||||
|
|
@ -42,10 +48,10 @@ public:
|
||||||
~string_t () = default;
|
~string_t () = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const char *chars (size_t shift = 0) const;
|
const char *chars (estring_t shift = 0) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
operator int () const {
|
operator estring_t () const {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +96,9 @@ extern enginefuncs_t engfuncs;
|
||||||
extern gamefuncs_t dllapi;
|
extern gamefuncs_t dllapi;
|
||||||
|
|
||||||
// Use this instead of ALLOC_STRING on constant strings
|
// Use this instead of ALLOC_STRING on constant strings
|
||||||
#define STRING(offset) (const char *)(globals->pStringBase + (int)offset)
|
static inline const char *STRING (estring_t offset) {
|
||||||
|
return globals->pStringBase + offset;
|
||||||
|
}
|
||||||
|
|
||||||
// form fwgs-hlsdk
|
// form fwgs-hlsdk
|
||||||
#if defined (CR_ARCH_X64)
|
#if defined (CR_ARCH_X64)
|
||||||
|
|
@ -103,12 +111,13 @@ static inline int MAKE_STRING (const char *val) {
|
||||||
return static_cast <int> (ptrdiff);
|
return static_cast <int> (ptrdiff);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#define MAKE_STRING(str) ((uint64_t)(str) - (uint64_t)(STRING(0)))
|
static inline estring_t MAKE_STRING (const char *str) {
|
||||||
|
return static_cast <estring_t> (reinterpret_cast <uint64_t> (str) - reinterpret_cast <uint64_t> (globals->pStringBase));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline const char *string_t::chars (size_t shift) const {
|
inline const char *string_t::chars (estring_t shift) const {
|
||||||
auto result = STRING (offset);
|
auto result = STRING (offset);
|
||||||
|
|
||||||
return cr::strings.isEmpty (result) ? &cr::kNullChar : (result + shift);
|
return cr::strings.isEmpty (result) ? &cr::kNullChar : (result + shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,25 +20,6 @@ extern globalvars_t *globals;
|
||||||
extern enginefuncs_t engfuncs;
|
extern enginefuncs_t engfuncs;
|
||||||
extern gamefuncs_t dllapi;
|
extern gamefuncs_t dllapi;
|
||||||
|
|
||||||
// Use this instead of ALLOC_STRING on constant strings
|
|
||||||
#define STRING(offset) (const char *)(globals->pStringBase + (int)offset)
|
|
||||||
|
|
||||||
// form fwgs-hlsdk
|
|
||||||
#if defined (CR_ARCH_X64)
|
|
||||||
static inline int MAKE_STRING (const char *val) {
|
|
||||||
long long ptrdiff = val - STRING (0);
|
|
||||||
|
|
||||||
if (ptrdiff > INT_MAX || ptrdiff < INT_MIN) {
|
|
||||||
return engfuncs.pfnAllocString (val);
|
|
||||||
}
|
|
||||||
return static_cast <int> (ptrdiff);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#define MAKE_STRING(str) ((uint64_t)(str) - (uint64_t)(STRING(0)))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ENGINE_STR(str) (const_cast <char *> (STRING (engfuncs.pfnAllocString (str))))
|
|
||||||
|
|
||||||
// Dot products for view cone checking
|
// Dot products for view cone checking
|
||||||
#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees
|
#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees
|
||||||
#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks
|
#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks
|
||||||
|
|
|
||||||
79
inc/analyze.h
Normal file
79
inc/analyze.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// next code is based on cs-ebot implemntation, devised by EfeDursun125
|
||||||
|
class GraphAnalyze : public Singleton <GraphAnalyze> {
|
||||||
|
public:
|
||||||
|
GraphAnalyze () = default;
|
||||||
|
~GraphAnalyze () = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_updateInterval {}; // time to update analyzer
|
||||||
|
|
||||||
|
bool m_basicsCreated {}; // basics waypoints were created?
|
||||||
|
bool m_isCrouch {}; // is node to be created as crouch ?
|
||||||
|
bool m_isAnalyzing {}; // we're in analyzing ?
|
||||||
|
bool m_isAnalyzed {}; // current waypoint is analyzed
|
||||||
|
bool m_expandedNodes[kMaxNodes] {}; // all nodes expanded ?
|
||||||
|
bool m_optimizedNodes[kMaxNodes] {}; // all nodes expanded ?
|
||||||
|
|
||||||
|
public:
|
||||||
|
// start analyzation process
|
||||||
|
void start ();
|
||||||
|
|
||||||
|
// update analyzation process
|
||||||
|
void update ();
|
||||||
|
|
||||||
|
// suspend aanalyzing
|
||||||
|
void suspend ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// flood with nodes
|
||||||
|
void flood (const Vector &pos, const Vector &next, float range);
|
||||||
|
|
||||||
|
// set update interval (keeps game from freezing)
|
||||||
|
void setUpdateInterval ();
|
||||||
|
|
||||||
|
// mark waypoints as goals
|
||||||
|
void markGoals ();
|
||||||
|
|
||||||
|
// terminate analyzation and save data
|
||||||
|
void finish ();
|
||||||
|
|
||||||
|
// optimize nodes a little
|
||||||
|
void optimize ();
|
||||||
|
|
||||||
|
// cleanup bad nodes
|
||||||
|
void cleanup ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// node should be created as crouch
|
||||||
|
bool isCrouch () const {
|
||||||
|
return m_isCrouch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is currently anaylyzing ?
|
||||||
|
bool isAnalyzing () const {
|
||||||
|
return m_isAnalyzing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// current graph is analyzed graph ?
|
||||||
|
bool isAnalyzed () const {
|
||||||
|
return m_isAnalyzed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark as optimized
|
||||||
|
void markOptimized (const int index) {
|
||||||
|
m_optimizedNodes[index] = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// explose global
|
||||||
|
CR_EXPOSE_GLOBAL_SINGLETON (GraphAnalyze, analyzer);
|
||||||
|
|
@ -216,7 +216,7 @@ public:
|
||||||
m_args.clear ();
|
m_args.clear ();
|
||||||
|
|
||||||
for (int i = 0; i < engfuncs.pfnCmd_Argc (); ++i) {
|
for (int i = 0; i < engfuncs.pfnCmd_Argc (); ++i) {
|
||||||
m_args.emplace (engfuncs.pfnCmd_Argv (i));
|
m_args.emplace (String (engfuncs.pfnCmd_Argv (i)).lowercase ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,5 +274,14 @@ template <typename ...Args> inline void BotControl::msg (const char *fmt, Args &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// graph heloer for sending message to correct channel
|
||||||
|
template <typename ...Args> inline void BotGraph::msg (const char *fmt, Args &&...args) {
|
||||||
|
if (m_silenceMessages) {
|
||||||
|
return; // no messages while analyzing (too much spam)
|
||||||
|
}
|
||||||
|
BotControl::instance ().msg (strings.format (conf.translate (fmt), cr::forward <Args> (args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// explose global
|
// explose global
|
||||||
CR_EXPOSE_GLOBAL_SINGLETON (BotControl, ctrl);
|
CR_EXPOSE_GLOBAL_SINGLETON (BotControl, ctrl);
|
||||||
|
|
|
||||||
17
inc/engine.h
17
inc/engine.h
|
|
@ -71,6 +71,7 @@ struct ConVarReg {
|
||||||
String info;
|
String info;
|
||||||
String init;
|
String init;
|
||||||
String regval;
|
String regval;
|
||||||
|
String name;
|
||||||
class ConVar *self;
|
class ConVar *self;
|
||||||
float initial, min, max;
|
float initial, min, max;
|
||||||
bool missing;
|
bool missing;
|
||||||
|
|
@ -420,6 +421,19 @@ public:
|
||||||
Game::instance ().addNewCvar (name, initval, info, bounded, min, max, type, regMissing, regVal, this);
|
Game::instance ().addNewCvar (name, initval, info, bounded, min, max, type, regMissing, regVal, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename U> constexpr U get () const {
|
||||||
|
if constexpr (cr::is_same <U, float>::value) {
|
||||||
|
return ptr->value;
|
||||||
|
}
|
||||||
|
else if constexpr (cr::is_same <U, bool>::value) {
|
||||||
|
return ptr->value > 0.0f;
|
||||||
|
}
|
||||||
|
else if constexpr (cr::is_same <U, int>::value) {
|
||||||
|
return static_cast <int> (ptr->value);
|
||||||
|
}
|
||||||
|
assert ("!Inavlid type requeted.");
|
||||||
|
}
|
||||||
|
|
||||||
bool bool_ () const {
|
bool bool_ () const {
|
||||||
return ptr->value > 0.0f;
|
return ptr->value > 0.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -447,6 +461,9 @@ public:
|
||||||
void set (const char *val) {
|
void set (const char *val) {
|
||||||
engfuncs.pfnCvar_DirectSet (ptr, val);
|
engfuncs.pfnCvar_DirectSet (ptr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// revet cvar to default value
|
||||||
|
void revert ();
|
||||||
};
|
};
|
||||||
|
|
||||||
class MessageWriter final {
|
class MessageWriter final {
|
||||||
|
|
|
||||||
236
inc/graph.h
236
inc/graph.h
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
constexpr int kMaxNodes = 4096; // max nodes per graph
|
||||||
|
constexpr int kMaxNodeLinks = 8; // max links for single node
|
||||||
|
|
||||||
// defines for nodes flags field (32 bits are available)
|
// defines for nodes flags field (32 bits are available)
|
||||||
CR_DECLARE_SCOPED_ENUM (NodeFlag,
|
CR_DECLARE_SCOPED_ENUM (NodeFlag,
|
||||||
Lift = cr::bit (1), // wait for lift to be down before approaching this node
|
Lift = cr::bit (1), // wait for lift to be down before approaching this node
|
||||||
|
|
@ -43,13 +46,6 @@ CR_DECLARE_SCOPED_ENUM (PathConnection,
|
||||||
Bidirectional
|
Bidirectional
|
||||||
)
|
)
|
||||||
|
|
||||||
// a* route state
|
|
||||||
CR_DECLARE_SCOPED_ENUM (RouteState,
|
|
||||||
Open = 0,
|
|
||||||
Closed,
|
|
||||||
New
|
|
||||||
)
|
|
||||||
|
|
||||||
// node edit states
|
// node edit states
|
||||||
CR_DECLARE_SCOPED_ENUM (GraphEdit,
|
CR_DECLARE_SCOPED_ENUM (GraphEdit,
|
||||||
On = cr::bit (1),
|
On = cr::bit (1),
|
||||||
|
|
@ -57,26 +53,6 @@ CR_DECLARE_SCOPED_ENUM (GraphEdit,
|
||||||
Auto = cr::bit (3)
|
Auto = cr::bit (3)
|
||||||
)
|
)
|
||||||
|
|
||||||
// storage header options
|
|
||||||
CR_DECLARE_SCOPED_ENUM (StorageOption,
|
|
||||||
Practice = cr::bit (0), // this is practice (experience) file
|
|
||||||
Matrix = cr::bit (1), // this is floyd warshal path & distance matrix
|
|
||||||
Vistable = cr::bit (2), // this is vistable data
|
|
||||||
Graph = cr::bit (3), // this is a node graph data
|
|
||||||
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
|
|
||||||
Exten = cr::bit (6) // this is additional flag indicates that there's extension info
|
|
||||||
)
|
|
||||||
|
|
||||||
// storage header versions
|
|
||||||
CR_DECLARE_SCOPED_ENUM (StorageVersion,
|
|
||||||
Graph = 2,
|
|
||||||
Practice = 1,
|
|
||||||
Vistable = 2,
|
|
||||||
Matrix = 1,
|
|
||||||
Podbot = 7
|
|
||||||
)
|
|
||||||
|
|
||||||
// lift usage states
|
// lift usage states
|
||||||
CR_DECLARE_SCOPED_ENUM (LiftState,
|
CR_DECLARE_SCOPED_ENUM (LiftState,
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
@ -103,29 +79,13 @@ CR_DECLARE_SCOPED_ENUM (NodeAddFlag,
|
||||||
Goal = 100
|
Goal = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// a* route
|
CR_DECLARE_SCOPED_ENUM (NotifySound,
|
||||||
struct Route {
|
Done = 0,
|
||||||
float g, f;
|
Change = 1,
|
||||||
int parent;
|
Added = 2
|
||||||
RouteState state;
|
)
|
||||||
};
|
|
||||||
|
|
||||||
// general stprage header information structure
|
#include <vistable.h>
|
||||||
struct StorageHeader {
|
|
||||||
int32_t magic;
|
|
||||||
int32_t version;
|
|
||||||
int32_t options;
|
|
||||||
int32_t length;
|
|
||||||
int32_t compressed;
|
|
||||||
int32_t uncompressed;
|
|
||||||
};
|
|
||||||
|
|
||||||
// extension header for graph information
|
|
||||||
struct ExtenHeader {
|
|
||||||
char author[32]; // original author of graph
|
|
||||||
int32_t mapSize; // bsp size for checksumming map consistency
|
|
||||||
char modified[32]; // by whom modified
|
|
||||||
};
|
|
||||||
|
|
||||||
// general waypoint header information structure
|
// general waypoint header information structure
|
||||||
struct PODGraphHeader {
|
struct PODGraphHeader {
|
||||||
|
|
@ -136,19 +96,6 @@ struct PODGraphHeader {
|
||||||
char author[32];
|
char author[32];
|
||||||
};
|
};
|
||||||
|
|
||||||
// floyd-warshall matrices
|
|
||||||
struct Matrix {
|
|
||||||
int16_t dist;
|
|
||||||
int16_t index;
|
|
||||||
};
|
|
||||||
|
|
||||||
// experience data hold in memory while playing
|
|
||||||
struct Practice {
|
|
||||||
int16_t damage[kGameTeamNum];
|
|
||||||
int16_t index[kGameTeamNum];
|
|
||||||
int16_t value[kGameTeamNum];
|
|
||||||
};
|
|
||||||
|
|
||||||
// defines linked waypoints
|
// defines linked waypoints
|
||||||
struct PathLink {
|
struct PathLink {
|
||||||
Vector velocity;
|
Vector velocity;
|
||||||
|
|
@ -157,11 +104,6 @@ struct PathLink {
|
||||||
int16_t index;
|
int16_t index;
|
||||||
};
|
};
|
||||||
|
|
||||||
// defines visibility count
|
|
||||||
struct PathVis {
|
|
||||||
uint16_t stand, crouch;
|
|
||||||
};
|
|
||||||
|
|
||||||
// define graph path structure for yapb
|
// define graph path structure for yapb
|
||||||
struct Path {
|
struct Path {
|
||||||
int32_t number, flags;
|
int32_t number, flags;
|
||||||
|
|
@ -183,74 +125,21 @@ struct PODPath {
|
||||||
PathVis vis;
|
PathVis vis;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this structure links nodes returned from pathfinder
|
// general stprage header information structure
|
||||||
class PathWalk final : public DenyCopying {
|
struct StorageHeader {
|
||||||
private:
|
int32_t magic;
|
||||||
size_t m_cursor {};
|
int32_t version;
|
||||||
size_t m_length {};
|
int32_t options;
|
||||||
|
int32_t length;
|
||||||
|
int32_t compressed;
|
||||||
|
int32_t uncompressed;
|
||||||
|
};
|
||||||
|
|
||||||
UniquePtr <int32_t[]> m_path {};
|
// extension header for graph information
|
||||||
|
struct ExtenHeader {
|
||||||
public:
|
char author[32]; // original author of graph
|
||||||
explicit PathWalk () = default;
|
int32_t mapSize; // bsp size for checksumming map consistency
|
||||||
~PathWalk () = default;
|
char modified[32]; // by whom modified
|
||||||
|
|
||||||
public:
|
|
||||||
int32_t &next () {
|
|
||||||
return at (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t &first () {
|
|
||||||
return at (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t &last () {
|
|
||||||
return at (length () - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t &at (size_t index) {
|
|
||||||
return m_path[m_cursor + index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void shift () {
|
|
||||||
++m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reverse () {
|
|
||||||
for (size_t i = 0; i < m_length / 2; ++i) {
|
|
||||||
cr::swap (m_path[i], m_path[m_length - 1 - i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t length () const {
|
|
||||||
if (m_cursor >= m_length) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return m_length - m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasNext () const {
|
|
||||||
return length () > m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty () const {
|
|
||||||
return !length ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void add (int32_t node) {
|
|
||||||
m_path[m_length++] = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear () {
|
|
||||||
m_cursor = 0;
|
|
||||||
m_length = 0;
|
|
||||||
|
|
||||||
m_path[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init (size_t length) {
|
|
||||||
m_path = cr::makeUnique <int32_t []> (length);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// graph operation class
|
// graph operation class
|
||||||
|
|
@ -260,7 +149,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_editFlags {};
|
int m_editFlags {};
|
||||||
int m_loadAttempts {};
|
|
||||||
int m_cacheNodeIndex {};
|
int m_cacheNodeIndex {};
|
||||||
int m_lastJumpNode {};
|
int m_lastJumpNode {};
|
||||||
int m_findWPIndex {};
|
int m_findWPIndex {};
|
||||||
|
|
@ -277,8 +165,8 @@ private:
|
||||||
bool m_endJumpPoint {};
|
bool m_endJumpPoint {};
|
||||||
bool m_jumpLearnNode {};
|
bool m_jumpLearnNode {};
|
||||||
bool m_hasChanged {};
|
bool m_hasChanged {};
|
||||||
bool m_needsVisRebuild {};
|
|
||||||
bool m_narrowChecked {};
|
bool m_narrowChecked {};
|
||||||
|
bool m_silenceMessages {};
|
||||||
|
|
||||||
Vector m_learnVelocity {};
|
Vector m_learnVelocity {};
|
||||||
Vector m_learnPosition {};
|
Vector m_learnPosition {};
|
||||||
|
|
@ -293,11 +181,8 @@ private:
|
||||||
IntArray m_rescuePoints {};
|
IntArray m_rescuePoints {};
|
||||||
IntArray m_visitedGoals {};
|
IntArray m_visitedGoals {};
|
||||||
|
|
||||||
SmallArray <Matrix> m_matrix {};
|
public:
|
||||||
SmallArray <Practice> m_practice {};
|
|
||||||
SmallArray <Path> m_paths {};
|
SmallArray <Path> m_paths {};
|
||||||
SmallArray <uint8_t> m_vistable {};
|
|
||||||
|
|
||||||
HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable;
|
HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable;
|
||||||
|
|
||||||
String m_graphAuthor {};
|
String m_graphAuthor {};
|
||||||
|
|
@ -315,13 +200,10 @@ public:
|
||||||
public:
|
public:
|
||||||
int getFacingIndex ();
|
int getFacingIndex ();
|
||||||
int getFarest (const Vector &origin, float maxDistance = 32.0);
|
int getFarest (const Vector &origin, float maxDistance = 32.0);
|
||||||
|
int getForAnalyzer (const Vector &origin, float maxDistance);
|
||||||
int getNearest (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1);
|
int getNearest (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1);
|
||||||
int getNearestNoBuckets (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1);
|
int getNearestNoBuckets (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1);
|
||||||
int getEditorNearest ();
|
int getEditorNearest ();
|
||||||
int getDangerIndex (int team, int start, int goal);
|
|
||||||
int getDangerValue (int team, int start, int goal);
|
|
||||||
int getDangerDamage (int team, int start, int goal);
|
|
||||||
int getPathDist (int srcIndex, int destIndex);
|
|
||||||
int clearConnections (int index);
|
int clearConnections (int index);
|
||||||
int getBspSize ();
|
int getBspSize ();
|
||||||
int locateBucket (const Vector &pos);
|
int locateBucket (const Vector &pos);
|
||||||
|
|
@ -329,30 +211,23 @@ public:
|
||||||
float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin);
|
float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin);
|
||||||
|
|
||||||
bool convertOldFormat ();
|
bool convertOldFormat ();
|
||||||
bool isVisible (int srcIndex, int destIndex);
|
|
||||||
bool isStandVisible (int srcIndex, int destIndex);
|
|
||||||
bool isDuckVisible (int srcIndex, int destIndex);
|
|
||||||
bool isConnected (int a, int b);
|
bool isConnected (int a, int b);
|
||||||
bool isConnected (int index);
|
bool isConnected (int index);
|
||||||
|
bool isNodeReacheableEx (const Vector &src, const Vector &destination, const float maxHeight);
|
||||||
bool isNodeReacheable (const Vector &src, const Vector &destination);
|
bool isNodeReacheable (const Vector &src, const Vector &destination);
|
||||||
|
bool isNodeReacheableWithJump (const Vector &src, const Vector &destination);
|
||||||
bool checkNodes (bool teleportPlayer);
|
bool checkNodes (bool teleportPlayer);
|
||||||
bool loadPathMatrix ();
|
|
||||||
bool isVisited (int index);
|
bool isVisited (int index);
|
||||||
|
bool isAnalyzed () const;
|
||||||
|
|
||||||
bool saveGraphData ();
|
bool saveGraphData ();
|
||||||
bool loadGraphData ();
|
bool loadGraphData ();
|
||||||
bool canDownload ();
|
bool canDownload ();
|
||||||
|
|
||||||
template <typename U> bool saveStorage (StringRef name, StorageOption options, StorageVersion version, const SmallArray <U> &data, ExtenHeader *exten);
|
|
||||||
template <typename U> bool loadStorage (StringRef name, StorageOption options, StorageVersion version, SmallArray <U> &data, ExtenHeader *exten, int32_t *outOptions);
|
|
||||||
template <typename ...Args> bool raiseLoadingError (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args);
|
|
||||||
|
|
||||||
void saveOldFormat ();
|
void saveOldFormat ();
|
||||||
void reset ();
|
void reset ();
|
||||||
void frame ();
|
void frame ();
|
||||||
void loadPractice ();
|
void populateNodes ();
|
||||||
void loadVisibility ();
|
|
||||||
void initNodesTypes ();
|
|
||||||
void initLightLevels ();
|
void initLightLevels ();
|
||||||
void initNarrowPlaces ();
|
void initNarrowPlaces ();
|
||||||
void addPath (int addIndex, int pathIndex, float distance);
|
void addPath (int addIndex, int pathIndex, float distance);
|
||||||
|
|
@ -360,16 +235,11 @@ public:
|
||||||
void erase (int target);
|
void erase (int target);
|
||||||
void toggleFlags (int toggleFlag);
|
void toggleFlags (int toggleFlag);
|
||||||
void setRadius (int index, float radius);
|
void setRadius (int index, float radius);
|
||||||
void rebuildVisibility ();
|
|
||||||
void pathCreate (char dir);
|
void pathCreate (char dir);
|
||||||
void erasePath ();
|
void erasePath ();
|
||||||
void cachePoint (int index);
|
void cachePoint (int index);
|
||||||
void calculatePathRadius (int index);
|
void calculatePathRadius (int index);
|
||||||
void savePractice ();
|
|
||||||
void saveVisibility ();
|
|
||||||
void addBasic ();
|
void addBasic ();
|
||||||
void eraseFromDisk ();
|
|
||||||
void savePathMatrix ();
|
|
||||||
void setSearchIndex (int index);
|
void setSearchIndex (int index);
|
||||||
void startLearnJump ();
|
void startLearnJump ();
|
||||||
void setVisited (int index);
|
void setVisited (int index);
|
||||||
|
|
@ -378,16 +248,14 @@ public:
|
||||||
void addToBucket (const Vector &pos, int index);
|
void addToBucket (const Vector &pos, int index);
|
||||||
void eraseFromBucket (const Vector &pos, int index);
|
void eraseFromBucket (const Vector &pos, int index);
|
||||||
void setBombOrigin (bool reset = false, const Vector &pos = nullptr);
|
void setBombOrigin (bool reset = false, const Vector &pos = nullptr);
|
||||||
void updateGlobalPractice ();
|
|
||||||
void unassignPath (int from, int to);
|
void unassignPath (int from, int to);
|
||||||
void setDangerValue (int team, int start, int goal, int value);
|
|
||||||
void setDangerDamage (int team, int start, int goal, int value);
|
|
||||||
void convertFromPOD (Path &path, const PODPath &pod);
|
void convertFromPOD (Path &path, const PODPath &pod);
|
||||||
void convertToPOD (const Path &path, PODPath &pod);
|
void convertToPOD (const Path &path, PODPath &pod);
|
||||||
void convertCampDirection (Path &path);
|
void convertCampDirection (Path &path);
|
||||||
void setAutoPathDistance (const float distance);
|
void setAutoPathDistance (const float distance);
|
||||||
void showStats ();
|
void showStats ();
|
||||||
void showFileInfo ();
|
void showFileInfo ();
|
||||||
|
void emitNotify (int32_t sound);
|
||||||
|
|
||||||
IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1);
|
IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1);
|
||||||
const IntArray &getNodesInBucket (const Vector &pos);
|
const IntArray &getNodesInBucket (const Vector &pos);
|
||||||
|
|
@ -463,6 +331,25 @@ public:
|
||||||
return m_editor;
|
return m_editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slicence all graph messages or not
|
||||||
|
void setMessageSilence (bool enable) {
|
||||||
|
m_silenceMessages = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set exten header from binary storage
|
||||||
|
void setExtenHeader (ExtenHeader *hdr) {
|
||||||
|
memcpy (&m_extenHeader, hdr, sizeof (ExtenHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set graph header from binary storage
|
||||||
|
void setGraphHeader (StorageHeader *hdr) {
|
||||||
|
memcpy (&m_graphHeader, hdr, sizeof (StorageHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// graph heloer for sending message to correct channel
|
||||||
|
template <typename ...Args> void msg (const char *fmt, Args &&...args);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Path *begin () {
|
Path *begin () {
|
||||||
return m_paths.begin ();
|
return m_paths.begin ();
|
||||||
|
|
@ -481,29 +368,8 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// we're need `bots`
|
|
||||||
#include <manager.h>
|
#include <manager.h>
|
||||||
|
#include <practice.h>
|
||||||
// helper for reporting load errors
|
|
||||||
template <typename ...Args> bool BotGraph::raiseLoadingError (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) {
|
|
||||||
auto result = strings.format (fmt, cr::forward <Args> (args)...);
|
|
||||||
|
|
||||||
// display error only for graph file
|
|
||||||
if (isGraph || isDebug) {
|
|
||||||
logger.error (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if graph reset paths
|
|
||||||
if (isGraph) {
|
|
||||||
bots.kickEveryone (true);
|
|
||||||
|
|
||||||
m_graphAuthor = result;
|
|
||||||
m_paths.clear ();
|
|
||||||
}
|
|
||||||
file.close ();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// explose global
|
// explose global
|
||||||
CR_EXPOSE_GLOBAL_SINGLETON (BotGraph, graph);
|
CR_EXPOSE_GLOBAL_SINGLETON (BotGraph, graph);
|
||||||
|
|
|
||||||
277
inc/planner.h
Normal file
277
inc/planner.h
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
const float kInfiniteHeuristic = 65535.0f; // max out heuristic value
|
||||||
|
|
||||||
|
// a* route state
|
||||||
|
CR_DECLARE_SCOPED_ENUM (RouteState,
|
||||||
|
Open = 0,
|
||||||
|
Closed,
|
||||||
|
New
|
||||||
|
)
|
||||||
|
|
||||||
|
// a * find path result
|
||||||
|
CR_DECLARE_SCOPED_ENUM (AStarResult,
|
||||||
|
Success = 0,
|
||||||
|
Failed,
|
||||||
|
InternalError,
|
||||||
|
)
|
||||||
|
|
||||||
|
// node added
|
||||||
|
using NodeAdderFn = Lambda <bool (int)>;
|
||||||
|
|
||||||
|
// route twin node
|
||||||
|
template <typename HT> struct RouteTwin final {
|
||||||
|
public:
|
||||||
|
int32_t index {};
|
||||||
|
HT heuristic {};
|
||||||
|
|
||||||
|
constexpr RouteTwin () = default;
|
||||||
|
~RouteTwin () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr RouteTwin (const int32_t &ri, const HT &rh) : index (ri), heuristic (rh) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr bool operator < (const RouteTwin &rhs) const {
|
||||||
|
return heuristic < rhs.heuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator > (const RouteTwin &rhs) const {
|
||||||
|
return heuristic > rhs.heuristic;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// bot heuristic functions for astar planner
|
||||||
|
class Heuristic final {
|
||||||
|
public:
|
||||||
|
using Func = Lambda <float (int, int, int)>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// least kills and number of nodes to goal for a team
|
||||||
|
static float gfunctionKillsDist (int team, int currentIndex, int parentIndex);;
|
||||||
|
|
||||||
|
// least kills and number of nodes to goal for a team (when with hostage)
|
||||||
|
static float gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex);;
|
||||||
|
|
||||||
|
// least kills to goal for a team
|
||||||
|
static float gfunctionKills (int team, int currentIndex, int);;
|
||||||
|
|
||||||
|
// least kills to goal for a team (when with hostage)
|
||||||
|
static auto gfunctionKillsCTWithHostage (int team, int currentIndex, int parentIndex) -> float;;
|
||||||
|
|
||||||
|
// least distance for a team
|
||||||
|
static float gfunctionPathDist (int, int currentIndex, int parentIndex);;
|
||||||
|
|
||||||
|
// least distance for a team (when with hostage)
|
||||||
|
static float gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex);;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// square distance heuristic
|
||||||
|
static float hfunctionPathDist (int index, int, int goalIndex);;
|
||||||
|
|
||||||
|
// square distance heuristic with hostages
|
||||||
|
static float hfunctionPathDistWithHostage (int index, int, int goalIndex);;
|
||||||
|
|
||||||
|
// none heuristic
|
||||||
|
static float hfunctionNone (int index, int, int goalIndex);;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A* algorithm for bots
|
||||||
|
class AStarAlgo final {
|
||||||
|
public:
|
||||||
|
using HeuristicFn = Heuristic::Func;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Route {
|
||||||
|
float g {}, f {};
|
||||||
|
int parent { kInvalidNodeIndex };
|
||||||
|
RouteState state { RouteState::New };
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
BinaryHeap <RouteTwin <float>> m_routeQue {};
|
||||||
|
Array <Route> m_routes {};
|
||||||
|
|
||||||
|
HeuristicFn m_hcalc;
|
||||||
|
HeuristicFn m_gcalc;
|
||||||
|
|
||||||
|
int m_length {};
|
||||||
|
|
||||||
|
Array <int> m_constructedPath;
|
||||||
|
Array <int> m_smoothedPath;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// cleares the currently built route
|
||||||
|
void clearRoute ();
|
||||||
|
|
||||||
|
// can the node can be skipped?
|
||||||
|
bool cantSkipNode (const int a, const int b);
|
||||||
|
|
||||||
|
// do a post-smoothing after a* finished constructing path
|
||||||
|
void postSmooth (NodeAdderFn onAddedNode);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AStarAlgo () = default;
|
||||||
|
~AStarAlgo () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// do the pathfinding
|
||||||
|
AStarResult find (int botTeam, int srcIndex, int destIndex, NodeAdderFn onAddedNode);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// initialize astar with valid path length
|
||||||
|
void init (const int length) {
|
||||||
|
m_length = length;
|
||||||
|
clearRoute ();
|
||||||
|
|
||||||
|
m_constructedPath.reserve (getMaxLength ());
|
||||||
|
m_smoothedPath.reserve (getMaxLength ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the g heuristic
|
||||||
|
void setG (HeuristicFn fn) {
|
||||||
|
m_gcalc = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the h heuristic
|
||||||
|
void setH (HeuristicFn fn) {
|
||||||
|
m_hcalc = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get route max length, route length should not be larger than half of map nodes
|
||||||
|
size_t getMaxLength () const {
|
||||||
|
return m_length / 2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// floyd-warshall shortest path algorithm
|
||||||
|
class FloydWarshallAlgo final {
|
||||||
|
private:
|
||||||
|
int m_length {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// floyd-warshall matrices
|
||||||
|
struct Matrix {
|
||||||
|
int16_t index { kInvalidNodeIndex };
|
||||||
|
int16_t dist { SHRT_MAX };
|
||||||
|
|
||||||
|
public:
|
||||||
|
Matrix () = default;
|
||||||
|
~Matrix () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Matrix (const int index, const int dist) : index (static_cast <int16_t> (index)), dist (static_cast <int16_t> (dist)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
SmallArray <Matrix> m_matrix {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
FloydWarshallAlgo () = default;
|
||||||
|
~FloydWarshallAlgo () = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// create floyd matrics
|
||||||
|
void rebuild ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// load matrices from disk
|
||||||
|
bool load ();
|
||||||
|
|
||||||
|
// flush matrices to disk, so we will not rebuild them on load same map
|
||||||
|
void save ();
|
||||||
|
|
||||||
|
// do the pathfinding
|
||||||
|
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// distance between two nodes with pathfinder
|
||||||
|
int dist (int srcIndex, int destIndex) {
|
||||||
|
return static_cast <int> ((m_matrix.data () + (srcIndex * m_length) + destIndex)->dist);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// dijkstra shortest path algorithm
|
||||||
|
class DijkstraAlgo final {
|
||||||
|
private:
|
||||||
|
using Route = Twin <int, int>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Array <int> m_distance {};
|
||||||
|
Array <int> m_parent {};
|
||||||
|
|
||||||
|
BinaryHeap <Route> m_queue {};
|
||||||
|
int m_length {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
DijkstraAlgo () = default;
|
||||||
|
~DijkstraAlgo () = default;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
// reset pathfinder state to defaults
|
||||||
|
void resetState ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// initialize dijkstra with valid path length
|
||||||
|
void init (const int length);
|
||||||
|
|
||||||
|
// do the pathfinding
|
||||||
|
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);
|
||||||
|
|
||||||
|
// distance between two nodes with pathfinder
|
||||||
|
int dist (int srcIndex, int destIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
// the bot path planner
|
||||||
|
class PathPlanner : public Singleton <PathPlanner> {
|
||||||
|
private:
|
||||||
|
UniquePtr <DijkstraAlgo> m_dijkstra;
|
||||||
|
UniquePtr <FloydWarshallAlgo> m_floyd;
|
||||||
|
UniquePtr <AStarAlgo > m_astar;
|
||||||
|
bool m_memoryLimitHit {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
PathPlanner ();
|
||||||
|
~PathPlanner () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// initialize all planners
|
||||||
|
void init ();
|
||||||
|
|
||||||
|
// has real path distance (instead of distance2d) ?
|
||||||
|
bool hasRealPathDistance () const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// get the dijkstra algo
|
||||||
|
decltype (auto) getDijkstra () {
|
||||||
|
return m_dijkstra.get ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the floyd algo
|
||||||
|
decltype (auto) getFloydWarshall () {
|
||||||
|
return m_floyd.get ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the floyd algo
|
||||||
|
decltype (auto) getAStar () {
|
||||||
|
return m_astar.get ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// do the pathfinding
|
||||||
|
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);
|
||||||
|
|
||||||
|
// distance between two nodes with pathfinder
|
||||||
|
int dist (int srcIndex, int destIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
CR_EXPOSE_GLOBAL_SINGLETON (PathPlanner, planner);
|
||||||
121
inc/practice.h
Normal file
121
inc/practice.h
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// limits for storing practice data
|
||||||
|
CR_DECLARE_SCOPED_ENUM_TYPE (PracticeLimit, int32_t,
|
||||||
|
Goal = 2040,
|
||||||
|
Damage = 2040
|
||||||
|
);
|
||||||
|
|
||||||
|
// storage for from, to, team
|
||||||
|
class DangerStorage final {
|
||||||
|
protected:
|
||||||
|
uint16_t data[3] {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr DangerStorage () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr DangerStorage (const int32_t &a, const int32_t &b, const int32_t &c) :
|
||||||
|
data { static_cast <uint16_t> (a), static_cast <uint16_t> (b), static_cast <uint16_t> (c) } {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr bool operator == (const DangerStorage &rhs) const {
|
||||||
|
return rhs.data[2] == data[2] && rhs.data[1] == data[1] && rhs.data[0] == data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator != (const DangerStorage &rhs) const {
|
||||||
|
return !operator == (rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// fnv1a for 3d vector hash
|
||||||
|
constexpr uint32_t hash () const {
|
||||||
|
constexpr uint32_t prime = 16777619u;
|
||||||
|
constexpr uint32_t seed = 2166136261u;
|
||||||
|
|
||||||
|
uint32_t hash = seed;
|
||||||
|
|
||||||
|
for (const auto &key : data) {
|
||||||
|
hash = (hash * prime) ^ key;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// define hash function for hash map
|
||||||
|
CR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
template <> struct Hash <DangerStorage> {
|
||||||
|
uint32_t operator () (const DangerStorage &key) const noexcept {
|
||||||
|
return key.hash ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CR_NAMESPACE_END
|
||||||
|
|
||||||
|
class BotPractice final : public Singleton <BotPractice> {
|
||||||
|
public:
|
||||||
|
// collected data
|
||||||
|
struct PracticeData {
|
||||||
|
int16_t damage {}, value {};
|
||||||
|
int16_t index { kInvalidNodeIndex };
|
||||||
|
};
|
||||||
|
|
||||||
|
// used to save-restore practice data
|
||||||
|
struct DangerSaveRestore {
|
||||||
|
DangerStorage danger {};
|
||||||
|
PracticeData data {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
DangerSaveRestore () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DangerSaveRestore (const DangerStorage &ds, const PracticeData &pd) : danger (ds), data (pd) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
HashMap <DangerStorage, PracticeData> m_data {};
|
||||||
|
int32_t m_teamHighestDamage[kGameTeamNum] {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
BotPractice () = default;
|
||||||
|
~BotPractice () = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline bool exists (int32_t team, int32_t start, int32_t goal) const {
|
||||||
|
return m_data.exists ({ start, goal, team });
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
int32_t getIndex (int32_t team, int32_t start, int32_t goal);
|
||||||
|
void setIndex (int32_t team, int32_t start, int32_t goal, int32_t value);
|
||||||
|
|
||||||
|
int32_t getValue (int32_t team, int32_t start, int32_t goal);
|
||||||
|
void setValue (int32_t team, int32_t start, int32_t goal, int32_t value);
|
||||||
|
|
||||||
|
int32_t getDamage (int32_t team, int32_t start, int32_t goal);
|
||||||
|
void setDamage (int32_t team, int32_t start, int32_t goal, int32_t value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void update ();
|
||||||
|
void load ();
|
||||||
|
void save ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
int32_t getHighestDamageForTeam (int32_t team) const {
|
||||||
|
return cr::max (1, m_teamHighestDamage[team]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHighestDamageForTeam (int32_t team, int32_t value) {
|
||||||
|
m_teamHighestDamage[team] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// explose global
|
||||||
|
CR_EXPOSE_GLOBAL_SINGLETON (BotPractice, practice);
|
||||||
103
inc/storage.h
Normal file
103
inc/storage.h
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// storage file magic (podbot)
|
||||||
|
constexpr char kPodbotMagic[8] = "PODWAY!";
|
||||||
|
|
||||||
|
constexpr int32_t kStorageMagic = 0x59415042; // storage magic for yapb-data files
|
||||||
|
constexpr int32_t kStorageMagicUB = 0x544f4255; //support also the fork format (merged back into yapb)
|
||||||
|
|
||||||
|
// storage header options
|
||||||
|
CR_DECLARE_SCOPED_ENUM (StorageOption,
|
||||||
|
Practice = cr::bit (0), // this is practice (experience) file
|
||||||
|
Matrix = cr::bit (1), // this is floyd warshal path & distance matrix
|
||||||
|
Vistable = cr::bit (2), // this is vistable data
|
||||||
|
Graph = cr::bit (3), // this is a node graph data
|
||||||
|
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
|
||||||
|
Exten = cr::bit (6), // this is additional flag indicates that there's extension info
|
||||||
|
Analyzed = cr::bit (7) // this graph has been analyzed
|
||||||
|
)
|
||||||
|
|
||||||
|
// storage header versions
|
||||||
|
CR_DECLARE_SCOPED_ENUM (StorageVersion,
|
||||||
|
Graph = 2,
|
||||||
|
Practice = 2,
|
||||||
|
Vistable = 3,
|
||||||
|
Matrix = 2,
|
||||||
|
Podbot = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
CR_DECLARE_SCOPED_ENUM_TYPE (BotFile, uint32_t,
|
||||||
|
Vistable = 0,
|
||||||
|
LogFile = 1,
|
||||||
|
Practice = 2,
|
||||||
|
Graph = 3,
|
||||||
|
Pathmatrix = 4,
|
||||||
|
PodbotPWF = 5,
|
||||||
|
EbotEWP = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
class BotStorage final : public Singleton <BotStorage> {
|
||||||
|
private:
|
||||||
|
struct SaveLoadData {
|
||||||
|
String name {};
|
||||||
|
int32_t option {};
|
||||||
|
int32_t version {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
SaveLoadData (StringRef name, int32_t option, int32_t version) : name (name), option (option), version (version) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_retries {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
BotStorage () = default;
|
||||||
|
~BotStorage () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// converts type to save/load options
|
||||||
|
template <typename U> SaveLoadData guessType ();
|
||||||
|
|
||||||
|
// loads the data and decompress ulz
|
||||||
|
template <typename U> bool load (SmallArray <U> &data, ExtenHeader *exten = nullptr, int32_t *outOptions = nullptr);
|
||||||
|
|
||||||
|
// saves the data and compress with ulz
|
||||||
|
template <typename U> bool save (const SmallArray <U> &data, ExtenHeader *exten = nullptr, int32_t passOptions = 0);
|
||||||
|
|
||||||
|
// report fatail error loading stuff
|
||||||
|
template <typename ...Args> bool error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args);
|
||||||
|
|
||||||
|
// builds the filename to requested filename
|
||||||
|
String buildPath (int32_t type, bool isMemoryLoad = false);
|
||||||
|
|
||||||
|
// converts storage option to stroage filename
|
||||||
|
int32_t storageToBotFile (int32_t options);
|
||||||
|
|
||||||
|
// remove all bot related files frorm disk
|
||||||
|
void unlinkFromDisk ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// loading the graph may attemp to recurse loading, with converting or download, reset retry counter
|
||||||
|
void resetRetries () {
|
||||||
|
m_retries = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS)
|
||||||
|
# define BOT_STORAGE_EXPLICIT_INSTANTIATIONS
|
||||||
|
# include "../src/storage.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef BOT_STORAGE_EXPLICIT_INSTANTIATIONS
|
||||||
|
|
||||||
|
// explose global
|
||||||
|
CR_EXPOSE_GLOBAL_SINGLETON (BotStorage, bstor);
|
||||||
|
|
||||||
|
|
@ -7,16 +7,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
CR_DECLARE_SCOPED_ENUM_TYPE (BotFile, uint32_t,
|
|
||||||
Vistable = 0,
|
|
||||||
LogFile = 1,
|
|
||||||
Practice = 2,
|
|
||||||
Graph = 3,
|
|
||||||
Pathmatrix = 4,
|
|
||||||
PodbotPWF = 5,
|
|
||||||
EbotEWP = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
class BotSupport final : public Singleton <BotSupport> {
|
class BotSupport final : public Singleton <BotSupport> {
|
||||||
private:
|
private:
|
||||||
bool m_needToSendWelcome {};
|
bool m_needToSendWelcome {};
|
||||||
|
|
@ -106,12 +96,6 @@ public:
|
||||||
// get the current date and time as string
|
// get the current date and time as string
|
||||||
String getCurrentDateTime ();
|
String getCurrentDateTime ();
|
||||||
|
|
||||||
// builds the filename to requested filename
|
|
||||||
String buildPath (int32_t type, bool isMemoryLoad = false);
|
|
||||||
|
|
||||||
// converts storage option to stroage filename
|
|
||||||
int32_t storageToBotFile (StorageOption options);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// re-show welcome after changelevel ?
|
// re-show welcome after changelevel ?
|
||||||
|
|
|
||||||
61
inc/vistable.h
Normal file
61
inc/vistable.h
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// limits for storing practice data
|
||||||
|
CR_DECLARE_SCOPED_ENUM_TYPE (VisIndex, int32_t,
|
||||||
|
None = 0,
|
||||||
|
Stand = 1,
|
||||||
|
Crouch = 2,
|
||||||
|
Any = Stand | Crouch
|
||||||
|
)
|
||||||
|
|
||||||
|
// defines visibility count
|
||||||
|
struct PathVis {
|
||||||
|
uint16_t stand, crouch;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GraphVistable final : public Singleton <GraphVistable> {
|
||||||
|
public:
|
||||||
|
using VisStorage = uint8_t;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SmallArray <VisStorage> m_vistable {};
|
||||||
|
bool m_rebuild {};
|
||||||
|
int m_length {};
|
||||||
|
|
||||||
|
int m_curIndex {};
|
||||||
|
int m_sliceIndex {};
|
||||||
|
|
||||||
|
float m_notifyMsgTimestamp {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
GraphVistable () = default;
|
||||||
|
~GraphVistable () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool visible (int srcIndex, int destIndex, VisIndex vis = VisIndex::Any);
|
||||||
|
|
||||||
|
void load ();
|
||||||
|
void save ();
|
||||||
|
void rebuild ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// triggers re-check for all the nodes
|
||||||
|
void startRebuild ();
|
||||||
|
|
||||||
|
// ready to use ?
|
||||||
|
bool isReady () const {
|
||||||
|
return !m_rebuild;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// explose global
|
||||||
|
CR_EXPOSE_GLOBAL_SINGLETON (GraphVistable, vistab);
|
||||||
|
|
||||||
103
inc/yapb.h
103
inc/yapb.h
|
|
@ -443,12 +443,6 @@ namespace TaskPri {
|
||||||
static constexpr float EscapeFromBomb { 100.0f };
|
static constexpr float EscapeFromBomb { 100.0f };
|
||||||
}
|
}
|
||||||
|
|
||||||
// storage file magic
|
|
||||||
constexpr char kPodbotMagic[8] = "PODWAY!";
|
|
||||||
|
|
||||||
constexpr int32_t kStorageMagic = 0x59415042; // storage magic for yapb-data files
|
|
||||||
constexpr int32_t kStorageMagicUB = 0x544f4255; //support also the fork format (merged back into yapb)
|
|
||||||
|
|
||||||
constexpr float kInfiniteDistance = 9999999.0f;
|
constexpr float kInfiniteDistance = 9999999.0f;
|
||||||
constexpr float kGrenadeCheckTime = 0.6f;
|
constexpr float kGrenadeCheckTime = 0.6f;
|
||||||
constexpr float kSprayDistance = 260.0f;
|
constexpr float kSprayDistance = 260.0f;
|
||||||
|
|
@ -456,10 +450,6 @@ constexpr float kDoubleSprayDistance = kSprayDistance * 2;
|
||||||
constexpr float kMaxChatterRepeatInterval = 99.0f;
|
constexpr float kMaxChatterRepeatInterval = 99.0f;
|
||||||
|
|
||||||
constexpr int kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
|
constexpr int kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
|
||||||
constexpr int kMaxNodeLinks = 8;
|
|
||||||
constexpr int kMaxPracticeDamageValue = 2040;
|
|
||||||
constexpr int kMaxPracticeGoalValue = 2040;
|
|
||||||
constexpr int kMaxNodes = 2048;
|
|
||||||
constexpr int kMaxWeapons = 32;
|
constexpr int kMaxWeapons = 32;
|
||||||
constexpr int kNumWeapons = 26;
|
constexpr int kNumWeapons = 26;
|
||||||
constexpr int kMaxCollideMoves = 3;
|
constexpr int kMaxCollideMoves = 3;
|
||||||
|
|
@ -598,11 +588,78 @@ struct ChatCollection {
|
||||||
// include bot graph stuff
|
// include bot graph stuff
|
||||||
#include <graph.h>
|
#include <graph.h>
|
||||||
|
|
||||||
|
// this structure links nodes returned from pathfinder
|
||||||
|
class PathWalk final : public DenyCopying {
|
||||||
|
private:
|
||||||
|
size_t m_cursor {};
|
||||||
|
size_t m_length {};
|
||||||
|
|
||||||
|
UniquePtr <int32_t[]> m_path {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PathWalk () = default;
|
||||||
|
~PathWalk () = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int32_t &next () {
|
||||||
|
return at (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t &first () {
|
||||||
|
return at (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t &last () {
|
||||||
|
return at (length () - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t &at (size_t index) {
|
||||||
|
return m_path[m_cursor + index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void shift () {
|
||||||
|
++m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reverse () {
|
||||||
|
for (size_t i = 0; i < m_length / 2; ++i) {
|
||||||
|
cr::swap (m_path[i], m_path[m_length - 1 - i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t length () const {
|
||||||
|
if (m_cursor >= m_length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_length - m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasNext () const {
|
||||||
|
return length () > m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty () const {
|
||||||
|
return !length ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add (int32_t node) {
|
||||||
|
m_path[m_length++] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear () {
|
||||||
|
m_cursor = 0;
|
||||||
|
m_length = 0;
|
||||||
|
|
||||||
|
m_path[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init (size_t length) {
|
||||||
|
m_path = cr::makeUnique <int32_t[]> (length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// main bot class
|
// main bot class
|
||||||
class Bot final {
|
class Bot final {
|
||||||
private:
|
|
||||||
using RouteTwin = Twin <int, float>;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
friend class BotManager;
|
friend class BotManager;
|
||||||
|
|
||||||
|
|
@ -632,6 +689,7 @@ private:
|
||||||
int m_tryOpenDoor {}; // attempt's to open the door
|
int m_tryOpenDoor {}; // attempt's to open the door
|
||||||
int m_liftState {}; // state of lift handling
|
int m_liftState {}; // state of lift handling
|
||||||
int m_radioSelect {}; // radio entry
|
int m_radioSelect {}; // radio entry
|
||||||
|
int m_lastPredictIndex {}; // last predicted index
|
||||||
|
|
||||||
float m_headedTime {};
|
float m_headedTime {};
|
||||||
float m_prevTime {}; // time previously checked movement speed
|
float m_prevTime {}; // time previously checked movement speed
|
||||||
|
|
@ -744,16 +802,14 @@ private:
|
||||||
|
|
||||||
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
|
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
|
||||||
Array <edict_t *> m_hostages {}; // pointer to used hostage entities
|
Array <edict_t *> m_hostages {}; // pointer to used hostage entities
|
||||||
Array <Route> m_routes {}; // pointer
|
|
||||||
Array <int32_t> m_nodeHistory {}; // history of selected goals
|
Array <int32_t> m_nodeHistory {}; // history of selected goals
|
||||||
|
|
||||||
BinaryHeap <RouteTwin> m_routeQue {};
|
|
||||||
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...)
|
||||||
FrustumPlane m_frustum[FrustumSide::Num] {};
|
FrustumPlane m_frustum[FrustumSide::Num] {};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int pickBestWeapon (int *vec, int count, int moneySave);
|
int pickBestWeapon (Array <int> &vec, int moneySave);
|
||||||
int getRandomCampDir ();
|
int getRandomCampDir ();
|
||||||
int findAimingNode (const Vector &to, int &pathLength);
|
int findAimingNode (const Vector &to, int &pathLength);
|
||||||
int findNearestNode ();
|
int findNearestNode ();
|
||||||
|
|
@ -863,9 +919,7 @@ private:
|
||||||
void updatePracticeDamage (edict_t *attacker, int damage);
|
void updatePracticeDamage (edict_t *attacker, int damage);
|
||||||
void findShortestPath (int srcIndex, int destIndex);
|
void findShortestPath (int srcIndex, int destIndex);
|
||||||
void calculateFrustum ();
|
void calculateFrustum ();
|
||||||
|
|
||||||
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
|
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
|
||||||
void clearRoute ();
|
|
||||||
void debugMsgInternal (const char *str);
|
void debugMsgInternal (const char *str);
|
||||||
void frame ();
|
void frame ();
|
||||||
void resetCollision ();
|
void resetCollision ();
|
||||||
|
|
@ -950,6 +1004,13 @@ private:
|
||||||
issueCommand ("drop");
|
issueCommand ("drop");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensures current node is ok
|
||||||
|
void ensureCurrentNodeIndex () {
|
||||||
|
if (m_currentNodeIndex == kInvalidNodeIndex) {
|
||||||
|
changeNodeIndex (findNearestNode ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
entvars_t *pev {};
|
entvars_t *pev {};
|
||||||
|
|
||||||
|
|
@ -1189,6 +1250,9 @@ public:
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
|
#include "planner.h"
|
||||||
|
#include "storage.h"
|
||||||
|
#include "analyze.h"
|
||||||
|
|
||||||
// very global convars
|
// very global convars
|
||||||
extern ConVar cv_jasonmode;
|
extern ConVar cv_jasonmode;
|
||||||
|
|
@ -1211,7 +1275,10 @@ extern ConVar cv_debug_goal;
|
||||||
extern ConVar cv_save_bots_names;
|
extern ConVar cv_save_bots_names;
|
||||||
extern ConVar cv_random_knife_attacks;
|
extern ConVar cv_random_knife_attacks;
|
||||||
extern ConVar cv_rotate_bots;
|
extern ConVar cv_rotate_bots;
|
||||||
|
extern ConVar cv_graph_url;
|
||||||
extern ConVar cv_graph_url_upload;
|
extern ConVar cv_graph_url_upload;
|
||||||
|
extern ConVar cv_graph_auto_save_count;
|
||||||
|
extern ConVar cv_graph_analyze_max_jump_height;
|
||||||
|
|
||||||
extern ConVar mp_freezetime;
|
extern ConVar mp_freezetime;
|
||||||
extern ConVar mp_roundtime;
|
extern ConVar mp_roundtime;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ project (
|
||||||
default_options: [
|
default_options: [
|
||||||
'buildtype=release',
|
'buildtype=release',
|
||||||
'b_ndebug=if-release',
|
'b_ndebug=if-release',
|
||||||
'cpp_std=c++14',
|
'cpp_std=c++17',
|
||||||
'warning_level=3',
|
'warning_level=3',
|
||||||
'werror=true',
|
'werror=true',
|
||||||
'backend=ninja',
|
'backend=ninja',
|
||||||
|
|
@ -222,6 +222,7 @@ add_global_link_arguments (ldflags, language: 'cpp')
|
||||||
|
|
||||||
# add the sources
|
# add the sources
|
||||||
sources = files(
|
sources = files(
|
||||||
|
'src/analyze.cpp',
|
||||||
'src/botlib.cpp',
|
'src/botlib.cpp',
|
||||||
'src/chatlib.cpp',
|
'src/chatlib.cpp',
|
||||||
'src/combat.cpp',
|
'src/combat.cpp',
|
||||||
|
|
@ -234,8 +235,12 @@ sources = files (
|
||||||
'src/module.cpp',
|
'src/module.cpp',
|
||||||
'src/message.cpp',
|
'src/message.cpp',
|
||||||
'src/navigate.cpp',
|
'src/navigate.cpp',
|
||||||
|
'src/planner.cpp',
|
||||||
|
'src/practice.cpp',
|
||||||
'src/sounds.cpp',
|
'src/sounds.cpp',
|
||||||
'src/support.cpp'
|
'src/storage.cpp',
|
||||||
|
'src/support.cpp',
|
||||||
|
'src/vistable.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
# add the include directories
|
# add the include directories
|
||||||
|
|
|
||||||
359
src/analyze.cpp
Normal file
359
src/analyze.cpp
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <yapb.h>
|
||||||
|
|
||||||
|
ConVar cv_graph_analyze_auto_start ("yb_graph_analyze_auto_start", "1", "Autostart analyzer if all other cases are failed.");
|
||||||
|
ConVar cv_graph_analyze_auto_save ("yb_graph_analyze_auto_save", "1", "Auto save results of analyzation to graph file. And re-add bots.");
|
||||||
|
ConVar cv_graph_analyze_distance ("yb_graph_analyze_distance", "64", "The minimum distance to keep nodes from each other.", true, 42.0f, 128.0f);
|
||||||
|
ConVar cv_graph_analyze_max_jump_height ("yb_graph_analyze_max_jump_height", "44", "Max jump height to test if next node will be unreachable.", true, 44.0f, 64.0f);
|
||||||
|
ConVar cv_graph_analyze_fps ("yb_graph_analyze_fps", "30.0", "The FPS at which analyzer process is running. This keeps game from freezing during analyzing.", false, 25.0f, 99.0f);
|
||||||
|
ConVar cv_graph_analyze_clean_paths_on_finish ("yb_graph_analyze_clean_paths_on_finish", "1", "Specifies if analyzer should clean the unnecessary paths upon finishing.");
|
||||||
|
ConVar cv_graph_analyze_optimize_nodes_on_finish ("yb_graph_analyze_optimize_nodes_on_finish", "1", "Specifies if analyzer should merge some near-placed nodes with much of connections together.");
|
||||||
|
ConVar cv_graph_analyze_mark_goals_on_finish ("yb_graph_analyze_mark_goals_on_finish", "1", "Specifies if analyzer should mark nodes as map goals automatically upon finish.");
|
||||||
|
|
||||||
|
void GraphAnalyze::start () {
|
||||||
|
// start analyzer in few seconds after level initialized
|
||||||
|
if (cv_graph_analyze_auto_start.bool_ ()) {
|
||||||
|
m_updateInterval = game.time () + 3.0f;
|
||||||
|
m_basicsCreated = false;
|
||||||
|
|
||||||
|
// set as we're analyzing
|
||||||
|
m_isAnalyzing = true;
|
||||||
|
|
||||||
|
// silence all graph messages
|
||||||
|
graph.setMessageSilence (true);
|
||||||
|
|
||||||
|
// set all nodes as not expanded
|
||||||
|
for (auto &expanded : m_expandedNodes) {
|
||||||
|
expanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set all nodes as not optimized
|
||||||
|
for (auto &optimized : m_optimizedNodes) {
|
||||||
|
optimized = false;
|
||||||
|
}
|
||||||
|
ctrl.msg ("Starting map analyzation.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_updateInterval = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::update () {
|
||||||
|
if (cr::fzero (m_updateInterval) || !m_isAnalyzing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_updateInterval >= game.time ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add basic nodes
|
||||||
|
if (!m_basicsCreated) {
|
||||||
|
graph.addBasic ();
|
||||||
|
m_basicsCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < graph.length (); ++i) {
|
||||||
|
if (m_updateInterval >= game.time ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graph.exists (i)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_expandedNodes[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_expandedNodes[i] = true;
|
||||||
|
setUpdateInterval ();
|
||||||
|
|
||||||
|
auto pos = graph[i].origin;
|
||||||
|
auto range = cv_graph_analyze_distance.float_ ();
|
||||||
|
|
||||||
|
for (int dir = 1; dir < kMaxNodeLinks; ++dir) {
|
||||||
|
switch (dir) {
|
||||||
|
case 1:
|
||||||
|
flood (pos, { pos.x + range, pos.y, pos.z }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
flood (pos, { pos.x - range, pos.y, pos.z }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
flood (pos, { pos.x, pos.y + range, pos.z }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
flood (pos, { pos.x, pos.y - range, pos.z }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
flood (pos, { pos.x + range, pos.y, pos.z + 128.0f }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
flood (pos, { pos.x - range, pos.y, pos.z + 128.0f }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
flood (pos, { pos.x, pos.y + range, pos.z + 128.0f }, range);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
flood (pos, { pos.x, pos.y - range, pos.z + 128.0f }, range);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish generation if no updates occurred recently
|
||||||
|
if (m_updateInterval + 2.0f < game.time ()) {
|
||||||
|
finish ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::suspend () {
|
||||||
|
m_updateInterval = 0.0f;
|
||||||
|
m_isAnalyzing = false;
|
||||||
|
m_isAnalyzed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::finish () {
|
||||||
|
// run optimization on finish
|
||||||
|
optimize ();
|
||||||
|
|
||||||
|
// mark goal nodes
|
||||||
|
markGoals ();
|
||||||
|
|
||||||
|
m_isAnalyzed = true;
|
||||||
|
m_isAnalyzing = false;
|
||||||
|
m_updateInterval = 0.0f;
|
||||||
|
|
||||||
|
// un-silence all graph messages
|
||||||
|
graph.setMessageSilence (false);
|
||||||
|
|
||||||
|
ctrl.msg ("Complete map analyzation.");
|
||||||
|
|
||||||
|
// autosave bots graph
|
||||||
|
if (cv_graph_analyze_auto_save.bool_ ()) {
|
||||||
|
if (!graph.saveGraphData ()) {
|
||||||
|
ctrl.msg ("Can't save analyzed graph. Internal error.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graph.loadGraphData ()) {
|
||||||
|
ctrl.msg ("Can't load analyzed graph. Internal error.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vistab.startRebuild ();
|
||||||
|
cv_quota.revert ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::optimize () {
|
||||||
|
if (graph.length () == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cv_graph_analyze_optimize_nodes_on_finish.bool_ ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cleanup ();
|
||||||
|
|
||||||
|
// clear the uselss connections
|
||||||
|
if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) {
|
||||||
|
for (auto i = 0; i < graph.length (); ++i) {
|
||||||
|
graph.clearConnections (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto smooth = [] (const Array <int> &nodes) {
|
||||||
|
Vector result;
|
||||||
|
|
||||||
|
for (const auto &node : nodes) {
|
||||||
|
result += graph[node].origin;
|
||||||
|
}
|
||||||
|
result /= kMaxNodeLinks;
|
||||||
|
result.z = graph[nodes.first ()].origin.z;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// set all nodes as not optimized
|
||||||
|
for (auto &optimized : m_optimizedNodes) {
|
||||||
|
optimized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < graph.length (); ++i) {
|
||||||
|
if (m_optimizedNodes[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &path = graph[i];
|
||||||
|
Array <int> indexes;
|
||||||
|
|
||||||
|
for (const auto &link : path.links) {
|
||||||
|
if (graph.exists (link.index) && !m_optimizedNodes[link.index] && cr::fequal (path.origin.z, graph[link.index].origin.z)) {
|
||||||
|
indexes.emplace (link.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're have max out node links
|
||||||
|
if (indexes.length () >= kMaxNodeLinks) {
|
||||||
|
const Vector &pos = smooth (indexes);
|
||||||
|
|
||||||
|
for (const auto &index : indexes) {
|
||||||
|
graph.erase (index);
|
||||||
|
}
|
||||||
|
graph.add (NodeAddFlag::Normal, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::cleanup () {
|
||||||
|
int connections = 0; // clean bad paths
|
||||||
|
|
||||||
|
for (auto i = 0; i < graph.length (); ++i) {
|
||||||
|
connections = 0;
|
||||||
|
|
||||||
|
for (const auto &link : graph[i].links) {
|
||||||
|
if (link.index != kInvalidNodeIndex) {
|
||||||
|
if (link.index > graph.length ()) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
++connections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no connections
|
||||||
|
if (!connections) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// path number differs
|
||||||
|
if (graph[i].number != i) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &link : graph[i].links) {
|
||||||
|
if (link.index != kInvalidNodeIndex) {
|
||||||
|
if (link.index >= graph.length () || link.index < -kInvalidNodeIndex) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
else if (link.index == i) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graph.isConnected (i)) {
|
||||||
|
graph.erase (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
||||||
|
range *= 0.75f;
|
||||||
|
|
||||||
|
TraceResult tr;
|
||||||
|
game.testHull (pos, { next.x, next.y, next.z + 19.0f }, TraceIgnore::Monsters, head_hull, nullptr, &tr);
|
||||||
|
|
||||||
|
// we're can't reach next point
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f) && !game.isShootableBreakable (tr.pHit)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're have something in around, skip
|
||||||
|
if (graph.exists (graph.getForAnalyzer (tr.vecEndPos, range))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.testHull (tr.vecEndPos, { tr.vecEndPos.x, tr.vecEndPos.y, tr.vecEndPos.z - 999.0f }, TraceIgnore::Monsters, head_hull, nullptr, &tr);
|
||||||
|
|
||||||
|
// ground is away for a break
|
||||||
|
if (cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Vector nextPos = { tr.vecEndPos.x, tr.vecEndPos.y, tr.vecEndPos.z + 19.0f };
|
||||||
|
|
||||||
|
const int endIndex = graph.getForAnalyzer (nextPos, range);
|
||||||
|
const int targetIndex = graph.getNearestNoBuckets (nextPos, 250.0f);
|
||||||
|
|
||||||
|
if (graph.exists (endIndex) || !graph.exists (targetIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto targetPos = graph[targetIndex].origin;
|
||||||
|
|
||||||
|
// re-check there's nothing nearby, and add something we're want
|
||||||
|
if (!graph.exists (graph.getNearestNoBuckets (nextPos, range))) {
|
||||||
|
m_isCrouch = false;
|
||||||
|
game.testLine (nextPos, { nextPos.x, nextPos.y, nextPos.z + 36.0f }, TraceIgnore::Monsters, nullptr, &tr);
|
||||||
|
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
m_isCrouch = true;
|
||||||
|
}
|
||||||
|
auto testPos = m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 18.0f } : nextPos;
|
||||||
|
|
||||||
|
if ((graph.isNodeReacheable (targetPos, testPos) && graph.isNodeReacheable (testPos, targetPos)) || (graph.isNodeReacheableWithJump (testPos, targetPos) && graph.isNodeReacheableWithJump (targetPos, testPos))) {
|
||||||
|
graph.add (NodeAddFlag::Normal, m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 9.0f } : nextPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::setUpdateInterval () {
|
||||||
|
const auto frametime (globals->frametime);
|
||||||
|
|
||||||
|
if ((cv_graph_analyze_fps.float_ () + frametime) <= 1.0f / frametime) {
|
||||||
|
m_updateInterval = game.time () + frametime * 0.06f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphAnalyze::markGoals () {
|
||||||
|
if (!cv_graph_analyze_mark_goals_on_finish.bool_ ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updateNodeFlags = [] (int type, const char *entity) {
|
||||||
|
game.searchEntities ("classname", entity, [&] (edict_t *ent) {
|
||||||
|
for (auto &path : graph) {
|
||||||
|
const auto &absOrigin = path.origin + Vector (1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
if (ent->v.absmin.x > absOrigin.x || ent->v.absmin.y > absOrigin.y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->v.absmax.x < absOrigin.x || ent->v.absmax.y < absOrigin.y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
path.flags |= type;
|
||||||
|
}
|
||||||
|
return EntitySearchResult::Continue;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (game.mapIs (MapFlags::Demolition)) {
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "func_bomb_target"); // bombspot zone
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "info_bomb_target"); // bombspot zone (same as above)
|
||||||
|
}
|
||||||
|
else if (game.mapIs (MapFlags::HostageRescue)) {
|
||||||
|
updateNodeFlags (NodeFlag::Rescue, "func_hostage_rescue"); // hostage rescue zone
|
||||||
|
updateNodeFlags (NodeFlag::Rescue, "info_hostage_rescue"); // hostage rescue zone (same as above)
|
||||||
|
updateNodeFlags (NodeFlag::Rescue, "info_player_start"); // then add ct spawnpoints
|
||||||
|
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "hostage_entity"); // hostage entities
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "monster_scientist"); // hostage entities (same as above)
|
||||||
|
}
|
||||||
|
else if (game.mapIs (MapFlags::Assassination)) {
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "func_vip_safetyzone"); // vip rescue (safety) zone
|
||||||
|
updateNodeFlags (NodeFlag::Goal, "func_escapezone"); // terrorist escape zone
|
||||||
|
}
|
||||||
|
}
|
||||||
184
src/botlib.cpp
184
src/botlib.cpp
|
|
@ -167,7 +167,7 @@ void Bot::avoidGrenades () {
|
||||||
float distance = pent->v.origin.distanceSq (pev->origin);
|
float distance = pent->v.origin.distanceSq (pev->origin);
|
||||||
float distanceMoved = pev->origin.distance (pent->v.origin + pent->v.velocity * m_frameInterval);
|
float distanceMoved = pev->origin.distance (pent->v.origin + pent->v.velocity * m_frameInterval);
|
||||||
|
|
||||||
if (distanceMoved < distance && distance < cr::square (500.0f)) {
|
if (distanceMoved < distance && distance < cr::sqrf (500.0f)) {
|
||||||
const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d ();
|
const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d ();
|
||||||
const auto &rightSide = pev->v_angle.right ().normalize2d ();
|
const auto &rightSide = pev->v_angle.right ().normalize2d ();
|
||||||
|
|
||||||
|
|
@ -242,12 +242,12 @@ void Bot::checkBreakablesAround () {
|
||||||
const auto lengthToObstacle = origin.distanceSq (pev->origin);
|
const auto lengthToObstacle = origin.distanceSq (pev->origin);
|
||||||
|
|
||||||
// too far, skip it
|
// too far, skip it
|
||||||
if (lengthToObstacle > cr::square (radius)) {
|
if (lengthToObstacle > cr::sqrf (radius)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// too close, skip it
|
// too close, skip it
|
||||||
if (lengthToObstacle < cr::square (100.0f)) {
|
if (lengthToObstacle < cr::sqrf (100.0f)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,7 +333,7 @@ void Bot::updatePickups () {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &intresting = bots.getIntrestingEntities ();
|
const auto &intresting = bots.getIntrestingEntities ();
|
||||||
const float radius = cr::square (cv_object_pickup_radius.float_ ());
|
const float radius = cr::sqrf (cv_object_pickup_radius.float_ ());
|
||||||
|
|
||||||
if (!game.isNullEntity (m_pickupItem)) {
|
if (!game.isNullEntity (m_pickupItem)) {
|
||||||
bool itemExists = false;
|
bool itemExists = false;
|
||||||
|
|
@ -573,7 +573,7 @@ void Bot::updatePickups () {
|
||||||
if (allowPickup) {
|
if (allowPickup) {
|
||||||
for (auto &client : util.getClients ()) {
|
for (auto &client : util.getClients ()) {
|
||||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::square (240.0f)) {
|
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||||
allowPickup = false;
|
allowPickup = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -620,7 +620,7 @@ void Bot::updatePickups () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pev->origin.distanceSq (origin) > cr::square (60.0f)) {
|
if (pev->origin.distanceSq (origin) > cr::sqrf (60.0f)) {
|
||||||
|
|
||||||
if (!graph.isNodeReacheable (pev->origin, origin)) {
|
if (!graph.isNodeReacheable (pev->origin, origin)) {
|
||||||
allowPickup = false;
|
allowPickup = false;
|
||||||
|
|
@ -731,7 +731,7 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
||||||
if (tr.flFraction < 1.0f) {
|
if (tr.flFraction < 1.0f) {
|
||||||
float length = tr.vecEndPos.distanceSq (src);
|
float length = tr.vecEndPos.distanceSq (src);
|
||||||
|
|
||||||
if (length > cr::square (1024.0f)) {
|
if (length > cr::sqrf (1024.0f)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -750,7 +750,7 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
||||||
if (link.index == kInvalidNodeIndex) {
|
if (link.index == kInvalidNodeIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto distance = static_cast <float> (graph.getPathDist (link.index, enemyIndex));
|
auto distance = static_cast <float> (planner.dist (link.index, enemyIndex));
|
||||||
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
|
|
@ -762,7 +762,7 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
||||||
return graph[lookAtWaypoint].origin;
|
return graph[lookAtWaypoint].origin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||||
|
|
||||||
if (graph.exists (dangerIndex)) {
|
if (graph.exists (dangerIndex)) {
|
||||||
return graph[dangerIndex].origin;
|
return graph[dangerIndex].origin;
|
||||||
|
|
@ -1110,9 +1110,12 @@ bool Bot::canReplaceWeapon () {
|
||||||
return isWeaponRestricted (m_currentWeapon);
|
return isWeaponRestricted (m_currentWeapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Bot::pickBestWeapon (int *vec, int count, int moneySave) {
|
int Bot::pickBestWeapon (Array <int> &vec, int moneySave) {
|
||||||
// this function picks best available weapon from random choice with money save
|
// this function picks best available weapon from random choice with money save
|
||||||
|
|
||||||
|
if (vec.length () < 2) {
|
||||||
|
return vec.first ();
|
||||||
|
}
|
||||||
bool needMoreRandomWeapon = (m_personality == Personality::Careful) || (rg.chance (25) && m_personality == Personality::Normal);
|
bool needMoreRandomWeapon = (m_personality == Personality::Careful) || (rg.chance (25) && m_personality == Personality::Normal);
|
||||||
|
|
||||||
if (needMoreRandomWeapon) {
|
if (needMoreRandomWeapon) {
|
||||||
|
|
@ -1121,14 +1124,11 @@ int Bot::pickBestWeapon (int *vec, int count, int moneySave) {
|
||||||
if (buyFactor < 1.0f) {
|
if (buyFactor < 1.0f) {
|
||||||
buyFactor = 1.0f;
|
buyFactor = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// swap array values
|
// swap array values
|
||||||
for (int *begin = vec, *end = vec + count - 1; begin < end; ++begin, --end) {
|
vec.reverse ();
|
||||||
cr::swap (*end, *begin);
|
|
||||||
}
|
|
||||||
return vec[static_cast <int> (static_cast <float> (count - 1) * cr::log10 (rg.get (1.0f, cr::powf (10.0f, buyFactor))) / buyFactor + 0.5f)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return vec[static_cast <int> (static_cast <float> (vec.length <int32_t> () - 1) * cr::log10 (rg.get (1.0f, cr::powf (10.0f, buyFactor))) / buyFactor + 0.5f)];
|
||||||
|
}
|
||||||
int chance = 95;
|
int chance = 95;
|
||||||
|
|
||||||
// high skilled bots almost always prefer best weapon
|
// high skilled bots almost always prefer best weapon
|
||||||
|
|
@ -1140,17 +1140,17 @@ int Bot::pickBestWeapon (int *vec, int count, int moneySave) {
|
||||||
chance = 75;
|
chance = 75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto &info = conf.getWeapons ();
|
const auto &tab = conf.getWeapons ();
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i) {
|
for (const auto &w : vec) {
|
||||||
auto &weapon = info[vec[i]];
|
const auto &weapon = tab[w];
|
||||||
|
|
||||||
// if wea have enough money for weapon buy it
|
// if wea have enough money for weapon buy it
|
||||||
if (weapon.price + moneySave < m_moneyAmount + rg.get (50, 200) && rg.chance (chance)) {
|
if (weapon.price + moneySave < m_moneyAmount + rg.get (50, 200) && rg.chance (chance)) {
|
||||||
return vec[i];
|
return w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vec[rg.get (0, count - 1)];
|
return vec.random ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::buyStuff () {
|
void Bot::buyStuff () {
|
||||||
|
|
@ -1163,8 +1163,8 @@ void Bot::buyStuff () {
|
||||||
m_nextBuyTime += rg.get (0.3f, 0.5f);
|
m_nextBuyTime += rg.get (0.3f, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = 0, weaponCount = 0;
|
int count = 0;
|
||||||
int choices[kNumWeapons] {};
|
Array <int32_t> choices;
|
||||||
|
|
||||||
// select the priority tab for this personality
|
// select the priority tab for this personality
|
||||||
const int *pref = conf.getWeaponPrefs (m_personality) + kNumWeapons;
|
const int *pref = conf.getWeaponPrefs (m_personality) + kNumWeapons;
|
||||||
|
|
@ -1312,23 +1312,14 @@ void Bot::buyStuff () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedWeapon->price <= (m_moneyAmount - moneySave)) {
|
if (selectedWeapon->price <= (m_moneyAmount - moneySave)) {
|
||||||
choices[weaponCount++] = *pref;
|
choices.emplace (*pref);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (count < kNumWeapons && weaponCount < 4);
|
} while (count < kNumWeapons && choices.length () < 4);
|
||||||
|
|
||||||
// found a desired weapon?
|
// found a desired weapon?
|
||||||
if (weaponCount > 0) {
|
if (!choices.empty ()) {
|
||||||
int chosenWeapon;
|
selectedWeapon = &tab[pickBestWeapon (choices, moneySave)];
|
||||||
|
|
||||||
// choose randomly from the best ones...
|
|
||||||
if (weaponCount > 1) {
|
|
||||||
chosenWeapon = pickBestWeapon (choices, weaponCount, moneySave);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chosenWeapon = choices[weaponCount - 1];
|
|
||||||
}
|
|
||||||
selectedWeapon = &tab[chosenWeapon];
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selectedWeapon = nullptr;
|
selectedWeapon = nullptr;
|
||||||
|
|
@ -1406,23 +1397,14 @@ void Bot::buyStuff () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedWeapon->price <= (m_moneyAmount - rg.get (100, 200))) {
|
if (selectedWeapon->price <= (m_moneyAmount - rg.get (100, 200))) {
|
||||||
choices[weaponCount++] = *pref;
|
choices.emplace (*pref);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (count < kNumWeapons && weaponCount < 4);
|
} while (count < kNumWeapons && choices.length () < 4);
|
||||||
|
|
||||||
// found a desired weapon?
|
// found a desired weapon?
|
||||||
if (weaponCount > 0) {
|
if (!choices.empty ()) {
|
||||||
int chosenWeapon;
|
selectedWeapon = &tab[pickBestWeapon (choices, rg.get (100, 200))];
|
||||||
|
|
||||||
// choose randomly from the best ones...
|
|
||||||
if (weaponCount > 1) {
|
|
||||||
chosenWeapon = pickBestWeapon (choices, weaponCount, rg.get (100, 200));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chosenWeapon = choices[weaponCount - 1];
|
|
||||||
}
|
|
||||||
selectedWeapon = &tab[chosenWeapon];
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selectedWeapon = nullptr;
|
selectedWeapon = nullptr;
|
||||||
|
|
@ -1708,14 +1690,17 @@ void Bot::setConditions () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty () && m_seeEnemyTime + 0.5f < game.time ()) {
|
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty () && m_seeEnemyTime + 0.5f < game.time ()) {
|
||||||
|
m_lastPredictIndex = kInvalidNodeIndex;
|
||||||
|
|
||||||
auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
|
auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
|
||||||
|
|
||||||
if (distanceToLastEnemySq < cr::square (1600.0f)) {
|
if (distanceToLastEnemySq < cr::sqrf (1600.0f)) {
|
||||||
auto pathLength = 0;
|
auto pathLength = 0;
|
||||||
auto nodeIndex = findAimingNode (m_lastEnemyOrigin, pathLength);
|
auto nodeIndex = findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||||
|
|
||||||
if (graph.exists (nodeIndex) && pathLength < cv_max_nodes_for_predict.int_ () && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::square (384.0f)) {
|
if (graph.exists (nodeIndex) && pathLength < cv_max_nodes_for_predict.int_ () && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::sqrf (384.0f)) {
|
||||||
m_aimFlags |= AimFlags::PredictPath;
|
m_aimFlags |= AimFlags::PredictPath;
|
||||||
|
m_lastPredictIndex = nodeIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2050,7 +2035,7 @@ bool Bot::isEnemyThreat () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enemy is near or facing us directly
|
// if enemy is near or facing us directly
|
||||||
if (m_enemy->v.origin.distanceSq (pev->origin) < cr::square (256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) {
|
if (m_enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -2071,13 +2056,13 @@ bool Bot::reactOnEnemy () {
|
||||||
}
|
}
|
||||||
int enemyIndex = graph.getNearest (m_enemy->v.origin);
|
int enemyIndex = graph.getNearest (m_enemy->v.origin);
|
||||||
|
|
||||||
auto lineDist = m_enemy->v.origin.distance (pev->origin);
|
auto lineDist = m_enemy->v.origin.distance2d (pev->origin);
|
||||||
auto pathDist = static_cast <float> (graph.getPathDist (ownIndex, enemyIndex));
|
auto pathDist = static_cast <float> (planner.dist (ownIndex, enemyIndex));
|
||||||
|
|
||||||
if (pathDist - lineDist > 112.0f || isOnLadder ()) {
|
if (pathDist - lineDist > (planner.hasRealPathDistance () ? 112.0f : 32.0f) || isOnLadder ()) {
|
||||||
m_isEnemyReachable = false;
|
m_isEnemyReachable = false;
|
||||||
}
|
}
|
||||||
else {
|
else if (vistab.visible (ownIndex, enemyIndex)) {
|
||||||
m_isEnemyReachable = true;
|
m_isEnemyReachable = true;
|
||||||
}
|
}
|
||||||
m_enemyReachableTimer = game.time () + 1.0f;
|
m_enemyReachableTimer = game.time () + 1.0f;
|
||||||
|
|
@ -2647,19 +2632,19 @@ void Bot::updateAimDir () {
|
||||||
else if (flags & AimFlags::PredictPath) {
|
else if (flags & AimFlags::PredictPath) {
|
||||||
bool changePredictedEnemy = true;
|
bool changePredictedEnemy = true;
|
||||||
|
|
||||||
if (m_timeNextTracking > game.time () && m_trackingEdict == m_lastEnemy) {
|
if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy) {
|
||||||
changePredictedEnemy = false;
|
changePredictedEnemy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changePredictedEnemy) {
|
if (changePredictedEnemy) {
|
||||||
auto pathLength = 0;
|
auto pathLength = 0;
|
||||||
auto aimNode = findAimingNode (m_lastEnemyOrigin, pathLength);
|
auto aimNode = graph.exists (m_lastPredictIndex) ? m_lastPredictIndex : findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||||
|
|
||||||
if (graph.exists (aimNode) && pathLength < cv_max_nodes_for_predict.int_ ()) {
|
if (graph.exists (aimNode) && pathLength < cv_max_nodes_for_predict.int_ ()) {
|
||||||
m_lookAt = graph[aimNode].origin;
|
m_lookAt = graph[aimNode].origin;
|
||||||
m_lookAtSafe = m_lookAt;
|
m_lookAtSafe = m_lookAt;
|
||||||
|
|
||||||
m_timeNextTracking = game.time () + 0.5f;
|
m_timeNextTracking = game.time () + 0.75f;
|
||||||
m_trackingEdict = m_lastEnemy;
|
m_trackingEdict = m_lastEnemy;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -2678,10 +2663,10 @@ void Bot::updateAimDir () {
|
||||||
else if (flags & AimFlags::Nav) {
|
else if (flags & AimFlags::Nav) {
|
||||||
m_lookAt = m_destOrigin;
|
m_lookAt = m_destOrigin;
|
||||||
|
|
||||||
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::square (512.0f)) {
|
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::sqrf (512.0f)) {
|
||||||
auto nextPathIndex = m_pathWalk.next ();
|
auto nextPathIndex = m_pathWalk.next ();
|
||||||
|
|
||||||
if (graph.isVisible (m_currentNodeIndex, nextPathIndex)) {
|
if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
|
||||||
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
|
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -2696,10 +2681,10 @@ void Bot::updateAimDir () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & NodeFlag::Ladder)) {
|
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & NodeFlag::Ladder)) {
|
||||||
auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||||
|
|
||||||
if (graph.exists (dangerIndex) && graph.isVisible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
if (graph.exists (dangerIndex) && vistab.visible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
||||||
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::square (160.0f)) {
|
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (160.0f)) {
|
||||||
m_lookAt = m_destOrigin;
|
m_lookAt = m_destOrigin;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -2730,7 +2715,7 @@ void Bot::checkDarkness () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not check every frame
|
// do not check every frame
|
||||||
if (m_checkDarkTime + 5.0f > game.time ()) {
|
if (m_checkDarkTime + 5.0f > game.time () || cr::fzero (m_path->light)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto skyColor = illum.getSkyColor ();
|
auto skyColor = illum.getSkyColor ();
|
||||||
|
|
@ -2798,7 +2783,7 @@ void Bot::frame () {
|
||||||
if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) {
|
if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) {
|
||||||
const Vector &bombPosition = graph.getBombOrigin ();
|
const Vector &bombPosition = graph.getBombOrigin ();
|
||||||
|
|
||||||
if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && pev->origin.distanceSq (bombPosition) < cr::square (1540.0f) && !isBombDefusing (bombPosition)) {
|
if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && pev->origin.distanceSq (bombPosition) < cr::sqrf (1540.0f) && !isBombDefusing (bombPosition)) {
|
||||||
m_itemIgnore = nullptr;
|
m_itemIgnore = nullptr;
|
||||||
m_itemCheckTime = game.time ();
|
m_itemCheckTime = game.time ();
|
||||||
|
|
||||||
|
|
@ -2823,7 +2808,7 @@ void Bot::frame () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear enemy far away
|
// clear enemy far away
|
||||||
if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::square (2048.0)) {
|
if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::sqrf (2048.0)) {
|
||||||
m_lastEnemy = nullptr;
|
m_lastEnemy = nullptr;
|
||||||
m_lastEnemyOrigin = nullptr;
|
m_lastEnemyOrigin = nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -3128,6 +3113,7 @@ void Bot::normal_ () {
|
||||||
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) {
|
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) {
|
||||||
pathSearchType = rg.chance (60) ? FindPath::Fast : FindPath::Optimal;
|
pathSearchType = rg.chance (60) ? FindPath::Fast : FindPath::Optimal;
|
||||||
}
|
}
|
||||||
|
ensureCurrentNodeIndex ();
|
||||||
|
|
||||||
// do pathfinding if it's not the current waypoint
|
// do pathfinding if it's not the current waypoint
|
||||||
if (destIndex != m_currentNodeIndex) {
|
if (destIndex != m_currentNodeIndex) {
|
||||||
|
|
@ -3347,6 +3333,8 @@ void Bot::seekCover_ () {
|
||||||
m_prevGoalIndex = destIndex;
|
m_prevGoalIndex = destIndex;
|
||||||
getTask ()->data = destIndex;
|
getTask ()->data = destIndex;
|
||||||
|
|
||||||
|
ensureCurrentNodeIndex ();
|
||||||
|
|
||||||
if (destIndex != m_currentNodeIndex) {
|
if (destIndex != m_currentNodeIndex) {
|
||||||
findPath (m_currentNodeIndex, destIndex, FindPath::Fast);
|
findPath (m_currentNodeIndex, destIndex, FindPath::Fast);
|
||||||
}
|
}
|
||||||
|
|
@ -3432,6 +3420,7 @@ void Bot::blind_ () {
|
||||||
}
|
}
|
||||||
else if (!hasActiveGoal ()) {
|
else if (!hasActiveGoal ()) {
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
|
ensureCurrentNodeIndex ();
|
||||||
|
|
||||||
m_prevGoalIndex = m_blindNodeIndex;
|
m_prevGoalIndex = m_blindNodeIndex;
|
||||||
getTask ()->data = m_blindNodeIndex;
|
getTask ()->data = m_blindNodeIndex;
|
||||||
|
|
@ -3652,6 +3641,7 @@ void Bot::moveToPos_ () {
|
||||||
m_prevGoalIndex = destIndex;
|
m_prevGoalIndex = destIndex;
|
||||||
getTask ()->data = destIndex;
|
getTask ()->data = destIndex;
|
||||||
|
|
||||||
|
ensureCurrentNodeIndex ();
|
||||||
findPath (m_currentNodeIndex, destIndex, m_pathType);
|
findPath (m_currentNodeIndex, destIndex, m_pathType);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -3918,7 +3908,7 @@ void Bot::followUser_ () {
|
||||||
m_reloadState = Reload::Primary;
|
m_reloadState = Reload::Primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_targetEntity->v.origin.distanceSq (pev->origin) > cr::square (130.0f)) {
|
if (m_targetEntity->v.origin.distanceSq (pev->origin) > cr::sqrf (130.0f)) {
|
||||||
m_followWaitTime = 0.0f;
|
m_followWaitTime = 0.0f;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -3998,7 +3988,7 @@ void Bot::throwExplosive_ () {
|
||||||
|
|
||||||
ignoreCollision ();
|
ignoreCollision ();
|
||||||
|
|
||||||
if (pev->origin.distanceSq (dest) < cr::square (450.0f)) {
|
if (pev->origin.distanceSq (dest) < cr::sqrf (450.0f)) {
|
||||||
// heck, I don't wanna blow up myself
|
// heck, I don't wanna blow up myself
|
||||||
m_grenadeCheckTime = game.time () + kGrenadeCheckTime * 2.0f;
|
m_grenadeCheckTime = game.time () + kGrenadeCheckTime * 2.0f;
|
||||||
|
|
||||||
|
|
@ -4065,7 +4055,7 @@ void Bot::throwFlashbang_ () {
|
||||||
|
|
||||||
ignoreCollision ();
|
ignoreCollision ();
|
||||||
|
|
||||||
if (pev->origin.distanceSq (dest) < cr::square (450.0f)) {
|
if (pev->origin.distanceSq (dest) < cr::sqrf (450.0f)) {
|
||||||
m_grenadeCheckTime = game.time () + kGrenadeCheckTime * 2.0f; // heck, I don't wanna blow up myself
|
m_grenadeCheckTime = game.time () + kGrenadeCheckTime * 2.0f; // heck, I don't wanna blow up myself
|
||||||
|
|
||||||
selectBestWeapon ();
|
selectBestWeapon ();
|
||||||
|
|
@ -4255,7 +4245,7 @@ void Bot::escapeFromBomb_ () {
|
||||||
completeTask (); // we're done
|
completeTask (); // we're done
|
||||||
|
|
||||||
// press duck button if we still have some enemies
|
// press duck button if we still have some enemies
|
||||||
if (numEnemiesNear (pev->origin, 2048.0f)) {
|
if (m_numEnemiesLeft > 0) {
|
||||||
m_campButtons = IN_DUCK;
|
m_campButtons = IN_DUCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4267,37 +4257,39 @@ void Bot::escapeFromBomb_ () {
|
||||||
else if (!hasActiveGoal ()) {
|
else if (!hasActiveGoal ()) {
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
|
|
||||||
int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = kInfiniteDistanceLong;
|
int bestIndex = kInvalidNodeIndex;
|
||||||
|
|
||||||
float safeRadius = rg.get (1513.0f, 2048.0f);
|
float safeRadius = rg.get (1513.0f, 2048.0f);
|
||||||
|
float closestDistance = kInfiniteDistance;
|
||||||
|
|
||||||
for (const auto &path : graph) {
|
for (const auto &path : graph) {
|
||||||
if (path.origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (path.number)) {
|
if (path.origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (path.number)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int pathDistance = graph.getPathDist (m_currentNodeIndex, path.number);
|
float distance = pev->origin.distance (path.origin);
|
||||||
|
|
||||||
if (minPathDistance > pathDistance) {
|
if (closestDistance > distance) {
|
||||||
minPathDistance = pathDistance;
|
closestDistance = distance;
|
||||||
lastSelectedGoal = path.number;
|
bestIndex = path.number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSelectedGoal < 0) {
|
if (bestIndex < 0) {
|
||||||
lastSelectedGoal = graph.getFarest (pev->origin, safeRadius);
|
bestIndex = graph.getFarest (pev->origin, safeRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
// still no luck?
|
// still no luck?
|
||||||
if (lastSelectedGoal < 0) {
|
if (bestIndex < 0) {
|
||||||
completeTask (); // we're done
|
completeTask (); // we're done
|
||||||
|
|
||||||
// we have no destination point, so just sit down and camp
|
// we have no destination point, so just sit down and camp
|
||||||
startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + 10.0f, true);
|
startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + 10.0f, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_prevGoalIndex = lastSelectedGoal;
|
m_prevGoalIndex = bestIndex;
|
||||||
getTask ()->data = lastSelectedGoal;
|
getTask ()->data = bestIndex;
|
||||||
|
|
||||||
findPath (m_currentNodeIndex, lastSelectedGoal, FindPath::Fast);
|
findPath (m_currentNodeIndex, bestIndex, FindPath::Fast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4511,7 +4503,7 @@ void Bot::pickupItem_ () {
|
||||||
// check if hostage is with a human teammate (hack)
|
// check if hostage is with a human teammate (hack)
|
||||||
for (auto &client : util.getClients ()) {
|
for (auto &client : util.getClients ()) {
|
||||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::square (240.0f)) {
|
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||||
return EntitySearchResult::Continue;
|
return EntitySearchResult::Continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4760,7 +4752,7 @@ void Bot::logic () {
|
||||||
|
|
||||||
// calculate 2 direction vectors, 1 without the up/down component
|
// calculate 2 direction vectors, 1 without the up/down component
|
||||||
const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
|
const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
|
||||||
const Vector &dirNormal = dirOld.normalize2d ();
|
const Vector &dirNormal = dirOld.normalize2d_apx ();
|
||||||
|
|
||||||
m_moveAngles = dirOld.angles ();
|
m_moveAngles = dirOld.angles ();
|
||||||
m_moveAngles.clampAngles ();
|
m_moveAngles.clampAngles ();
|
||||||
|
|
@ -4968,7 +4960,7 @@ bool Bot::hasHostage () {
|
||||||
if (!game.isNullEntity (hostage)) {
|
if (!game.isNullEntity (hostage)) {
|
||||||
|
|
||||||
// don't care about dead hostages
|
// don't care about dead hostages
|
||||||
if (hostage->v.health <= 0.0f || pev->origin.distanceSq (hostage->v.origin) > cr::square (600.0f)) {
|
if (hostage->v.health <= 0.0f || pev->origin.distanceSq (hostage->v.origin) > cr::sqrf (600.0f)) {
|
||||||
hostage = nullptr;
|
hostage = nullptr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -5114,10 +5106,13 @@ void Bot::updatePracticeValue (int damage) {
|
||||||
}
|
}
|
||||||
auto health = static_cast <int> (m_healthValue);
|
auto health = static_cast <int> (m_healthValue);
|
||||||
|
|
||||||
|
// max goal value
|
||||||
|
constexpr int maxGoalValue = PracticeLimit::Goal;
|
||||||
|
|
||||||
// only rate goal waypoint if bot died because of the damage
|
// only rate goal waypoint if bot died because of the damage
|
||||||
// FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons
|
// FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons
|
||||||
if (health - damage <= 0) {
|
if (health - damage <= 0) {
|
||||||
graph.setDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (graph.getDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - health / 20, -kMaxPracticeGoalValue, kMaxPracticeGoalValue));
|
practice.setValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (practice.getValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - health / 20, -maxGoalValue, maxGoalValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5134,6 +5129,7 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
|
||||||
if (attackerTeam == victimTeam) {
|
if (attackerTeam == victimTeam) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
constexpr int maxDamageValue = PracticeLimit::Damage;
|
||||||
|
|
||||||
// if these are bots also remember damage to rank destination of the bot
|
// if these are bots also remember damage to rank destination of the bot
|
||||||
m_goalValue -= static_cast <float> (damage);
|
m_goalValue -= static_cast <float> (damage);
|
||||||
|
|
@ -5155,18 +5151,18 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
|
||||||
|
|
||||||
if (m_healthValue > 20.0f) {
|
if (m_healthValue > 20.0f) {
|
||||||
if (victimTeam == Team::Terrorist || victimTeam == Team::CT) {
|
if (victimTeam == Team::Terrorist || victimTeam == Team::CT) {
|
||||||
graph.setDangerDamage (victimIndex, victimIndex, victimIndex, cr::clamp (graph.getDangerDamage (victimTeam, victimIndex, victimIndex), 0, kMaxPracticeDamageValue));
|
practice.setDamage (victimIndex, victimIndex, victimIndex, cr::clamp (practice.getDamage (victimTeam, victimIndex, victimIndex), 0, maxDamageValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto updateDamage = util.isFakeClient (attacker) ? 10 : 7;
|
auto updateDamage = util.isFakeClient (attacker) ? 10 : 7;
|
||||||
|
|
||||||
// store away the damage done
|
// store away the damage done
|
||||||
int damageValue = cr::clamp (graph.getDangerDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, kMaxPracticeDamageValue);
|
int damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, maxDamageValue);
|
||||||
|
|
||||||
if (damageValue > graph.getHighestDamageForTeam (m_team)) {
|
if (damageValue > graph.getHighestDamageForTeam (m_team)) {
|
||||||
graph.setHighestDamageForTeam (m_team, damageValue);
|
graph.setHighestDamageForTeam (m_team, damageValue);
|
||||||
}
|
}
|
||||||
graph.setDangerDamage (m_team, victimIndex, attackerIndex, damageValue);
|
practice.setDamage (m_team, victimIndex, attackerIndex, damageValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::pushChatMessage (int type, bool isTeamSay) {
|
void Bot::pushChatMessage (int type, bool isTeamSay) {
|
||||||
|
|
@ -5434,10 +5430,10 @@ bool Bot::isOutOfBombTimer () {
|
||||||
if (timeLeft > 13.0f) {
|
if (timeLeft > 13.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const Vector &bombOrigin = graph.getBombOrigin ();
|
const auto &bombOrigin = graph.getBombOrigin ();
|
||||||
|
|
||||||
// for terrorist, if timer is lower than 13 seconds, return true
|
// for terrorist, if timer is lower than 13 seconds, return true
|
||||||
if (timeLeft < 13.0f && m_team == Team::Terrorist && bombOrigin.distanceSq (pev->origin) < cr::square (964.0f)) {
|
if (timeLeft < 13.0f && m_team == Team::Terrorist && bombOrigin.distanceSq (pev->origin) < cr::sqrf (964.0f)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool hasTeammatesWithDefuserKit = false;
|
bool hasTeammatesWithDefuserKit = false;
|
||||||
|
|
@ -5445,7 +5441,7 @@ bool Bot::isOutOfBombTimer () {
|
||||||
// check if our teammates has defusal kit
|
// check if our teammates has defusal kit
|
||||||
for (const auto &bot : bots) {
|
for (const auto &bot : bots) {
|
||||||
// search players with defuse kit
|
// search players with defuse kit
|
||||||
if (bot.get () != this && bot->m_team == Team::CT && bot->m_hasDefuser && bombOrigin.distanceSq (bot->pev->origin) < cr::square (512.0f)) {
|
if (bot.get () != this && bot->m_team == Team::CT && bot->m_hasDefuser && bombOrigin.distanceSq (bot->pev->origin) < cr::sqrf (512.0f)) {
|
||||||
hasTeammatesWithDefuserKit = true;
|
hasTeammatesWithDefuserKit = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -5474,7 +5470,7 @@ void Bot::updateHearing () {
|
||||||
|
|
||||||
// loop through all enemy clients to check for hearable stuff
|
// loop through all enemy clients to check for hearable stuff
|
||||||
for (int i = 0; i < game.maxClients (); ++i) {
|
for (int i = 0; i < game.maxClients (); ++i) {
|
||||||
const Client &client = util.getClient (i);
|
const auto &client = util.getClient (i);
|
||||||
|
|
||||||
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.noise.last < game.time ()) {
|
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.noise.last < game.time ()) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -5615,7 +5611,7 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool defusingInProgress = false;
|
bool defusingInProgress = false;
|
||||||
constexpr auto distanceToBomb = cr::square (165.0f);
|
constexpr auto distanceToBomb = cr::sqrf (165.0f);
|
||||||
|
|
||||||
for (const auto &client : util.getClients ()) {
|
for (const auto &client : util.getClients ()) {
|
||||||
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) {
|
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ int Bot::numFriendsNear (const Vector &origin, float radius) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.origin.distanceSq (origin) < cr::square (radius)) {
|
if (client.origin.distanceSq (origin) < cr::sqrf (radius)) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ int Bot::numEnemiesNear (const Vector &origin, float radius) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.origin.distanceSq (origin) < cr::square (radius)) {
|
if (client.origin.distanceSq (origin) < cr::sqrf (radius)) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +231,7 @@ bool Bot::lookupEnemies () {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
edict_t *player, *newEnemy = nullptr;
|
edict_t *player, *newEnemy = nullptr;
|
||||||
float nearestDistance = cr::square (m_viewDistance);
|
float nearestDistance = cr::sqrf (m_viewDistance);
|
||||||
|
|
||||||
// clear suspected flag
|
// clear suspected flag
|
||||||
if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) {
|
if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) {
|
||||||
|
|
@ -300,7 +300,7 @@ bool Bot::lookupEnemies () {
|
||||||
|
|
||||||
// extra skill player can see thru smoke... if beeing attacked
|
// extra skill player can see thru smoke... if beeing attacked
|
||||||
if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && m_difficulty == Difficulty::Expert) {
|
if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && m_difficulty == Difficulty::Expert) {
|
||||||
nearestDistance = cr::square (m_maxViewDistance);
|
nearestDistance = cr::sqrf (m_maxViewDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if bot can see the player...
|
// see if bot can see the player...
|
||||||
|
|
@ -645,7 +645,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
|
||||||
game.testLine (dest, source, TraceIgnore::Monsters, ent (), &tr);
|
game.testLine (dest, source, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
||||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
if (tr.vecEndPos.distanceSq (dest) > cr::square (800.0f)) {
|
if (tr.vecEndPos.distanceSq (dest) > cr::sqrf (800.0f)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -655,7 +655,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
|
||||||
obstacleDistance = tr.vecEndPos.distanceSq (source);
|
obstacleDistance = tr.vecEndPos.distanceSq (source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const float distance = cr::square (75.0f);
|
const float distance = cr::sqrf (75.0f);
|
||||||
|
|
||||||
if (obstacleDistance > 0.0f) {
|
if (obstacleDistance > 0.0f) {
|
||||||
while (power > 0) {
|
while (power > 0) {
|
||||||
|
|
@ -679,7 +679,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vector &source = getEyesPos ();
|
const Vector &source = getEyesPos ();
|
||||||
const Vector &direction = (dest - source).normalize (); // 1 unit long
|
const Vector &direction = (dest - source).normalize_apx (); // 1 unit long
|
||||||
|
|
||||||
int thikness = 0;
|
int thikness = 0;
|
||||||
int numHits = 0;
|
int numHits = 0;
|
||||||
|
|
@ -703,7 +703,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numHits < 3 && thikness < 98) {
|
if (numHits < 3 && thikness < 98) {
|
||||||
if (dest.distanceSq (point) < cr::square (112.0f)) {
|
if (dest.distanceSq (point) < cr::sqrf (112.0f)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -724,7 +724,7 @@ bool Bot::isPenetrableObstacle3 (const Vector &dest) {
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
|
|
||||||
Vector source = getEyesPos ();
|
Vector source = getEyesPos ();
|
||||||
const auto &dir = (dest - source).normalize () * 8.0f;
|
const auto &dir = (dest - source).normalize_apx () * 8.0f;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
game.testLine (source, dest, TraceIgnore::Monsters, ent (), &tr);
|
game.testLine (source, dest, TraceIgnore::Monsters, ent (), &tr);
|
||||||
|
|
@ -773,8 +773,8 @@ bool Bot::needToPauseFiring (float distance) {
|
||||||
else if (distance < kDoubleSprayDistance) {
|
else if (distance < kDoubleSprayDistance) {
|
||||||
offset = 2.75f;
|
offset = 2.75f;
|
||||||
}
|
}
|
||||||
const float xPunch = cr::square (cr::deg2rad (pev->punchangle.x));
|
const float xPunch = cr::sqrf (cr::deg2rad (pev->punchangle.x));
|
||||||
const float yPunch = cr::square (cr::deg2rad (pev->punchangle.y));
|
const float yPunch = cr::sqrf (cr::deg2rad (pev->punchangle.y));
|
||||||
|
|
||||||
const float interval = m_frameInterval;
|
const float interval = m_frameInterval;
|
||||||
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;
|
||||||
|
|
@ -1277,7 +1277,7 @@ void Bot::attackMovement () {
|
||||||
else if ((distance > kDoubleSprayDistance && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
|
else if ((distance > kDoubleSprayDistance && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
|
||||||
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
||||||
|
|
||||||
if (graph.isVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
|
if (vistab.visible (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch) && vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) {
|
||||||
m_duckTime = game.time () + m_frameInterval * 2.0f;
|
m_duckTime = game.time () + m_frameInterval * 2.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1657,7 +1657,7 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.ent->v.origin.distanceSq (location) < cr::square (radius)) {
|
if (client.ent->v.origin.distanceSq (location) < cr::sqrf (radius)) {
|
||||||
// don't target our teammates...
|
// don't target our teammates...
|
||||||
if (client.team == m_team) {
|
if (client.team == m_team) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,21 @@ int BotControl::cmdKickBot () {
|
||||||
}
|
}
|
||||||
|
|
||||||
int BotControl::cmdKickBots () {
|
int BotControl::cmdKickBots () {
|
||||||
enum args { alias = 1, instant };
|
enum args { alias = 1, instant, team };
|
||||||
|
|
||||||
// check if we're need to remove bots instantly
|
// check if we're need to remove bots instantly
|
||||||
auto kickInstant = strValue (instant) == "instant";
|
auto kickInstant = strValue (instant) == "instant";
|
||||||
|
|
||||||
// kick the bots
|
// if team is specified, kick from specified tram
|
||||||
|
if (strValue (alias).endsWith ("_ct") || intValue (team) == 2 || strValue (team) == "ct") {
|
||||||
|
bots.kickFromTeam (Team::CT, true);
|
||||||
|
}
|
||||||
|
else if (strValue (alias).endsWith ("_t") || intValue (team) == 1 || strValue (team) == "t") {
|
||||||
|
bots.kickFromTeam (Team::Terrorist, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
bots.kickEveryone (kickInstant);
|
bots.kickEveryone (kickInstant);
|
||||||
|
}
|
||||||
return BotCommandResult::Handled;
|
return BotCommandResult::Handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -529,7 +536,7 @@ int BotControl::cmdNodeErase () {
|
||||||
|
|
||||||
// prevent accidents when graph are deleted unintentionally
|
// prevent accidents when graph are deleted unintentionally
|
||||||
if (strValue (iamsure) == "iamsure") {
|
if (strValue (iamsure) == "iamsure") {
|
||||||
graph.eraseFromDisk ();
|
bstor.unlinkFromDisk ();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg ("Please, append \"iamsure\" as parameter to get graph erased from the disk.");
|
msg ("Please, append \"iamsure\" as parameter to get graph erased from the disk.");
|
||||||
|
|
@ -761,10 +768,16 @@ int BotControl::cmdNodeReleaseEditor () {
|
||||||
int BotControl::cmdNodeUpload () {
|
int BotControl::cmdNodeUpload () {
|
||||||
enum args { graph_cmd = 1, cmd };
|
enum args { graph_cmd = 1, cmd };
|
||||||
|
|
||||||
|
// do not allow to upload analyzed graphs
|
||||||
|
if (graph.isAnalyzed ()) {
|
||||||
|
msg ("Sorry, unable to upload graph that was generated automatically.");
|
||||||
|
return BotCommandResult::Handled;
|
||||||
|
}
|
||||||
|
|
||||||
// do not allow to upload bad graph
|
// do not allow to upload bad graph
|
||||||
if (!graph.checkNodes (false)) {
|
if (!graph.checkNodes (false)) {
|
||||||
msg ("Sorry, unable to upload graph file that contains errors. Please type \"wp check\" to verify graph consistency.");
|
msg ("Sorry, unable to upload graph file that contains errors. Please type \"wp check\" to verify graph consistency.");
|
||||||
return BotCommandResult::BadFormat;
|
return BotCommandResult::Handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg ("\n");
|
msg ("\n");
|
||||||
|
|
@ -776,7 +789,7 @@ int BotControl::cmdNodeUpload () {
|
||||||
String uploadUrl = cv_graph_url_upload.str ();
|
String uploadUrl = cv_graph_url_upload.str ();
|
||||||
|
|
||||||
// try to upload the file
|
// try to upload the file
|
||||||
if (http.uploadFile (uploadUrl, util.buildPath (BotFile::Graph))) {
|
if (http.uploadFile (uploadUrl, bstor.buildPath (BotFile::Graph))) {
|
||||||
msg ("Graph file was successfully validated and uploaded to the YaPB Graph DB (%s).", product.download);
|
msg ("Graph file was successfully validated and uploaded to the YaPB Graph DB (%s).", product.download);
|
||||||
msg ("It will be available for download for all YaPB users in a few minutes.");
|
msg ("It will be available for download for all YaPB users in a few minutes.");
|
||||||
msg ("\n");
|
msg ("\n");
|
||||||
|
|
@ -1717,7 +1730,7 @@ bool BotControl::executeCommands () {
|
||||||
if (m_args.empty ()) {
|
if (m_args.empty ()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto &prefix = m_args[0];
|
const auto &prefix = m_args.first ();
|
||||||
|
|
||||||
// no handling if not for us
|
// no handling if not for us
|
||||||
if (prefix != product.cmdPri && prefix != product.cmdSec) {
|
if (prefix != product.cmdPri && prefix != product.cmdSec) {
|
||||||
|
|
@ -2055,7 +2068,7 @@ BotControl::BotControl () {
|
||||||
|
|
||||||
m_cmds.emplace ("add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct", "add [difficulty] [personality] [team] [model] [name]", "Adding specific bot into the game.", &BotControl::cmdAddBot);
|
m_cmds.emplace ("add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct", "add [difficulty] [personality] [team] [model] [name]", "Adding specific bot into the game.", &BotControl::cmdAddBot);
|
||||||
m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot);
|
m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot);
|
||||||
m_cmds.emplace ("removebots/kickbots/kickall", "removebots [instant]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
|
m_cmds.emplace ("removebots/kickbots/kickall/kickall_ct/kickall_t", "removebots [instant] [team]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
|
||||||
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
|
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
|
||||||
m_cmds.emplace ("fill/fillserver", "fill [team] [count] [difficulty] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
|
m_cmds.emplace ("fill/fillserver", "fill [team] [count] [difficulty] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
|
||||||
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
|
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,7 @@ void Game::precache () {
|
||||||
|
|
||||||
m_engineWrap.precacheSound ("weapons/xbow_hit1.wav"); // waypoint add
|
m_engineWrap.precacheSound ("weapons/xbow_hit1.wav"); // waypoint add
|
||||||
m_engineWrap.precacheSound ("weapons/mine_activate.wav"); // waypoint delete
|
m_engineWrap.precacheSound ("weapons/mine_activate.wav"); // waypoint delete
|
||||||
m_engineWrap.precacheSound ("common/wpn_hudoff.wav"); // path add/delete start
|
|
||||||
m_engineWrap.precacheSound ("common/wpn_hudon.wav"); // path add/delete done
|
m_engineWrap.precacheSound ("common/wpn_hudon.wav"); // path add/delete done
|
||||||
m_engineWrap.precacheSound ("common/wpn_moveselect.wav"); // path add/delete cancel
|
|
||||||
m_engineWrap.precacheSound ("common/wpn_denyselect.wav"); // path add/delete error
|
|
||||||
|
|
||||||
m_mapFlags = 0; // reset map type as worldspawn is the first entity spawned
|
m_mapFlags = 0; // reset map type as worldspawn is the first entity spawned
|
||||||
|
|
||||||
|
|
@ -68,27 +65,27 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
||||||
// update worldmodel
|
// update worldmodel
|
||||||
illum.resetWorldModel ();
|
illum.resetWorldModel ();
|
||||||
|
|
||||||
// do level initialization stuff here...
|
|
||||||
graph.loadGraphData ();
|
|
||||||
|
|
||||||
// execute main config
|
// execute main config
|
||||||
conf.loadMainConfig ();
|
conf.loadMainConfig ();
|
||||||
|
|
||||||
// load map-specific config
|
// load map-specific config
|
||||||
conf.loadMapSpecificConfig ();
|
conf.loadMapSpecificConfig ();
|
||||||
|
|
||||||
|
// do level initialization stuff here...
|
||||||
|
graph.loadGraphData ();
|
||||||
|
|
||||||
// initialize quota management
|
// initialize quota management
|
||||||
bots.initQuota ();
|
bots.initQuota ();
|
||||||
|
|
||||||
// rebuild vistable if needed
|
|
||||||
graph.rebuildVisibility ();
|
|
||||||
|
|
||||||
// install the sendto hook to fake queries
|
// install the sendto hook to fake queries
|
||||||
util.installSendTo ();
|
util.installSendTo ();
|
||||||
|
|
||||||
// flush any print queue
|
// flush any print queue
|
||||||
ctrl.resetFlushTimestamp ();
|
ctrl.resetFlushTimestamp ();
|
||||||
|
|
||||||
|
// suspend any analyzer tasks
|
||||||
|
analyzer.suspend ();
|
||||||
|
|
||||||
// go thru the all entities on map, and do whatever we're want
|
// go thru the all entities on map, and do whatever we're want
|
||||||
for (int i = 0; i < max; ++i) {
|
for (int i = 0; i < max; ++i) {
|
||||||
auto ent = entities + i;
|
auto ent = entities + i;
|
||||||
|
|
@ -413,11 +410,11 @@ void Game::playSound (edict_t *ent, const char *sound) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::setPlayerStartDrawModels () {
|
void Game::setPlayerStartDrawModels () {
|
||||||
HashMap <String, String> models;
|
static HashMap <String, String> models {
|
||||||
|
{"info_player_start", "models/player/urban/urban.mdl"},
|
||||||
models["info_player_start"] = "models/player/urban/urban.mdl";
|
{"info_player_deathmatch", "models/player/terror/terror.mdl"},
|
||||||
models["info_player_deathmatch"] = "models/player/terror/terror.mdl";
|
{"info_vip_start", "models/player/vip/vip.mdl"}
|
||||||
models["info_vip_start"] = "models/player/vip/vip.mdl";
|
};
|
||||||
|
|
||||||
models.foreach ([&] (const String &key, const String &val) {
|
models.foreach ([&] (const String &key, const String &val) {
|
||||||
game.searchEntities ("classname", key, [&] (edict_t *ent) {
|
game.searchEntities ("classname", key, [&] (edict_t *ent) {
|
||||||
|
|
@ -620,6 +617,7 @@ void Game::addNewCvar (const char *name, const char *value, const char *info, bo
|
||||||
|
|
||||||
reg.reg.name = const_cast <char *> (name);
|
reg.reg.name = const_cast <char *> (name);
|
||||||
reg.reg.string = const_cast <char *> (value);
|
reg.reg.string = const_cast <char *> (value);
|
||||||
|
reg.name = name;
|
||||||
reg.missing = missingAction;
|
reg.missing = missingAction;
|
||||||
reg.init = value;
|
reg.init = value;
|
||||||
reg.info = info;
|
reg.info = info;
|
||||||
|
|
@ -654,6 +652,20 @@ void Game::addNewCvar (const char *name, const char *value, const char *info, bo
|
||||||
m_cvars.push (cr::move (reg));
|
m_cvars.push (cr::move (reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConVar::revert () {
|
||||||
|
if (!ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &cvars = game.getCvars ();
|
||||||
|
|
||||||
|
for (const auto &var : cvars) {
|
||||||
|
if (var.name == ptr->name) {
|
||||||
|
set (var.initial);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Game::checkCvarsBounds () {
|
void Game::checkCvarsBounds () {
|
||||||
for (const auto &var : m_cvars) {
|
for (const auto &var : m_cvars) {
|
||||||
|
|
||||||
|
|
@ -669,14 +681,14 @@ void Game::checkCvarsBounds () {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto value = var.self->float_ ();
|
auto value = var.self->float_ ();
|
||||||
auto str = var.self->str ();
|
auto str = String (var.self->str ());
|
||||||
|
|
||||||
// check the bounds and set default if out of bounds
|
// check the bounds and set default if out of bounds
|
||||||
if (value > var.max || value < var.min || (!strings.isEmpty (str) && isalpha (*str))) {
|
if (value > var.max || value < var.min || (!str.empty () && isalpha (str[0]))) {
|
||||||
var.self->set (var.initial);
|
var.self->set (var.initial);
|
||||||
|
|
||||||
// notify about that
|
// notify about that
|
||||||
ctrl.msg ("Bogus value for cvar '%s', min is '%.1f' and max is '%.1f', and we're got '%s', value reverted to default '%.1f'.", var.reg.name, var.min, var.max, str, var.initial);
|
ctrl.msg ("Bogus value for cvar '%s', min is '%.1f' and max is '%.1f', and we're got '%s', value reverted to default '%.1f'.", var.name, var.min, var.max, str, var.initial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -741,19 +753,7 @@ bool Game::loadCSBinary () {
|
||||||
if (!modname) {
|
if (!modname) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StringArray libs;
|
StringArray libs { "mp", "cs", "cs_i386" };
|
||||||
|
|
||||||
if (plat.win) {
|
|
||||||
libs.push ("mp.dll");
|
|
||||||
libs.push ("cs.dll");
|
|
||||||
}
|
|
||||||
else if (plat.nix) {
|
|
||||||
libs.push ("cs.so");
|
|
||||||
libs.push ("cs_i386.so");
|
|
||||||
}
|
|
||||||
else if (plat.osx) {
|
|
||||||
libs.push ("cs.dylib");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto libCheck = [&] (StringRef mod, StringRef dll) {
|
auto libCheck = [&] (StringRef mod, StringRef dll) {
|
||||||
// try to load gamedll
|
// try to load gamedll
|
||||||
|
|
@ -771,7 +771,7 @@ bool Game::loadCSBinary () {
|
||||||
|
|
||||||
// search the libraries inside game dlls directory
|
// search the libraries inside game dlls directory
|
||||||
for (const auto &lib : libs) {
|
for (const auto &lib : libs) {
|
||||||
auto path = strings.format ("%s/dlls/%s", modname, lib);
|
auto path = strings.format ("%s/dlls/%s.%s", modname, lib, DLL_SUFFIX);
|
||||||
|
|
||||||
// if we can't read file, skip it
|
// if we can't read file, skip it
|
||||||
if (!File::exists (path)) {
|
if (!File::exists (path)) {
|
||||||
|
|
@ -834,7 +834,7 @@ bool Game::loadCSBinary () {
|
||||||
bool Game::postload () {
|
bool Game::postload () {
|
||||||
|
|
||||||
// register logger
|
// register logger
|
||||||
logger.initialize (util.buildPath (BotFile::LogFile), [] (const char *msg) {
|
logger.initialize (bstor.buildPath (BotFile::LogFile), [] (const char *msg) {
|
||||||
game.print (msg);
|
game.print (msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
1020
src/graph.cpp
1020
src/graph.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -332,8 +332,8 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
||||||
// any case, when the new map will be booting, ServerActivate() will be called, so we'll do
|
// any case, when the new map will be booting, ServerActivate() will be called, so we'll do
|
||||||
// the loading of new bots and the new BSP data parsing there.
|
// the loading of new bots and the new BSP data parsing there.
|
||||||
|
|
||||||
// save collected experience on shutdown
|
// save collected practice on shutdown
|
||||||
graph.savePractice ();
|
practice.save ();
|
||||||
|
|
||||||
// destroy global killer entity
|
// destroy global killer entity
|
||||||
bots.destroyKillerEntity ();
|
bots.destroyKillerEntity ();
|
||||||
|
|
@ -384,9 +384,15 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
||||||
graph.frame ();
|
graph.frame ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update analyzer if needed
|
||||||
|
analyzer.update ();
|
||||||
|
|
||||||
// run stuff periodically
|
// run stuff periodically
|
||||||
game.slowFrame ();
|
game.slowFrame ();
|
||||||
|
|
||||||
|
// rebuild vistable if needed
|
||||||
|
vistab.rebuild ();
|
||||||
|
|
||||||
if (bots.hasBotsOnline ()) {
|
if (bots.hasBotsOnline ()) {
|
||||||
// keep track of grenades on map
|
// keep track of grenades on map
|
||||||
bots.updateActiveGrenade ();
|
bots.updateActiveGrenade ();
|
||||||
|
|
@ -903,8 +909,9 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
|
||||||
}
|
}
|
||||||
bots.kickEveryone (true); // kick all bots off this server
|
bots.kickEveryone (true); // kick all bots off this server
|
||||||
|
|
||||||
// save collected experience on shutdown
|
// save collected practice on shutdown
|
||||||
graph.savePractice ();
|
practice.save ();
|
||||||
|
|
||||||
util.disableSendTo ();
|
util.disableSendTo ();
|
||||||
|
|
||||||
// make sure all stuff cleared
|
// make sure all stuff cleared
|
||||||
|
|
|
||||||
|
|
@ -1251,7 +1251,6 @@ void Bot::newRound () {
|
||||||
|
|
||||||
// delete all allocated path nodes
|
// delete all allocated path nodes
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
clearRoute ();
|
|
||||||
|
|
||||||
m_pathOrigin = nullptr;
|
m_pathOrigin = nullptr;
|
||||||
m_destOrigin = nullptr;
|
m_destOrigin = nullptr;
|
||||||
|
|
@ -1688,16 +1687,21 @@ void BotManager::notifyBombDefuse () {
|
||||||
if (!isBombPlanted ()) {
|
if (!isBombPlanted ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto bombPos = graph.getBombOrigin ();
|
||||||
|
|
||||||
for (const auto &bot : bots) {
|
for (const auto &bot : bots) {
|
||||||
if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) {
|
auto task = bot->getCurrentTaskId ();
|
||||||
|
|
||||||
|
if (bot->m_notKilled && task != Task::MoveToPosition && task != Task::DefuseBomb && task != Task::EscapeFromBomb) {
|
||||||
|
if (bot->m_team == Team::CT || bot->pev->origin.distanceSq (bombPos) < cr::sqrf (384.0f)) {
|
||||||
bot->clearSearchNodes ();
|
bot->clearSearchNodes ();
|
||||||
|
|
||||||
bot->m_position = graph.getBombOrigin ();
|
bot->m_position = bombPos;
|
||||||
bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
|
bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BotManager::updateActiveGrenade () {
|
void BotManager::updateActiveGrenade () {
|
||||||
if (m_grenadeUpdateTime > game.time ()) {
|
if (m_grenadeUpdateTime > game.time ()) {
|
||||||
|
|
@ -1891,7 +1895,7 @@ void BotManager::initRound () {
|
||||||
m_botsCanPause = false;
|
m_botsCanPause = false;
|
||||||
|
|
||||||
resetFilters ();
|
resetFilters ();
|
||||||
graph.updateGlobalPractice (); // update experience data on round start
|
practice.update (); // update practice data on round start
|
||||||
|
|
||||||
// calculate the round mid/end in world time
|
// calculate the round mid/end in world time
|
||||||
m_timeRoundStart = game.time () + mp_freezetime.float_ ();
|
m_timeRoundStart = game.time () + mp_freezetime.float_ ();
|
||||||
|
|
|
||||||
|
|
@ -371,7 +371,7 @@ void MessageDispatcher::netMsgBarTime () {
|
||||||
m_bot->m_hasProgressBar = true; // the progress bar on a hud
|
m_bot->m_hasProgressBar = true; // the progress bar on a hud
|
||||||
|
|
||||||
// notify bots about defusing has started
|
// notify bots about defusing has started
|
||||||
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_bot->m_team == Team::CT) {
|
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) {
|
||||||
bots.notifyBombDefuse ();
|
bots.notifyBombDefuse ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
617
src/navigate.cpp
617
src/navigate.cpp
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
#include <yapb.h>
|
#include <yapb.h>
|
||||||
|
|
||||||
ConVar cv_path_heuristic_mode ("yb_path_heuristic_mode", "4", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f);
|
|
||||||
ConVar cv_path_danger_factor_min ("yb_path_danger_factor_min", "200", "Lower bound of danger factor that used to add additional danger to path based on practice.", true, 100.0f, 2400.0f);
|
|
||||||
ConVar cv_path_danger_factor_max ("yb_path_danger_factor_max", "400", "Upper bound of danger factor that used to add additional danger to path based on practice.", true, 200.0f, 4800.0f);
|
|
||||||
|
|
||||||
int Bot::findBestGoal () {
|
int Bot::findBestGoal () {
|
||||||
auto pushToHistroy = [&] (int32_t goal) -> int32_t {
|
auto pushToHistroy = [&] (int32_t goal) -> int32_t {
|
||||||
m_nodeHistory.push (goal);
|
m_nodeHistory.push (goal);
|
||||||
|
|
@ -233,7 +229,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) {
|
||||||
for (auto &point : graph.m_goalPoints) {
|
for (auto &point : graph.m_goalPoints) {
|
||||||
float distance = graph[point].origin.distanceSq (pev->origin);
|
float distance = graph[point].origin.distanceSq (pev->origin);
|
||||||
|
|
||||||
if (distance > cr::square (1024.0f)) {
|
if (distance > cr::sqrf (1024.0f)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (distance < minDist) {
|
if (distance < minDist) {
|
||||||
|
|
@ -299,7 +295,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph.getDangerValue (m_team, m_currentNodeIndex, goalChoices[i]) < graph.getDangerValue (m_team, m_currentNodeIndex, goalChoices[i + 1])) {
|
if (practice.getValue (m_team, m_currentNodeIndex, goalChoices[i]) < practice.getValue (m_team, m_currentNodeIndex, goalChoices[i + 1])) {
|
||||||
cr::swap (goalChoices[i + 1], goalChoices[i]);
|
cr::swap (goalChoices[i + 1], goalChoices[i]);
|
||||||
sorting = true;
|
sorting = true;
|
||||||
}
|
}
|
||||||
|
|
@ -368,7 +364,7 @@ void Bot::ignoreCollision () {
|
||||||
|
|
||||||
void Bot::doPlayerAvoidance (const Vector &normal) {
|
void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
m_hindrance = nullptr;
|
m_hindrance = nullptr;
|
||||||
float distance = cr::square (348.0f);
|
float distance = cr::sqrf (348.0f);
|
||||||
|
|
||||||
if (getCurrentTaskId () == Task::Attack || isOnLadder ()) {
|
if (getCurrentTaskId () == Task::Attack || isOnLadder ()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -400,7 +396,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
}
|
}
|
||||||
auto nearest = client.ent->v.origin.distanceSq (pev->origin);
|
auto nearest = client.ent->v.origin.distanceSq (pev->origin);
|
||||||
|
|
||||||
if (nearest < cr::square (pev->maxspeed) && nearest < distance) {
|
if (nearest < cr::sqrf (pev->maxspeed) && nearest < distance) {
|
||||||
m_hindrance = client.ent;
|
m_hindrance = client.ent;
|
||||||
distance = nearest;
|
distance = nearest;
|
||||||
}
|
}
|
||||||
|
|
@ -425,19 +421,19 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
auto nextFrameDistance = pev->origin.distanceSq (m_hindrance->v.origin + m_hindrance->v.velocity * interval);
|
auto nextFrameDistance = pev->origin.distanceSq (m_hindrance->v.origin + m_hindrance->v.velocity * interval);
|
||||||
|
|
||||||
// is player that near now or in future that we need to steer away?
|
// is player that near now or in future that we need to steer away?
|
||||||
if (movedDistance <= cr::square (48.0f) || (distance <= cr::square (56.0f) && nextFrameDistance < distance)) {
|
if (movedDistance <= cr::sqrf (48.0f) || (distance <= cr::sqrf (56.0f) && nextFrameDistance < distance)) {
|
||||||
auto dir = (pev->origin - m_hindrance->v.origin).normalize2d ();
|
auto dir = (pev->origin - m_hindrance->v.origin).normalize2d_apx ();
|
||||||
|
|
||||||
// to start strafing, we have to first figure out if the target is on the left side or right side
|
// to start strafing, we have to first figure out if the target is on the left side or right side
|
||||||
if ((dir | right.normalize2d ()) > 0.0f) {
|
if ((dir | right.normalize2d_apx ()) > 0.0f) {
|
||||||
setStrafeSpeed (normal, pev->maxspeed);
|
setStrafeSpeed (normal, pev->maxspeed);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setStrafeSpeed (normal, -pev->maxspeed);
|
setStrafeSpeed (normal, -pev->maxspeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distance < cr::square (76.0f)) {
|
if (distance < cr::sqrf (76.0f)) {
|
||||||
if ((dir | forward.normalize2d ()) < 0.0f) {
|
if ((dir | forward.normalize2d_apx ()) < 0.0f) {
|
||||||
m_moveSpeed = -pev->maxspeed;
|
m_moveSpeed = -pev->maxspeed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -495,7 +491,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// bot is stuc, but not yet decided what to do?
|
// bot is stuck, but not yet decided what to do?
|
||||||
if (m_collisionState == CollisionState::Undecided) {
|
if (m_collisionState == CollisionState::Undecided) {
|
||||||
uint32_t bits = 0;
|
uint32_t bits = 0;
|
||||||
|
|
||||||
|
|
@ -541,19 +537,20 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
||||||
else {
|
else {
|
||||||
dirLeft = true;
|
dirLeft = true;
|
||||||
}
|
}
|
||||||
const Vector &testDir = m_moveSpeed > 0.0f ? forward : -forward;
|
const auto &testDir = m_moveSpeed > 0.0f ? forward : -forward;
|
||||||
|
constexpr float blockDistance = 56.0f;
|
||||||
|
|
||||||
// now check which side is blocked
|
// now check which side is blocked
|
||||||
src = pev->origin + right * 32.0f;
|
src = pev->origin + right * blockDistance;
|
||||||
dst = src + testDir * 32.0f;
|
dst = src + testDir * blockDistance;
|
||||||
|
|
||||||
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||||
|
|
||||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
blockedRight = true;
|
blockedRight = true;
|
||||||
}
|
}
|
||||||
src = pev->origin - right * 32.0f;
|
src = pev->origin - right * blockDistance;
|
||||||
dst = src + testDir * 32.0f;
|
dst = src + testDir * blockDistance;
|
||||||
|
|
||||||
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||||
|
|
||||||
|
|
@ -1057,7 +1054,7 @@ bool Bot::updateNavigation () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// needs precise placement - check if we get past the point
|
// needs precise placement - check if we get past the point
|
||||||
if (desiredDistance < 22.0f && nodeDistance < 30.0f && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= cr::square (nodeDistance)) {
|
if (desiredDistance < 22.0f && nodeDistance < 30.0f && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= cr::sqrf (nodeDistance)) {
|
||||||
desiredDistance = nodeDistance + 1.0f;
|
desiredDistance = nodeDistance + 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1078,15 +1075,16 @@ bool Bot::updateNavigation () {
|
||||||
// did we reach a destination node?
|
// did we reach a destination node?
|
||||||
if (getTask ()->data == m_currentNodeIndex) {
|
if (getTask ()->data == m_currentNodeIndex) {
|
||||||
if (m_chosenGoalIndex != kInvalidNodeIndex) {
|
if (m_chosenGoalIndex != kInvalidNodeIndex) {
|
||||||
|
constexpr int maxGoalValue = PracticeLimit::Goal;
|
||||||
|
|
||||||
// add goal values
|
// add goal values
|
||||||
int goalValue = graph.getDangerValue (m_team, m_chosenGoalIndex, m_currentNodeIndex);
|
int goalValue = practice.getValue (m_team, m_chosenGoalIndex, m_currentNodeIndex);
|
||||||
int addedValue = static_cast <int> (m_healthValue * 0.5f + m_goalValue * 0.5f);
|
int addedValue = static_cast <int> (m_healthValue * 0.5f + m_goalValue * 0.5f);
|
||||||
|
|
||||||
goalValue = cr::clamp (goalValue + addedValue, -kMaxPracticeGoalValue, kMaxPracticeGoalValue);
|
goalValue = cr::clamp (goalValue + addedValue, -maxGoalValue, maxGoalValue);
|
||||||
|
|
||||||
// update the practice for team
|
// update the practice for team
|
||||||
graph.setDangerValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue);
|
practice.setValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1193,7 +1191,7 @@ bool Bot::updateLiftHandling () {
|
||||||
m_destOrigin = m_liftTravelPos;
|
m_destOrigin = m_liftTravelPos;
|
||||||
|
|
||||||
// check if we enough to destination
|
// check if we enough to destination
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
|
|
||||||
// need to wait our following teammate ?
|
// need to wait our following teammate ?
|
||||||
|
|
@ -1247,7 +1245,7 @@ bool Bot::updateLiftHandling () {
|
||||||
if (needWaitForTeammate) {
|
if (needWaitForTeammate) {
|
||||||
m_destOrigin = m_liftTravelPos;
|
m_destOrigin = m_liftTravelPos;
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1278,7 +1276,7 @@ bool Bot::updateLiftHandling () {
|
||||||
m_liftState = LiftState::TravelingBy;
|
m_liftState = LiftState::TravelingBy;
|
||||||
m_liftUsageTime = game.time () + 14.0f;
|
m_liftUsageTime = game.time () + 14.0f;
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1286,7 @@ bool Bot::updateLiftHandling () {
|
||||||
if (m_liftState == LiftState::TravelingBy) {
|
if (m_liftState == LiftState::TravelingBy) {
|
||||||
m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z);
|
m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z);
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1305,7 +1303,7 @@ bool Bot::updateLiftHandling () {
|
||||||
m_destOrigin = pev->origin;
|
m_destOrigin = pev->origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1338,7 +1336,7 @@ bool Bot::updateLiftHandling () {
|
||||||
m_destOrigin = button->v.origin;
|
m_destOrigin = button->v.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1369,7 +1367,7 @@ bool Bot::updateLiftHandling () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_destOrigin) < cr::square (20.0f)) {
|
if (pev->origin.distanceSq (m_destOrigin) < cr::sqrf (20.0f)) {
|
||||||
wait ();
|
wait ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1433,344 +1431,15 @@ bool Bot::updateLiftStates () {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::findShortestPath (int srcIndex, int destIndex) {
|
|
||||||
// this function finds the shortest path from source index to destination index
|
|
||||||
|
|
||||||
if (!graph.exists (srcIndex)) {
|
|
||||||
logger.error ("%s source path index not valid (%d).", __FUNCTION__, srcIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!graph.exists (destIndex)) {
|
|
||||||
logger.error ("%s destination path index not valid (%d).", __FUNCTION__, destIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearSearchNodes ();
|
|
||||||
|
|
||||||
m_chosenGoalIndex = srcIndex;
|
|
||||||
m_goalValue = 0.0f;
|
|
||||||
|
|
||||||
m_pathWalk.add (srcIndex);
|
|
||||||
|
|
||||||
while (srcIndex != destIndex) {
|
|
||||||
srcIndex = (graph.m_matrix.data () + (srcIndex * graph.length ()) + destIndex)->index;
|
|
||||||
|
|
||||||
if (srcIndex < 0) {
|
|
||||||
m_prevGoalIndex = kInvalidNodeIndex;
|
|
||||||
getTask ()->data = kInvalidNodeIndex;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_pathWalk.add (srcIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) {
|
|
||||||
// this function finds a path from srcIndex to destIndex
|
|
||||||
|
|
||||||
auto dangerFactor = [&] () -> float {
|
|
||||||
return rg.get (cv_path_danger_factor_min.float_ (), cv_path_danger_factor_max.float_ ()) * 2.0f / cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f);
|
|
||||||
};
|
|
||||||
|
|
||||||
// least kills and number of nodes to goal for a team
|
|
||||||
auto gfunctionKillsDist = [&dangerFactor] (int team, int currentIndex, int parentIndex) -> float {
|
|
||||||
if (parentIndex == kInvalidNodeIndex) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
auto cost = static_cast <float> (graph.getDangerDamage (team, currentIndex, currentIndex) + graph.getHighestDamageForTeam (team));
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
for (auto &neighbour : current.links) {
|
|
||||||
if (neighbour.index != kInvalidNodeIndex) {
|
|
||||||
cost += static_cast <float> (graph.getDangerDamage (team, neighbour.index, neighbour.index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.flags & NodeFlag::Crouch) {
|
|
||||||
cost *= 1.5f;
|
|
||||||
}
|
|
||||||
return cost + dangerFactor ();
|
|
||||||
};
|
|
||||||
|
|
||||||
// least kills and number of nodes to goal for a team
|
|
||||||
auto gfunctionKillsDistCTWithHostage = [&gfunctionKillsDist] (int team, int currentIndex, int parentIndex) -> float {
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
|
||||||
return 65355.0f;
|
|
||||||
}
|
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
|
||||||
return gfunctionKillsDist (team, currentIndex, parentIndex) * 500.0f;
|
|
||||||
}
|
|
||||||
return gfunctionKillsDist (team, currentIndex, parentIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
// least kills to goal for a team
|
|
||||||
auto gfunctionKills = [&dangerFactor] (int team, int currentIndex, int) -> float {
|
|
||||||
auto cost = static_cast <float> (graph.getDangerDamage (team, currentIndex, currentIndex));
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
for (auto &neighbour : current.links) {
|
|
||||||
if (neighbour.index != kInvalidNodeIndex) {
|
|
||||||
cost += static_cast <float> (graph.getDangerDamage (team, neighbour.index, neighbour.index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.flags & NodeFlag::Crouch) {
|
|
||||||
cost *= 1.5f;
|
|
||||||
}
|
|
||||||
return cost + dangerFactor () + 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
// least kills to goal for a team
|
|
||||||
auto gfunctionKillsCTWithHostage = [&gfunctionKills] (int team, int currentIndex, int parentIndex) -> float {
|
|
||||||
if (parentIndex == kInvalidNodeIndex) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
|
||||||
return 65355.0f;
|
|
||||||
}
|
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
|
||||||
return gfunctionKills (team, currentIndex, parentIndex) * 500.0f;
|
|
||||||
}
|
|
||||||
return gfunctionKills (team, currentIndex, parentIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto gfunctionPathDist = [] (int, int currentIndex, int parentIndex) -> float {
|
|
||||||
if (parentIndex == kInvalidNodeIndex) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
const auto &parent = graph[parentIndex];
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
for (const auto &link : parent.links) {
|
|
||||||
if (link.index == currentIndex) {
|
|
||||||
const auto distance = static_cast <float> (link.distance);
|
|
||||||
|
|
||||||
// we don't like ladder or crouch point
|
|
||||||
if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
|
||||||
return distance * 1.5f;
|
|
||||||
}
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 65355.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto gfunctionPathDistWithHostage = [&gfunctionPathDist] (int, int currentIndex, int parentIndex) -> float {
|
|
||||||
const auto ¤t = graph[currentIndex];
|
|
||||||
|
|
||||||
if (current.flags & NodeFlag::NoHostage) {
|
|
||||||
return 65355.0f;
|
|
||||||
}
|
|
||||||
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
|
||||||
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 500.0f;
|
|
||||||
}
|
|
||||||
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
// square distance heuristic
|
|
||||||
auto hfunctionPathDist = [] (int index, int, int goalIndex) -> float {
|
|
||||||
const auto &start = graph[index];
|
|
||||||
const auto &goal = graph[goalIndex];
|
|
||||||
|
|
||||||
float x = cr::abs (start.origin.x - goal.origin.x);
|
|
||||||
float y = cr::abs (start.origin.y - goal.origin.y);
|
|
||||||
float z = cr::abs (start.origin.z - goal.origin.z);
|
|
||||||
|
|
||||||
switch (cv_path_heuristic_mode.int_ ()) {
|
|
||||||
case 0:
|
|
||||||
default:
|
|
||||||
return cr::max (cr::max (x, y), z); // chebyshev distance
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return x + y + z; // manhattan distance
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return 0.0f; // no heuristic
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
case 4:
|
|
||||||
// euclidean based distance
|
|
||||||
float euclidean = cr::powf (cr::powf (x, 2.0f) + cr::powf (y, 2.0f) + cr::powf (z, 2.0f), 0.5f);
|
|
||||||
|
|
||||||
if (cv_path_heuristic_mode.int_ () == 4) {
|
|
||||||
return 1000.0f * (cr::ceilf (euclidean) - euclidean);
|
|
||||||
}
|
|
||||||
return euclidean;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// square distance heuristic with hostages
|
|
||||||
auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
|
|
||||||
if (graph[index].flags & NodeFlag::NoHostage) {
|
|
||||||
return 65355.0f;
|
|
||||||
}
|
|
||||||
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
// none heuristic
|
|
||||||
auto hfunctionNone = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
|
|
||||||
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!graph.exists (srcIndex)) {
|
|
||||||
logger.error ("%s source path index not valid (%d).", __FUNCTION__, srcIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!graph.exists (destIndex)) {
|
|
||||||
|
|
||||||
logger.error ("%s destination path index not valid (%d).", __FUNCTION__, destIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// astar needs some nodes to work with
|
|
||||||
if (graph.length () < kMaxNodeLinks) {
|
|
||||||
logger.error ("A* Search for bot \"%s\" has failed due to too small number of nodes (%d). Seems to be graph is broken.", pev->netname.chars (), graph.length ());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// holders for heuristic functions
|
|
||||||
static Lambda <float (int, int, int)> gcalc, hcalc;
|
|
||||||
|
|
||||||
// get correct calculation for heuristic
|
|
||||||
if (pathType == FindPath::Optimal) {
|
|
||||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
|
||||||
hcalc = hfunctionPathDistWithHostage;
|
|
||||||
gcalc = gfunctionKillsDistCTWithHostage;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hcalc = hfunctionPathDist;
|
|
||||||
gcalc = gfunctionKillsDist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pathType == FindPath::Safe) {
|
|
||||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
|
||||||
hcalc = hfunctionNone;
|
|
||||||
gcalc = gfunctionKillsCTWithHostage;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hcalc = hfunctionNone;
|
|
||||||
gcalc = gfunctionKills;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
|
||||||
hcalc = hfunctionPathDistWithHostage;
|
|
||||||
gcalc = gfunctionPathDistWithHostage;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hcalc = hfunctionPathDist;
|
|
||||||
gcalc = gfunctionPathDist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearSearchNodes ();
|
|
||||||
|
|
||||||
m_chosenGoalIndex = srcIndex;
|
|
||||||
m_goalValue = 0.0f;
|
|
||||||
|
|
||||||
clearRoute ();
|
|
||||||
auto srcRoute = &m_routes[srcIndex];
|
|
||||||
|
|
||||||
// put start node into open list
|
|
||||||
srcRoute->g = gcalc (m_team, srcIndex, kInvalidNodeIndex);
|
|
||||||
srcRoute->f = srcRoute->g + hcalc (srcIndex, kInvalidNodeIndex, destIndex);
|
|
||||||
srcRoute->state = RouteState::Open;
|
|
||||||
|
|
||||||
m_routeQue.clear ();
|
|
||||||
m_routeQue.emplace (srcIndex, srcRoute->g);
|
|
||||||
|
|
||||||
while (!m_routeQue.empty ()) {
|
|
||||||
// remove the first node from the open list
|
|
||||||
int currentIndex = m_routeQue.pop ().first;
|
|
||||||
|
|
||||||
// safes us from bad graph...
|
|
||||||
if (m_routeQue.length () >= graph.getMaxRouteLength () - 1) {
|
|
||||||
logger.error ("A* Search for bot \"%s\" has tried to build path with at least %d nodes. Seems to be graph is broken.", pev->netname.chars (), m_routeQue.length ());
|
|
||||||
|
|
||||||
kick (); // kick the bot off...
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// is the current node the goal node?
|
|
||||||
if (currentIndex == destIndex) {
|
|
||||||
|
|
||||||
// build the complete path
|
|
||||||
do {
|
|
||||||
m_pathWalk.add (currentIndex);
|
|
||||||
currentIndex = m_routes[currentIndex].parent;
|
|
||||||
|
|
||||||
} while (currentIndex != kInvalidNodeIndex);
|
|
||||||
|
|
||||||
// reverse path for path follower
|
|
||||||
m_pathWalk.reverse ();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto curRoute = &m_routes[currentIndex];
|
|
||||||
|
|
||||||
if (curRoute->state != RouteState::Open) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// put current node into CLOSED list
|
|
||||||
curRoute->state = RouteState::Closed;
|
|
||||||
|
|
||||||
// now expand the current node
|
|
||||||
for (const auto &child : graph[currentIndex].links) {
|
|
||||||
if (child.index == kInvalidNodeIndex) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto childRoute = &m_routes[child.index];
|
|
||||||
|
|
||||||
// calculate the F value as F = G + H
|
|
||||||
const float g = curRoute->g + gcalc (m_team, child.index, currentIndex);
|
|
||||||
const float h = hcalc (child.index, kInvalidNodeIndex, destIndex);
|
|
||||||
const float f = g + h;
|
|
||||||
|
|
||||||
if (childRoute->state == RouteState::New || childRoute->f > f) {
|
|
||||||
// put the current child into open list
|
|
||||||
childRoute->parent = currentIndex;
|
|
||||||
childRoute->state = RouteState::Open;
|
|
||||||
|
|
||||||
childRoute->g = g;
|
|
||||||
childRoute->f = f;
|
|
||||||
|
|
||||||
m_routeQue.emplace (child.index, g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.error ("A* Search for bot \"%s\" has failed. Falling back to shortest-path algorithm. Seems to be graph is broken.", pev->netname.chars ());
|
|
||||||
|
|
||||||
// fallback to shortest path
|
|
||||||
findShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bot::clearSearchNodes () {
|
void Bot::clearSearchNodes () {
|
||||||
m_pathWalk.clear ();
|
m_pathWalk.clear ();
|
||||||
m_chosenGoalIndex = kInvalidNodeIndex;
|
m_chosenGoalIndex = kInvalidNodeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::clearRoute () {
|
|
||||||
m_routes.resize (static_cast <size_t> (graph.length ()));
|
|
||||||
|
|
||||||
for (const auto &path : graph) {
|
|
||||||
auto route = &m_routes[path.number];
|
|
||||||
|
|
||||||
route->g = route->f = 0.0f;
|
|
||||||
route->parent = kInvalidNodeIndex;
|
|
||||||
route->state = RouteState::New;
|
|
||||||
}
|
|
||||||
m_routes.clear ();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Bot::findAimingNode (const Vector &to, int &pathLength) {
|
int Bot::findAimingNode (const Vector &to, int &pathLength) {
|
||||||
// return the most distant node which is seen from the bot to the target and is within count
|
// return the most distant node which is seen from the bot to the target and is within count
|
||||||
|
|
||||||
if (m_currentNodeIndex == kInvalidNodeIndex) {
|
ensureCurrentNodeIndex ();
|
||||||
changeNodeIndex (findNearestNode ());
|
|
||||||
}
|
|
||||||
|
|
||||||
int destIndex = graph.getNearest (to);
|
int destIndex = graph.getNearest (to);
|
||||||
int bestIndex = m_currentNodeIndex;
|
int bestIndex = m_currentNodeIndex;
|
||||||
|
|
@ -1779,19 +1448,15 @@ int Bot::findAimingNode (const Vector &to, int &pathLength) {
|
||||||
return kInvalidNodeIndex;
|
return kInvalidNodeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (destIndex != m_currentNodeIndex) {
|
planner.find (destIndex, m_currentNodeIndex, [&] (int index) {
|
||||||
destIndex = (graph.m_matrix.data () + (destIndex * graph.length ()) + m_currentNodeIndex)->index;
|
|
||||||
|
|
||||||
if (destIndex < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++pathLength;
|
++pathLength;
|
||||||
|
|
||||||
if (graph.isVisible (m_currentNodeIndex, destIndex)) {
|
if (vistab.visible (m_currentNodeIndex, index)) {
|
||||||
bestIndex = destIndex;
|
bestIndex = index;
|
||||||
break;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (bestIndex == m_currentNodeIndex) {
|
if (bestIndex == m_currentNodeIndex) {
|
||||||
return kInvalidNodeIndex;
|
return kInvalidNodeIndex;
|
||||||
|
|
@ -1920,7 +1585,7 @@ float Bot::getEstimatedNodeReachTime () {
|
||||||
float distance = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin);
|
float distance = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin);
|
||||||
|
|
||||||
// caclulate estimated time
|
// caclulate estimated time
|
||||||
estimatedTime = 5.0f * (distance / cr::square (m_moveSpeed + 1.0f));
|
estimatedTime = 5.0f * (distance / cr::sqrf (m_moveSpeed + 1.0f));
|
||||||
|
|
||||||
bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK);
|
bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK);
|
||||||
|
|
||||||
|
|
@ -1942,21 +1607,23 @@ void Bot::findValidNode () {
|
||||||
findNextBestNode ();
|
findNextBestNode ();
|
||||||
}
|
}
|
||||||
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time ()) {
|
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time ()) {
|
||||||
|
constexpr int maxDamageValue = PracticeLimit::Damage;
|
||||||
|
|
||||||
// increase danager for both teams
|
// increase danager for both teams
|
||||||
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||||
int damageValue = graph.getDangerDamage (team, m_currentNodeIndex, m_currentNodeIndex);
|
int damageValue = practice.getDamage (team, m_currentNodeIndex, m_currentNodeIndex);
|
||||||
damageValue = cr::clamp (damageValue + 100, 0, kMaxPracticeDamageValue);
|
damageValue = cr::clamp (damageValue + 100, 0, maxDamageValue);
|
||||||
|
|
||||||
// affect nearby connected with victim nodes
|
// affect nearby connected with victim nodes
|
||||||
for (auto &neighbour : m_path->links) {
|
for (auto &neighbour : m_path->links) {
|
||||||
if (graph.exists (neighbour.index)) {
|
if (graph.exists (neighbour.index)) {
|
||||||
int neighbourValue = graph.getDangerDamage (team, neighbour.index, neighbour.index);
|
int neighbourValue = practice.getDamage (team, neighbour.index, neighbour.index);
|
||||||
neighbourValue = cr::clamp (neighbourValue + 100, 0, kMaxPracticeDamageValue);
|
neighbourValue = cr::clamp (neighbourValue + 100, 0, maxDamageValue);
|
||||||
|
|
||||||
graph.setDangerDamage (m_team, neighbour.index, neighbour.index, neighbourValue);
|
practice.setDamage (m_team, neighbour.index, neighbour.index, neighbourValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graph.setDangerDamage (m_team, m_currentNodeIndex, m_currentNodeIndex, damageValue);
|
practice.setDamage (m_team, m_currentNodeIndex, m_currentNodeIndex, damageValue);
|
||||||
}
|
}
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
findNextBestNode ();
|
findNextBestNode ();
|
||||||
|
|
@ -1990,7 +1657,7 @@ int Bot::findNearestNode () {
|
||||||
constexpr float kMaxDistance = 1024.0f;
|
constexpr float kMaxDistance = 1024.0f;
|
||||||
|
|
||||||
int index = kInvalidNodeIndex;
|
int index = kInvalidNodeIndex;
|
||||||
float minimumDistance = cr::square (kMaxDistance);
|
float minimumDistance = cr::sqrf (kMaxDistance);
|
||||||
|
|
||||||
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
|
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
|
||||||
const auto &bucket = graph.getNodesInBucket (origin);
|
const auto &bucket = graph.getNodesInBucket (origin);
|
||||||
|
|
@ -2005,7 +1672,7 @@ int Bot::findNearestNode () {
|
||||||
|
|
||||||
if (distance < minimumDistance) {
|
if (distance < minimumDistance) {
|
||||||
// if bot doing navigation, make sure node really visible and reacheable
|
// if bot doing navigation, make sure node really visible and reacheable
|
||||||
if (graph.isVisible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) {
|
if (vistab.visible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) {
|
||||||
index = path.number;
|
index = path.number;
|
||||||
minimumDistance = distance;
|
minimumDistance = distance;
|
||||||
}
|
}
|
||||||
|
|
@ -2014,7 +1681,7 @@ int Bot::findNearestNode () {
|
||||||
|
|
||||||
// try to search ANYTHING that can be reachaed
|
// try to search ANYTHING that can be reachaed
|
||||||
if (index == kInvalidNodeIndex) {
|
if (index == kInvalidNodeIndex) {
|
||||||
minimumDistance = cr::square (kMaxDistance);
|
minimumDistance = cr::sqrf (kMaxDistance);
|
||||||
const auto &nearestNodes = graph.getNarestInRadius (kMaxDistance, pev->origin);
|
const auto &nearestNodes = graph.getNarestInRadius (kMaxDistance, pev->origin);
|
||||||
|
|
||||||
for (const auto &i : nearestNodes) {
|
for (const auto &i : nearestNodes) {
|
||||||
|
|
@ -2059,7 +1726,7 @@ int Bot::findBombNode () {
|
||||||
const auto &audible = isBombAudible ();
|
const auto &audible = isBombAudible ();
|
||||||
|
|
||||||
// take the nearest to bomb nodes instead of goal if close enough
|
// take the nearest to bomb nodes instead of goal if close enough
|
||||||
if (pev->origin.distanceSq (bomb) < cr::square (96.0f)) {
|
if (pev->origin.distanceSq (bomb) < cr::sqrf (96.0f)) {
|
||||||
int node = graph.getNearest (bomb, 420.0f);
|
int node = graph.getNearest (bomb, 420.0f);
|
||||||
|
|
||||||
m_bombSearchOverridden = true;
|
m_bombSearchOverridden = true;
|
||||||
|
|
@ -2105,9 +1772,7 @@ int Bot::findDefendNode (const Vector &origin) {
|
||||||
// this function tries to find a good position which has a line of sight to a position,
|
// this function tries to find a good position which has a line of sight to a position,
|
||||||
// provides enough cover point, and is far away from the defending position
|
// provides enough cover point, and is far away from the defending position
|
||||||
|
|
||||||
if (m_currentNodeIndex == kInvalidNodeIndex) {
|
ensureCurrentNodeIndex ();
|
||||||
changeNodeIndex (findNearestNode ());
|
|
||||||
}
|
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
|
|
||||||
int nodeIndex[kMaxNodeLinks] {};
|
int nodeIndex[kMaxNodeLinks] {};
|
||||||
|
|
@ -2132,12 +1797,12 @@ int Bot::findDefendNode (const Vector &origin) {
|
||||||
// find the best node now
|
// find the best node now
|
||||||
for (const auto &path : graph) {
|
for (const auto &path : graph) {
|
||||||
// exclude ladder & current nodes
|
// exclude ladder & current nodes
|
||||||
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || !graph.isVisible (path.number, posIndex)) {
|
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || !vistab.visible (path.number, posIndex)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the 'real' pathfinding distances
|
// use the 'real' pathfinding distances
|
||||||
int distance = graph.getPathDist (srcIndex, path.number);
|
int distance = planner.dist (srcIndex, path.number);
|
||||||
|
|
||||||
// skip wayponts too far
|
// skip wayponts too far
|
||||||
if (distance > kMaxDistance) {
|
if (distance > kMaxDistance) {
|
||||||
|
|
@ -2192,11 +1857,11 @@ int Bot::findDefendNode (const Vector &origin) {
|
||||||
// use statistic if we have them
|
// use statistic if we have them
|
||||||
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||||
if (nodeIndex[i] != kInvalidNodeIndex) {
|
if (nodeIndex[i] != kInvalidNodeIndex) {
|
||||||
int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
int practiceDamage = practice.getDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
||||||
practice = (practice * 100) / graph.getHighestDamageForTeam (m_team);
|
practiceDamage = (practiceDamage * 100) / practice.getHighestDamageForTeam (m_team);
|
||||||
|
|
||||||
minDistance[i] = (practice * 100) / 8192;
|
minDistance[i] = (practiceDamage * 100) / 8192;
|
||||||
minDistance[i] += practice;
|
minDistance[i] += practiceDamage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool sorting = false;
|
bool sorting = false;
|
||||||
|
|
@ -2220,7 +1885,7 @@ int Bot::findDefendNode (const Vector &origin) {
|
||||||
IntArray found;
|
IntArray found;
|
||||||
|
|
||||||
for (const auto &path : graph) {
|
for (const auto &path : graph) {
|
||||||
if (origin.distanceSq (path.origin) < cr::square (static_cast <float> (kMaxDistance)) && graph.isVisible (path.number, posIndex) && !isOccupiedNode (path.number)) {
|
if (origin.distanceSq (path.origin) < cr::sqrf (static_cast <float> (kMaxDistance)) && vistab.visible (path.number, posIndex) && !isOccupiedNode (path.number)) {
|
||||||
found.push (path.number);
|
found.push (path.number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2284,13 +1949,13 @@ int Bot::findCoverNode (float maxDistance) {
|
||||||
// find the best node now
|
// find the best node now
|
||||||
for (const auto &path : graph) {
|
for (const auto &path : graph) {
|
||||||
// exclude ladder, current node and nodes seen by the enemy
|
// exclude ladder, current node and nodes seen by the enemy
|
||||||
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || graph.isVisible (enemyIndex, path.number)) {
|
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || vistab.visible (enemyIndex, path.number)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bool neighbourVisible = false; // now check neighbour nodes for visibility
|
bool neighbourVisible = false; // now check neighbour nodes for visibility
|
||||||
|
|
||||||
for (auto &enemy : enemies) {
|
for (auto &enemy : enemies) {
|
||||||
if (graph.isVisible (enemy, path.number)) {
|
if (vistab.visible (enemy, path.number)) {
|
||||||
neighbourVisible = true;
|
neighbourVisible = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -2302,8 +1967,8 @@ int Bot::findCoverNode (float maxDistance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the 'real' pathfinding distances
|
// use the 'real' pathfinding distances
|
||||||
int distance = graph.getPathDist (srcIndex, path.number);
|
int distance = planner.dist (srcIndex, path.number);
|
||||||
int enemyDistance = graph.getPathDist (enemyIndex, path.number);
|
int enemyDistance = planner.dist (enemyIndex, path.number);
|
||||||
|
|
||||||
if (distance >= enemyDistance) {
|
if (distance >= enemyDistance) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2346,11 +2011,11 @@ int Bot::findCoverNode (float maxDistance) {
|
||||||
// use statistic if we have them
|
// use statistic if we have them
|
||||||
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||||
if (nodeIndex[i] != kInvalidNodeIndex) {
|
if (nodeIndex[i] != kInvalidNodeIndex) {
|
||||||
int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
int practiceDamage = practice.getDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
||||||
practice = (practice * 100) / graph.getHighestDamageForTeam (m_team);
|
practiceDamage = (practiceDamage * 100) / practice.getHighestDamageForTeam (m_team);
|
||||||
|
|
||||||
minDistance[i] = (practice * 100) / 8192;
|
minDistance[i] = (practiceDamage * 100) / 8192;
|
||||||
minDistance[i] += practice;
|
minDistance[i] += practiceDamage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool sorting = false;
|
bool sorting = false;
|
||||||
|
|
@ -2396,23 +2061,36 @@ bool Bot::selectBestNextNode () {
|
||||||
assert (!m_pathWalk.empty ());
|
assert (!m_pathWalk.empty ());
|
||||||
assert (m_pathWalk.hasNext ());
|
assert (m_pathWalk.hasNext ());
|
||||||
|
|
||||||
if (!isOccupiedNode (m_pathWalk.first ())) {
|
auto nextNodeIndex = m_pathWalk.next ();
|
||||||
|
auto currentNodeIndex = m_pathWalk.first ();
|
||||||
|
auto prevNodeIndex = m_currentNodeIndex;
|
||||||
|
|
||||||
|
if (!isOccupiedNode (currentNodeIndex)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &link : m_path->links) {
|
// check the links
|
||||||
if (graph.exists (link.index) && m_pathWalk.first () != link.index && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) {
|
for (const auto &link : graph[prevNodeIndex].links) {
|
||||||
|
|
||||||
// don't use ladder nodes as alternative
|
// skip invalid links, or links that points to itself
|
||||||
if (graph[link.index].flags & (NodeFlag::Ladder | PathFlag::Jump)) {
|
if (!graph.exists (link.index) || currentNodeIndex == link.index) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f && !isOccupiedNode (link.index)) {
|
// skip itn't connected links
|
||||||
m_pathWalk.first () = link.index;
|
if (!graph.isConnected (link.index, nextNodeIndex) || !graph.isConnected (link.index, prevNodeIndex)) {
|
||||||
|
continue;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't use ladder nodes as alternative
|
||||||
|
if (graph[link.index].flags & (NodeFlag::Ladder | NodeFlag::Camp | PathFlag::Jump)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not occupied, just set advance
|
||||||
|
if (!isOccupiedNode (link.index)) {
|
||||||
|
m_pathWalk.first () = link.index;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -2462,7 +2140,7 @@ bool Bot::advanceMovement () {
|
||||||
m_campButtons = 0;
|
m_campButtons = 0;
|
||||||
|
|
||||||
const int nextIndex = m_pathWalk.next ();
|
const int nextIndex = m_pathWalk.next ();
|
||||||
auto kills = static_cast <float> (graph.getDangerDamage (m_team, nextIndex, nextIndex));
|
auto kills = static_cast <float> (practice.getDamage (m_team, nextIndex, nextIndex));
|
||||||
|
|
||||||
// if damage done higher than one
|
// if damage done higher than one
|
||||||
if (kills > 1.0f && bots.getRoundMidTime () > game.time ()) {
|
if (kills > 1.0f && bots.getRoundMidTime () > game.time ()) {
|
||||||
|
|
@ -3010,7 +2688,7 @@ bool Bot::isDeadlyMove (const Vector &to) {
|
||||||
// this function eturns if given location would hurt Bot with falling damage
|
// this function eturns if given location would hurt Bot with falling damage
|
||||||
|
|
||||||
TraceResult tr {};
|
TraceResult tr {};
|
||||||
const auto &direction = (to - pev->origin).normalize (); // 1 unit long
|
const auto &direction = (to - pev->origin).normalize_apx (); // 1 unit long
|
||||||
|
|
||||||
Vector check = to, down = to;
|
Vector check = to, down = to;
|
||||||
down.z -= 1000.0f; // straight down 1000 units
|
down.z -= 1000.0f; // straight down 1000 units
|
||||||
|
|
@ -3022,13 +2700,13 @@ bool Bot::isDeadlyMove (const Vector &to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
float lastHeight = tr.flFraction * 1000.0f; // height from ground
|
float lastHeight = tr.flFraction * 1000.0f; // height from ground
|
||||||
float distance = to.distance (check); // distance from goal
|
float distance = to.distanceSq (check); // distance from goal
|
||||||
|
|
||||||
if (distance <= 30.0f && lastHeight > 150.0f) {
|
if (distance <= cr::sqrf (30.0f) && lastHeight > 150.0f) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (distance > 30.0f) {
|
while (distance > cr::sqrf (30.0f)) {
|
||||||
check = check - direction * 30.0f; // move 10 units closer to the goal...
|
check = check - direction * 30.0f; // move 10 units closer to the goal...
|
||||||
|
|
||||||
down = check;
|
down = check;
|
||||||
|
|
@ -3047,7 +2725,7 @@ bool Bot::isDeadlyMove (const Vector &to) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
lastHeight = height;
|
lastHeight = height;
|
||||||
distance = to.distance (check); // distance from goal
|
distance = to.distanceSq (check); // distance from goal
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -3116,9 +2794,7 @@ void Bot::changeYaw (float speed) {
|
||||||
int Bot::getRandomCampDir () {
|
int Bot::getRandomCampDir () {
|
||||||
// find a good node to look at when camping
|
// find a good node to look at when camping
|
||||||
|
|
||||||
if (m_currentNodeIndex == kInvalidNodeIndex) {
|
ensureCurrentNodeIndex ();
|
||||||
changeNodeIndex (findNearestNode ());
|
|
||||||
}
|
|
||||||
constexpr int kMaxNodesToSearch = 5;
|
constexpr int kMaxNodesToSearch = 5;
|
||||||
|
|
||||||
int count = 0, indices[kMaxNodesToSearch] {};
|
int count = 0, indices[kMaxNodesToSearch] {};
|
||||||
|
|
@ -3126,7 +2802,7 @@ int Bot::getRandomCampDir () {
|
||||||
uint16_t visibility[kMaxNodesToSearch] {};
|
uint16_t visibility[kMaxNodesToSearch] {};
|
||||||
|
|
||||||
for (const auto &path : graph) {
|
for (const auto &path : graph) {
|
||||||
if (m_currentNodeIndex == path.number || !graph.isVisible (m_currentNodeIndex, path.number)) {
|
if (m_currentNodeIndex == path.number || !vistab.visible (m_currentNodeIndex, path.number)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3196,7 +2872,7 @@ void Bot::updateLookAngles () {
|
||||||
float stiffness = 200.0f;
|
float stiffness = 200.0f;
|
||||||
float damping = 25.0f;
|
float damping = 25.0f;
|
||||||
|
|
||||||
if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_isEnemyReachable) && m_difficulty > Difficulty::Normal) {
|
if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_wantsToFire) && m_difficulty > Difficulty::Normal) {
|
||||||
if (m_difficulty == Difficulty::Expert) {
|
if (m_difficulty == Difficulty::Expert) {
|
||||||
accelerate += 600.0f;
|
accelerate += 600.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -3355,7 +3031,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not check clients far away from us
|
// do not check clients far away from us
|
||||||
if (pev->origin.distanceSq (client.origin) > cr::square (320.0f)) {
|
if (pev->origin.distanceSq (client.origin) > cr::sqrf (320.0f)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3364,7 +3040,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
|
||||||
}
|
}
|
||||||
auto length = client.origin.distanceSq (graph[index].origin);
|
auto length = client.origin.distanceSq (graph[index].origin);
|
||||||
|
|
||||||
if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (40.0f), cr::square (90.0f))) {
|
if (length < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (40.0f), cr::sqrf (90.0f))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto bot = bots[client.ent];
|
auto bot = bots[client.ent];
|
||||||
|
|
@ -3417,7 +3093,7 @@ bool Bot::isReachableNode (int index) {
|
||||||
const Vector &dst = graph[index].origin;
|
const Vector &dst = graph[index].origin;
|
||||||
|
|
||||||
// is the destination close enough?
|
// is the destination close enough?
|
||||||
if (dst.distanceSq (src) >= cr::square (600.0f)) {
|
if (dst.distanceSq (src) >= cr::sqrf (600.0f)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3473,3 +3149,100 @@ bool Bot::isBannedNode (int index) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bot::findShortestPath (int srcIndex, int destIndex) {
|
||||||
|
// this function finds the shortest path from source index to destination index
|
||||||
|
|
||||||
|
clearSearchNodes ();
|
||||||
|
|
||||||
|
m_chosenGoalIndex = srcIndex;
|
||||||
|
m_goalValue = 0.0f;
|
||||||
|
|
||||||
|
bool success = planner.find (srcIndex, destIndex, [&] (int index) {
|
||||||
|
m_pathWalk.add (index);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
m_prevGoalIndex = kInvalidNodeIndex;
|
||||||
|
getTask ()->data = kInvalidNodeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) {
|
||||||
|
// this function finds a path from srcIndex to destIndex
|
||||||
|
|
||||||
|
if (!graph.exists (srcIndex)) {
|
||||||
|
logger.error ("%s source path index not valid (%d).", __FUNCTION__, srcIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!graph.exists (destIndex)) {
|
||||||
|
logger.error ("%s destination path index not valid (%d).", __FUNCTION__, destIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto pathPlanner = planner.getAStar ();
|
||||||
|
|
||||||
|
// get correct calculation for heuristic
|
||||||
|
if (pathType == FindPath::Optimal) {
|
||||||
|
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionKillsDistCTWithHostage);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionPathDist);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionKillsDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pathType == FindPath::Safe) {
|
||||||
|
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionNone);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionKillsCTWithHostage);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionNone);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionKills);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionPathDistWithHostage);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pathPlanner->setH (Heuristic::hfunctionPathDist);
|
||||||
|
pathPlanner->setG (Heuristic::gfunctionPathDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearSearchNodes ();
|
||||||
|
|
||||||
|
m_chosenGoalIndex = srcIndex;
|
||||||
|
m_goalValue = 0.0f;
|
||||||
|
|
||||||
|
auto result = pathPlanner->find (m_team, srcIndex, destIndex, [&] (int index) {
|
||||||
|
m_pathWalk.add (index);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// view the results
|
||||||
|
switch (result) {
|
||||||
|
case AStarResult::Success:
|
||||||
|
m_pathWalk.reverse (); // reverse path for path follower
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AStarResult::InternalError:
|
||||||
|
// bot should not roam when this occurs
|
||||||
|
kick (); // kick the bot off...
|
||||||
|
|
||||||
|
logger.error ("A* Search for bot \"%s\" failed with internal pathfinder error. Seems to be graph is broken. Bot removed (re-added).", pev->netname.chars ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AStarResult::Failed:
|
||||||
|
// fallback to shortest path
|
||||||
|
findShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead
|
||||||
|
|
||||||
|
if (cv_debug.bool_ ()) {
|
||||||
|
logger.error ("A* Search for bot \"%s\" has failed. Falling back to shortest-path algorithm. Seems to be graph is broken.", pev->netname.chars ());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
531
src/planner.cpp
Normal file
531
src/planner.cpp
Normal file
|
|
@ -0,0 +1,531 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <yapb.h>
|
||||||
|
|
||||||
|
ConVar cv_path_heuristic_mode ("yb_path_heuristic_mode", "3", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f);
|
||||||
|
ConVar cv_path_floyd_memory_limit ("yb_path_floyd_memory_limit", "3", "Limit maximum floyd-warshall memory (megabytes). Use Dijkstra if memory exceeds.", true, 0.0, 12.0f);
|
||||||
|
ConVar cv_path_dijkstra_simple_distance ("yb_path_dijkstra_simple_distance", "1", "Use simple distance path calculation instead of running full Dijkstra path cycle. Used only when Floyd matrices unavailable due to memory limit.");
|
||||||
|
ConVar cv_path_astar_post_smooth ("yb_path_astar_post_smooth", "1", "Enables post-smoothing for A*. Reduces zig-zags on paths at cost of some CPU cycles.");
|
||||||
|
|
||||||
|
float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) {
|
||||||
|
if (parentIndex == kInvalidNodeIndex) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
auto cost = static_cast <float> (practice.getDamage (team, currentIndex, currentIndex) + practice.getHighestDamageForTeam (team));
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
for (const auto &neighbour : current.links) {
|
||||||
|
if (neighbour.index != kInvalidNodeIndex) {
|
||||||
|
cost += static_cast <float> (practice.getDamage (team, neighbour.index, neighbour.index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.flags & NodeFlag::Crouch) {
|
||||||
|
cost *= 1.5f;
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex) {
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
|
return kInfiniteHeuristic;
|
||||||
|
}
|
||||||
|
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
||||||
|
return gfunctionKillsDist (team, currentIndex, parentIndex) * 500.0f;
|
||||||
|
}
|
||||||
|
return gfunctionKillsDist (team, currentIndex, parentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::gfunctionKills (int team, int currentIndex, int) {
|
||||||
|
auto cost = static_cast <float> (practice.getDamage (team, currentIndex, currentIndex));
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
for (const auto &neighbour : current.links) {
|
||||||
|
if (neighbour.index != kInvalidNodeIndex) {
|
||||||
|
cost += static_cast <float> (practice.getDamage (team, neighbour.index, neighbour.index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.flags & NodeFlag::Crouch) {
|
||||||
|
cost *= 1.5f;
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Heuristic::gfunctionKillsCTWithHostage (int team, int currentIndex, int parentIndex) -> float {
|
||||||
|
if (parentIndex == kInvalidNodeIndex) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
|
return kInfiniteHeuristic;
|
||||||
|
}
|
||||||
|
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
||||||
|
return gfunctionKills (team, currentIndex, parentIndex) * 500.0f;
|
||||||
|
}
|
||||||
|
return gfunctionKills (team, currentIndex, parentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) {
|
||||||
|
if (parentIndex == kInvalidNodeIndex) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &parent = graph[parentIndex];
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
for (const auto &link : parent.links) {
|
||||||
|
if (link.index == currentIndex) {
|
||||||
|
const auto distance = static_cast <float> (link.distance);
|
||||||
|
|
||||||
|
// we don't like ladder or crouch point
|
||||||
|
if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
||||||
|
return distance * 1.5f;
|
||||||
|
}
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kInfiniteHeuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex) {
|
||||||
|
const auto ¤t = graph[currentIndex];
|
||||||
|
|
||||||
|
if (current.flags & NodeFlag::NoHostage) {
|
||||||
|
return kInfiniteHeuristic;
|
||||||
|
}
|
||||||
|
else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) {
|
||||||
|
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 500.0f;
|
||||||
|
}
|
||||||
|
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::hfunctionPathDist (int index, int, int goalIndex) {
|
||||||
|
const auto &start = graph[index];
|
||||||
|
const auto &goal = graph[goalIndex];
|
||||||
|
|
||||||
|
const float x = start.origin.x - goal.origin.x;
|
||||||
|
const float y = start.origin.y - goal.origin.y;
|
||||||
|
const float z = start.origin.z - goal.origin.z;
|
||||||
|
|
||||||
|
switch (cv_path_heuristic_mode.int_ ()) {
|
||||||
|
case 0:
|
||||||
|
return cr::max (cr::max (cr::abs (x), cr::abs (y)), cr::abs (z)); // chebyshev distance
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return cr::abs (x) + cr::abs (y) + cr::abs (z); // manhattan distance
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return 0.0f; // no heuristic
|
||||||
|
|
||||||
|
case 3: {
|
||||||
|
const float dx = cr::abs (x);
|
||||||
|
const float dy = cr::abs (y);
|
||||||
|
const float dz = cr::abs (z);
|
||||||
|
|
||||||
|
const float dmin = cr::min (cr::min (dx, dy), dz);
|
||||||
|
const float dmax = cr::max (cr::max (dx, dy), dz);
|
||||||
|
const float dmid = dx + dy + dz - dmin - dmax;
|
||||||
|
|
||||||
|
const float d1 = 1.0f;
|
||||||
|
const float d2 = cr::sqrtf (2.0f);
|
||||||
|
const float d3 = cr::sqrtf (3.0f);
|
||||||
|
|
||||||
|
return (d3 - d2) * dmin + (d2 - d1) * dmid + d1 * dmax; // diagonal distance
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
case 4:
|
||||||
|
return 10.0f * cr::sqrtf (cr::sqrf (x) + cr::sqrf (y) + cr::sqrf (z)); // euclidean distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::hfunctionPathDistWithHostage (int index, int, int goalIndex) {
|
||||||
|
if (graph[index].flags & NodeFlag::NoHostage) {
|
||||||
|
return kInfiniteHeuristic;
|
||||||
|
}
|
||||||
|
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Heuristic::hfunctionNone (int index, int, int goalIndex) {
|
||||||
|
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStarAlgo::clearRoute () {
|
||||||
|
m_routes.resize (static_cast <size_t> (m_length));
|
||||||
|
|
||||||
|
for (const auto &path : graph) {
|
||||||
|
auto route = &m_routes[path.number];
|
||||||
|
|
||||||
|
route->g = route->f = 0.0f;
|
||||||
|
route->parent = kInvalidNodeIndex;
|
||||||
|
route->state = RouteState::New;
|
||||||
|
}
|
||||||
|
m_routes.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AStarAlgo::cantSkipNode (const int a, const int b) {
|
||||||
|
const auto &ag = graph[a];
|
||||||
|
const auto &bg = graph[b];
|
||||||
|
|
||||||
|
const bool notVisible = !vistab.visible (ag.number, bg.number);
|
||||||
|
|
||||||
|
if (notVisible) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const bool tooHigh = cr::abs (ag.origin.z - bg.origin.z) > 17.0f;
|
||||||
|
|
||||||
|
if (tooHigh) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const bool tooNarrow = (ag.flags | bg.flags) & NodeFlag::Narrow;
|
||||||
|
|
||||||
|
if (tooNarrow) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const bool tooFar = ag.origin.distanceSq (bg.origin) > cr::sqrf (300.0f);
|
||||||
|
|
||||||
|
if (tooFar) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool hasJumps = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||||
|
if ((ag.links[i].flags | bg.links[i].flags) & PathFlag::Jump) {
|
||||||
|
hasJumps = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasJumps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStarAlgo::postSmooth (NodeAdderFn onAddedNode) {
|
||||||
|
int index = 0;
|
||||||
|
m_smoothedPath.emplace (m_constructedPath.first ());
|
||||||
|
|
||||||
|
for (size_t i = 1; i < m_constructedPath.length () - 1; ++i) {
|
||||||
|
if (cantSkipNode (m_smoothedPath[index], m_constructedPath[i + 1])) {
|
||||||
|
++index;
|
||||||
|
m_smoothedPath.emplace (m_constructedPath[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_smoothedPath.emplace (m_constructedPath.last ());
|
||||||
|
|
||||||
|
for (const auto &spn : m_smoothedPath) {
|
||||||
|
onAddedNode (spn);
|
||||||
|
}
|
||||||
|
m_constructedPath.clear ();
|
||||||
|
m_smoothedPath.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdderFn onAddedNode) {
|
||||||
|
if (m_length < kMaxNodeLinks) {
|
||||||
|
return AStarResult::InternalError; // astar needs some nodes to work with
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRoute ();
|
||||||
|
auto srcRoute = &m_routes[srcIndex];
|
||||||
|
|
||||||
|
// put start node into open list
|
||||||
|
srcRoute->g = m_gcalc (botTeam, srcIndex, kInvalidNodeIndex);
|
||||||
|
srcRoute->f = srcRoute->g + m_hcalc (srcIndex, kInvalidNodeIndex, destIndex);
|
||||||
|
srcRoute->state = RouteState::Open;
|
||||||
|
|
||||||
|
m_routeQue.clear ();
|
||||||
|
m_routeQue.emplace (srcIndex, srcRoute->g);
|
||||||
|
|
||||||
|
bool postSmoothPath = cv_path_astar_post_smooth.bool_ ();
|
||||||
|
|
||||||
|
while (!m_routeQue.empty ()) {
|
||||||
|
// remove the first node from the open list
|
||||||
|
int currentIndex = m_routeQue.pop ().index;
|
||||||
|
|
||||||
|
// safes us from bad graph...
|
||||||
|
if (m_routeQue.length () >= getMaxLength () - 1) {
|
||||||
|
return AStarResult::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the current node the goal node?
|
||||||
|
if (currentIndex == destIndex) {
|
||||||
|
// build the complete path
|
||||||
|
do {
|
||||||
|
if (postSmoothPath) {
|
||||||
|
m_constructedPath.emplace (currentIndex);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
onAddedNode (currentIndex);
|
||||||
|
}
|
||||||
|
currentIndex = m_routes[currentIndex].parent;
|
||||||
|
} while (currentIndex != kInvalidNodeIndex);
|
||||||
|
|
||||||
|
// do a post-smooth if requested
|
||||||
|
if (postSmoothPath) {
|
||||||
|
postSmooth (onAddedNode);
|
||||||
|
}
|
||||||
|
return AStarResult::Success;
|
||||||
|
}
|
||||||
|
auto curRoute = &m_routes[currentIndex];
|
||||||
|
|
||||||
|
if (curRoute->state != RouteState::Open) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// put current node into CLOSED list
|
||||||
|
curRoute->state = RouteState::Closed;
|
||||||
|
|
||||||
|
// now expand the current node
|
||||||
|
for (const auto &child : graph[currentIndex].links) {
|
||||||
|
if (child.index == kInvalidNodeIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto childRoute = &m_routes[child.index];
|
||||||
|
|
||||||
|
// calculate the F value as F = G + H
|
||||||
|
const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex);
|
||||||
|
const float h = m_hcalc (child.index, kInvalidNodeIndex, destIndex);
|
||||||
|
const float f = g + h;
|
||||||
|
|
||||||
|
if (childRoute->state == RouteState::New || childRoute->f > f) {
|
||||||
|
// put the current child into open list
|
||||||
|
childRoute->parent = currentIndex;
|
||||||
|
childRoute->state = RouteState::Open;
|
||||||
|
|
||||||
|
childRoute->g = g;
|
||||||
|
childRoute->f = f;
|
||||||
|
|
||||||
|
m_routeQue.emplace (child.index, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AStarResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloydWarshallAlgo::rebuild () {
|
||||||
|
m_length = graph.length ();
|
||||||
|
m_matrix.resize (cr::sqrf (m_length));
|
||||||
|
|
||||||
|
auto matrix = m_matrix.data ();
|
||||||
|
|
||||||
|
// re-initialize matrix every load
|
||||||
|
for (int i = 0; i < m_length; ++i) {
|
||||||
|
for (int j = 0; j < m_length; ++j) {
|
||||||
|
*(matrix + (i * m_length) + j) = { kInvalidNodeIndex, SHRT_MAX };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_length; ++i) {
|
||||||
|
for (const auto &link : graph[i].links) {
|
||||||
|
if (!graph.exists (link.index)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*(matrix + (i * m_length) + link.index) = { link.index, link.distance };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_length; ++i) {
|
||||||
|
(matrix + (i * m_length) + i)->dist = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = 0; k < m_length; ++k) {
|
||||||
|
for (int i = 0; i < m_length; ++i) {
|
||||||
|
for (int j = 0; j < m_length; ++j) {
|
||||||
|
int distance = (matrix + (i * m_length) + k)->dist + (matrix + (k * m_length) + j)->dist;
|
||||||
|
|
||||||
|
if (distance < (matrix + (i * m_length) + j)->dist) {
|
||||||
|
*(matrix + (i * m_length) + j) = { (matrix + (i * m_length) + k)->index, distance };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save (); // save path matrix to file for faster access
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FloydWarshallAlgo::load () {
|
||||||
|
m_length = graph.length ();
|
||||||
|
|
||||||
|
if (!m_length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool dataLoaded = bstor.load <Matrix> (m_matrix);
|
||||||
|
|
||||||
|
// do not rebuild if loaded
|
||||||
|
if (dataLoaded) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
rebuild (); // rebuilds matrix
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloydWarshallAlgo::save () {
|
||||||
|
if (!m_length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bstor.save <Matrix> (m_matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FloydWarshallAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance) {
|
||||||
|
onAddedNode (srcIndex);
|
||||||
|
|
||||||
|
while (srcIndex != destIndex) {
|
||||||
|
srcIndex = (m_matrix.data () + (srcIndex * m_length) + destIndex)->index;
|
||||||
|
|
||||||
|
if (srcIndex < 0) {
|
||||||
|
// only fill path distance on full path
|
||||||
|
if (pathDistance != nullptr) {
|
||||||
|
*pathDistance = dist (srcIndex, destIndex);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!onAddedNode (srcIndex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DijkstraAlgo::init (const int length) {
|
||||||
|
m_length = length;
|
||||||
|
|
||||||
|
m_distance.resize (length);
|
||||||
|
m_parent.resize (length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DijkstraAlgo::resetState () {
|
||||||
|
m_queue.clear ();
|
||||||
|
|
||||||
|
m_parent.fill (kInvalidNodeIndex);
|
||||||
|
m_distance.fill (kInfiniteDistanceLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DijkstraAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance) {
|
||||||
|
resetState ();
|
||||||
|
|
||||||
|
m_queue.emplace (0, srcIndex);
|
||||||
|
m_distance[srcIndex] = 0;
|
||||||
|
|
||||||
|
while (!m_queue.empty ()) {
|
||||||
|
auto p = cr::move (m_queue.pop ());
|
||||||
|
auto current = p.second;
|
||||||
|
|
||||||
|
// finished search
|
||||||
|
if (current == destIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_distance[current] != p.first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &link : graph[current].links) {
|
||||||
|
if (link.index != kInvalidNodeIndex) {
|
||||||
|
auto dlink = m_distance[current] + link.distance;
|
||||||
|
|
||||||
|
if (dlink < m_distance[link.index]) {
|
||||||
|
m_distance[link.index] = dlink;
|
||||||
|
m_parent[link.index] = current;
|
||||||
|
|
||||||
|
m_queue.emplace (m_distance[link.index], link.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static SmallArray <int> pathInReverse;
|
||||||
|
pathInReverse.clear ();
|
||||||
|
|
||||||
|
for (int i = destIndex; i != kInvalidNodeIndex; i = m_parent[i]) {
|
||||||
|
pathInReverse.emplace (i);
|
||||||
|
}
|
||||||
|
pathInReverse.reverse ();
|
||||||
|
|
||||||
|
for (const auto &node : pathInReverse) {
|
||||||
|
if (!onAddedNode (node)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// always fill path distance if we're need to
|
||||||
|
if (pathDistance != nullptr) {
|
||||||
|
*pathDistance = m_distance[destIndex];
|
||||||
|
}
|
||||||
|
return m_distance[destIndex] < kInfiniteDistanceLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DijkstraAlgo::dist (int srcIndex, int destIndex) {
|
||||||
|
int pathDistance = 0;
|
||||||
|
|
||||||
|
find (srcIndex, destIndex, [&] (int) {
|
||||||
|
return true;
|
||||||
|
}, &pathDistance);
|
||||||
|
|
||||||
|
return pathDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathPlanner::PathPlanner () {
|
||||||
|
m_dijkstra = cr::makeUnique <DijkstraAlgo> ();
|
||||||
|
m_floyd = cr::makeUnique <FloydWarshallAlgo> ();
|
||||||
|
m_astar = cr::makeUnique <AStarAlgo> ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathPlanner::init () {
|
||||||
|
const int length = graph.length ();
|
||||||
|
|
||||||
|
const float limitInMb = cv_path_floyd_memory_limit.float_ ();
|
||||||
|
const float memoryUse = static_cast <float> (sizeof (FloydWarshallAlgo::Matrix) * cr::sqrf (length) / 1024 / 1024);
|
||||||
|
|
||||||
|
// if we're have too much memory for floyd matrices, planner will use dijkstra or uniform planner for other than pathfinding needs
|
||||||
|
if (memoryUse > limitInMb) {
|
||||||
|
m_memoryLimitHit = true;
|
||||||
|
}
|
||||||
|
m_dijkstra->init (length);
|
||||||
|
m_astar->init (length);
|
||||||
|
|
||||||
|
// load (re-create) floyds, if we're not hitting memory limits
|
||||||
|
if (!m_memoryLimitHit) {
|
||||||
|
m_floyd->load ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PathPlanner::hasRealPathDistance () const {
|
||||||
|
return !m_memoryLimitHit || !cv_path_dijkstra_simple_distance.bool_ ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PathPlanner::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance) {
|
||||||
|
if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit hit, use dijkstra
|
||||||
|
if (m_memoryLimitHit) {
|
||||||
|
return m_dijkstra->find (srcIndex, destIndex, onAddedNode, pathDistance);
|
||||||
|
}
|
||||||
|
return m_floyd->find (srcIndex, destIndex, onAddedNode, pathDistance);;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PathPlanner::dist (int srcIndex, int destIndex) {
|
||||||
|
if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
|
||||||
|
return kInfiniteDistanceLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srcIndex == destIndex) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit hit, use dijkstra
|
||||||
|
if (m_memoryLimitHit) {
|
||||||
|
if (cv_path_dijkstra_simple_distance.bool_ ()) {
|
||||||
|
return static_cast <int> (graph[srcIndex].origin.distance2d (graph[destIndex].origin));
|
||||||
|
}
|
||||||
|
return m_dijkstra->dist (srcIndex, destIndex);
|
||||||
|
}
|
||||||
|
return m_floyd->dist (srcIndex, destIndex);
|
||||||
|
}
|
||||||
157
src/practice.cpp
Normal file
157
src/practice.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <yapb.h>
|
||||||
|
|
||||||
|
int32_t BotPractice::getIndex (int32_t team, int32_t start, int32_t goal) {
|
||||||
|
if (!exists (team, start, goal)) {
|
||||||
|
return kInvalidNodeIndex;
|
||||||
|
}
|
||||||
|
return m_data[{start, goal, team}].index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::setIndex (int32_t team, int32_t start, int32_t goal, int32_t value) {
|
||||||
|
if (team != Team::Terrorist && team != Team::CT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reliability check
|
||||||
|
if (!graph.exists (start) || !graph.exists (goal) || !graph.exists (value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_data[{start, goal, team}].index = static_cast <int16_t> (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t BotPractice::getValue (int32_t team, int32_t start, int32_t goal) {
|
||||||
|
if (!exists (team, start, goal)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_data[{start, goal, team}].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::setValue (int32_t team, int32_t start, int32_t goal, int32_t value) {
|
||||||
|
if (team != Team::Terrorist && team != Team::CT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reliability check
|
||||||
|
if (!graph.exists (start) || !graph.exists (goal)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_data[{start, goal, team}].value = static_cast <int16_t> (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t BotPractice::getDamage (int32_t team, int32_t start, int32_t goal) {
|
||||||
|
if (!exists (team, start, goal)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_data[{start, goal, team}].damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t value) {
|
||||||
|
if (team != Team::Terrorist && team != Team::CT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reliability check
|
||||||
|
if (!graph.exists (start) || !graph.exists (goal)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_data[{start, goal, team}].damage = static_cast <int16_t> (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::update () {
|
||||||
|
// this function called after each end of the round to update knowledge about most dangerous nodes for each team.
|
||||||
|
|
||||||
|
auto graphLength = graph.length ();
|
||||||
|
|
||||||
|
// no nodes, no practice used or nodes edited or being edited?
|
||||||
|
if (!graphLength || graph.hasChanged () || !vistab.isReady ()) {
|
||||||
|
return; // no action
|
||||||
|
}
|
||||||
|
auto adjustValues = false;
|
||||||
|
|
||||||
|
// get the most dangerous node for this position for both teams
|
||||||
|
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||||
|
auto bestIndex = kInvalidNodeIndex; // best index to store
|
||||||
|
|
||||||
|
for (int i = 0; i < graphLength; ++i) {
|
||||||
|
auto maxDamage = 0;
|
||||||
|
bestIndex = kInvalidNodeIndex;
|
||||||
|
|
||||||
|
for (int j = 0; j < graphLength; ++j) {
|
||||||
|
if (i == j || !vistab.visible (i, j) || !exists (team, i, j)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto actDamage = getDamage (team, i, j);
|
||||||
|
|
||||||
|
if (actDamage > maxDamage) {
|
||||||
|
maxDamage = actDamage;
|
||||||
|
bestIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDamage > PracticeLimit::Damage) {
|
||||||
|
adjustValues = true;
|
||||||
|
}
|
||||||
|
setIndex (team, i, i, bestIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constexpr auto kFullDamageVal = static_cast <int32_t> (PracticeLimit::Damage);
|
||||||
|
constexpr auto kHalfDamageVal = static_cast <int32_t> (PracticeLimit::Damage / 2);
|
||||||
|
|
||||||
|
// adjust values if overflow is about to happen
|
||||||
|
if (adjustValues) {
|
||||||
|
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||||
|
for (int i = 0; i < graphLength; ++i) {
|
||||||
|
for (int j = 0; j < graphLength; ++j) {
|
||||||
|
if (i == j || !exists (team, i, j)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
setDamage (team, i, j, cr::clamp (getDamage (team, i, j) - kHalfDamageVal, 0, kFullDamageVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||||
|
m_teamHighestDamage[team] = cr::clamp (m_teamHighestDamage[team] - kHalfDamageVal, 1, kFullDamageVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::save () {
|
||||||
|
if (!graph.length () || graph.hasChanged ()) {
|
||||||
|
return; // no action
|
||||||
|
}
|
||||||
|
SmallArray <DangerSaveRestore> data;
|
||||||
|
|
||||||
|
// copy hash-map data to our vector
|
||||||
|
m_data.foreach ([&data] (const DangerStorage &ds, const PracticeData &pd) {
|
||||||
|
data.emplace (ds, pd);
|
||||||
|
});
|
||||||
|
bstor.save <DangerSaveRestore> (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotPractice::load () {
|
||||||
|
if (!graph.length ()) {
|
||||||
|
return; // no action
|
||||||
|
}
|
||||||
|
SmallArray <DangerSaveRestore> data;
|
||||||
|
m_data.clear ();
|
||||||
|
|
||||||
|
bool dataLoaded = bstor.load <DangerSaveRestore> (data);
|
||||||
|
|
||||||
|
// copy back to hash table
|
||||||
|
if (dataLoaded) {
|
||||||
|
for (const auto &dsr : data) {
|
||||||
|
if (dsr.data.damage > 0 || dsr.data.index != kInvalidNodeIndex || dsr.data.value > 0) {
|
||||||
|
m_data.insert (dsr.danger, dsr.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
394
src/storage.cpp
Normal file
394
src/storage.cpp
Normal file
|
|
@ -0,0 +1,394 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <yapb.h>
|
||||||
|
|
||||||
|
#if defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS)
|
||||||
|
|
||||||
|
template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *exten, int32_t *outOptions) {
|
||||||
|
auto type = guessType <U> ();
|
||||||
|
String filename = buildPath (storageToBotFile (type.option), true);
|
||||||
|
|
||||||
|
extern ConVar cv_debug, cv_graph_url;
|
||||||
|
|
||||||
|
// graphs can be downloaded...
|
||||||
|
auto isGraph = !!(type.option & StorageOption::Graph);
|
||||||
|
auto isDebug = cv_debug.bool_ ();
|
||||||
|
|
||||||
|
MemFile file (filename); // open the file
|
||||||
|
data.clear ();
|
||||||
|
|
||||||
|
// resize data to fit the stuff
|
||||||
|
auto resizeData = [&] (const size_t length) {
|
||||||
|
data.resize (length); // for non-graph data the graph should be already loaded
|
||||||
|
data.shrink (); // free up memory to minimum
|
||||||
|
|
||||||
|
// ensure we're have enough memory to decompress the data
|
||||||
|
data.ensure (length + ULZ::Excess);
|
||||||
|
};
|
||||||
|
|
||||||
|
// if graph & attempted to load multiple times, bail out, we're failed
|
||||||
|
if (isGraph && ++m_retries > 2) {
|
||||||
|
resetRetries ();
|
||||||
|
|
||||||
|
return error (isGraph, isDebug, file, "Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", type.name, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloader for graph
|
||||||
|
auto download = [&] () -> bool {
|
||||||
|
if (!graph.canDownload ()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto downloadAddress = cv_graph_url.str ();
|
||||||
|
|
||||||
|
auto toDownload = buildPath (storageToBotFile (type.option), false);
|
||||||
|
auto fromDownload = strings.format ("http://%s/graph/%s.graph", downloadAddress, game.getMapName ());
|
||||||
|
|
||||||
|
// try to download
|
||||||
|
if (http.downloadFile (fromDownload, toDownload)) {
|
||||||
|
ctrl.msg ("%s file '%s' successfully downloaded. Processing...", type.name, filename);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctrl.msg ("Can't download '%s' from '%s' to '%s'... (%d).", filename, fromDownload, toDownload, http.getLastStatusCode ());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// tries to reload or open pwf file
|
||||||
|
auto tryReload = [&] () -> bool {
|
||||||
|
file.close ();
|
||||||
|
|
||||||
|
if (!isGraph) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download ()) {
|
||||||
|
return load (data, exten, outOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graph.convertOldFormat ()) {
|
||||||
|
return load (data, exten, outOptions);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// no open no fun
|
||||||
|
if (!file) {
|
||||||
|
if (tryReload ()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the header
|
||||||
|
StorageHeader hdr {};
|
||||||
|
file.read (&hdr, sizeof (StorageHeader));
|
||||||
|
|
||||||
|
// check the magic
|
||||||
|
if (hdr.magic != kStorageMagic && hdr.magic != kStorageMagicUB) {
|
||||||
|
if (tryReload ()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return error (isGraph, isDebug, file, "Unable to read magic of %s (filename: '%s').", type.name, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the path-numbers
|
||||||
|
if (!isGraph && hdr.length != graph.length ()) {
|
||||||
|
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", type.name, filename, hdr.length, graph.length ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the count
|
||||||
|
if (hdr.length == 0 || hdr.length > kMaxNodes || hdr.length < kMaxNodeLinks) {
|
||||||
|
if (tryReload ()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", type.name, filename, hdr.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the version
|
||||||
|
if (hdr.version > type.version && isGraph) {
|
||||||
|
ctrl.msg ("Graph version mismatch %s (filename: '%s'). Version number differs (got: '%d', need: '%d') Please, upgrade %s.", type.name, filename, hdr.version, type.version, product.name);
|
||||||
|
}
|
||||||
|
else if (hdr.version != type.version && !isGraph) {
|
||||||
|
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", type.name, filename, hdr.version, type.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save graph version
|
||||||
|
if (isGraph) {
|
||||||
|
graph.setGraphHeader (&hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the storage type
|
||||||
|
if ((hdr.options & type.option) != type.option) {
|
||||||
|
return error (isGraph, isDebug, file, "Incorrect storage format for %s (filename: '%s').", type.name, filename);
|
||||||
|
}
|
||||||
|
auto compressedSize = static_cast <size_t> (hdr.compressed);
|
||||||
|
auto numberNodes = static_cast <size_t> (hdr.length);
|
||||||
|
|
||||||
|
SmallArray <uint8_t> compressed (compressedSize + sizeof (uint8_t) * ULZ::Excess);
|
||||||
|
|
||||||
|
// graph is not resized upon load
|
||||||
|
if (isGraph) {
|
||||||
|
resizeData (numberNodes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resizeData (hdr.uncompressed / sizeof (U));
|
||||||
|
}
|
||||||
|
|
||||||
|
// read compressed data
|
||||||
|
if (file.read (compressed.data (), sizeof (uint8_t), compressedSize) == compressedSize) {
|
||||||
|
|
||||||
|
// try to uncompress
|
||||||
|
if (ulz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast <uint8_t *> (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) {
|
||||||
|
return error (isGraph, isDebug, file, "Unable to decompress ULZ data for %s (filename: '%s').", type.name, filename);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (outOptions) {
|
||||||
|
outOptions = &hdr.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// author of graph.. save
|
||||||
|
if ((hdr.options & StorageOption::Exten) && exten != nullptr) {
|
||||||
|
auto extenSize = sizeof (ExtenHeader);
|
||||||
|
auto actuallyRead = file.read (exten, extenSize) * extenSize;
|
||||||
|
|
||||||
|
if (isGraph) {
|
||||||
|
resetRetries ();
|
||||||
|
|
||||||
|
ExtenHeader extenHeader;
|
||||||
|
strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author));
|
||||||
|
|
||||||
|
if (extenSize <= actuallyRead) {
|
||||||
|
// write modified by, only if the name is different
|
||||||
|
if (!strings.isEmpty (extenHeader.author) && strncmp (extenHeader.author, exten->modified, cr::bufsize (extenHeader.author)) != 0) {
|
||||||
|
strings.copy (extenHeader.modified, exten->modified, cr::bufsize (exten->modified));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strings.copy (extenHeader.modified, "(none)", cr::bufsize (exten->modified));
|
||||||
|
}
|
||||||
|
extenHeader.mapSize = exten->mapSize;
|
||||||
|
|
||||||
|
// tell graph about exten header
|
||||||
|
graph.setExtenHeader (&extenHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
|
||||||
|
file.close ();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error (isGraph, isDebug, file, "Unable to read ULZ data for %s (filename: '%s').", type.name, filename);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U> bool BotStorage::save (const SmallArray <U> &data, ExtenHeader *exten, int32_t passOptions) {
|
||||||
|
auto type = guessType <U> ();
|
||||||
|
|
||||||
|
// append additional options
|
||||||
|
if (passOptions != 0) {
|
||||||
|
type.option |= passOptions;
|
||||||
|
}
|
||||||
|
auto isGraph = !!(type.option & StorageOption::Graph);
|
||||||
|
|
||||||
|
// do not allow to save graph with less than 8 nodes
|
||||||
|
if (isGraph && graph.length () < kMaxNodeLinks) {
|
||||||
|
ctrl.msg ("Can't save graph data with less than %d nodes. Please add some more before saving.", kMaxNodeLinks);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String filename = buildPath (storageToBotFile (type.option));
|
||||||
|
|
||||||
|
if (data.empty ()) {
|
||||||
|
logger.error ("Unable to save %s file. Empty data. (filename: '%s').", type.name, filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (isGraph) {
|
||||||
|
for (auto &path : graph) {
|
||||||
|
path.display = 0.0f;
|
||||||
|
path.light = illum.getLightLevel (path.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the file
|
||||||
|
File file (filename, "wb");
|
||||||
|
|
||||||
|
// no open no fun
|
||||||
|
if (!file) {
|
||||||
|
logger.error ("Unable to open %s file for writing (filename: '%s').", type.name, filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto rawLength = data.length () * sizeof (U);
|
||||||
|
SmallArray <uint8_t> compressed (rawLength + sizeof (uint8_t) * ULZ::Excess);
|
||||||
|
|
||||||
|
// try to compress
|
||||||
|
auto compressedLength = static_cast <size_t> (ulz.compress (reinterpret_cast <uint8_t *> (data.data ()), static_cast <int32_t> (rawLength), reinterpret_cast <uint8_t *> (compressed.data ())));
|
||||||
|
|
||||||
|
if (compressedLength > 0) {
|
||||||
|
StorageHeader hdr {};
|
||||||
|
|
||||||
|
hdr.magic = kStorageMagic;
|
||||||
|
hdr.version = type.version;
|
||||||
|
hdr.options = type.option;
|
||||||
|
hdr.length = graph.length ();
|
||||||
|
hdr.compressed = static_cast <int32_t> (compressedLength);
|
||||||
|
hdr.uncompressed = static_cast <int32_t> (rawLength);
|
||||||
|
|
||||||
|
file.write (&hdr, sizeof (StorageHeader));
|
||||||
|
file.write (compressed.data (), sizeof (uint8_t), compressedLength);
|
||||||
|
|
||||||
|
// add extension
|
||||||
|
if ((type.option & StorageOption::Exten) && exten != nullptr) {
|
||||||
|
file.write (exten, sizeof (ExtenHeader));
|
||||||
|
}
|
||||||
|
extern ConVar cv_debug;
|
||||||
|
|
||||||
|
// notify only about graph
|
||||||
|
if (isGraph || cv_debug.bool_ ()) {
|
||||||
|
ctrl.msg ("Successfully saved Bots %s data.", type.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error ("Unable to compress %s data (filename: '%s').", type.name, filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Args> bool BotStorage::error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) {
|
||||||
|
auto result = strings.format (fmt, cr::forward <Args> (args)...);
|
||||||
|
|
||||||
|
// display error only for graph file
|
||||||
|
if (isGraph || isDebug) {
|
||||||
|
logger.error (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if graph reset paths
|
||||||
|
if (isGraph) {
|
||||||
|
bots.kickEveryone (true);
|
||||||
|
graph.reset ();
|
||||||
|
}
|
||||||
|
file.close ();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U> BotStorage::SaveLoadData BotStorage::guessType () {
|
||||||
|
if constexpr (cr::is_same <U, FloydWarshallAlgo::Matrix>::value) {
|
||||||
|
return { "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix };
|
||||||
|
}
|
||||||
|
else if constexpr (cr::is_same <U, BotPractice::DangerSaveRestore>::value) {
|
||||||
|
return { "Practice", StorageOption::Practice, StorageVersion::Practice };
|
||||||
|
}
|
||||||
|
else if constexpr (cr::is_same <U, GraphVistable::VisStorage>::value) {
|
||||||
|
return { "Vistable", StorageOption::Vistable, StorageVersion::Vistable };
|
||||||
|
}
|
||||||
|
else if constexpr (cr::is_same <U, Path>::value) {
|
||||||
|
return { "Graph", StorageOption::Graph, StorageVersion::Graph };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
String BotStorage::buildPath (int32_t file, bool isMemoryLoad) {
|
||||||
|
using FilePath = Twin <String, String>;
|
||||||
|
|
||||||
|
static HashMap <int32_t, FilePath> paths = {
|
||||||
|
{ BotFile::Vistable, FilePath ("train", "vis")},
|
||||||
|
{ BotFile::Practice, FilePath ("train", "prc")},
|
||||||
|
{ BotFile::Pathmatrix, FilePath ("train", "pmx")},
|
||||||
|
{ BotFile::LogFile, FilePath ("logs", "txt")},
|
||||||
|
{ BotFile::Graph, FilePath ("graph", "graph")},
|
||||||
|
{ BotFile::PodbotPWF, FilePath ("pwf", "pwf")},
|
||||||
|
{ BotFile::EbotEWP, FilePath ("ewp", "ewp")},
|
||||||
|
};
|
||||||
|
|
||||||
|
static StringArray path;
|
||||||
|
path.clear ();
|
||||||
|
|
||||||
|
// if not memory file we're don't need game dir
|
||||||
|
if (!isMemoryLoad) {
|
||||||
|
path.emplace (game.getRunningModName ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// allways append addons/product
|
||||||
|
path.emplace ("addons");
|
||||||
|
path.emplace (product.folder);
|
||||||
|
|
||||||
|
// the datadir
|
||||||
|
path.emplace ("data");
|
||||||
|
|
||||||
|
// append real filepath
|
||||||
|
path.emplace (paths[file].first);
|
||||||
|
|
||||||
|
// if file is logfile use correct logfile name with date
|
||||||
|
if (file == BotFile::LogFile) {
|
||||||
|
time_t ticks = time (&ticks);
|
||||||
|
tm timeinfo {};
|
||||||
|
|
||||||
|
plat.loctime (&timeinfo, &ticks);
|
||||||
|
auto timebuf = strings.chars ();
|
||||||
|
|
||||||
|
strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo);
|
||||||
|
path.emplace (strings.format ("%s_%s.%s", product.folder, timebuf, paths[file].second));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String mapName = game.getMapName ();
|
||||||
|
path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second));
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally use correct path separarators for us
|
||||||
|
return String::join (path, PATH_SEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t BotStorage::storageToBotFile (int32_t options) {
|
||||||
|
// converts storage option to stroage filename
|
||||||
|
|
||||||
|
if (options & StorageOption::Graph) {
|
||||||
|
return BotFile::Graph;
|
||||||
|
}
|
||||||
|
else if (options & StorageOption::Matrix) {
|
||||||
|
return BotFile::Pathmatrix;
|
||||||
|
}
|
||||||
|
else if (options & StorageOption::Vistable) {
|
||||||
|
return BotFile::Vistable;
|
||||||
|
}
|
||||||
|
else if (options & StorageOption::Practice) {
|
||||||
|
return BotFile::Practice;
|
||||||
|
}
|
||||||
|
return BotFile::Graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotStorage::unlinkFromDisk () {
|
||||||
|
// this function removes graph file from the hard disk
|
||||||
|
|
||||||
|
StringArray unlinkable;
|
||||||
|
bots.kickEveryone (true);
|
||||||
|
|
||||||
|
// if we're delete graph, delete all corresponding to it files
|
||||||
|
unlinkable.emplace (buildPath (BotFile::Graph)); // graph itself
|
||||||
|
unlinkable.emplace (buildPath (BotFile::Practice)); // corresponding to practice
|
||||||
|
unlinkable.emplace (buildPath (BotFile::Vistable)); // corresponding to vistable
|
||||||
|
unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix
|
||||||
|
|
||||||
|
for (const auto &item : unlinkable) {
|
||||||
|
if (File::exists (item)) {
|
||||||
|
plat.removeFile (item.chars ());
|
||||||
|
ctrl.msg ("File %s, has been deleted from the hard disk", item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error ("Unable to open %s", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph.reset (); // re-intialize points
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOT_STORAGE_EXPLICIT_INSTANTIATIONS
|
||||||
|
|
@ -538,75 +538,6 @@ String BotSupport::getCurrentDateTime () {
|
||||||
return String (timebuf);
|
return String (timebuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
String BotSupport::buildPath (int32_t file, bool isMemoryLoad) {
|
|
||||||
using FilePath = Twin <String, String>;
|
|
||||||
|
|
||||||
static HashMap <int32_t, FilePath> paths = {
|
|
||||||
{ BotFile::Vistable, FilePath ("train", "vis")},
|
|
||||||
{ BotFile::Practice, FilePath ("train", "prc")},
|
|
||||||
{ BotFile::Pathmatrix, FilePath ("train", "pmx")},
|
|
||||||
{ BotFile::LogFile, FilePath ("logs", "txt")},
|
|
||||||
{ BotFile::Graph, FilePath ("graph", "graph")},
|
|
||||||
{ BotFile::PodbotPWF, FilePath ("pwf", "pwf")},
|
|
||||||
{ BotFile::EbotEWP, FilePath ("ewp", "ewp")},
|
|
||||||
};
|
|
||||||
|
|
||||||
static StringArray path;
|
|
||||||
path.clear ();
|
|
||||||
|
|
||||||
// if not memory file we're don't need game dir
|
|
||||||
if (!isMemoryLoad) {
|
|
||||||
path.emplace (game.getRunningModName ());
|
|
||||||
}
|
|
||||||
|
|
||||||
// allways append addons/product
|
|
||||||
path.emplace ("addons");
|
|
||||||
path.emplace (product.folder);
|
|
||||||
|
|
||||||
// the datadir
|
|
||||||
path.emplace ("data");
|
|
||||||
|
|
||||||
// append real filepath
|
|
||||||
path.emplace (paths[file].first);
|
|
||||||
|
|
||||||
// if file is logfile use correct logfile name with date
|
|
||||||
if (file == BotFile::LogFile) {
|
|
||||||
time_t ticks = time (&ticks);
|
|
||||||
tm timeinfo {};
|
|
||||||
|
|
||||||
plat.loctime (&timeinfo, &ticks);
|
|
||||||
auto timebuf = strings.chars ();
|
|
||||||
|
|
||||||
strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo);
|
|
||||||
path.emplace (strings.format ("%s_%s.%s", product.folder, timebuf, paths[file].second));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String mapName = game.getMapName ();
|
|
||||||
path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second));
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally use correct path separarators for us
|
|
||||||
return String::join (path, plat.win ? "\\" : "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
// converts storage option to stroage filename
|
|
||||||
|
|
||||||
int32_t BotSupport::storageToBotFile (StorageOption options) {
|
|
||||||
if (options & StorageOption::Graph) {
|
|
||||||
return BotFile::Graph;
|
|
||||||
}
|
|
||||||
else if (options & StorageOption::Matrix) {
|
|
||||||
return BotFile::Pathmatrix;
|
|
||||||
}
|
|
||||||
else if (options & StorageOption::Vistable) {
|
|
||||||
return BotFile::Vistable;
|
|
||||||
}
|
|
||||||
else if (options & StorageOption::Practice) {
|
|
||||||
return BotFile::Practice;
|
|
||||||
}
|
|
||||||
return BotFile::Graph;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) {
|
int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) {
|
||||||
const auto send = [&] (const Twin <const uint8_t *, size_t> &msg) -> int32_t {
|
const auto send = [&] (const Twin <const uint8_t *, size_t> &msg) -> int32_t {
|
||||||
return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength);
|
return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength);
|
||||||
|
|
|
||||||
178
src/vistable.cpp
Normal file
178
src/vistable.cpp
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
//
|
||||||
|
// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge.
|
||||||
|
// Copyright © 2004-2023 YaPB Project <yapb@jeefo.net>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <yapb.h>
|
||||||
|
|
||||||
|
|
||||||
|
void GraphVistable::rebuild () {
|
||||||
|
if (!m_rebuild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_length = graph.length ();
|
||||||
|
|
||||||
|
TraceResult tr {};
|
||||||
|
uint8_t res, shift;
|
||||||
|
|
||||||
|
if (!graph.exists (m_sliceIndex)) {
|
||||||
|
m_sliceIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graph.exists (m_curIndex)) {
|
||||||
|
m_curIndex = 0;
|
||||||
|
}
|
||||||
|
const auto &vis = graph[m_curIndex];
|
||||||
|
|
||||||
|
auto sourceCrouch = vis.origin;
|
||||||
|
auto sourceStand = vis.origin;
|
||||||
|
|
||||||
|
if (vis.flags & NodeFlag::Crouch) {
|
||||||
|
sourceCrouch.z += 12.0f;
|
||||||
|
sourceStand.z += 18.0f + 28.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sourceCrouch.z += -18.0f + 12.0f;
|
||||||
|
sourceStand.z += 28.0f;
|
||||||
|
}
|
||||||
|
auto end = m_sliceIndex + rg.get (250, 400);
|
||||||
|
|
||||||
|
if (end > m_length) {
|
||||||
|
end = m_length;
|
||||||
|
}
|
||||||
|
uint16_t standCount = 0, crouchCount = 0;
|
||||||
|
|
||||||
|
for (int i = m_sliceIndex; i < end; ++i) {
|
||||||
|
const auto &path = graph[i];
|
||||||
|
|
||||||
|
// first check ducked visibility
|
||||||
|
Vector dest = path.origin;
|
||||||
|
|
||||||
|
game.testLine (sourceCrouch, dest, TraceIgnore::Monsters, nullptr, &tr);
|
||||||
|
|
||||||
|
// check if line of sight to object is not blocked (i.e. visible)
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
res = VisIndex::Stand;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = VisIndex::None;
|
||||||
|
}
|
||||||
|
res <<= 1;
|
||||||
|
|
||||||
|
game.testLine (sourceStand, dest, TraceIgnore::Monsters, nullptr, &tr);
|
||||||
|
|
||||||
|
// check if line of sight to object is not blocked (i.e. visible)
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
res |= VisIndex::Stand;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res != VisIndex::None) {
|
||||||
|
dest = path.origin;
|
||||||
|
|
||||||
|
// first check ducked visibility
|
||||||
|
if (path.flags & NodeFlag::Crouch) {
|
||||||
|
dest.z += 18.0f + 28.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dest.z += 28.0f;
|
||||||
|
}
|
||||||
|
game.testLine (sourceCrouch, dest, TraceIgnore::Monsters, nullptr, &tr);
|
||||||
|
|
||||||
|
// check if line of sight to object is not blocked (i.e. visible)
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
res |= VisIndex::Crouch;;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res &= VisIndex::Stand;;
|
||||||
|
}
|
||||||
|
game.testLine (sourceStand, dest, TraceIgnore::Monsters, nullptr, &tr);
|
||||||
|
|
||||||
|
// check if line of sight to object is not blocked (i.e. visible)
|
||||||
|
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||||
|
res |= VisIndex::Stand;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res &= VisIndex::Crouch;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shift = static_cast <uint8_t> ((path.number % 4) << 1);
|
||||||
|
|
||||||
|
m_vistable[vis.number * m_length + path.number] &= ~static_cast <uint8_t> (VisIndex::Any << shift);
|
||||||
|
m_vistable[vis.number * m_length + path.number] |= res << shift;
|
||||||
|
|
||||||
|
if (!(res & VisIndex::Crouch)) {
|
||||||
|
++crouchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(res & VisIndex::Stand)) {
|
||||||
|
++standCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph[vis.number].vis.crouch = crouchCount;
|
||||||
|
graph[vis.number].vis.stand = standCount;
|
||||||
|
|
||||||
|
if (end == m_length) {
|
||||||
|
m_sliceIndex = 0;
|
||||||
|
m_curIndex++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_sliceIndex += rg.get (250, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify host about rebuilding
|
||||||
|
if (m_notifyMsgTimestamp > 0.0f && m_notifyMsgTimestamp < game.time () && end == m_length) {
|
||||||
|
game.print ("Rebuilding vistable... %d%% done.", m_curIndex * 100 / m_length);
|
||||||
|
m_notifyMsgTimestamp = game.time () + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_curIndex == m_length && end == m_length) {
|
||||||
|
m_rebuild = false;
|
||||||
|
m_notifyMsgTimestamp = 0.0f;
|
||||||
|
|
||||||
|
save ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphVistable::startRebuild () {
|
||||||
|
m_rebuild = true;
|
||||||
|
m_notifyMsgTimestamp = game.time ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphVistable::visible (int srcIndex, int destIndex, VisIndex vis) {
|
||||||
|
if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !(((m_vistable[srcIndex * m_length + destIndex] >> ((destIndex % 4) << 1)) & vis) == vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphVistable::load () {
|
||||||
|
m_rebuild = true;
|
||||||
|
m_length = graph.length ();
|
||||||
|
|
||||||
|
m_sliceIndex = 0;
|
||||||
|
m_curIndex = 0;
|
||||||
|
m_notifyMsgTimestamp = 0.0f;
|
||||||
|
|
||||||
|
if (!m_length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool dataLoaded = bstor.load <VisStorage> (m_vistable);
|
||||||
|
|
||||||
|
// if loaded, do not recalculate visibility
|
||||||
|
if (dataLoaded) {
|
||||||
|
m_rebuild = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_vistable.resize (cr::sqrf (m_length));
|
||||||
|
m_notifyMsgTimestamp = game.time ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphVistable::save () {
|
||||||
|
if (!graph.length () || graph.hasChanged () || m_rebuild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bstor.save <VisStorage> (m_vistable);
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
<ClInclude Include="..\ext\hlsdk\model.h" />
|
<ClInclude Include="..\ext\hlsdk\model.h" />
|
||||||
<ClInclude Include="..\ext\hlsdk\progdefs.h" />
|
<ClInclude Include="..\ext\hlsdk\progdefs.h" />
|
||||||
<ClInclude Include="..\ext\hlsdk\util.h" />
|
<ClInclude Include="..\ext\hlsdk\util.h" />
|
||||||
|
<ClInclude Include="..\inc\analyze.h" />
|
||||||
<ClInclude Include="..\inc\config.h" />
|
<ClInclude Include="..\inc\config.h" />
|
||||||
<ClInclude Include="..\inc\control.h" />
|
<ClInclude Include="..\inc\control.h" />
|
||||||
<ClInclude Include="..\inc\engine.h" />
|
<ClInclude Include="..\inc\engine.h" />
|
||||||
|
|
@ -51,13 +52,18 @@
|
||||||
<ClInclude Include="..\inc\manager.h" />
|
<ClInclude Include="..\inc\manager.h" />
|
||||||
<ClInclude Include="..\inc\message.h" />
|
<ClInclude Include="..\inc\message.h" />
|
||||||
<ClInclude Include="..\inc\module.h" />
|
<ClInclude Include="..\inc\module.h" />
|
||||||
|
<ClInclude Include="..\inc\planner.h" />
|
||||||
|
<ClInclude Include="..\inc\practice.h" />
|
||||||
<ClInclude Include="..\inc\product.h" />
|
<ClInclude Include="..\inc\product.h" />
|
||||||
<ClInclude Include="..\inc\sounds.h" />
|
<ClInclude Include="..\inc\sounds.h" />
|
||||||
|
<ClInclude Include="..\inc\storage.h" />
|
||||||
<ClInclude Include="..\inc\support.h" />
|
<ClInclude Include="..\inc\support.h" />
|
||||||
|
<ClInclude Include="..\inc\vistable.h" />
|
||||||
<ClInclude Include="..\inc\yapb.h" />
|
<ClInclude Include="..\inc\yapb.h" />
|
||||||
<ClInclude Include="..\inc\version.h" />
|
<ClInclude Include="..\inc\version.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\src\analyze.cpp" />
|
||||||
<ClCompile Include="..\src\entities.cpp">
|
<ClCompile Include="..\src\entities.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
|
@ -74,8 +80,12 @@
|
||||||
<ClCompile Include="..\src\message.cpp" />
|
<ClCompile Include="..\src\message.cpp" />
|
||||||
<ClCompile Include="..\src\module.cpp" />
|
<ClCompile Include="..\src\module.cpp" />
|
||||||
<ClCompile Include="..\src\navigate.cpp" />
|
<ClCompile Include="..\src\navigate.cpp" />
|
||||||
|
<ClCompile Include="..\src\planner.cpp" />
|
||||||
|
<ClCompile Include="..\src\practice.cpp" />
|
||||||
<ClCompile Include="..\src\sounds.cpp" />
|
<ClCompile Include="..\src\sounds.cpp" />
|
||||||
|
<ClCompile Include="..\src\storage.cpp" />
|
||||||
<ClCompile Include="..\src\support.cpp" />
|
<ClCompile Include="..\src\support.cpp" />
|
||||||
|
<ClCompile Include="..\src\vistable.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="yapb.rc">
|
<ResourceCompile Include="yapb.rc">
|
||||||
|
|
@ -175,7 +185,7 @@
|
||||||
<InlineFunctionExpansion>Default</InlineFunctionExpansion>
|
<InlineFunctionExpansion>Default</InlineFunctionExpansion>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
<DisableLanguageExtensions>false</DisableLanguageExtensions>
|
<DisableLanguageExtensions>false</DisableLanguageExtensions>
|
||||||
<LanguageStandard>Default</LanguageStandard>
|
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||||
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
|
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ResourceCompile>
|
<ResourceCompile>
|
||||||
|
|
@ -256,7 +266,7 @@
|
||||||
<StringPooling>true</StringPooling>
|
<StringPooling>true</StringPooling>
|
||||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<LanguageStandard>Default</LanguageStandard>
|
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ResourceCompile>
|
<ResourceCompile>
|
||||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,21 @@
|
||||||
<ClInclude Include="..\inc\sounds.h">
|
<ClInclude Include="..\inc\sounds.h">
|
||||||
<Filter>inc</Filter>
|
<Filter>inc</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\analyze.h">
|
||||||
|
<Filter>inc</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\practice.h">
|
||||||
|
<Filter>inc</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\storage.h">
|
||||||
|
<Filter>inc</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\planner.h">
|
||||||
|
<Filter>inc</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\vistable.h">
|
||||||
|
<Filter>inc</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\src\botlib.cpp">
|
<ClCompile Include="..\src\botlib.cpp">
|
||||||
|
|
@ -203,6 +218,21 @@
|
||||||
<ClCompile Include="..\src\sounds.cpp">
|
<ClCompile Include="..\src\sounds.cpp">
|
||||||
<Filter>src</Filter>
|
<Filter>src</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\analyze.cpp">
|
||||||
|
<Filter>src</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\practice.cpp">
|
||||||
|
<Filter>src</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\storage.cpp">
|
||||||
|
<Filter>src</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\planner.cpp">
|
||||||
|
<Filter>src</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\vistable.cpp">
|
||||||
|
<Filter>src</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="yapb.rc">
|
<ResourceCompile Include="yapb.rc">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue