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:
jeefo 2023-05-02 09:42:43 +03:00 committed by GitHub
commit e7712a551a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 3114 additions and 1722 deletions

79
inc/analyze.h Normal file
View 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);

View file

@ -216,7 +216,7 @@ public:
m_args.clear ();
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
CR_EXPOSE_GLOBAL_SINGLETON (BotControl, ctrl);

View file

@ -71,6 +71,7 @@ struct ConVarReg {
String info;
String init;
String regval;
String name;
class ConVar *self;
float initial, min, max;
bool missing;
@ -420,6 +421,19 @@ public:
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 {
return ptr->value > 0.0f;
}
@ -447,6 +461,9 @@ public:
void set (const char *val) {
engfuncs.pfnCvar_DirectSet (ptr, val);
}
// revet cvar to default value
void revert ();
};
class MessageWriter final {

View file

@ -7,6 +7,9 @@
#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)
CR_DECLARE_SCOPED_ENUM (NodeFlag,
Lift = cr::bit (1), // wait for lift to be down before approaching this node
@ -43,13 +46,6 @@ CR_DECLARE_SCOPED_ENUM (PathConnection,
Bidirectional
)
// a* route state
CR_DECLARE_SCOPED_ENUM (RouteState,
Open = 0,
Closed,
New
)
// node edit states
CR_DECLARE_SCOPED_ENUM (GraphEdit,
On = cr::bit (1),
@ -57,26 +53,6 @@ CR_DECLARE_SCOPED_ENUM (GraphEdit,
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
CR_DECLARE_SCOPED_ENUM (LiftState,
None = 0,
@ -103,29 +79,13 @@ CR_DECLARE_SCOPED_ENUM (NodeAddFlag,
Goal = 100
)
// a* route
struct Route {
float g, f;
int parent;
RouteState state;
};
CR_DECLARE_SCOPED_ENUM (NotifySound,
Done = 0,
Change = 1,
Added = 2
)
// general stprage header information structure
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
};
#include <vistable.h>
// general waypoint header information structure
struct PODGraphHeader {
@ -136,19 +96,6 @@ struct PODGraphHeader {
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
struct PathLink {
Vector velocity;
@ -157,11 +104,6 @@ struct PathLink {
int16_t index;
};
// defines visibility count
struct PathVis {
uint16_t stand, crouch;
};
// define graph path structure for yapb
struct Path {
int32_t number, flags;
@ -183,74 +125,21 @@ struct PODPath {
PathVis vis;
};
// this structure links nodes returned from pathfinder
class PathWalk final : public DenyCopying {
private:
size_t m_cursor {};
size_t m_length {};
// general stprage header information structure
struct StorageHeader {
int32_t magic;
int32_t version;
int32_t options;
int32_t length;
int32_t compressed;
int32_t uncompressed;
};
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);
}
// 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
};
// graph operation class
@ -260,7 +149,6 @@ public:
private:
int m_editFlags {};
int m_loadAttempts {};
int m_cacheNodeIndex {};
int m_lastJumpNode {};
int m_findWPIndex {};
@ -277,8 +165,8 @@ private:
bool m_endJumpPoint {};
bool m_jumpLearnNode {};
bool m_hasChanged {};
bool m_needsVisRebuild {};
bool m_narrowChecked {};
bool m_silenceMessages {};
Vector m_learnVelocity {};
Vector m_learnPosition {};
@ -293,11 +181,8 @@ private:
IntArray m_rescuePoints {};
IntArray m_visitedGoals {};
SmallArray <Matrix> m_matrix {};
SmallArray <Practice> m_practice {};
public:
SmallArray <Path> m_paths {};
SmallArray <uint8_t> m_vistable {};
HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable;
String m_graphAuthor {};
@ -315,13 +200,10 @@ public:
public:
int getFacingIndex ();
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 getNearestNoBuckets (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1);
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 getBspSize ();
int locateBucket (const Vector &pos);
@ -329,30 +211,23 @@ public:
float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin);
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 index);
bool isNodeReacheableEx (const Vector &src, const Vector &destination, const float maxHeight);
bool isNodeReacheable (const Vector &src, const Vector &destination);
bool isNodeReacheableWithJump (const Vector &src, const Vector &destination);
bool checkNodes (bool teleportPlayer);
bool loadPathMatrix ();
bool isVisited (int index);
bool isAnalyzed () const;
bool saveGraphData ();
bool loadGraphData ();
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 reset ();
void frame ();
void loadPractice ();
void loadVisibility ();
void initNodesTypes ();
void populateNodes ();
void initLightLevels ();
void initNarrowPlaces ();
void addPath (int addIndex, int pathIndex, float distance);
@ -360,16 +235,11 @@ public:
void erase (int target);
void toggleFlags (int toggleFlag);
void setRadius (int index, float radius);
void rebuildVisibility ();
void pathCreate (char dir);
void erasePath ();
void cachePoint (int index);
void calculatePathRadius (int index);
void savePractice ();
void saveVisibility ();
void addBasic ();
void eraseFromDisk ();
void savePathMatrix ();
void setSearchIndex (int index);
void startLearnJump ();
void setVisited (int index);
@ -378,16 +248,14 @@ public:
void addToBucket (const Vector &pos, int index);
void eraseFromBucket (const Vector &pos, int index);
void setBombOrigin (bool reset = false, const Vector &pos = nullptr);
void updateGlobalPractice ();
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 convertToPOD (const Path &path, PODPath &pod);
void convertCampDirection (Path &path);
void setAutoPathDistance (const float distance);
void showStats ();
void showFileInfo ();
void emitNotify (int32_t sound);
IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1);
const IntArray &getNodesInBucket (const Vector &pos);
@ -463,6 +331,25 @@ public:
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:
Path *begin () {
return m_paths.begin ();
@ -481,29 +368,8 @@ public:
}
};
// we're need `bots`
#include <manager.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;
}
#include <practice.h>
// explose global
CR_EXPOSE_GLOBAL_SINGLETON (BotGraph, graph);

277
inc/planner.h Normal file
View 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
View 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
View 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);

View file

@ -7,16 +7,6 @@
#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> {
private:
bool m_needToSendWelcome {};
@ -106,12 +96,6 @@ public:
// get the current date and time as string
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:
// re-show welcome after changelevel ?

61
inc/vistable.h Normal file
View 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);

View file

@ -443,12 +443,6 @@ namespace TaskPri {
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 kGrenadeCheckTime = 0.6f;
constexpr float kSprayDistance = 260.0f;
@ -456,10 +450,6 @@ constexpr float kDoubleSprayDistance = kSprayDistance * 2;
constexpr float kMaxChatterRepeatInterval = 99.0f;
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 kNumWeapons = 26;
constexpr int kMaxCollideMoves = 3;
@ -598,11 +588,78 @@ struct ChatCollection {
// include bot graph stuff
#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
class Bot final {
private:
using RouteTwin = Twin <int, float>;
public:
friend class BotManager;
@ -632,6 +689,7 @@ private:
int m_tryOpenDoor {}; // attempt's to open the door
int m_liftState {}; // state of lift handling
int m_radioSelect {}; // radio entry
int m_lastPredictIndex {}; // last predicted index
float m_headedTime {};
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_hostages {}; // pointer to used hostage entities
Array <Route> m_routes {}; // pointer
Array <int32_t> m_nodeHistory {}; // history of selected goals
BinaryHeap <RouteTwin> m_routeQue {};
Path *m_path {}; // pointer to the current path node
String m_chatBuffer {}; // space for strings (say text...)
FrustumPlane m_frustum[FrustumSide::Num] {};
private:
int pickBestWeapon (int *vec, int count, int moneySave);
int pickBestWeapon (Array <int> &vec, int moneySave);
int getRandomCampDir ();
int findAimingNode (const Vector &to, int &pathLength);
int findNearestNode ();
@ -863,9 +919,7 @@ private:
void updatePracticeDamage (edict_t *attacker, int damage);
void findShortestPath (int srcIndex, int destIndex);
void calculateFrustum ();
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
void clearRoute ();
void debugMsgInternal (const char *str);
void frame ();
void resetCollision ();
@ -950,6 +1004,13 @@ private:
issueCommand ("drop");
}
// ensures current node is ok
void ensureCurrentNodeIndex () {
if (m_currentNodeIndex == kInvalidNodeIndex) {
changeNodeIndex (findNearestNode ());
}
}
public:
entvars_t *pev {};
@ -1189,6 +1250,9 @@ public:
#include "engine.h"
#include "manager.h"
#include "control.h"
#include "planner.h"
#include "storage.h"
#include "analyze.h"
// very global convars
extern ConVar cv_jasonmode;
@ -1211,7 +1275,10 @@ extern ConVar cv_debug_goal;
extern ConVar cv_save_bots_names;
extern ConVar cv_random_knife_attacks;
extern ConVar cv_rotate_bots;
extern ConVar cv_graph_url;
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_roundtime;