bot: implemented asynchronous pathfinding
nav: floyd-warshall matrices and practice updates are done asynchronously by now add: yb_threadpool_workers cvar, that controls number of worker threads bot will use add: cv_autovacate_keep_slots, the amount of slots to keep by auto vacate aim: enemy prediction is now done asynchronously by now bot: minor fixes and refactoring, including analyze suspend mistake (ref #441) note: the master builds are now NOT production ready, please test before installing on real servers!
This commit is contained in:
parent
e7712a551a
commit
a616f25b1a
30 changed files with 743 additions and 421 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 4d370ec500675ad23e9507831bbbbd6bb166ce3a
|
||||
Subproject commit 95405e93fcdad85586103c843a20f8cb5985ae94
|
||||
|
|
@ -15,10 +15,10 @@
|
|||
|
||||
#ifndef CONST_H
|
||||
#define CONST_H
|
||||
//
|
||||
// Constants shared by the engine and dlls
|
||||
// This header file included by engine files and DLL files.
|
||||
// Most came from server.h
|
||||
//
|
||||
// Constants shared by the engine and dlls
|
||||
// This header file included by engine files and DLL files.
|
||||
// Most came from server.h
|
||||
|
||||
#define INTERFACE_VERSION 140
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ typedef struct usercmd_s {
|
|||
byte msec; // Duration in ms of command
|
||||
vec3_t viewangles; // Command view angles.
|
||||
|
||||
// intended velocities
|
||||
// intended velocities
|
||||
float forwardmove; // Forward velocity.
|
||||
float sidemove; // Sideways velocity.
|
||||
float upmove; // Upward velocity.
|
||||
|
|
@ -63,7 +63,7 @@ typedef struct usercmd_s {
|
|||
byte impulse; // Impulse command issued.
|
||||
byte weaponselect; // Current weapon id
|
||||
|
||||
// Experimental player impact stuff.
|
||||
// Experimental player impact stuff.
|
||||
int impact_index;
|
||||
vec3_t impact_position;
|
||||
} usercmd_t;
|
||||
|
|
|
|||
|
|
@ -39,11 +39,9 @@ private:
|
|||
estring_t offset;
|
||||
|
||||
public:
|
||||
explicit string_t () : offset (0)
|
||||
{ }
|
||||
explicit string_t () : offset (0) {}
|
||||
|
||||
string_t (int offset) : offset (offset)
|
||||
{ }
|
||||
string_t (int offset) : offset (offset) {}
|
||||
|
||||
~string_t () = default;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
// Simplified version by Wei Mingzhi
|
||||
// Simplified version by Wei Mingzhi
|
||||
|
||||
#ifndef META_API_H
|
||||
#define META_API_H
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
// Simplified version by Wei Mingzhi
|
||||
// Simplified version by Wei Mingzhi
|
||||
|
||||
#ifndef METAMOD_H
|
||||
#define METAMOD_H
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
*
|
||||
****/
|
||||
|
||||
// stripped down version of com_model.h & pm_info.h
|
||||
// stripped down version of com_model.h & pm_info.h
|
||||
|
||||
#ifndef MODEL_H
|
||||
#define MODEL_H
|
||||
|
|
@ -103,7 +103,7 @@ typedef struct msurface_s {
|
|||
} msurface_t;
|
||||
|
||||
typedef struct cache_user_s {
|
||||
void* data; // extradata
|
||||
void *data; // extradata
|
||||
} cache_user_t;
|
||||
|
||||
typedef struct hull_s {
|
||||
|
|
@ -166,7 +166,7 @@ typedef struct physent_s {
|
|||
char name[32]; // name of model, or "player" or "world".
|
||||
int player;
|
||||
vec3_t origin; // model's origin in world coordinates.
|
||||
struct model_s* model; // only for bsp models
|
||||
struct model_s *model; // only for bsp models
|
||||
} physent_t;
|
||||
|
||||
typedef struct playermove_s {
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ GNU General Public License for more details.
|
|||
#define LUMP_SAVE_NO_DATA 7
|
||||
#define LUMP_SAVE_CORRUPTED 8
|
||||
|
||||
typedef struct areanode_s
|
||||
{
|
||||
typedef struct areanode_s {
|
||||
int axis; // -1 = leaf node
|
||||
float dist;
|
||||
struct areanode_s *children[2];
|
||||
|
|
@ -58,107 +57,105 @@ typedef struct areanode_s
|
|||
link_t portal_edicts;
|
||||
} areanode_t;
|
||||
|
||||
typedef struct server_physics_api_s
|
||||
{
|
||||
typedef struct server_physics_api_s {
|
||||
// unlink edict from old position and link onto new
|
||||
void ( *pfnLinkEdict) ( edict_t *ent, qboolean touch_triggers );
|
||||
double ( *pfnGetServerTime )( void ); // unclamped
|
||||
double ( *pfnGetFrameTime )( void ); // unclamped
|
||||
void* ( *pfnGetModel )( int modelindex );
|
||||
areanode_t* ( *pfnGetHeadnode )( void ); // AABB tree for all physic entities
|
||||
int ( *pfnServerState )( void );
|
||||
void ( *pfnHost_Error )( const char *error, ... ); // cause Host Error
|
||||
// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6
|
||||
void (*pfnLinkEdict) (edict_t *ent, qboolean touch_triggers);
|
||||
double (*pfnGetServerTime)(void); // unclamped
|
||||
double (*pfnGetFrameTime)(void); // unclamped
|
||||
void *(*pfnGetModel)(int modelindex);
|
||||
areanode_t *(*pfnGetHeadnode)(void); // AABB tree for all physic entities
|
||||
int (*pfnServerState)(void);
|
||||
void (*pfnHost_Error)(const char *error, ...); // cause Host Error
|
||||
// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6
|
||||
struct triangleapi_s *pTriAPI; // draw coliisions etc. Only for local system
|
||||
|
||||
// draw debug messages (must be called from DrawOrthoTriangles). Only for local system
|
||||
int ( *pfnDrawConsoleString )( int x, int y, char *string );
|
||||
void ( *pfnDrawSetTextColor )( float r, float g, float b );
|
||||
void ( *pfnDrawConsoleStringLen )( const char *string, int *length, int *height );
|
||||
void ( *Con_NPrintf )( int pos, const char *fmt, ... );
|
||||
void ( *Con_NXPrintf )( struct con_nprint_s *info, const char *fmt, ... );
|
||||
const char *( *pfnGetLightStyle )( int style ); // read custom appreance for selected lightstyle
|
||||
void ( *pfnUpdateFogSettings )( unsigned int packed_fog );
|
||||
char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly );
|
||||
struct msurface_s *(*pfnTraceSurface)( edict_t *pTextureEntity, const float *v1, const float *v2 );
|
||||
const byte *(*pfnGetTextureData)( unsigned int texnum );
|
||||
int (*pfnDrawConsoleString)(int x, int y, char *string);
|
||||
void (*pfnDrawSetTextColor)(float r, float g, float b);
|
||||
void (*pfnDrawConsoleStringLen)(const char *string, int *length, int *height);
|
||||
void (*Con_NPrintf)(int pos, const char *fmt, ...);
|
||||
void (*Con_NXPrintf)(struct con_nprint_s *info, const char *fmt, ...);
|
||||
const char *(*pfnGetLightStyle)(int style); // read custom appreance for selected lightstyle
|
||||
void (*pfnUpdateFogSettings)(unsigned int packed_fog);
|
||||
char **(*pfnGetFilesList)(const char *pattern, int *numFiles, int gamedironly);
|
||||
struct msurface_s *(*pfnTraceSurface)(edict_t *pTextureEntity, const float *v1, const float *v2);
|
||||
const byte *(*pfnGetTextureData)(unsigned int texnum);
|
||||
|
||||
// static allocations
|
||||
void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline );
|
||||
void (*pfnMemFree)( void *mem, const char *filename, const int fileline );
|
||||
void *(*pfnMemAlloc)(size_t cb, const char *filename, const int fileline);
|
||||
void (*pfnMemFree)(void *mem, const char *filename, const int fileline);
|
||||
|
||||
// trace & contents
|
||||
int (*pfnMaskPointContents)( const float *pos, int groupmask );
|
||||
struct trace_t (*pfnTrace)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e );
|
||||
struct trace_t (*pfnTraceNoEnts)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e );
|
||||
int (*pfnBoxInPVS)( const float *org, const float *boxmins, const float *boxmaxs );
|
||||
int (*pfnMaskPointContents)(const float *pos, int groupmask);
|
||||
struct trace_t (*pfnTrace)(const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e);
|
||||
struct trace_t (*pfnTraceNoEnts)(const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e);
|
||||
int (*pfnBoxInPVS)(const float *org, const float *boxmins, const float *boxmaxs);
|
||||
|
||||
// message handler (missed function to write raw bytes)
|
||||
void (*pfnWriteBytes)( const byte *bytes, int count );
|
||||
void (*pfnWriteBytes)(const byte *bytes, int count);
|
||||
|
||||
// BSP lump management
|
||||
int (*pfnCheckLump)( const char *filename, const int lump, int *lumpsize );
|
||||
int (*pfnReadLump)( const char *filename, const int lump, void **lumpdata, int *lumpsize );
|
||||
int (*pfnSaveLump)( const char *filename, const int lump, void *lumpdata, int lumpsize );
|
||||
int (*pfnCheckLump)(const char *filename, const int lump, int *lumpsize);
|
||||
int (*pfnReadLump)(const char *filename, const int lump, void **lumpdata, int *lumpsize);
|
||||
int (*pfnSaveLump)(const char *filename, const int lump, void *lumpdata, int lumpsize);
|
||||
|
||||
// FS tools
|
||||
int (*pfnSaveFile)( const char *filename, const void *data, int len );
|
||||
const byte *(*pfnLoadImagePixels)( const char *filename, int *width, int *height );
|
||||
int (*pfnSaveFile)(const char *filename, const void *data, int len);
|
||||
const byte *(*pfnLoadImagePixels)(const char *filename, int *width, int *height);
|
||||
|
||||
const char* (*pfnGetModelName)( int modelindex );
|
||||
const char *(*pfnGetModelName)(int modelindex);
|
||||
} server_physics_api_t;
|
||||
|
||||
// physic callbacks
|
||||
typedef struct physics_interface_s
|
||||
{
|
||||
typedef struct physics_interface_s {
|
||||
int version;
|
||||
// passed through pfnCreate (0 is attempt to create, -1 is reject)
|
||||
int ( *SV_CreateEntity )( edict_t *pent, const char *szName );
|
||||
int (*SV_CreateEntity)(edict_t *pent, const char *szName);
|
||||
// run custom physics for each entity (return 0 to use built-in engine physic)
|
||||
int ( *SV_PhysicsEntity )( edict_t *pEntity );
|
||||
int (*SV_PhysicsEntity)(edict_t *pEntity);
|
||||
// spawn entities with internal mod function e.g. for re-arrange spawn order (0 - use engine parser, 1 - use mod parser)
|
||||
int ( *SV_LoadEntities )( const char *mapname, char *entities );
|
||||
int (*SV_LoadEntities)(const char *mapname, char *entities);
|
||||
// update conveyor belt for clients
|
||||
void ( *SV_UpdatePlayerBaseVelocity )( edict_t *ent );
|
||||
void (*SV_UpdatePlayerBaseVelocity)(edict_t *ent);
|
||||
// The game .dll should return 1 if save game should be allowed
|
||||
int ( *SV_AllowSaveGame )( void );
|
||||
// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6
|
||||
int (*SV_AllowSaveGame)(void);
|
||||
// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6
|
||||
// override trigger area checking and touching
|
||||
int ( *SV_TriggerTouch )( edict_t *pent, edict_t *trigger );
|
||||
int (*SV_TriggerTouch)(edict_t *pent, edict_t *trigger);
|
||||
// some engine features can be enabled only through this function
|
||||
unsigned int ( *SV_CheckFeatures )( void );
|
||||
unsigned int (*SV_CheckFeatures)(void);
|
||||
// used for draw debug collisions for custom physic engine etc
|
||||
void ( *DrawDebugTriangles )( void );
|
||||
void (*DrawDebugTriangles)(void);
|
||||
// used for draw debug overlay (textured)
|
||||
void ( *DrawNormalTriangles )( void );
|
||||
void (*DrawNormalTriangles)(void);
|
||||
// used for draw debug messages (2d mode)
|
||||
void ( *DrawOrthoTriangles )( void );
|
||||
void (*DrawOrthoTriangles)(void);
|
||||
// tracing entities with SOLID_CUSTOM mode on a server (not used by pmove code)
|
||||
void ( *ClipMoveToEntity)( edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace );
|
||||
void (*ClipMoveToEntity)(edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace);
|
||||
// tracing entities with SOLID_CUSTOM mode on a server (only used by pmove code)
|
||||
void ( *ClipPMoveToEntity)( struct physent_s *pe, const float *start, float *mins, float *maxs, const float *end, struct pmtrace_s *tr );
|
||||
void (*ClipPMoveToEntity)(struct physent_s *pe, const float *start, float *mins, float *maxs, const float *end, struct pmtrace_s *tr);
|
||||
// called at end the frame of SV_Physics call
|
||||
void ( *SV_EndFrame )( void );
|
||||
void (*SV_EndFrame)(void);
|
||||
// obsolete
|
||||
void (*pfnPrepWorldFrame)( void );
|
||||
void (*pfnPrepWorldFrame)(void);
|
||||
// called through save\restore process
|
||||
void (*pfnCreateEntitiesInRestoreList)( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world );
|
||||
void (*pfnCreateEntitiesInRestoreList)(SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world);
|
||||
// allocate custom string (e.g. using user implementation of stringtable, not engine strings)
|
||||
string_t (*pfnAllocString)( const char *szValue );
|
||||
string_t (*pfnAllocString)(const char *szValue);
|
||||
// make custom string (e.g. using user implementation of stringtable, not engine strings)
|
||||
string_t (*pfnMakeString)( const char *szValue );
|
||||
string_t (*pfnMakeString)(const char *szValue);
|
||||
// read custom string (e.g. using user implementation of stringtable, not engine strings)
|
||||
const char* (*pfnGetString)( string_t iString );
|
||||
const char *(*pfnGetString)(string_t iString);
|
||||
// helper for restore custom decals that have custom message (e.g. Paranoia)
|
||||
int (*pfnRestoreDecal)( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent );
|
||||
int (*pfnRestoreDecal)(struct decallist_s *entry, edict_t *pEdict, qboolean adjacent);
|
||||
// handle custom trigger touching for player
|
||||
void (*PM_PlayerTouch)( struct playermove_s *ppmove, edict_t *client );
|
||||
void (*PM_PlayerTouch)(struct playermove_s *ppmove, edict_t *client);
|
||||
// alloc or destroy model custom data (called only for dedicated servers, otherwise using an client version)
|
||||
void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer );
|
||||
void (*Mod_ProcessUserData)(struct model_s *mod, qboolean create, const byte *buffer);
|
||||
// select BSP-hull for trace with specified mins\maxs
|
||||
void *(*SV_HullForBsp)( edict_t *ent, const float *mins, const float *maxs, float *offset );
|
||||
void *(*SV_HullForBsp)(edict_t *ent, const float *mins, const float *maxs, float *offset);
|
||||
// handle player custom think function
|
||||
int (*SV_PlayerThink)( edict_t *ent, float frametime, double time );
|
||||
int (*SV_PlayerThink)(edict_t *ent, float frametime, double time);
|
||||
} physics_interface_t;
|
||||
|
||||
#endif//PHYSINT_H
|
||||
|
|
|
|||
|
|
@ -146,6 +146,9 @@ public:
|
|||
// initialize levels
|
||||
void levelInitialize (edict_t *entities, int max);
|
||||
|
||||
// shutdown levels
|
||||
void levelShutdown ();
|
||||
|
||||
// display world line
|
||||
void drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type = DrawLine::Simple);
|
||||
|
||||
|
|
@ -688,7 +691,6 @@ public:
|
|||
int close (DLSYM_HANDLE module) {
|
||||
if (m_self.handle () == module) {
|
||||
disable ();
|
||||
|
||||
return m_dlclose (module);
|
||||
}
|
||||
return m_dlclose (module);
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ private:
|
|||
int m_lastJumpNode {};
|
||||
int m_findWPIndex {};
|
||||
int m_facingAtIndex {};
|
||||
int m_highestDamage[kGameTeamNum] {};
|
||||
int m_autoSaveCount {};
|
||||
|
||||
float m_timeJumpStarted {};
|
||||
|
|
@ -265,14 +264,6 @@ public:
|
|||
return m_paths.length () / 2;
|
||||
}
|
||||
|
||||
int getHighestDamageForTeam (int team) const {
|
||||
return cr::max (1, m_highestDamage[team]);
|
||||
}
|
||||
|
||||
void setHighestDamageForTeam (int team, int value) {
|
||||
m_highestDamage[team] = value;
|
||||
}
|
||||
|
||||
StringRef getAuthor () const {
|
||||
return m_graphAuthor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ private:
|
|||
float m_maintainTime {}; // time to maintain bot creation
|
||||
float m_quotaMaintainTime {}; // time to maintain bot quota
|
||||
float m_grenadeUpdateTime {}; // time to update active grenades
|
||||
float m_entityUpdateTime {}; // time to update intresting entities
|
||||
float m_entityUpdateTime {}; // time to update interesting entities
|
||||
float m_plantSearchUpdateTime {}; // time to update for searching planted bomb
|
||||
float m_lastChatTime {}; // global chat time timestamp
|
||||
float m_timeBombPlanted {}; // time the bomb were planted
|
||||
|
|
@ -81,7 +81,7 @@ private:
|
|||
bool m_roundOver {};
|
||||
|
||||
Array <edict_t *> m_activeGrenades {}; // holds currently active grenades on the map
|
||||
Array <edict_t *> m_intrestingEntities {}; // holds currently intresting entities on the map
|
||||
Array <edict_t *> m_interestingEntities {}; // holds currently interesting entities on the map
|
||||
|
||||
Deque <String> m_saveBotNames {}; // bots names that persist upon changelevel
|
||||
Deque <BotRequest> m_addRequests {}; // bot creation tab
|
||||
|
|
@ -143,7 +143,7 @@ public:
|
|||
void initFilters ();
|
||||
void resetFilters ();
|
||||
void updateActiveGrenade ();
|
||||
void updateIntrestingEntities ();
|
||||
void updateInterestingEntities ();
|
||||
void captureChatRadio (const char *cmd, const char *arg, edict_t *ent);
|
||||
void notifyBombDefuse ();
|
||||
void execGameEntity (edict_t *ent);
|
||||
|
|
@ -160,16 +160,16 @@ public:
|
|||
return m_activeGrenades;
|
||||
}
|
||||
|
||||
const Array <edict_t *> &getIntrestingEntities () {
|
||||
return m_intrestingEntities;
|
||||
const Array <edict_t *> &getInterestingEntities () {
|
||||
return m_interestingEntities;
|
||||
}
|
||||
|
||||
bool hasActiveGrenades () const {
|
||||
return !m_activeGrenades.empty ();
|
||||
}
|
||||
|
||||
bool hasIntrestingEntities () const {
|
||||
return !m_intrestingEntities.empty ();
|
||||
bool hasInterestingEntities () const {
|
||||
return !m_interestingEntities.empty ();
|
||||
}
|
||||
|
||||
bool checkTeamEco (int team) const {
|
||||
|
|
@ -302,5 +302,31 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
// bot async worker wrapper
|
||||
class BotThreadWorker final : public Singleton <BotThreadWorker> {
|
||||
private:
|
||||
ThreadPool m_botWorker;
|
||||
|
||||
public:
|
||||
BotThreadWorker () = default;
|
||||
~BotThreadWorker () = default;
|
||||
|
||||
public:
|
||||
void shutdown ();
|
||||
void startup (int workers);
|
||||
|
||||
public:
|
||||
template <typename F> void enqueue (F &&fn) {
|
||||
if (m_botWorker.threadCount () == 0) {
|
||||
fn (); // no threads, no fun, just run task in current thread
|
||||
return;
|
||||
}
|
||||
m_botWorker.enqueue (cr::forward <F> (fn));
|
||||
}
|
||||
};
|
||||
|
||||
// explose global
|
||||
CR_EXPOSE_GLOBAL_SINGLETON (BotManager, bots);
|
||||
|
||||
// expose async worker
|
||||
CR_EXPOSE_GLOBAL_SINGLETON (BotThreadWorker, worker);
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public:
|
|||
};
|
||||
|
||||
// A* algorithm for bots
|
||||
class AStarAlgo final {
|
||||
class AStarAlgo final : public DenyCopying {
|
||||
public:
|
||||
using HeuristicFn = Heuristic::Func;
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ private:
|
|||
Array <int> m_smoothedPath;
|
||||
|
||||
private:
|
||||
// cleares the currently built route
|
||||
// clears the currently built route
|
||||
void clearRoute ();
|
||||
|
||||
// can the node can be skipped?
|
||||
|
|
@ -118,6 +118,10 @@ private:
|
|||
void postSmooth (NodeAdderFn onAddedNode);
|
||||
|
||||
public:
|
||||
explicit AStarAlgo (const int length) {
|
||||
init (length);
|
||||
}
|
||||
|
||||
AStarAlgo () = default;
|
||||
~AStarAlgo () = default;
|
||||
|
||||
|
|
@ -180,6 +184,9 @@ public:
|
|||
|
||||
private:
|
||||
// create floyd matrics
|
||||
void syncRebuild ();
|
||||
|
||||
// async rebuild
|
||||
void rebuild ();
|
||||
|
||||
public:
|
||||
|
|
@ -201,6 +208,9 @@ public:
|
|||
|
||||
// dijkstra shortest path algorithm
|
||||
class DijkstraAlgo final {
|
||||
private:
|
||||
mutable Mutex m_cs {};
|
||||
|
||||
private:
|
||||
using Route = Twin <int, int>;
|
||||
|
||||
|
|
@ -215,11 +225,6 @@ 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);
|
||||
|
|
@ -236,7 +241,6 @@ class PathPlanner : public Singleton <PathPlanner> {
|
|||
private:
|
||||
UniquePtr <DijkstraAlgo> m_dijkstra;
|
||||
UniquePtr <FloydWarshallAlgo> m_floyd;
|
||||
UniquePtr <AStarAlgo > m_astar;
|
||||
bool m_memoryLimitHit {};
|
||||
|
||||
public:
|
||||
|
|
@ -261,17 +265,15 @@ public:
|
|||
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);
|
||||
float dist (int srcIndex, int destIndex);
|
||||
|
||||
// get the precise distanace regardless of cvar
|
||||
float preciseDistance (int srcIndex, int destIndex);
|
||||
};
|
||||
|
||||
CR_EXPOSE_GLOBAL_SINGLETON (PathPlanner, planner);
|
||||
|
|
|
|||
|
|
@ -80,17 +80,22 @@ public:
|
|||
DangerSaveRestore (const DangerStorage &ds, const PracticeData &pd) : danger (ds), data (pd) {}
|
||||
};
|
||||
|
||||
private:
|
||||
HashMap <DangerStorage, PracticeData> m_data {};
|
||||
int32_t m_teamHighestDamage[kGameTeamNum] {};
|
||||
|
||||
// avoid concurrent access to practice
|
||||
mutable Mutex m_damageUpdateLock {};
|
||||
|
||||
public:
|
||||
BotPractice () = default;
|
||||
~BotPractice () = default;
|
||||
|
||||
private:
|
||||
inline bool exists (int32_t team, int32_t start, int32_t goal) const {
|
||||
bool exists (int32_t team, int32_t start, int32_t goal) const {
|
||||
return m_data.exists ({ start, goal, team });
|
||||
}
|
||||
void syncUpdate ();
|
||||
|
||||
public:
|
||||
int32_t getIndex (int32_t team, int32_t start, int32_t goal);
|
||||
|
|
@ -102,17 +107,21 @@ public:
|
|||
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);
|
||||
|
||||
// interlocked get damage
|
||||
float plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage);
|
||||
|
||||
public:
|
||||
void update ();
|
||||
void load ();
|
||||
void save ();
|
||||
|
||||
public:
|
||||
int32_t getHighestDamageForTeam (int32_t team) const {
|
||||
return cr::max (1, m_teamHighestDamage[team]);
|
||||
template <typename U = int32_t> U getHighestDamageForTeam (int32_t team) const {
|
||||
return static_cast <U> (cr::max (1, m_teamHighestDamage[team]));
|
||||
}
|
||||
|
||||
void setHighestDamageForTeam (int32_t team, int32_t value) {
|
||||
MutexScopedLock lock (m_damageUpdateLock);
|
||||
m_teamHighestDamage[team] = value;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
#pragma once
|
||||
|
||||
class BotSupport final : public Singleton <BotSupport> {
|
||||
private:
|
||||
mutable Mutex m_cs {};
|
||||
|
||||
private:
|
||||
bool m_needToSendWelcome {};
|
||||
float m_welcomeReceiveTime {};
|
||||
|
|
@ -78,6 +81,9 @@ public:
|
|||
// generates ping bitmask for SVC_PINGS message
|
||||
int getPingBitmask (edict_t *ent, int loss, int ping);
|
||||
|
||||
// calculate our own pings for all the players
|
||||
void syncCalculatePings ();
|
||||
|
||||
// calculate our own pings for all the players
|
||||
void calculatePings ();
|
||||
|
||||
|
|
|
|||
26
inc/yapb.h
26
inc/yapb.h
|
|
@ -663,6 +663,10 @@ class Bot final {
|
|||
public:
|
||||
friend class BotManager;
|
||||
|
||||
private:
|
||||
mutable Mutex m_pathFindLock {};
|
||||
mutable Mutex m_predictLock {};
|
||||
|
||||
private:
|
||||
uint32_t m_states {}; // sensing bitstates
|
||||
uint32_t m_collideMoves[kMaxCollideMoves] {}; // sorted array of movements
|
||||
|
|
@ -689,7 +693,9 @@ 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
|
||||
|
||||
int m_lastPredictIndex { kInvalidNodeIndex }; // last predicted path index
|
||||
int m_lastPredictLength {}; // last predicted path length
|
||||
|
||||
float m_headedTime {};
|
||||
float m_prevTime {}; // time previously checked movement speed
|
||||
|
|
@ -747,6 +753,7 @@ private:
|
|||
|
||||
bool m_moveToGoal {}; // bot currently moving to goal??
|
||||
bool m_isStuck {}; // bot is stuck
|
||||
bool m_isStale {}; // bot is leaving server
|
||||
bool m_isReloading {}; // bot is reloading a gun
|
||||
bool m_forceRadio {}; // should bot use radio anyway?
|
||||
bool m_defendedBomb {}; // defend action issued
|
||||
|
|
@ -773,6 +780,7 @@ private:
|
|||
FindPath m_pathType {}; // which pathfinder to use
|
||||
uint8_t m_enemyParts {}; // visibility flags
|
||||
TraceResult m_lastTrace[TraceChannel::Num] {}; // last trace result
|
||||
UniquePtr <class AStarAlgo> m_planner;
|
||||
|
||||
edict_t *m_pickupItem {}; // pointer to entity of item to use/pickup
|
||||
edict_t *m_itemIgnore {}; // pointer to entity to ignore for pickup
|
||||
|
|
@ -888,7 +896,6 @@ private:
|
|||
|
||||
void doPlayerAvoidance (const Vector &normal);
|
||||
void selectCampButtons (int index);
|
||||
void markStale ();
|
||||
void instantChatter (int type);
|
||||
void update ();
|
||||
void runMovement ();
|
||||
|
|
@ -920,6 +927,7 @@ private:
|
|||
void findShortestPath (int srcIndex, int destIndex);
|
||||
void calculateFrustum ();
|
||||
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
|
||||
void syncFindPath (int srcIndex, int destIndex, FindPath pathType);
|
||||
void debugMsgInternal (const char *str);
|
||||
void frame ();
|
||||
void resetCollision ();
|
||||
|
|
@ -933,6 +941,7 @@ private:
|
|||
void decideFollowUser ();
|
||||
void attackMovement ();
|
||||
void findValidNode ();
|
||||
void setPathOrigin ();
|
||||
void fireWeapons ();
|
||||
void selectWeapons (float distance, int index, int id, int choosen);
|
||||
void focusEnemy ();
|
||||
|
|
@ -940,6 +949,9 @@ private:
|
|||
void selectSecondary ();
|
||||
void selectWeaponById (int id);
|
||||
void selectWeaponByIndex (int index);
|
||||
void refreshEnemyPredict ();
|
||||
void syncUpdatePredictedIndex ();
|
||||
void updatePredictedIndex ();
|
||||
|
||||
void completeTask ();
|
||||
void executeTasks ();
|
||||
|
|
@ -1094,11 +1106,13 @@ public:
|
|||
bool m_hasNVG {}; // does bot has nightvision goggles
|
||||
bool m_usesNVG {}; // does nightvision goggles turned on
|
||||
bool m_hasC4 {}; // does bot has c4 bomb
|
||||
bool m_hasHostage {}; // does bot owns some hostages
|
||||
bool m_hasProgressBar {}; // has progress bar on a HUD
|
||||
bool m_jumpReady {}; // is double jump ready
|
||||
bool m_canChooseAimDirection {}; // can choose aiming direction
|
||||
bool m_isEnemyReachable {}; // direct line to enemy
|
||||
bool m_kickedByRotation {}; // is bot kicked due to rotation ?
|
||||
bool m_kickMeFromServer {}; // kick the bot off the server?
|
||||
|
||||
edict_t *m_doubleJumpEntity {}; // pointer to entity that request double jump
|
||||
edict_t *m_radioEntity {}; // pointer to entity issuing a radio command
|
||||
|
|
@ -1121,7 +1135,11 @@ public:
|
|||
|
||||
public:
|
||||
Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
|
||||
~Bot () = default;
|
||||
|
||||
// need to wait until all threads will finish it's work before terminating bot object
|
||||
~Bot () {
|
||||
MutexScopedLock lock (m_pathFindLock);
|
||||
}
|
||||
|
||||
public:
|
||||
void logic (); /// the things that can be executed while skipping frames
|
||||
|
|
@ -1154,7 +1172,7 @@ public:
|
|||
void resetDoubleJump ();
|
||||
void startDoubleJump (edict_t *ent);
|
||||
void sendBotToOrigin (const Vector &origin);
|
||||
|
||||
void markStale ();
|
||||
bool hasHostage ();
|
||||
bool usesRifle ();
|
||||
bool usesPistol ();
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
|||
# link needed libraries
|
||||
if os == 'linux'
|
||||
ldflags += [
|
||||
'-lm','-ldl'
|
||||
'-lm','-ldl', '-lpthread'
|
||||
]
|
||||
elif os == 'windows'
|
||||
if cxx == 'gcc' or (cross and cxx == 'clang')
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ void GraphAnalyze::suspend () {
|
|||
m_updateInterval = 0.0f;
|
||||
m_isAnalyzing = false;
|
||||
m_isAnalyzed = false;
|
||||
m_basicsCreated = false;
|
||||
}
|
||||
|
||||
void GraphAnalyze::finish () {
|
||||
|
|
@ -144,7 +145,7 @@ void GraphAnalyze::finish () {
|
|||
|
||||
ctrl.msg ("Complete map analyzation.");
|
||||
|
||||
// autosave bots graph
|
||||
// auto save bots graph
|
||||
if (cv_graph_analyze_auto_save.bool_ ()) {
|
||||
if (!graph.saveGraphData ()) {
|
||||
ctrl.msg ("Can't save analyzed graph. Internal error.");
|
||||
|
|
@ -170,7 +171,7 @@ void GraphAnalyze::optimize () {
|
|||
}
|
||||
cleanup ();
|
||||
|
||||
// clear the uselss connections
|
||||
// clear the useless connections
|
||||
if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) {
|
||||
for (auto i = 0; i < graph.length (); ++i) {
|
||||
graph.clearConnections (i);
|
||||
|
|
|
|||
219
src/botlib.cpp
219
src/botlib.cpp
|
|
@ -13,7 +13,7 @@ ConVar cv_user_follow_percent ("yb_user_follow_percent", "20", "Specifies the pe
|
|||
ConVar cv_user_max_followers ("yb_user_max_followers", "1", "Specifies how many bots can follow a single user.", true, 0.0f, static_cast <float> (kGameMaxPlayers / 4));
|
||||
|
||||
ConVar cv_jasonmode ("yb_jasonmode", "0", "If enabled, all bots will be forced only the knife, skipping weapon buying routines.");
|
||||
ConVar cv_radio_mode ("yb_radio_mode", "2", "Allows bots to use radio or chattter.\nAllowed values: '0', '1', '2'.\nIf '0', radio and chatter is disabled.\nIf '1', only radio allowed.\nIf '2' chatter and radio allowed.", true, 0.0f, 2.0f);
|
||||
ConVar cv_radio_mode ("yb_radio_mode", "2", "Allows bots to use radio or chatter.\nAllowed values: '0', '1', '2'.\nIf '0', radio and chatter is disabled.\nIf '1', only radio allowed.\nIf '2' chatter and radio allowed.", true, 0.0f, 2.0f);
|
||||
|
||||
ConVar cv_economics_rounds ("yb_economics_rounds", "1", "Specifies whether bots able to use team economics, like do not buy any weapons for whole team to keep money for better guns.");
|
||||
ConVar cv_economics_disrespect_percent ("yb_economics_disrespect_percent", "25", "Allows bots to ignore economics and buy weapons with disrespect of economics.", true, 0.0f, 100.0f);
|
||||
|
|
@ -44,7 +44,7 @@ ConVar cv_pickup_best ("yb_pickup_best", "1", "Allows or disallows bots to picku
|
|||
ConVar cv_ignore_objectives ("yb_ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages.");
|
||||
ConVar cv_random_knife_attacks ("yb_random_knife_attacks", "1", "Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby.");
|
||||
|
||||
ConVar cv_max_nodes_for_predict ("yb_max_nodes_for_predict", "30", "Maximum number for path length, to predict the enemy.", true, 15.0f, 256.0f);
|
||||
ConVar cv_max_nodes_for_predict ("yb_max_nodes_for_predict", "20", "Maximum number for path length, to predict the enemy.", true, 15.0f, 256.0f);
|
||||
|
||||
// game console variables
|
||||
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
||||
|
|
@ -103,7 +103,7 @@ bool Bot::seesItem (const Vector &destination, const char *classname) {
|
|||
game.testLine (getEyesPos (), destination, TraceIgnore::None, ent (), &tr);
|
||||
|
||||
// check if line of sight to object is not blocked (i.e. visible)
|
||||
if (tr.flFraction < 1.0f && tr.pHit) {
|
||||
if (tr.flFraction < 1.0f && tr.pHit && !tr.fStartSolid) {
|
||||
return strcmp (tr.pHit->v.classname.chars (), classname) == 0;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -217,7 +217,7 @@ void Bot::checkBreakablesAround () {
|
|||
}
|
||||
auto radius = cv_object_destroy_radius.float_ ();
|
||||
|
||||
// check if we're have some breakbles in 400 units range
|
||||
// check if we're have some breakables in 400 units range
|
||||
for (const auto &breakable : game.getBreakables ()) {
|
||||
bool ignoreBreakable = false;
|
||||
|
||||
|
|
@ -325,23 +325,23 @@ void Bot::updatePickups () {
|
|||
// this function finds Items to collect or use in the near of a bot
|
||||
|
||||
// don't try to pickup anything while on ladder or trying to escape from bomb...
|
||||
if ((m_states & Sense::SeeingEnemy) || isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || !bots.hasIntrestingEntities ()) {
|
||||
if ((m_states & Sense::SeeingEnemy) || isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || !bots.hasInterestingEntities ()) {
|
||||
m_pickupItem = nullptr;
|
||||
m_pickupType = Pickup::None;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &intresting = bots.getIntrestingEntities ();
|
||||
const auto &interesting = bots.getInterestingEntities ();
|
||||
const float radius = cr::sqrf (cv_object_pickup_radius.float_ ());
|
||||
|
||||
if (!game.isNullEntity (m_pickupItem)) {
|
||||
bool itemExists = false;
|
||||
auto pickupItem = m_pickupItem;
|
||||
|
||||
for (auto &ent : intresting) {
|
||||
for (auto &ent : interesting) {
|
||||
|
||||
// in the periods of updating intresting entities we can get fake ones, that already were picked up, so double check if drawn
|
||||
// in the periods of updating interesting entities we can get fake ones, that already were picked up, so double check if drawn
|
||||
if (ent->v.effects & EF_NODRAW) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -376,14 +376,14 @@ void Bot::updatePickups () {
|
|||
m_pickupItem = nullptr;
|
||||
m_pickupType = Pickup::None;
|
||||
|
||||
for (const auto &ent : intresting) {
|
||||
for (const auto &ent : interesting) {
|
||||
bool allowPickup = false; // assume can't use it until known otherwise
|
||||
|
||||
// get the entity origin
|
||||
const auto &origin = game.getEntityOrigin (ent);
|
||||
|
||||
if ((ent->v.effects & EF_NODRAW) || ent == m_itemIgnore || cr::abs (origin.z - pev->origin.z) > 96.0f) {
|
||||
continue; // someone owns this weapon or it hasn't respawned yet
|
||||
continue; // someone owns this weapon or it hasn't re-spawned yet
|
||||
}
|
||||
|
||||
// too far from us ?
|
||||
|
|
@ -692,7 +692,7 @@ void Bot::ensureEntitiesClear () {
|
|||
|
||||
if (currentTask == Task::PickupItem || (m_states & Sense::PickupItem)) {
|
||||
if (!game.isNullEntity (m_pickupItem) && !m_hasProgressBar) {
|
||||
m_itemIgnore = m_pickupItem; // clear these pointers, bot mingh be stuck getting to them
|
||||
m_itemIgnore = m_pickupItem; // clear these pointers, bot might be stuck getting to them
|
||||
}
|
||||
|
||||
m_itemCheckTime = game.time () + 5.0f;
|
||||
|
|
@ -720,7 +720,7 @@ void Bot::ensureEntitiesClear () {
|
|||
|
||||
Vector Bot::getCampDirection (const Vector &dest) {
|
||||
// this function check if view on last enemy position is blocked - replace with better vector then
|
||||
// mostly used for getting a good camping direction vector if not camping on a camp waypoint
|
||||
// mostly used for getting a good camping direction vector if not camping on a camp node
|
||||
|
||||
TraceResult tr {};
|
||||
const Vector &src = getEyesPos ();
|
||||
|
|
@ -750,7 +750,7 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
|||
if (link.index == kInvalidNodeIndex) {
|
||||
continue;
|
||||
}
|
||||
auto distance = static_cast <float> (planner.dist (link.index, enemyIndex));
|
||||
auto distance = planner.dist (link.index, enemyIndex);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
|
|
@ -771,7 +771,7 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
|||
}
|
||||
|
||||
void Bot::showChatterIcon (bool show, bool disconnect) {
|
||||
// this function depending on show boolen, shows/remove chatter, icon, on the head of bot.
|
||||
// this function depending on show boolean, shows/remove chatter, icon, on the head of bot.
|
||||
|
||||
if (!game.is (GameFlags::HasBotVoice) || cv_radio_mode.int_ () != 2) {
|
||||
return;
|
||||
|
|
@ -1588,6 +1588,78 @@ void Bot::overrideConditions () {
|
|||
}
|
||||
}
|
||||
|
||||
void Bot::syncUpdatePredictedIndex () {
|
||||
auto wipePredict = [this] () {
|
||||
m_lastPredictIndex = kInvalidNodeIndex;
|
||||
m_lastPredictLength = 0;
|
||||
};
|
||||
|
||||
if (!m_predictLock.tryLock ()) {
|
||||
return; // allow only single instance of search per-bot
|
||||
}
|
||||
ScopedUnlock <Mutex> unlock (m_predictLock);
|
||||
|
||||
auto lastEnemyOrigin = m_lastEnemyOrigin;
|
||||
auto currentNodeIndex = m_currentNodeIndex;
|
||||
auto botOrigin = pev->origin;
|
||||
|
||||
if (lastEnemyOrigin.empty () || !vistab.isReady ()) {
|
||||
wipePredict ();
|
||||
return;
|
||||
}
|
||||
|
||||
int destIndex = graph.getNearest (lastEnemyOrigin);
|
||||
int bestIndex = kInvalidNodeIndex;
|
||||
|
||||
if (destIndex == kInvalidNodeIndex) {
|
||||
return;
|
||||
}
|
||||
int pathLength = 0;
|
||||
|
||||
planner.find (destIndex, currentNodeIndex, [&] (int index) {
|
||||
++pathLength;
|
||||
|
||||
if (vistab.visible (currentNodeIndex, index) && botOrigin.distanceSq (graph[index].origin) > cr::sqrf (256.0f)) {
|
||||
bestIndex = index;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (bestIndex != kInvalidNodeIndex) {
|
||||
m_lastPredictIndex = bestIndex;
|
||||
m_lastPredictLength = pathLength;
|
||||
|
||||
return;
|
||||
}
|
||||
wipePredict ();
|
||||
}
|
||||
|
||||
void Bot::updatePredictedIndex () {
|
||||
worker.enqueue ([this] () {
|
||||
syncUpdatePredictedIndex ();
|
||||
});
|
||||
}
|
||||
|
||||
void Bot::refreshEnemyPredict () {
|
||||
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) {
|
||||
const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
|
||||
|
||||
if (distanceToLastEnemySq > cr::sqrf (384.0f) && (distanceToLastEnemySq < cr::sqrf (1600.0f) || usesSniper ())) {
|
||||
m_aimFlags |= AimFlags::PredictPath;
|
||||
}
|
||||
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f);
|
||||
|
||||
if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_aimFlags & AimFlags::PredictPath) {
|
||||
updatePredictedIndex ();
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::setConditions () {
|
||||
// this function carried out each frame. does all of the sensing, calculates emotions and finally sets the desired
|
||||
// action after applying all of the Filters
|
||||
|
|
@ -1648,7 +1720,7 @@ void Bot::setConditions () {
|
|||
}
|
||||
}
|
||||
|
||||
// if no more enemies found AND bomb planted, switch to knife to get to bombplace faster
|
||||
// if no more enemies found AND bomb planted, switch to knife to get to bomb place faster
|
||||
if (m_team == Team::CT && !usesKnife () && m_numEnemiesLeft == 0 && bots.isBombPlanted ()) {
|
||||
selectWeaponById (Weapon::Knife);
|
||||
m_plantedBombNodeIndex = getNearestToPlantedBomb ();
|
||||
|
|
@ -1665,6 +1737,9 @@ void Bot::setConditions () {
|
|||
m_lastVictim = nullptr;
|
||||
}
|
||||
|
||||
m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance);
|
||||
m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance);
|
||||
|
||||
auto clearLastEnemy = [&] () {
|
||||
m_lastEnemyOrigin = nullptr;
|
||||
m_lastEnemy = nullptr;
|
||||
|
|
@ -1688,26 +1763,7 @@ void Bot::setConditions () {
|
|||
else if (m_heardSoundTime < game.time ()) {
|
||||
m_states &= ~Sense::HearingEnemy;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (distanceToLastEnemySq < cr::sqrf (1600.0f)) {
|
||||
auto pathLength = 0;
|
||||
auto nodeIndex = findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||
|
||||
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_lastPredictIndex = nodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (seesEntity (m_lastEnemyOrigin, true)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
refreshEnemyPredict ();
|
||||
|
||||
// check for grenades depending on difficulty
|
||||
if (rg.chance (cr::max (25, m_difficulty * 25))) {
|
||||
|
|
@ -1818,7 +1874,7 @@ void Bot::filterTasks () {
|
|||
}
|
||||
|
||||
// if half of the round is over, allow hunting
|
||||
if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && !m_isVIP && bots.getRoundMidTime () < game.time () && !hasHostage () && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_personality != Personality::Careful && !cv_ignore_enemies.bool_ ()) {
|
||||
if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && !m_isVIP && bots.getRoundMidTime () < game.time () && !m_hasHostage && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_personality != Personality::Careful && !cv_ignore_enemies.bool_ ()) {
|
||||
float desireLevel = 4096.0f - ((1.0f - tempAgression) * m_lastEnemyOrigin.distance (pev->origin));
|
||||
|
||||
desireLevel = (100.0f * desireLevel) / 4096.0f;
|
||||
|
|
@ -2056,13 +2112,13 @@ bool Bot::reactOnEnemy () {
|
|||
}
|
||||
int enemyIndex = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
auto lineDist = m_enemy->v.origin.distance2d (pev->origin);
|
||||
auto pathDist = static_cast <float> (planner.dist (ownIndex, enemyIndex));
|
||||
auto lineDist = m_enemy->v.origin.distance (pev->origin);
|
||||
auto pathDist = planner.preciseDistance (ownIndex, enemyIndex);
|
||||
|
||||
if (pathDist - lineDist > (planner.hasRealPathDistance () ? 112.0f : 32.0f) || isOnLadder ()) {
|
||||
if (pathDist - lineDist > 112.0f || isOnLadder ()) {
|
||||
m_isEnemyReachable = false;
|
||||
}
|
||||
else if (vistab.visible (ownIndex, enemyIndex)) {
|
||||
else {
|
||||
m_isEnemyReachable = true;
|
||||
}
|
||||
m_enemyReachableTimer = game.time () + 1.0f;
|
||||
|
|
@ -2088,7 +2144,7 @@ void Bot::checkRadioQueue () {
|
|||
|
||||
|
||||
// don't allow bot listen you if bot is busy
|
||||
if (getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PlantBomb || hasHostage () || m_hasC4) {
|
||||
if (getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PlantBomb || m_hasHostage || m_hasC4) {
|
||||
m_radioOrder = 0;
|
||||
return;
|
||||
}
|
||||
|
|
@ -2631,32 +2687,56 @@ void Bot::updateAimDir () {
|
|||
}
|
||||
else if (flags & AimFlags::PredictPath) {
|
||||
bool changePredictedEnemy = true;
|
||||
bool predictFailed = false;
|
||||
|
||||
if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy) {
|
||||
changePredictedEnemy = false;
|
||||
}
|
||||
|
||||
if (changePredictedEnemy) {
|
||||
auto pathLength = 0;
|
||||
auto aimNode = graph.exists (m_lastPredictIndex) ? m_lastPredictIndex : findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||
auto doFailPredict = [this] () {
|
||||
m_aimFlags &= ~AimFlags::PredictPath;
|
||||
m_trackingEdict = nullptr;
|
||||
};
|
||||
|
||||
if (graph.exists (aimNode) && pathLength < cv_max_nodes_for_predict.int_ ()) {
|
||||
m_lookAt = graph[aimNode].origin;
|
||||
if (changePredictedEnemy) {
|
||||
int pathLength = m_lastPredictLength;
|
||||
int predictNode = m_lastPredictIndex;
|
||||
|
||||
if (predictNode != kInvalidNodeIndex) {
|
||||
TraceResult tr;
|
||||
game.testLine (getEyesPos (), graph[predictNode].origin, TraceIgnore::Everything, ent (), &tr);
|
||||
|
||||
if (tr.flFraction < 0.2f) {
|
||||
pathLength = kInfiniteDistanceLong;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathLength < cv_max_nodes_for_predict.int_ ()) {
|
||||
m_lookAt = graph[predictNode].origin;
|
||||
m_lookAtSafe = m_lookAt;
|
||||
|
||||
m_timeNextTracking = game.time () + 0.75f;
|
||||
m_timeNextTracking = game.time () + 0.25f;
|
||||
m_trackingEdict = m_lastEnemy;
|
||||
}
|
||||
else {
|
||||
if (!m_lookAtSafe.empty ()) {
|
||||
m_lookAt = m_lookAtSafe;
|
||||
}
|
||||
|
||||
// feel free to fire if shootable
|
||||
if (!usesSniper () && lastEnemyShootable ()) {
|
||||
m_wantsToFire = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
doFailPredict ();
|
||||
predictFailed = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (predictFailed) {
|
||||
doFailPredict ();
|
||||
}
|
||||
else {
|
||||
m_lookAt = m_lookAtSafe;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (flags & AimFlags::Camp) {
|
||||
m_lookAt = m_lookAtSafe;
|
||||
}
|
||||
|
|
@ -2777,8 +2857,6 @@ void Bot::frame () {
|
|||
if (m_slowFrameTimestamp > game.time ()) {
|
||||
return;
|
||||
}
|
||||
m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance);
|
||||
m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance);
|
||||
|
||||
if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) {
|
||||
const Vector &bombPosition = graph.getBombOrigin ();
|
||||
|
|
@ -2801,7 +2879,7 @@ void Bot::frame () {
|
|||
|
||||
// kick the bot if stay time is over, the quota maintain will add new bot for us later
|
||||
if (cv_rotate_bots.bool_ () && m_stayTime < game.time ()) {
|
||||
m_kickedByRotation = true; // kicked by roration, so not save bot name if save bot names is active
|
||||
m_kickedByRotation = true; // kicked by rotation, so not save bot name if save bot names is active
|
||||
|
||||
kick ();
|
||||
return;
|
||||
|
|
@ -2816,6 +2894,12 @@ void Bot::frame () {
|
|||
}
|
||||
|
||||
void Bot::update () {
|
||||
|
||||
// kick bot from server if requested
|
||||
if (m_kickMeFromServer) {
|
||||
kick ();
|
||||
return;
|
||||
}
|
||||
pev->button = 0;
|
||||
|
||||
m_moveSpeed = 0.0f;
|
||||
|
|
@ -2834,6 +2918,9 @@ void Bot::update () {
|
|||
m_hasC4 = false;
|
||||
}
|
||||
}
|
||||
else if (m_team == Team::CT && game.mapIs (MapFlags::HostageRescue)) {
|
||||
m_hasHostage = hasHostage ();
|
||||
}
|
||||
|
||||
// is bot movement enabled
|
||||
bool botMovement = false;
|
||||
|
|
@ -2869,7 +2956,7 @@ void Bot::update () {
|
|||
}
|
||||
checkMsgQueue ();
|
||||
|
||||
if (botMovement) {
|
||||
if (!m_isStale && botMovement) {
|
||||
logic (); // execute main code
|
||||
}
|
||||
else if (pev->maxspeed < 10.0f) {
|
||||
|
|
@ -2928,7 +3015,7 @@ void Bot::normal_ () {
|
|||
}
|
||||
|
||||
// bots rushing with knife, when have no enemy (thanks for idea to nicebot project)
|
||||
if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasHostage () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) {
|
||||
if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !m_hasHostage && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) {
|
||||
if (rg.chance (40)) {
|
||||
pev->button |= IN_ATTACK;
|
||||
}
|
||||
|
|
@ -2970,7 +3057,7 @@ void Bot::normal_ () {
|
|||
if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ () && !isKnifeMode ()) {
|
||||
|
||||
// check if bot has got a primary weapon and hasn't camped before
|
||||
if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.time () && !hasHostage ()) {
|
||||
if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.time () && !m_hasHostage) {
|
||||
bool campingAllowed = true;
|
||||
|
||||
// Check if it's not allowed for this team to camp here
|
||||
|
|
@ -3016,7 +3103,7 @@ void Bot::normal_ () {
|
|||
m_campDirection = 0;
|
||||
|
||||
// tell the world we're camping
|
||||
if (rg.chance (40)) {
|
||||
if (rg.chance (25)) {
|
||||
pushRadioMessage (Radio::ImInPosition);
|
||||
}
|
||||
m_moveToGoal = false;
|
||||
|
|
@ -3031,7 +3118,7 @@ void Bot::normal_ () {
|
|||
// some goal waypoints are map dependant so check it out...
|
||||
if (game.mapIs (MapFlags::HostageRescue)) {
|
||||
// CT Bot has some hostages following?
|
||||
if (m_team == Team::CT && hasHostage ()) {
|
||||
if (m_team == Team::CT && m_hasHostage) {
|
||||
// and reached a rescue point?
|
||||
if (m_pathFlags & NodeFlag::Rescue) {
|
||||
m_hostages.clear ();
|
||||
|
|
@ -3127,7 +3214,7 @@ void Bot::normal_ () {
|
|||
}
|
||||
float shiftSpeed = getShiftSpeed ();
|
||||
|
||||
if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () && !bots.isBombPlanted ()) {
|
||||
if (!m_isStuck && (!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () && !bots.isBombPlanted ()) {
|
||||
m_moveSpeed = shiftSpeed;
|
||||
}
|
||||
|
||||
|
|
@ -5099,7 +5186,7 @@ void Bot::takeBlind (int alpha) {
|
|||
|
||||
void Bot::updatePracticeValue (int damage) {
|
||||
// gets called each time a bot gets damaged by some enemy. tries to achieve a statistic about most/less dangerous
|
||||
// waypoints for a destination goal used for pathfinding
|
||||
// nodes for a destination goal used for pathfinding
|
||||
|
||||
if (graph.length () < 1 || graph.hasChanged () || m_chosenGoalIndex < 0 || m_prevGoalIndex < 0) {
|
||||
return;
|
||||
|
|
@ -5109,7 +5196,7 @@ void Bot::updatePracticeValue (int damage) {
|
|||
// max goal value
|
||||
constexpr int maxGoalValue = PracticeLimit::Goal;
|
||||
|
||||
// only rate goal waypoint if bot died because of the damage
|
||||
// only rate goal node 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
|
||||
if (health - damage <= 0) {
|
||||
practice.setValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (practice.getValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - health / 20, -maxGoalValue, maxGoalValue));
|
||||
|
|
@ -5117,7 +5204,7 @@ void Bot::updatePracticeValue (int damage) {
|
|||
}
|
||||
|
||||
void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
|
||||
// this function gets called each time a bot gets damaged by some enemy. sotores the damage (teamspecific) done by victim.
|
||||
// this function gets called each time a bot gets damaged by some enemy. stores the damage (team-specific) done by victim.
|
||||
|
||||
if (!util.isPlayer (attacker)) {
|
||||
return;
|
||||
|
|
@ -5159,8 +5246,8 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
|
|||
// store away the damage done
|
||||
int damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, maxDamageValue);
|
||||
|
||||
if (damageValue > graph.getHighestDamageForTeam (m_team)) {
|
||||
graph.setHighestDamageForTeam (m_team, damageValue);
|
||||
if (damageValue > practice.getHighestDamageForTeam (m_team)) {
|
||||
practice.setHighestDamageForTeam (m_team, damageValue);
|
||||
}
|
||||
practice.setDamage (m_team, victimIndex, attackerIndex, damageValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,25 +262,25 @@ bool Bot::lookupEnemies () {
|
|||
|
||||
if (cv_attack_monsters.bool_ ()) {
|
||||
// search the world for monsters...
|
||||
for (const auto &intresting : bots.getIntrestingEntities ()) {
|
||||
if (!util.isMonster (intresting)) {
|
||||
for (const auto &interesting : bots.getInterestingEntities ()) {
|
||||
if (!util.isMonster (interesting)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check the engine PVS
|
||||
if (!isEnemyInFrustum (intresting) || !game.checkVisibility (intresting, set)) {
|
||||
if (!isEnemyInFrustum (interesting) || !game.checkVisibility (interesting, set)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// see if bot can see the monster...
|
||||
if (seesEnemy (intresting)) {
|
||||
if (seesEnemy (interesting)) {
|
||||
// higher priority for big monsters
|
||||
float scaleFactor = (1.0f / calculateScaleFactor (intresting));
|
||||
float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor;
|
||||
float scaleFactor = (1.0f / calculateScaleFactor (interesting));
|
||||
float distance = interesting->v.origin.distanceSq (pev->origin) * scaleFactor;
|
||||
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
newEnemy = intresting;
|
||||
newEnemy = interesting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -347,7 +347,7 @@ bool Bot::lookupEnemies () {
|
|||
return true;
|
||||
}
|
||||
else {
|
||||
if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) {
|
||||
if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || m_hasHostage || !game.isNullEntity (m_targetEntity))) {
|
||||
pushRadioMessage (Radio::EnemySpotted);
|
||||
}
|
||||
m_targetEntity = nullptr; // stop following when we see an enemy...
|
||||
|
|
@ -516,6 +516,7 @@ Vector Bot::getBodyOffsetError (float distance) {
|
|||
}
|
||||
else if (m_enemyParts & Visibility::Body) {
|
||||
spot = m_enemy->v.origin;
|
||||
spot.z += 3.0f;
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Other) {
|
||||
spot = m_enemyOrigin;
|
||||
|
|
@ -752,7 +753,7 @@ bool Bot::isPenetrableObstacle3 (const Vector &dest) {
|
|||
bool Bot::needToPauseFiring (float distance) {
|
||||
// returns true if bot needs to pause between firing to compensate for punchangle & weapon spread
|
||||
|
||||
if (usesSniper () || m_isUsingGrenade) {
|
||||
if (usesSniper () || m_isUsingGrenade || ((m_states & Sense::SuspectEnemy) && distance < 400.0f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1949,7 +1950,7 @@ void Bot::checkGrenadesThrow () {
|
|||
break;
|
||||
}
|
||||
|
||||
for (const auto predict : predicted) {
|
||||
for (const auto &predict : predicted) {
|
||||
allowThrowing = true;
|
||||
|
||||
if (!graph.exists (predict)) {
|
||||
|
|
|
|||
|
|
@ -1192,7 +1192,7 @@ int BotControl::menuCommands (int item) {
|
|||
switch (item) {
|
||||
case 1:
|
||||
case 2:
|
||||
if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->hasHostage ()) {
|
||||
if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->m_hasHostage) {
|
||||
if (item == 1) {
|
||||
m_djump->startDoubleJump (m_ent);
|
||||
}
|
||||
|
|
@ -1765,7 +1765,7 @@ bool BotControl::executeCommands () {
|
|||
|
||||
for (auto &item : m_cmds) {
|
||||
if (!hasSecondArg) {
|
||||
cmd = item.name.split ("/")[0];
|
||||
cmd = item.name.split ("/").first ();
|
||||
}
|
||||
|
||||
if (!hasSecondArg || aliasMatch (item.name, strValue (2), cmd)) {
|
||||
|
|
@ -1804,7 +1804,7 @@ bool BotControl::executeCommands () {
|
|||
msg ("valid commands are: ");
|
||||
|
||||
for (auto &item : m_cmds) {
|
||||
msg (" %s - %s", item.name.split ("/")[0], conf.translate (item.help));
|
||||
msg (" %-14.11s - %s", item.name.split ("/").first (), String (conf.translate (item.help)).lowercase ());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
ConVar cv_csdm_mode ("yb_csdm_mode", "0", "Enables or disables CSDM / FFA mode for bots.\nAllowed values: '0', '1', '2', '3'.\nIf '0', CSDM / FFA mode is auto-detected.\nIf '1', CSDM mode is enabled, but FFA is disabled.\nIf '2', CSDM and FFA mode is enabled.\nIf '3', CSDM and FFA mode is disabled.", true, 0.0f, 3.0f);
|
||||
ConVar cv_breakable_health_limit ("yb_breakable_health_limit", "500.0", "Specifies the maximum health of breakable object, that bot will consider to destroy.", true, 1.0f, 3000.0);
|
||||
ConVar cv_threadpool_workers ("yb_threadpool_workers", "-1", "Maximum number of threads bot will run to process some tasks. -1 means half of CPU cores used.", true, -1.0f, static_cast <float> (plat.hardwareConcurrency ()));
|
||||
|
||||
ConVar sv_skycolor_r ("sv_skycolor_r", nullptr, Var::GameRef);
|
||||
ConVar sv_skycolor_g ("sv_skycolor_g", nullptr, Var::GameRef);
|
||||
|
|
@ -53,6 +54,12 @@ void Game::precache () {
|
|||
void Game::levelInitialize (edict_t *entities, int max) {
|
||||
// this function precaches needed models and initialize class variables
|
||||
|
||||
// re-initialize bot's array
|
||||
bots.destroy ();
|
||||
|
||||
// startup threaded worker
|
||||
worker.startup (cv_threadpool_workers.int_ ());
|
||||
|
||||
m_spawnCount[Team::CT] = 0;
|
||||
m_spawnCount[Team::Terrorist] = 0;
|
||||
|
||||
|
|
@ -83,9 +90,6 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
|||
// flush any print queue
|
||||
ctrl.resetFlushTimestamp ();
|
||||
|
||||
// suspend any analyzer tasks
|
||||
analyzer.suspend ();
|
||||
|
||||
// go thru the all entities on map, and do whatever we're want
|
||||
for (int i = 0; i < max; ++i) {
|
||||
auto ent = entities + i;
|
||||
|
|
@ -162,6 +166,40 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
|||
m_halfSecondFrame = 0.0f;
|
||||
}
|
||||
|
||||
void Game::levelShutdown () {
|
||||
// stop thread pool
|
||||
worker.shutdown ();
|
||||
|
||||
// save collected practice on shutdown
|
||||
practice.save ();
|
||||
|
||||
// destroy global killer entity
|
||||
bots.destroyKillerEntity ();
|
||||
|
||||
// ensure players are off on xash3d
|
||||
if (game.is (GameFlags::Xash3D)) {
|
||||
bots.kickEveryone (true, false);
|
||||
}
|
||||
|
||||
// set state to unprecached
|
||||
game.setUnprecached ();
|
||||
|
||||
// enable lightstyle animations on level change
|
||||
illum.enableAnimation (true);
|
||||
|
||||
// send message on new map
|
||||
util.setNeedForWelcome (false);
|
||||
|
||||
// clear local entity
|
||||
game.setLocalEntity (nullptr);
|
||||
|
||||
// reset graph state
|
||||
graph.reset ();
|
||||
|
||||
// suspend any analyzer tasks
|
||||
analyzer.suspend ();
|
||||
}
|
||||
|
||||
void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type) {
|
||||
// this function draws a arrow visible from the client side of the player whose player entity
|
||||
// is pointed to by ent, from the vector location start to the vector location end,
|
||||
|
|
@ -963,6 +1001,9 @@ void Game::slowFrame () {
|
|||
// refresh bomb origin in case some plugin moved it out
|
||||
graph.setBombOrigin ();
|
||||
|
||||
// update client pings
|
||||
util.calculatePings ();
|
||||
|
||||
// update next update time
|
||||
m_halfSecondFrame = nextUpdate * 0.25f + time ();
|
||||
}
|
||||
|
|
@ -978,9 +1019,6 @@ void Game::slowFrame () {
|
|||
// check if we're need to autokill bots
|
||||
bots.maintainAutoKill ();
|
||||
|
||||
// update client pings
|
||||
util.calculatePings ();
|
||||
|
||||
// maintain leaders selection upon round start
|
||||
bots.maintainLeaders ();
|
||||
|
||||
|
|
|
|||
|
|
@ -1196,6 +1196,7 @@ void BotGraph::showFileInfo () {
|
|||
msg (" compressed_size: %dkB", m_graphHeader.compressed / 1024);
|
||||
msg (" uncompressed_size: %dkB", m_graphHeader.uncompressed / 1024);
|
||||
msg (" options: %d", m_graphHeader.options); // display as string ?
|
||||
msg (" analyzed: %s", (m_graphHeader.options & StorageOption::Analyzed) ? "yes" : "no"); // display as string ?
|
||||
|
||||
msg ("");
|
||||
|
||||
|
|
|
|||
|
|
@ -94,11 +94,11 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
|||
plat.bzero (table, sizeof (gamefuncs_t));
|
||||
|
||||
if (!(game.is (GameFlags::Metamod))) {
|
||||
auto api_GetEntityAPI = game.lib ().resolve <decltype (&GetEntityAPI)> (__FUNCTION__);
|
||||
auto api_GetEntityAPI = game.lib ().resolve <decltype (&GetEntityAPI)> (__func__);
|
||||
|
||||
// pass other DLLs engine callbacks to function table...
|
||||
if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) {
|
||||
logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__);
|
||||
logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __func__);
|
||||
}
|
||||
dllfuncs.dllapi_table = &dllapi;
|
||||
gpGamedllFuncs = &dllfuncs;
|
||||
|
|
@ -332,29 +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
|
||||
// the loading of new bots and the new BSP data parsing there.
|
||||
|
||||
// save collected practice on shutdown
|
||||
practice.save ();
|
||||
|
||||
// destroy global killer entity
|
||||
bots.destroyKillerEntity ();
|
||||
|
||||
// set state to unprecached
|
||||
game.setUnprecached ();
|
||||
|
||||
// enable lightstyle animations on level change
|
||||
illum.enableAnimation (true);
|
||||
|
||||
// send message on new map
|
||||
util.setNeedForWelcome (false);
|
||||
|
||||
// clear local entity
|
||||
game.setLocalEntity (nullptr);
|
||||
|
||||
// reset graph state
|
||||
graph.reset ();
|
||||
|
||||
// clear all the bots
|
||||
bots.destroy ();
|
||||
// notify level shutdown
|
||||
game.levelShutdown ();
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_IGNORED);
|
||||
|
|
@ -397,8 +376,8 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
|||
// keep track of grenades on map
|
||||
bots.updateActiveGrenade ();
|
||||
|
||||
// keep track of intresting entities
|
||||
bots.updateIntrestingEntities ();
|
||||
// keep track of interesting entities
|
||||
bots.updateInterestingEntities ();
|
||||
}
|
||||
|
||||
// keep bot number up to date
|
||||
|
|
@ -527,7 +506,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
|
|||
}
|
||||
|
||||
table->pfnLightStyle = [] (int style, char *val) {
|
||||
// ths function update lightstyle for the bots
|
||||
// this function update lightstyle for the bots
|
||||
|
||||
illum.updateLight (style, val);
|
||||
|
||||
|
|
@ -572,7 +551,6 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
|
|||
|
||||
table->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) {
|
||||
// this function called each time a message is about to sent.
|
||||
|
||||
msgs.start (ed, msgType);
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
|
|
@ -801,11 +779,11 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion)
|
|||
plat.bzero (table, sizeof (newgamefuncs_t));
|
||||
|
||||
if (!(game.is (GameFlags::Metamod))) {
|
||||
auto api_GetNewDLLFunctions = game.lib ().resolve <decltype (&GetNewDLLFunctions)> (__FUNCTION__);
|
||||
auto api_GetNewDLLFunctions = game.lib ().resolve <decltype (&GetNewDLLFunctions)> (__func__);
|
||||
|
||||
// pass other DLLs engine callbacks to function table...
|
||||
if (!api_GetNewDLLFunctions || api_GetNewDLLFunctions (&newapi, interfaceVersion) == 0) {
|
||||
logger.error ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__);
|
||||
logger.error ("Could not resolve symbol \"%s\" in the game dll.", __func__);
|
||||
}
|
||||
dllfuncs.newapi_table = &newapi;
|
||||
memcpy (table, &newapi, sizeof (newgamefuncs_t));
|
||||
|
|
@ -817,6 +795,7 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion)
|
|||
if (bot->m_enemy == ent) {
|
||||
bot->m_enemy = nullptr;
|
||||
bot->m_lastEnemy = nullptr;
|
||||
bot->markStale ();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -854,7 +833,6 @@ CR_LINKAGE_C int GetEngineFunctions_Post (enginefuncs_t *table, int *) {
|
|||
|
||||
RETURN_META_VALUE (MRES_IGNORED, 0);
|
||||
};
|
||||
|
||||
return HLTrue;
|
||||
}
|
||||
|
||||
|
|
@ -907,11 +885,16 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
|
|||
logger.error ("%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
|
||||
return HLFalse; // returning FALSE prevents metamod from unloading this plugin
|
||||
}
|
||||
bots.kickEveryone (true); // kick all bots off this server
|
||||
// stop the worker
|
||||
worker.shutdown ();
|
||||
|
||||
// kick all bots off this server
|
||||
bots.kickEveryone (true);
|
||||
|
||||
// save collected practice on shutdown
|
||||
practice.save ();
|
||||
|
||||
// disable hooks
|
||||
util.disableSendTo ();
|
||||
|
||||
// make sure all stuff cleared
|
||||
|
|
@ -963,10 +946,10 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) {
|
|||
if (game.postload ()) {
|
||||
return;
|
||||
}
|
||||
auto api_GiveFnptrsToDll = game.lib ().resolve <decltype (&GiveFnptrsToDll)> (__FUNCTION__);
|
||||
auto api_GiveFnptrsToDll = game.lib ().resolve <decltype (&GiveFnptrsToDll)> (__func__);
|
||||
|
||||
if (!api_GiveFnptrsToDll) {
|
||||
logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__);
|
||||
logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __func__);
|
||||
}
|
||||
GetEngineFunctions (table, nullptr);
|
||||
|
||||
|
|
@ -984,10 +967,10 @@ CR_EXPORT int Server_GetBlendingInterface (int version, struct sv_blending_inter
|
|||
// of the body move, which bones, which hitboxes and how) between the server and the game DLL.
|
||||
// some MODs can be using a different hitbox scheme than the standard one.
|
||||
|
||||
auto api_GetBlendingInterface = game.lib ().resolve <decltype (&Server_GetBlendingInterface)> (__FUNCTION__);
|
||||
auto api_GetBlendingInterface = game.lib ().resolve <decltype (&Server_GetBlendingInterface)> (__func__);
|
||||
|
||||
if (!api_GetBlendingInterface) {
|
||||
logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __FUNCTION__);
|
||||
logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __func__);
|
||||
return HLFalse;
|
||||
}
|
||||
return api_GetBlendingInterface (version, ppinterface, pstudio, rotationmatrix, bonetransform);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <yapb.h>
|
||||
|
||||
ConVar cv_autovacate ("yb_autovacate", "1", "Kick bots to automatically make room for human players.");
|
||||
ConVar cv_autovacate_keep_slots ("yb_autovacate_keep_slots", "1", "How many slots autovacate feature should keep for human players", true, 1.0f, 8.0f);
|
||||
ConVar cv_kick_after_player_connect ("yb_kick_after_player_connect", "1", "Kick the bot immediately when a human player joins the server (yb_autovacate must be enabled).");
|
||||
|
||||
ConVar cv_quota ("yb_quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (kGameMaxPlayers));
|
||||
|
|
@ -405,10 +406,10 @@ void BotManager::maintainQuota () {
|
|||
|
||||
if (cv_autovacate.bool_ ()) {
|
||||
if (cv_kick_after_player_connect.bool_ ()) {
|
||||
desiredBotCount = cr::min <int> (desiredBotCount, maxClients - (totalHumansInGame + 1));
|
||||
desiredBotCount = cr::min <int> (desiredBotCount, maxClients - (totalHumansInGame + cv_autovacate_keep_slots.int_ ()));
|
||||
}
|
||||
else {
|
||||
desiredBotCount = cr::min <int> (desiredBotCount, maxClients - (humanPlayersInGame + 1));
|
||||
desiredBotCount = cr::min <int> (desiredBotCount, maxClients - (humanPlayersInGame + cv_autovacate_keep_slots.int_ ()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -515,7 +516,7 @@ void BotManager::reset () {
|
|||
m_timeBombPlanted = 0.0f;
|
||||
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
|
||||
|
||||
m_intrestingEntities.clear ();
|
||||
m_interestingEntities.clear ();
|
||||
m_activeGrenades.clear ();
|
||||
}
|
||||
|
||||
|
|
@ -569,7 +570,7 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int
|
|||
// this function fill server with bots, with specified team & personality
|
||||
|
||||
// always keep one slot
|
||||
int maxClients = cv_autovacate.bool_ () ? game.maxClients () - 1 - (game.isDedicated () ? 0 : getHumansCount ()) : game.maxClients ();
|
||||
int maxClients = cv_autovacate.bool_ () ? game.maxClients () - cv_autovacate_keep_slots.int_ () - (game.isDedicated () ? 0 : getHumansCount ()) : game.maxClients ();
|
||||
|
||||
if (getBotCount () >= maxClients - getHumansCount ()) {
|
||||
return;
|
||||
|
|
@ -942,6 +943,9 @@ void BotManager::balanceBotDifficulties () {
|
|||
void BotManager::destroy () {
|
||||
// this function free all bots slots (used on server shutdown)
|
||||
|
||||
for (auto &bot : m_bots) {
|
||||
bot->markStale ();
|
||||
}
|
||||
m_bots.clear ();
|
||||
}
|
||||
|
||||
|
|
@ -1078,7 +1082,10 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
|
|||
// init path walker
|
||||
m_pathWalk.init (graph.getMaxRouteLength ());
|
||||
|
||||
// bot is not kicked by rorataion
|
||||
// init async planner
|
||||
m_planner = cr::makeUnique <AStarAlgo> (graph.length ());
|
||||
|
||||
// bot is not kicked by rotation
|
||||
m_kickedByRotation = false;
|
||||
|
||||
// assign team and class
|
||||
|
|
@ -1141,7 +1148,7 @@ int BotManager::getPlayerPriority (edict_t *ent) {
|
|||
}
|
||||
|
||||
// give bots some priority
|
||||
if (bot->m_hasC4 || bot->m_isVIP || bot->hasHostage () || bot->m_healthValue < ent->v.health) {
|
||||
if (bot->m_hasC4 || bot->m_isVIP || bot->m_hasHostage || bot->m_healthValue < ent->v.health) {
|
||||
return bot->entindex () + highPrio;
|
||||
}
|
||||
return bot->entindex ();
|
||||
|
|
@ -1183,7 +1190,6 @@ void BotManager::erase (Bot *bot) {
|
|||
m_bots.erase (index, 1); // remove from bots array
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
||||
|
|
@ -1246,6 +1252,7 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void Bot::newRound () {
|
||||
// this function initializes a bot after creation & at the start of each round
|
||||
|
||||
|
|
@ -1276,7 +1283,6 @@ void Bot::newRound () {
|
|||
for (auto &node : m_previousNodes) {
|
||||
node = kInvalidNodeIndex;
|
||||
}
|
||||
|
||||
m_navTimeset = game.time ();
|
||||
m_team = game.getTeam (ent ());
|
||||
|
||||
|
|
@ -1407,6 +1413,7 @@ void Bot::newRound () {
|
|||
m_inBombZone = false;
|
||||
m_ignoreBuyDelay = false;
|
||||
m_hasC4 = false;
|
||||
m_hasHostage = false;
|
||||
|
||||
m_fallDownTime = 0.0f;
|
||||
m_shieldCheckTime = 0.0f;
|
||||
|
|
@ -1505,8 +1512,12 @@ void Bot::kick () {
|
|||
}
|
||||
|
||||
void Bot::markStale () {
|
||||
// switch chatter icon off
|
||||
showChatterIcon (false, true);
|
||||
|
||||
// mark bot as leaving
|
||||
m_isStale = true;
|
||||
|
||||
// clear the bot name
|
||||
conf.clearUsedName (this);
|
||||
|
||||
|
|
@ -1723,13 +1734,13 @@ void BotManager::updateActiveGrenade () {
|
|||
m_grenadeUpdateTime = game.time () + 0.25f;
|
||||
}
|
||||
|
||||
void BotManager::updateIntrestingEntities () {
|
||||
void BotManager::updateInterestingEntities () {
|
||||
if (m_entityUpdateTime > game.time ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear previously stored entities
|
||||
m_intrestingEntities.clear ();
|
||||
m_interestingEntities.clear ();
|
||||
|
||||
// search the map for any type of grenade
|
||||
game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) {
|
||||
|
|
@ -1737,26 +1748,26 @@ void BotManager::updateIntrestingEntities () {
|
|||
|
||||
// search for grenades, weaponboxes, weapons, items and armoury entities
|
||||
if (strncmp ("weaponbox", classname, 9) == 0 || strncmp ("grenade", classname, 7) == 0 || util.isItem (e) || strncmp ("armoury", classname, 7) == 0) {
|
||||
m_intrestingEntities.push (e);
|
||||
m_interestingEntities.push (e);
|
||||
}
|
||||
|
||||
// pickup some csdm stuff if we're running csdm
|
||||
if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) {
|
||||
m_intrestingEntities.push (e);
|
||||
m_interestingEntities.push (e);
|
||||
}
|
||||
|
||||
// add buttons
|
||||
if (game.mapIs (MapFlags::HasButtons) && strncmp ("func_button", classname, 11) == 0) {
|
||||
m_intrestingEntities.push (e);
|
||||
m_interestingEntities.push (e);
|
||||
}
|
||||
|
||||
// pickup some csdm stuff if we're running csdm
|
||||
if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) {
|
||||
m_intrestingEntities.push (e);
|
||||
m_interestingEntities.push (e);
|
||||
}
|
||||
|
||||
if (cv_attack_monsters.bool_ () && util.isMonster (e)) {
|
||||
m_intrestingEntities.push (e);
|
||||
m_interestingEntities.push (e);
|
||||
}
|
||||
|
||||
// continue iteration
|
||||
|
|
@ -1914,3 +1925,35 @@ void BotManager::setBombPlanted (bool isPlanted) {
|
|||
}
|
||||
m_bombPlanted = isPlanted;
|
||||
}
|
||||
|
||||
void BotThreadWorker::shutdown () {
|
||||
game.print ("Shutting down bot thread worker.");
|
||||
m_botWorker.shutdown ();
|
||||
}
|
||||
|
||||
void BotThreadWorker::startup (int workers) {
|
||||
size_t count = m_botWorker.threadCount ();
|
||||
|
||||
if (count > 0) {
|
||||
logger.error ("Tried to start thread pool with existing %d threads in pool.", count);
|
||||
return;
|
||||
}
|
||||
|
||||
int requetedThreadCount = workers;
|
||||
int hardwareConcurrency = plat.hardwareConcurrency ();
|
||||
|
||||
if (requetedThreadCount == -1) {
|
||||
requetedThreadCount = hardwareConcurrency / 4;
|
||||
|
||||
if (requetedThreadCount == 0) {
|
||||
requetedThreadCount = 1;
|
||||
}
|
||||
}
|
||||
requetedThreadCount = cr::clamp (requetedThreadCount, 1, hardwareConcurrency);
|
||||
|
||||
// notify user
|
||||
game.print ("Starting up bot thread worker with %d threads.", requetedThreadCount);
|
||||
|
||||
// start up the worker
|
||||
m_botWorker.startup (static_cast <int> (requetedThreadCount));
|
||||
}
|
||||
|
|
|
|||
181
src/navigate.cpp
181
src/navigate.cpp
|
|
@ -54,7 +54,7 @@ int Bot::findBestGoal () {
|
|||
tactic = 3;
|
||||
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
|
||||
}
|
||||
else if (m_team == Team::CT && hasHostage ()) {
|
||||
else if (m_team == Team::CT && m_hasHostage) {
|
||||
tactic = 4;
|
||||
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
|
||||
}
|
||||
|
|
@ -406,7 +406,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
|||
if (game.isNullEntity (m_hindrance)) {
|
||||
return;
|
||||
}
|
||||
const float interval = m_frameInterval * 7.25f;
|
||||
const float interval = m_frameInterval * (pev->velocity.lengthSq2d () > 0.0f ? 9.0f : 2.0f);
|
||||
|
||||
// use our movement angles, try to predict where we should be next frame
|
||||
Vector right, forward;
|
||||
|
|
@ -538,7 +538,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
|||
dirLeft = true;
|
||||
}
|
||||
const auto &testDir = m_moveSpeed > 0.0f ? forward : -forward;
|
||||
constexpr float blockDistance = 56.0f;
|
||||
constexpr float blockDistance = 40.0f;
|
||||
|
||||
// now check which side is blocked
|
||||
src = pev->origin + right * blockDistance;
|
||||
|
|
@ -813,11 +813,8 @@ bool Bot::updateNavigation () {
|
|||
// check if we need to find a node...
|
||||
if (m_currentNodeIndex == kInvalidNodeIndex) {
|
||||
findValidNode ();
|
||||
setPathOrigin ();
|
||||
|
||||
// if graph node radius non zero vary origin a bit depending on the body angles
|
||||
if (m_path->radius > 0.0f) {
|
||||
m_pathOrigin += Vector (pev->angles.x, cr::wrapAngle (pev->angles.y + rg.get (-90.0f, 90.0f)), 0.0f).forward () * rg.get (0.0f, m_path->radius);
|
||||
}
|
||||
m_navTimeset = game.time ();
|
||||
}
|
||||
m_destOrigin = m_pathOrigin + pev->view_ofs;
|
||||
|
|
@ -841,7 +838,8 @@ bool Bot::updateNavigation () {
|
|||
m_desiredVelocity = nullptr;
|
||||
}
|
||||
}
|
||||
else if (!isKnifeMode () && m_switchedToKnifeDuringJump && isOnFloor ()) {
|
||||
}
|
||||
else if (m_jumpDistance > 0.0f && !isKnifeMode () && m_switchedToKnifeDuringJump && isOnFloor ()) {
|
||||
selectBestWeapon ();
|
||||
|
||||
// if jump distance was big enough, cooldown a little
|
||||
|
|
@ -851,7 +849,6 @@ bool Bot::updateNavigation () {
|
|||
m_jumpDistance = 0.0f;
|
||||
m_switchedToKnifeDuringJump = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pathFlags & NodeFlag::Ladder) {
|
||||
if (!m_pathWalk.empty ()) {
|
||||
|
|
@ -1495,7 +1492,7 @@ bool Bot::findNextBestNode () {
|
|||
}
|
||||
|
||||
// cts with hostages should not pick nodes with no hostage flag
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[path.number].flags & NodeFlag::NoHostage) && hasHostage ()) {
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[path.number].flags & NodeFlag::NoHostage) && m_hasHostage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1566,9 +1563,7 @@ bool Bot::findNextBestNode () {
|
|||
if (selected == kInvalidNodeIndex) {
|
||||
selected = findNearestNode ();
|
||||
}
|
||||
m_nodeHistory.push (selected);
|
||||
changeNodeIndex (selected);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1665,14 +1660,14 @@ int Bot::findNearestNode () {
|
|||
for (const auto &i : bucket) {
|
||||
const auto &path = graph[i];
|
||||
|
||||
if (!graph.exists (path.number) || !graph.exists (m_currentNodeIndex) || path.number == m_currentNodeIndex) {
|
||||
if (!graph.exists (path.number)) {
|
||||
continue;
|
||||
}
|
||||
const float distance = path.origin.distanceSq (pev->origin);
|
||||
|
||||
if (distance < minimumDistance) {
|
||||
// if bot doing navigation, make sure node really visible and reacheable
|
||||
if (vistab.visible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) {
|
||||
if (isReachableNode (path.number)) {
|
||||
index = path.number;
|
||||
minimumDistance = distance;
|
||||
}
|
||||
|
|
@ -1680,7 +1675,7 @@ int Bot::findNearestNode () {
|
|||
}
|
||||
|
||||
// try to search ANYTHING that can be reachaed
|
||||
if (index == kInvalidNodeIndex) {
|
||||
if (!graph.exists (index)) {
|
||||
minimumDistance = cr::sqrf (kMaxDistance);
|
||||
const auto &nearestNodes = graph.getNarestInRadius (kMaxDistance, pev->origin);
|
||||
|
||||
|
|
@ -1776,18 +1771,18 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
TraceResult tr {};
|
||||
|
||||
int nodeIndex[kMaxNodeLinks] {};
|
||||
int minDistance[kMaxNodeLinks] {};
|
||||
float minDistance[kMaxNodeLinks] {};
|
||||
|
||||
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||
nodeIndex[i] = kInvalidNodeIndex;
|
||||
minDistance[i] = 128;
|
||||
minDistance[i] = 128.0f;
|
||||
}
|
||||
|
||||
int posIndex = graph.getNearest (origin);
|
||||
int srcIndex = m_currentNodeIndex;
|
||||
|
||||
// max search distance
|
||||
const int kMaxDistance = cr::clamp (148 * bots.getBotCount (), 256, 1024);
|
||||
const auto kMaxDistance = cr::clamp (148.0f * bots.getBotCount (), 256.0f, 1024.0f);
|
||||
|
||||
// some of points not found, return random one
|
||||
if (srcIndex == kInvalidNodeIndex || posIndex == kInvalidNodeIndex) {
|
||||
|
|
@ -1802,7 +1797,7 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
}
|
||||
|
||||
// use the 'real' pathfinding distances
|
||||
int distance = planner.dist (srcIndex, path.number);
|
||||
auto distance = planner.dist (srcIndex, path.number);
|
||||
|
||||
// skip wayponts too far
|
||||
if (distance > kMaxDistance) {
|
||||
|
|
@ -1860,13 +1855,13 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
int practiceDamage = practice.getDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
||||
practiceDamage = (practiceDamage * 100) / practice.getHighestDamageForTeam (m_team);
|
||||
|
||||
minDistance[i] = (practiceDamage * 100) / 8192;
|
||||
minDistance[i] += practiceDamage;
|
||||
minDistance[i] = static_cast <float> ((practiceDamage * 100) / 8192);
|
||||
minDistance[i] += static_cast <float> (practiceDamage);
|
||||
}
|
||||
}
|
||||
bool sorting = false;
|
||||
|
||||
// sort results nodes for farest distance
|
||||
// sort resulting nodes for farest distance
|
||||
do {
|
||||
sorting = false;
|
||||
|
||||
|
|
@ -1925,11 +1920,11 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
IntArray enemies;
|
||||
|
||||
int nodeIndex[kMaxNodeLinks] {};
|
||||
int minDistance[kMaxNodeLinks] {};
|
||||
float minDistance[kMaxNodeLinks] {};
|
||||
|
||||
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||
nodeIndex[i] = kInvalidNodeIndex;
|
||||
minDistance[i] = static_cast <int> (maxDistance);
|
||||
minDistance[i] = maxDistance;
|
||||
}
|
||||
|
||||
if (enemyIndex == kInvalidNodeIndex) {
|
||||
|
|
@ -1967,8 +1962,8 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
}
|
||||
|
||||
// use the 'real' pathfinding distances
|
||||
int distance = planner.dist (srcIndex, path.number);
|
||||
int enemyDistance = planner.dist (enemyIndex, path.number);
|
||||
float distance = planner.dist (srcIndex, path.number);
|
||||
float enemyDistance = planner.dist (enemyIndex, path.number);
|
||||
|
||||
if (distance >= enemyDistance) {
|
||||
continue;
|
||||
|
|
@ -2014,8 +2009,8 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
int practiceDamage = practice.getDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
||||
practiceDamage = (practiceDamage * 100) / practice.getHighestDamageForTeam (m_team);
|
||||
|
||||
minDistance[i] = (practiceDamage * 100) / 8192;
|
||||
minDistance[i] += practiceDamage;
|
||||
minDistance[i] = static_cast <float> ((practiceDamage * 100) / 8192);
|
||||
minDistance[i] += static_cast <float> (practiceDamage);
|
||||
}
|
||||
}
|
||||
bool sorting = false;
|
||||
|
|
@ -2105,7 +2100,6 @@ bool Bot::advanceMovement () {
|
|||
if (m_pathWalk.empty ()) {
|
||||
return false;
|
||||
}
|
||||
TraceResult tr {};
|
||||
|
||||
m_pathWalk.shift (); // advance in list
|
||||
m_currentTravelFlags = 0; // reset travel flags (jumping etc)
|
||||
|
|
@ -2136,7 +2130,7 @@ bool Bot::advanceMovement () {
|
|||
Task taskID = getCurrentTaskId ();
|
||||
|
||||
// only if we in normal task and bomb is not planted
|
||||
if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !hasHostage ()) {
|
||||
if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !m_hasHostage) {
|
||||
m_campButtons = 0;
|
||||
|
||||
const int nextIndex = m_pathWalk.next ();
|
||||
|
|
@ -2241,22 +2235,55 @@ bool Bot::advanceMovement () {
|
|||
changeNodeIndex (destIndex);
|
||||
}
|
||||
}
|
||||
setPathOrigin ();
|
||||
m_navTimeset = game.time ();
|
||||
|
||||
// if wayzone radius non zero vary origin a bit depending on the body angles
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bot::setPathOrigin () {
|
||||
constexpr int kMaxAlternatives = 5;
|
||||
|
||||
// if node radius non zero vary origin a bit depending on the body angles
|
||||
if (m_path->radius > 0.0f) {
|
||||
int nearestIndex = kInvalidNodeIndex;
|
||||
|
||||
if (!m_pathWalk.empty () && m_pathWalk.hasNext ()) {
|
||||
Vector orgs[kMaxAlternatives] {};
|
||||
|
||||
for (int i = 0; i < kMaxAlternatives; ++i) {
|
||||
orgs[i] = m_pathOrigin + Vector (rg.get (-m_path->radius, m_path->radius), rg.get (-m_path->radius, m_path->radius), 0.0f);
|
||||
}
|
||||
float closest = kInfiniteDistance;
|
||||
|
||||
for (int i = 0; i < kMaxAlternatives; ++i) {
|
||||
float distance = pev->origin.distanceSq (orgs[i]);
|
||||
|
||||
if (distance < closest) {
|
||||
nearestIndex = i;
|
||||
closest = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// set the origin if found alternative
|
||||
if (nearestIndex != kInvalidNodeIndex) {
|
||||
m_pathOrigin = orgs[nearestIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestIndex == kInvalidNodeIndex) {
|
||||
m_pathOrigin += Vector (pev->angles.x, cr::wrapAngle (pev->angles.y + rg.get (-90.0f, 90.0f)), 0.0f).forward () * rg.get (0.0f, m_path->radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnLadder ()) {
|
||||
TraceResult tr {};
|
||||
game.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_pathOrigin, TraceIgnore::Everything, ent (), &tr);
|
||||
|
||||
if (tr.flFraction < 1.0f) {
|
||||
m_pathOrigin = m_pathOrigin + (pev->origin - m_pathOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f);
|
||||
}
|
||||
}
|
||||
m_navTimeset = game.time ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
|
||||
|
|
@ -3158,67 +3185,89 @@ void Bot::findShortestPath (int srcIndex, int destIndex) {
|
|||
m_chosenGoalIndex = srcIndex;
|
||||
m_goalValue = 0.0f;
|
||||
|
||||
bool success = planner.find (srcIndex, destIndex, [&] (int index) {
|
||||
bool success = planner.find (srcIndex, destIndex, [this] (int index) {
|
||||
m_pathWalk.add (index);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (success) {
|
||||
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
|
||||
void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
|
||||
// this function finds a path from srcIndex to destIndex;
|
||||
|
||||
if (!m_pathFindLock.tryLock ()) {
|
||||
return; // allow only single instance of syncFindPath per-bot
|
||||
}
|
||||
ScopedUnlock <Mutex> unlock (m_pathFindLock);
|
||||
|
||||
if (!graph.exists (srcIndex)) {
|
||||
logger.error ("%s source path index not valid (%d).", __FUNCTION__, srcIndex);
|
||||
srcIndex = changeNodeIndex (graph.getNearestNoBuckets (pev->origin, 256.0f));
|
||||
|
||||
if (!graph.exists (srcIndex)) {
|
||||
printf ("%s source path index not valid (%d).", __func__, srcIndex);
|
||||
return;
|
||||
}
|
||||
else if (!graph.exists (destIndex)) {
|
||||
logger.error ("%s destination path index not valid (%d).", __FUNCTION__, destIndex);
|
||||
}
|
||||
else if (!graph.exists (destIndex) || destIndex == srcIndex) {
|
||||
destIndex = graph.getNearestNoBuckets (pev->origin, kInfiniteDistance, NodeFlag::Goal);
|
||||
|
||||
if (!graph.exists (destIndex) || srcIndex == destIndex) {
|
||||
destIndex = rg.get (0, graph.length () - 1);
|
||||
|
||||
if (!graph.exists (destIndex)) {
|
||||
printf ("%s dest path index not valid (%d).", __func__, destIndex);
|
||||
return;
|
||||
}
|
||||
auto pathPlanner = planner.getAStar ();
|
||||
}
|
||||
}
|
||||
|
||||
// do not process if src points to dst
|
||||
if (srcIndex == destIndex) {
|
||||
printf ("%s source path is same as dest (%d).", __func__, destIndex);
|
||||
return;
|
||||
}
|
||||
clearSearchNodes ();
|
||||
|
||||
// get correct calculation for heuristic
|
||||
if (pathType == FindPath::Optimal) {
|
||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||
pathPlanner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||
pathPlanner->setG (Heuristic::gfunctionKillsDistCTWithHostage);
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
|
||||
m_planner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||
m_planner->setG (Heuristic::gfunctionKillsDistCTWithHostage);
|
||||
}
|
||||
else {
|
||||
pathPlanner->setH (Heuristic::hfunctionPathDist);
|
||||
pathPlanner->setG (Heuristic::gfunctionKillsDist);
|
||||
m_planner->setH (Heuristic::hfunctionPathDist);
|
||||
m_planner->setG (Heuristic::gfunctionKillsDist);
|
||||
}
|
||||
}
|
||||
else if (pathType == FindPath::Safe) {
|
||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||
pathPlanner->setH (Heuristic::hfunctionNone);
|
||||
pathPlanner->setG (Heuristic::gfunctionKillsCTWithHostage);
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
|
||||
m_planner->setH (Heuristic::hfunctionNone);
|
||||
m_planner->setG (Heuristic::gfunctionKillsCTWithHostage);
|
||||
}
|
||||
else {
|
||||
pathPlanner->setH (Heuristic::hfunctionNone);
|
||||
pathPlanner->setG (Heuristic::gfunctionKills);
|
||||
m_planner->setH (Heuristic::hfunctionNone);
|
||||
m_planner->setG (Heuristic::gfunctionKills);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) {
|
||||
pathPlanner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||
pathPlanner->setG (Heuristic::gfunctionPathDistWithHostage);
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
|
||||
m_planner->setH (Heuristic::hfunctionPathDistWithHostage);
|
||||
m_planner->setG (Heuristic::gfunctionPathDistWithHostage);
|
||||
}
|
||||
else {
|
||||
pathPlanner->setH (Heuristic::hfunctionPathDist);
|
||||
pathPlanner->setG (Heuristic::gfunctionPathDist);
|
||||
m_planner->setH (Heuristic::hfunctionPathDist);
|
||||
m_planner->setG (Heuristic::gfunctionPathDist);
|
||||
}
|
||||
}
|
||||
clearSearchNodes ();
|
||||
|
||||
m_chosenGoalIndex = srcIndex;
|
||||
m_goalValue = 0.0f;
|
||||
|
||||
auto result = pathPlanner->find (m_team, srcIndex, destIndex, [&] (int index) {
|
||||
auto result = m_planner->find (m_team, srcIndex, destIndex, [this] (int index) {
|
||||
m_pathWalk.add (index);
|
||||
return true;
|
||||
});
|
||||
|
|
@ -3230,10 +3279,10 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
break;
|
||||
|
||||
case AStarResult::InternalError:
|
||||
// bot should not roam when this occurs
|
||||
kick (); // kick the bot off...
|
||||
m_kickMeFromServer = true; // bot should be kicked within main thread, not here
|
||||
|
||||
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 ());
|
||||
// bot should not roam when this occurs
|
||||
printf ("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:
|
||||
|
|
@ -3241,8 +3290,14 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
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 ());
|
||||
printf ("A* Search for bot \"%s\" has failed. Falling back to shortest-path algorithm. Seems to be graph is broken.", pev->netname.chars ());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) {
|
||||
worker.enqueue ([this, srcIndex, destIndex, pathType] () {
|
||||
syncFindPath (srcIndex, destIndex, pathType);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@
|
|||
#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_floyd_memory_limit ("yb_path_floyd_memory_limit", "6", "Limit maximum floyd-warshall memory (megabytes). Use Dijkstra if memory exceeds.", true, 0.0, 32.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.");
|
||||
ConVar cv_path_astar_post_smooth ("yb_path_astar_post_smooth", "0", "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));
|
||||
auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, true);
|
||||
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));
|
||||
cost += practice.plannerGetDamage (team, neighbour.index, neighbour.index, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,12 +44,12 @@ float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, in
|
|||
}
|
||||
|
||||
float Heuristic::gfunctionKills (int team, int currentIndex, int) {
|
||||
auto cost = static_cast <float> (practice.getDamage (team, currentIndex, currentIndex));
|
||||
auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, false);
|
||||
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));
|
||||
cost += practice.plannerGetDamage (team, neighbour.index, neighbour.index, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,11 +176,17 @@ 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);
|
||||
const bool hasZeroRadius = cr::fzero (ag.radius) || cr::fzero (ag.radius);
|
||||
|
||||
if (hasZeroRadius) {
|
||||
return true;
|
||||
}
|
||||
const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number);
|
||||
|
||||
if (notVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool tooHigh = cr::abs (ag.origin.z - bg.origin.z) > 17.0f;
|
||||
|
||||
if (tooHigh) {
|
||||
|
|
@ -191,9 +197,12 @@ bool AStarAlgo::cantSkipNode (const int a, const int b) {
|
|||
if (tooNarrow) {
|
||||
return true;
|
||||
}
|
||||
const bool tooFar = ag.origin.distanceSq (bg.origin) > cr::sqrf (300.0f);
|
||||
const float distance = ag.origin.distanceSq (bg.origin);
|
||||
|
||||
if (tooFar) {
|
||||
const bool tooFar = distance > cr::sqrf (400.0f);
|
||||
const bool tooClose = distance < cr::sqrtf (40.0f);
|
||||
|
||||
if (tooFar || tooClose) {
|
||||
return true;
|
||||
}
|
||||
bool hasJumps = false;
|
||||
|
|
@ -208,22 +217,23 @@ bool AStarAlgo::cantSkipNode (const int a, const int b) {
|
|||
}
|
||||
|
||||
void AStarAlgo::postSmooth (NodeAdderFn onAddedNode) {
|
||||
m_smoothedPath.clear ();
|
||||
|
||||
int index = 0;
|
||||
m_smoothedPath.emplace (m_constructedPath.first ());
|
||||
m_smoothedPath.push (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.push (m_constructedPath[i]);
|
||||
}
|
||||
}
|
||||
m_smoothedPath.emplace (m_constructedPath.last ());
|
||||
m_smoothedPath.push (m_constructedPath.last ());
|
||||
|
||||
// give nodes back to bot
|
||||
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) {
|
||||
|
|
@ -242,7 +252,10 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
|
|||
m_routeQue.clear ();
|
||||
m_routeQue.emplace (srcIndex, srcRoute->g);
|
||||
|
||||
bool postSmoothPath = cv_path_astar_post_smooth.bool_ ();
|
||||
bool postSmoothPath = cv_path_astar_post_smooth.bool_ () && vistab.isReady ();
|
||||
|
||||
// always clear constructed path
|
||||
m_constructedPath.clear ();
|
||||
|
||||
while (!m_routeQue.empty ()) {
|
||||
// remove the first node from the open list
|
||||
|
|
@ -258,7 +271,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
|
|||
// build the complete path
|
||||
do {
|
||||
if (postSmoothPath) {
|
||||
m_constructedPath.emplace (currentIndex);
|
||||
m_constructedPath.push (currentIndex);
|
||||
}
|
||||
else {
|
||||
onAddedNode (currentIndex);
|
||||
|
|
@ -312,6 +325,12 @@ void FloydWarshallAlgo::rebuild () {
|
|||
m_length = graph.length ();
|
||||
m_matrix.resize (cr::sqrf (m_length));
|
||||
|
||||
worker.enqueue ([this] () {
|
||||
syncRebuild ();
|
||||
});
|
||||
}
|
||||
|
||||
void FloydWarshallAlgo::syncRebuild () {
|
||||
auto matrix = m_matrix.data ();
|
||||
|
||||
// re-initialize matrix every load
|
||||
|
|
@ -379,18 +398,19 @@ bool FloydWarshallAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNo
|
|||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!onAddedNode (srcIndex)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
// only fill path distance on full path
|
||||
if (pathDistance != nullptr) {
|
||||
*pathDistance = dist (srcIndex, destIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DijkstraAlgo::init (const int length) {
|
||||
|
|
@ -400,15 +420,13 @@ void DijkstraAlgo::init (const int length) {
|
|||
m_parent.resize (length);
|
||||
}
|
||||
|
||||
void DijkstraAlgo::resetState () {
|
||||
bool DijkstraAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance) {
|
||||
MutexScopedLock lock (m_cs);
|
||||
|
||||
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;
|
||||
|
|
@ -473,7 +491,6 @@ int DijkstraAlgo::dist (int srcIndex, int destIndex) {
|
|||
PathPlanner::PathPlanner () {
|
||||
m_dijkstra = cr::makeUnique <DijkstraAlgo> ();
|
||||
m_floyd = cr::makeUnique <FloydWarshallAlgo> ();
|
||||
m_astar = cr::makeUnique <AStarAlgo> ();
|
||||
}
|
||||
|
||||
void PathPlanner::init () {
|
||||
|
|
@ -487,7 +504,6 @@ void PathPlanner::init () {
|
|||
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) {
|
||||
|
|
@ -503,7 +519,6 @@ bool PathPlanner::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, in
|
|||
if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// limit hit, use dijkstra
|
||||
if (m_memoryLimitHit) {
|
||||
return m_dijkstra->find (srcIndex, destIndex, onAddedNode, pathDistance);
|
||||
|
|
@ -511,7 +526,7 @@ bool PathPlanner::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, in
|
|||
return m_floyd->find (srcIndex, destIndex, onAddedNode, pathDistance);;
|
||||
}
|
||||
|
||||
int PathPlanner::dist (int srcIndex, int destIndex) {
|
||||
float PathPlanner::dist (int srcIndex, int destIndex) {
|
||||
if (!graph.exists (srcIndex) || !graph.exists (destIndex)) {
|
||||
return kInfiniteDistanceLong;
|
||||
}
|
||||
|
|
@ -523,9 +538,17 @@ int PathPlanner::dist (int srcIndex, int destIndex) {
|
|||
// 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 graph[srcIndex].origin.distance2d (graph[destIndex].origin);
|
||||
}
|
||||
return m_dijkstra->dist (srcIndex, destIndex);
|
||||
return static_cast <float> (m_dijkstra->dist (srcIndex, destIndex));
|
||||
}
|
||||
return m_floyd->dist (srcIndex, destIndex);
|
||||
return static_cast <float> (m_floyd->dist (srcIndex, destIndex));
|
||||
}
|
||||
|
||||
float PathPlanner::preciseDistance (int srcIndex, int destIndex) {
|
||||
// limit hit, use dijkstra
|
||||
if (m_memoryLimitHit) {
|
||||
return static_cast <float> (m_dijkstra->dist (srcIndex, destIndex));
|
||||
}
|
||||
return static_cast <float> (m_floyd->dist (srcIndex, destIndex));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ void BotPractice::setIndex (int32_t team, int32_t start, int32_t goal, int32_t v
|
|||
if (team != Team::Terrorist && team != Team::CT) {
|
||||
return;
|
||||
}
|
||||
MutexScopedLock lock (m_damageUpdateLock);
|
||||
|
||||
// reliability check
|
||||
if (!graph.exists (start) || !graph.exists (goal) || !graph.exists (value)) {
|
||||
|
|
@ -37,6 +38,7 @@ void BotPractice::setValue (int32_t team, int32_t start, int32_t goal, int32_t v
|
|||
if (team != Team::Terrorist && team != Team::CT) {
|
||||
return;
|
||||
}
|
||||
MutexScopedLock lock (m_damageUpdateLock);
|
||||
|
||||
// reliability check
|
||||
if (!graph.exists (start) || !graph.exists (goal)) {
|
||||
|
|
@ -46,7 +48,7 @@ void BotPractice::setValue (int32_t team, int32_t start, int32_t goal, int32_t v
|
|||
}
|
||||
|
||||
int32_t BotPractice::getDamage (int32_t team, int32_t start, int32_t goal) {
|
||||
if (!exists (team, start, goal)) {
|
||||
if (!vistab.isReady () || !exists (team, start, goal)) {
|
||||
return 0;
|
||||
}
|
||||
return m_data[{start, goal, team}].damage;
|
||||
|
|
@ -56,6 +58,7 @@ void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t
|
|||
if (team != Team::Terrorist && team != Team::CT) {
|
||||
return;
|
||||
}
|
||||
MutexScopedLock lock (m_damageUpdateLock);
|
||||
|
||||
// reliability check
|
||||
if (!graph.exists (start) || !graph.exists (goal)) {
|
||||
|
|
@ -64,7 +67,26 @@ void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t
|
|||
m_data[{start, goal, team}].damage = static_cast <int16_t> (value);
|
||||
}
|
||||
|
||||
float BotPractice::plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage) {
|
||||
if (!m_damageUpdateLock.tryLock ()) {
|
||||
return 1.0f;
|
||||
}
|
||||
ScopedUnlock <Mutex> unlock (m_damageUpdateLock);
|
||||
auto damage = static_cast <float> (getDamage (team, start, goal));
|
||||
|
||||
if (addTeamHighestDamage) {
|
||||
damage += getHighestDamageForTeam <float> (team);
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
void BotPractice::update () {
|
||||
worker.enqueue ([this] () {
|
||||
syncUpdate ();
|
||||
});
|
||||
}
|
||||
|
||||
void BotPractice::syncUpdate () {
|
||||
// this function called after each end of the round to update knowledge about most dangerous nodes for each team.
|
||||
|
||||
auto graphLength = graph.length ();
|
||||
|
|
@ -98,9 +120,12 @@ void BotPractice::update () {
|
|||
if (maxDamage > PracticeLimit::Damage) {
|
||||
adjustValues = true;
|
||||
}
|
||||
|
||||
if (graph.exists (bestIndex)) {
|
||||
setIndex (team, i, i, bestIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
constexpr auto kFullDamageVal = static_cast <int32_t> (PracticeLimit::Damage);
|
||||
constexpr auto kHalfDamageVal = static_cast <int32_t> (PracticeLimit::Damage / 2);
|
||||
|
||||
|
|
@ -117,6 +142,7 @@ void BotPractice::update () {
|
|||
}
|
||||
}
|
||||
}
|
||||
MutexScopedLock lock (m_damageUpdateLock);
|
||||
|
||||
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||
m_teamHighestDamage[team] = cr::clamp (m_teamHighestDamage[team] - kHalfDamageVal, 1, kFullDamageVal);
|
||||
|
|
|
|||
|
|
@ -405,9 +405,16 @@ int BotSupport::getPingBitmask (edict_t *ent, int loss, int ping) {
|
|||
}
|
||||
|
||||
void BotSupport::calculatePings () {
|
||||
worker.enqueue ([this] () {
|
||||
syncCalculatePings ();
|
||||
});
|
||||
}
|
||||
|
||||
void BotSupport::syncCalculatePings () {
|
||||
if (!game.is (GameFlags::HasFakePings) || cv_show_latency.int_ () != 2) {
|
||||
return;
|
||||
}
|
||||
MutexScopedLock lock (m_cs);
|
||||
|
||||
Twin <int, int> average { 0, 0 };
|
||||
int numHumans = 0;
|
||||
|
|
@ -444,7 +451,7 @@ void BotSupport::calculatePings () {
|
|||
}
|
||||
auto bot = bots[client.ent];
|
||||
|
||||
// we're only intrested in bots here
|
||||
// we're only interested in bots here
|
||||
if (!bot) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<ClInclude Include="..\ext\crlib\crlib\random.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\simd.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\string.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\thread.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\twin.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\ulz.h" />
|
||||
<ClInclude Include="..\ext\crlib\crlib\uniqueptr.h" />
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
<ClInclude Include="..\ext\hlsdk\metamod.h" />
|
||||
<ClInclude Include="..\ext\hlsdk\meta_api.h" />
|
||||
<ClInclude Include="..\ext\hlsdk\model.h" />
|
||||
<ClInclude Include="..\ext\hlsdk\physint.h" />
|
||||
<ClInclude Include="..\ext\hlsdk\progdefs.h" />
|
||||
<ClInclude Include="..\ext\hlsdk\util.h" />
|
||||
<ClInclude Include="..\inc\analyze.h" />
|
||||
|
|
|
|||
|
|
@ -171,6 +171,12 @@
|
|||
<ClInclude Include="..\inc\vistable.h">
|
||||
<Filter>inc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\crlib\crlib\thread.h">
|
||||
<Filter>inc\ext\crlib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\hlsdk\physint.h">
|
||||
<Filter>inc\ext\hlsdk</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\botlib.cpp">
|
||||
|
|
@ -221,15 +227,15 @@
|
|||
<ClCompile Include="..\src\analyze.cpp">
|
||||
<Filter>src</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\planner.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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue