2019-07-27 17:36:24 +03:00
//
2023-05-24 23:41:23 +03:00
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
2019-07-27 17:36:24 +03:00
//
2020-11-03 08:57:12 +03:00
// SPDX-License-Identifier: MIT
2019-07-27 17:36:24 +03:00
//
# include <yapb.h>
2023-07-21 21:43:36 +03:00
ConVar cv_graph_fixcamp ( " graph_fixcamp " , " 0 " , " Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format. " ) ;
ConVar cv_graph_url ( " graph_url " , product . download . chars ( ) , " Specifies the URL from which bots will be able to download graph in case of missing local one. Set to empty, if no downloads needed. " , false , 0.0f , 0.0f ) ;
2024-01-26 19:52:00 +03:00
ConVar cv_graph_url_upload ( " graph_url_upload " , product . upload . chars ( ) , " Specifies the URL to which bots will try to upload the graph file to database. " , false , 0.0f , 0.0f ) ;
2023-07-21 21:43:36 +03:00
ConVar cv_graph_auto_save_count ( " graph_auto_save_count " , " 15 " , " Every N graph nodes placed on map, the graph will be saved automatically (without checks) . " , true, 0.0f, kMaxNodes) ;
ConVar cv_graph_draw_distance ( " graph_draw_distance " , " 400 " , " Maximum distance to draw graph nodes from editor viewport. " , true , 64.0f , 3072.0f ) ;
2024-02-10 22:50:53 +03:00
ConVar cv_graph_auto_collect_db ( " graph_auto_collect_db " , " 1 " , " Allows bots to exchange your graph files with graph database automatically. " ) ;
2019-07-27 17:36:24 +03:00
2020-09-26 18:41:53 +03:00
void BotGraph : : reset ( ) {
2019-07-27 17:36:24 +03:00
// this function initialize the graph structures..
m_editFlags = 0 ;
2022-01-16 01:57:21 +03:00
m_autoSaveCount = 0 ;
2019-07-27 17:36:24 +03:00
2024-08-25 14:32:57 +03:00
m_learnVelocity . clear ( ) ;
m_learnPosition . clear ( ) ;
m_lastNode . clear ( ) ;
2019-07-27 17:36:24 +03:00
m_pathDisplayTime = 0.0f ;
m_arrowDisplayTime = 0.0f ;
m_autoPathDistance = 250.0f ;
m_hasChanged = false ;
2020-01-08 18:29:28 +03:00
m_narrowChecked = false ;
2023-07-18 15:59:15 +03:00
m_lightChecked = false ;
2019-07-27 17:36:24 +03:00
2022-10-04 12:30:14 +03:00
m_graphAuthor . clear ( ) ;
m_graphModified . clear ( ) ;
2023-05-02 09:42:43 +03:00
m_paths . clear ( ) ;
2019-07-27 17:36:24 +03:00
}
int BotGraph : : clearConnections ( int index ) {
// this function removes the useless paths connections from and to node pointed by index. This is based on code from POD-bot MM from KWo
if ( ! exists ( index ) ) {
return 0 ;
}
int numFixedLinks = 0 ;
// wrapper form unassiged paths
auto clearPath = [ & ] ( int from , int to ) {
unassignPath ( from , to ) ;
+ + numFixedLinks ;
} ;
if ( bots . hasBotsOnline ( ) ) {
bots . kickEveryone ( true ) ;
}
struct Connection {
2020-11-23 00:06:18 +03:00
int index { } ;
int number { } ;
2023-04-02 12:17:12 +03:00
float distance { } ;
2020-11-23 00:06:18 +03:00
float angles { } ;
2019-07-27 17:36:24 +03:00
public :
Connection ( ) {
reset ( ) ;
}
public :
void reset ( ) {
index = kInvalidNodeIndex ;
number = kInvalidNodeIndex ;
2023-04-02 12:17:12 +03:00
distance = kInfiniteDistance ;
2019-07-27 17:36:24 +03:00
angles = 0.0f ;
}
} ;
auto & path = m_paths [ index ] ;
2024-05-16 21:15:41 +03:00
Connection sorted [ kMaxNodeLinks ] { } ;
Connection top { } ;
2019-07-27 17:36:24 +03:00
for ( int i = 0 ; i < kMaxNodeLinks ; + + i ) {
auto & cur = sorted [ i ] ;
const auto & link = path . links [ i ] ;
cur . number = i ;
cur . index = link . index ;
2023-04-02 12:17:12 +03:00
cur . distance = static_cast < float > ( link . distance ) ;
2019-07-27 17:36:24 +03:00
if ( cur . index = = kInvalidNodeIndex ) {
2023-04-02 12:17:12 +03:00
cur . distance = kInfiniteDistance ;
2019-07-27 17:36:24 +03:00
}
if ( cur . distance < top . distance ) {
2023-04-02 12:17:12 +03:00
top . distance = static_cast < float > ( link . distance ) ;
2019-07-27 17:36:24 +03:00
top . number = i ;
top . index = cur . index ;
}
}
if ( top . number = = kInvalidNodeIndex ) {
2023-05-02 09:42:43 +03:00
msg ( " Cannot find path to the closest connected node to node number %d. " , index ) ;
2019-07-27 17:36:24 +03:00
return numFixedLinks ;
}
bool sorting = false ;
// sort paths from the closest node to the farest away one...
do {
sorting = false ;
for ( int i = 0 ; i < kMaxNodeLinks - 1 ; + + i ) {
if ( sorted [ i ] . distance > sorted [ i + 1 ] . distance ) {
cr : : swap ( sorted [ i ] , sorted [ i + 1 ] ) ;
sorting = true ;
}
}
} while ( sorting ) ;
// calculate angles related to the angle of the closeset connected node
for ( auto & cur : sorted ) {
if ( cur . index = = kInvalidNodeIndex ) {
2019-08-04 18:22:01 +03:00
cur . distance = kInfiniteDistanceLong ;
2019-07-27 17:36:24 +03:00
cur . angles = 360.0f ;
}
else if ( exists ( cur . index ) ) {
cur . angles = ( ( m_paths [ cur . index ] . origin - path . origin ) . angles ( ) - ( m_paths [ sorted [ 0 ] . index ] . origin - path . origin ) . angles ( ) ) . y ;
if ( cur . angles < 0.0f ) {
cur . angles + = 360.0f ;
}
}
}
// sort the paths from the lowest to the highest angle (related to the vector closest node - checked index)...
do {
sorting = false ;
for ( int i = 0 ; i < kMaxNodeLinks - 1 ; + + i ) {
if ( sorted [ i ] . index ! = kInvalidNodeIndex & & sorted [ i ] . angles > sorted [ i + 1 ] . angles ) {
cr : : swap ( sorted [ i ] , sorted [ i + 1 ] ) ;
sorting = true ;
}
}
} while ( sorting ) ;
// reset top state
top . reset ( ) ;
// printing all the stuff causes reliable message overflow
ctrl . setRapidOutput ( true ) ;
// check pass 0
auto inspect_p0 = [ & ] ( const int id ) - > bool {
if ( id < 2 ) {
return false ;
}
auto & cur = sorted [ id ] , & prev = sorted [ id - 1 ] , & prev2 = sorted [ id - 2 ] ;
if ( cur . index = = kInvalidNodeIndex | | prev . index = = kInvalidNodeIndex | | prev2 . index = = kInvalidNodeIndex ) {
return false ;
}
// store the highest index which should be tested later...
top . index = cur . index ;
top . distance = cur . distance ;
top . angles = cur . angles ;
if ( cur . angles - prev2 . angles < 80.0f ) {
// leave alone ladder connections and don't remove jump connections..
2024-02-16 00:57:41 +03:00
if ( ( ( path . flags & NodeFlag : : Ladder )
2024-05-05 01:06:56 +03:00
& & ( m_paths [ prev . index ] . flags & NodeFlag : : Ladder ) ) | | ( path . links [ prev . number ] . flags & PathFlag : : Jump ) ) {
2024-02-16 00:57:41 +03:00
2019-07-27 17:36:24 +03:00
return false ;
}
2023-04-02 12:17:12 +03:00
if ( ( cur . distance + prev2 . distance ) * 1.1f / 2.0f < prev . distance ) {
2019-07-27 17:36:24 +03:00
if ( path . links [ prev . number ] . index = = prev . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.0.1) connection from index = %d to %d. " , index , prev . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , prev . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ prev . index ] . links [ j ] . index = = index & & ! ( m_paths [ prev . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.0.2) connection from index = %d to %d. " , prev . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( prev . index , j ) ;
}
}
prev . index = kInvalidNodeIndex ;
for ( int j = id - 1 ; j < kMaxNodeLinks - 1 ; + + j ) {
sorted [ j ] = cr : : move ( sorted [ j + 1 ] ) ;
}
sorted [ kMaxNodeLinks - 1 ] . index = kInvalidNodeIndex ;
// do a second check
return true ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.0) connection from index = %d to %d. " , index , prev . index ) ;
2019-07-27 17:36:24 +03:00
return false ;
}
}
}
return false ;
} ;
for ( int i = 2 ; i < kMaxNodeLinks ; + + i ) {
2023-03-13 15:39:15 +03:00
while ( inspect_p0 ( i ) ) { }
2019-07-27 17:36:24 +03:00
}
// check pass 1
if ( exists ( top . index ) & & exists ( sorted [ 0 ] . index ) & & exists ( sorted [ 1 ] . index ) ) {
2024-02-16 00:57:41 +03:00
if ( ( sorted [ 1 ] . angles - top . angles < 80.0f | | 360.0f - ( sorted [ 1 ] . angles - top . angles ) < 80.0f )
2024-05-05 01:06:56 +03:00
& & ( ! ( m_paths [ sorted [ 0 ] . index ] . flags & NodeFlag : : Ladder ) | | ! ( path . flags & NodeFlag : : Ladder ) )
& & ! ( path . links [ sorted [ 0 ] . number ] . flags & PathFlag : : Jump ) ) {
2024-02-16 00:57:41 +03:00
2023-04-02 12:17:12 +03:00
if ( ( sorted [ 1 ] . distance + top . distance ) * 1.1f / 2.0f < sorted [ 0 ] . distance ) {
2019-07-27 17:36:24 +03:00
if ( path . links [ sorted [ 0 ] . number ] . index = = sorted [ 0 ] . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.1.1) connection from index = %d to %d. " , index , sorted [ 0 ] . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , sorted [ 0 ] . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ sorted [ 0 ] . index ] . links [ j ] . index = = index & & ! ( m_paths [ sorted [ 0 ] . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.1.2) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( sorted [ 0 ] . index , j ) ;
}
}
sorted [ 0 ] . index = kInvalidNodeIndex ;
for ( int j = 0 ; j < kMaxNodeLinks - 1 ; + + j ) {
sorted [ j ] = cr : : move ( sorted [ j + 1 ] ) ;
}
sorted [ kMaxNodeLinks - 1 ] . index = kInvalidNodeIndex ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.1) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
}
}
}
}
top . reset ( ) ;
// check pass 2
auto inspect_p2 = [ & ] ( const int id ) - > bool {
if ( id < 1 ) {
return false ;
}
auto & cur = sorted [ id ] , & prev = sorted [ id - 1 ] ;
if ( cur . index = = kInvalidNodeIndex | | prev . index = = kInvalidNodeIndex ) {
return false ;
}
if ( cur . angles - prev . angles < 40.0f ) {
2023-04-02 12:17:12 +03:00
if ( prev . distance < cur . distance * 1.1f ) {
2019-07-27 17:36:24 +03:00
// leave alone ladder connections and don't remove jump connections..
2024-02-16 00:57:41 +03:00
if ( ( ( path . flags & NodeFlag : : Ladder )
2024-05-05 01:06:56 +03:00
& & ( m_paths [ cur . index ] . flags & NodeFlag : : Ladder ) ) | | ( path . links [ cur . number ] . flags & PathFlag : : Jump ) ) {
2024-02-16 00:57:41 +03:00
2019-07-27 17:36:24 +03:00
return false ;
}
if ( path . links [ cur . number ] . index = = cur . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.2.1) connection from index = %d to %d. " , index , cur . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , cur . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ cur . index ] . links [ j ] . index = = index & & ! ( m_paths [ cur . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.2.2) connection from index = %d to %d. " , cur . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( cur . index , j ) ;
}
}
cur . index = kInvalidNodeIndex ;
for ( int j = id - 1 ; j < kMaxNodeLinks - 1 ; + + j ) {
sorted [ j ] = cr : : move ( sorted [ j + 1 ] ) ;
}
sorted [ kMaxNodeLinks - 1 ] . index = kInvalidNodeIndex ;
return true ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.2) connection from index = %d to %d. " , index , cur . index ) ;
2019-07-27 17:36:24 +03:00
}
}
2023-04-02 12:17:12 +03:00
else if ( cur . distance < prev . distance * 1.1f ) {
2019-07-27 17:36:24 +03:00
// leave alone ladder connections and don't remove jump connections..
2024-02-16 00:57:41 +03:00
if ( ( ( path . flags & NodeFlag : : Ladder )
2024-05-05 01:06:56 +03:00
& & ( m_paths [ prev . index ] . flags & NodeFlag : : Ladder ) )
| | ( path . links [ prev . number ] . flags & PathFlag : : Jump ) ) {
2024-02-16 00:57:41 +03:00
2019-07-27 17:36:24 +03:00
return false ;
}
if ( path . links [ prev . number ] . index = = prev . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.2.3) connection from index = %d to %d. " , index , prev . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , prev . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ prev . index ] . links [ j ] . index = = index & & ! ( m_paths [ prev . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.2.4) connection from index = %d to %d. " , prev . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( prev . index , j ) ;
}
}
prev . index = kInvalidNodeIndex ;
for ( int j = id - 1 ; j < kMaxNodeLinks - 1 ; + + j ) {
sorted [ j ] = cr : : move ( sorted [ j + 1 ] ) ;
}
sorted [ kMaxNodeLinks - 1 ] . index = kInvalidNodeIndex ;
// do a second check
return true ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.2) connection from index = %d to %d. " , index , prev . index ) ;
2019-07-27 17:36:24 +03:00
}
}
}
else {
top = cur ;
}
return false ;
} ;
for ( int i = 1 ; i < kMaxNodeLinks ; + + i ) {
2023-03-13 15:39:15 +03:00
while ( inspect_p2 ( i ) ) { }
2019-07-27 17:36:24 +03:00
}
// check pass 3
if ( exists ( top . index ) & & exists ( sorted [ 0 ] . index ) ) {
2024-02-16 00:57:41 +03:00
if ( ( top . angles - sorted [ 0 ] . angles < 40.0f | | ( 360.0f - top . angles - sorted [ 0 ] . angles ) < 40.0f )
2024-05-05 01:06:56 +03:00
& & ( ! ( m_paths [ sorted [ 0 ] . index ] . flags & NodeFlag : : Ladder ) | | ! ( path . flags & NodeFlag : : Ladder ) )
& & ! ( path . links [ sorted [ 0 ] . number ] . flags & PathFlag : : Jump ) ) {
2024-02-16 00:57:41 +03:00
2023-04-02 12:17:12 +03:00
if ( top . distance * 1.1f < sorted [ 0 ] . distance ) {
2019-07-27 17:36:24 +03:00
if ( path . links [ sorted [ 0 ] . number ] . index = = sorted [ 0 ] . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.3.1) connection from index = %d to %d. " , index , sorted [ 0 ] . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , sorted [ 0 ] . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ sorted [ 0 ] . index ] . links [ j ] . index = = index & & ! ( m_paths [ sorted [ 0 ] . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.3.2) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( sorted [ 0 ] . index , j ) ;
}
}
sorted [ 0 ] . index = kInvalidNodeIndex ;
for ( int j = 0 ; j < kMaxNodeLinks - 1 ; + + j ) {
sorted [ j ] = cr : : move ( sorted [ j + 1 ] ) ;
}
sorted [ kMaxNodeLinks - 1 ] . index = kInvalidNodeIndex ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.3) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
}
}
2023-04-02 12:17:12 +03:00
else if ( sorted [ 0 ] . distance * 1.1f < top . distance & & ! ( path . links [ top . number ] . flags & PathFlag : : Jump ) ) {
2019-07-27 17:36:24 +03:00
if ( path . links [ top . number ] . index = = top . index ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.3.3) connection from index = %d to %d. " , index , sorted [ 0 ] . index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( index , top . number ) ;
for ( int j = 0 ; j < kMaxNodeLinks ; + + j ) {
if ( m_paths [ top . index ] . links [ j ] . index = = index & & ! ( m_paths [ top . index ] . links [ j ] . flags & PathFlag : : Jump ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Removing a useless (P.3.4) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
// unassign this path
clearPath ( top . index , j ) ;
}
}
sorted [ 0 ] . index = kInvalidNodeIndex ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Failed to remove a useless (P.3) connection from index = %d to %d. " , sorted [ 0 ] . index , index ) ;
2019-07-27 17:36:24 +03:00
}
}
}
}
ctrl . setRapidOutput ( false ) ;
return numFixedLinks ;
}
2021-09-20 13:50:00 +03:00
int BotGraph : : getBspSize ( ) {
2025-02-28 00:39:52 +03:00
if ( File bsp { strings . joinPath ( game . getRunningModName ( ) , " maps " , game . getMapName ( ) ) + " .bsp " , " rb " } ) {
return static_cast < int > ( bsp . length ( ) ) ;
}
2021-09-20 13:50:00 +03:00
2025-02-28 00:39:52 +03:00
// worst case, load using engine (engfuncs.pfnGetFileSize isn't available on some legacy engines)
if ( MemFile bsp { strings . joinPath ( game . getRunningModName ( ) , " maps " , game . getMapName ( ) ) + " .bsp " } ) {
return static_cast < int > ( bsp . length ( ) ) ;
2021-09-20 13:50:00 +03:00
}
return 0 ;
}
2019-07-27 17:36:24 +03:00
void BotGraph : : addPath ( int addIndex , int pathIndex , float distance ) {
2023-05-02 09:42:43 +03:00
if ( ! exists ( addIndex ) | | ! exists ( pathIndex ) | | pathIndex = = addIndex ) {
2019-07-27 17:36:24 +03:00
return ;
}
auto & path = m_paths [ addIndex ] ;
// don't allow paths get connected twice
for ( const auto & link : path . links ) {
if ( link . index = = pathIndex ) {
2023-05-02 09:42:43 +03:00
msg ( " Denied path creation from %d to %d (path already exists). " , addIndex , pathIndex ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
2024-01-19 00:03:45 +03:00
auto integerDistance = cr : : abs ( static_cast < int > ( distance ) ) ;
2019-07-27 17:36:24 +03:00
// check for free space in the connection indices
for ( auto & link : path . links ) {
if ( link . index = = kInvalidNodeIndex ) {
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
link . index = static_cast < int16_t > ( pathIndex ) ;
2024-01-19 00:03:45 +03:00
link . distance = integerDistance ;
2024-05-05 01:06:56 +03:00
2023-05-02 09:42:43 +03:00
msg ( " Path added from %d to %d. " , addIndex , pathIndex ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
// there wasn't any free space. try exchanging it with a long-distance path
2019-08-04 18:22:01 +03:00
int maxDistance = - kInfiniteDistanceLong ;
2019-07-27 17:36:24 +03:00
int slot = kInvalidNodeIndex ;
for ( int i = 0 ; i < kMaxNodeLinks ; + + i ) {
if ( path . links [ i ] . distance > maxDistance ) {
maxDistance = path . links [ i ] . distance ;
slot = i ;
}
}
if ( slot ! = kInvalidNodeIndex ) {
2023-05-02 09:42:43 +03:00
msg ( " Path added from %d to %d. " , addIndex , pathIndex ) ;
2019-07-27 17:36:24 +03:00
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
path . links [ slot ] . index = static_cast < int16_t > ( pathIndex ) ;
2024-01-19 00:03:45 +03:00
path . links [ slot ] . distance = integerDistance ;
2019-07-27 17:36:24 +03:00
}
}
2023-06-24 02:36:51 +03:00
int BotGraph : : getFarest ( const Vector & origin , const float maxRange ) {
2019-07-27 17:36:24 +03:00
// find the farest node to that origin, and return the index to this node
int index = kInvalidNodeIndex ;
2023-06-24 02:36:51 +03:00
auto maxDistanceSq = cr : : sqrf ( maxRange ) ;
2019-07-27 17:36:24 +03:00
for ( const auto & path : m_paths ) {
2023-06-24 02:36:51 +03:00
const float distanceSq = path . origin . distanceSq ( origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq > maxDistanceSq ) {
2019-07-27 17:36:24 +03:00
index = path . number ;
2023-06-24 02:36:51 +03:00
maxDistanceSq = distanceSq ;
2019-07-27 17:36:24 +03:00
}
}
return index ;
}
2023-06-24 02:36:51 +03:00
int BotGraph : : getForAnalyzer ( const Vector & origin , const float maxRange ) {
2023-05-02 09:42:43 +03:00
// find the farest node to that origin, and return the index to this node
int index = kInvalidNodeIndex ;
2023-06-24 02:36:51 +03:00
float maximumDistanceSq = cr : : sqrf ( maxRange ) ;
2023-05-02 09:42:43 +03:00
for ( const auto & path : m_paths ) {
2023-06-24 02:36:51 +03:00
const float distanceSq = path . origin . distanceSq ( origin ) ;
2023-05-02 09:42:43 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq < maximumDistanceSq ) {
2023-05-02 09:42:43 +03:00
index = path . number ;
2023-06-24 02:36:51 +03:00
maximumDistanceSq = distanceSq ;
2023-05-02 09:42:43 +03:00
}
}
return index ;
}
2024-06-06 20:38:05 +03:00
int BotGraph : : getNearestNoBuckets ( const Vector & origin , const float range , int flags ) {
2019-07-27 17:36:24 +03:00
// find the nearest node to that origin and return the index
// fallback and go thru wall the nodes...
int index = kInvalidNodeIndex ;
2024-06-06 20:38:05 +03:00
float nearestDistanceSq = cr : : sqrf ( range ) ;
2019-07-27 17:36:24 +03:00
for ( const auto & path : m_paths ) {
if ( flags ! = - 1 & & ! ( path . flags & flags ) ) {
continue ; // if flag not -1 and node has no this flag, skip node
}
2023-06-24 02:36:51 +03:00
const float distanceSq = path . origin . distanceSq ( origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq < nearestDistanceSq ) {
2019-07-27 17:36:24 +03:00
index = path . number ;
2023-06-24 02:36:51 +03:00
nearestDistanceSq = distanceSq ;
2019-07-27 17:36:24 +03:00
}
}
return index ;
}
2024-01-19 00:03:45 +03:00
int BotGraph : : getEditorNearest ( const float maxRange ) {
2019-07-27 17:36:24 +03:00
if ( ! hasEditFlag ( GraphEdit : : On ) ) {
return kInvalidNodeIndex ;
}
2024-01-19 00:03:45 +03:00
return getNearestNoBuckets ( m_editor - > v . origin , maxRange ) ;
2019-07-27 17:36:24 +03:00
}
2023-06-24 02:36:51 +03:00
int BotGraph : : getNearest ( const Vector & origin , const float range , int flags ) {
2019-07-27 17:36:24 +03:00
// find the nearest node to that origin and return the index
2024-12-20 01:04:59 +03:00
// if not alot of nodes on the map, do not bother to use buckets here, we're dont
// get any performance improvement
constexpr auto kMinNodesForBucketsThreshold = 164 ;
if ( length ( ) < kMinNodesForBucketsThreshold ) {
return getNearestNoBuckets ( origin , range , flags ) ;
}
2023-06-24 02:36:51 +03:00
if ( range > 256.0f & & ! cr : : fequal ( range , kInfiniteDistance ) ) {
return getNearestNoBuckets ( origin , range , flags ) ;
2023-04-11 22:32:28 +03:00
}
const auto & bucket = getNodesInBucket ( origin ) ;
2019-07-27 17:36:24 +03:00
2023-04-11 22:32:28 +03:00
if ( bucket . length ( ) < kMaxNodeLinks ) {
2023-06-24 02:36:51 +03:00
return getNearestNoBuckets ( origin , range , flags ) ;
2019-07-27 17:36:24 +03:00
}
int index = kInvalidNodeIndex ;
2023-06-24 02:36:51 +03:00
auto nearestDistanceSq = cr : : sqrf ( range ) ;
2019-07-27 17:36:24 +03:00
for ( const auto & at : bucket ) {
if ( flags ! = - 1 & & ! ( m_paths [ at ] . flags & flags ) ) {
continue ; // if flag not -1 and node has no this flag, skip node
}
2023-06-24 02:36:51 +03:00
const float distanceSq = origin . distanceSq ( m_paths [ at ] . origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq < nearestDistanceSq ) {
2019-07-27 17:36:24 +03:00
index = at ;
2023-06-24 02:36:51 +03:00
nearestDistanceSq = distanceSq ;
2019-07-27 17:36:24 +03:00
}
}
// nothing found, try to find without buckets
if ( index = = kInvalidNodeIndex ) {
2023-06-24 02:36:51 +03:00
return getNearestNoBuckets ( origin , range , flags ) ;
2019-07-27 17:36:24 +03:00
}
return index ;
}
2024-06-06 20:38:05 +03:00
IntArray BotGraph : : getNearestInRadius ( float radius , const Vector & origin , int maxCount ) {
2019-07-27 17:36:24 +03:00
// returns all nodes within radius from position
2024-06-06 20:38:05 +03:00
const float radiusSq = cr : : sqrf ( radius ) ;
2023-04-11 22:32:28 +03:00
2024-05-16 21:15:41 +03:00
IntArray result { } ;
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
const auto & bucket = getNodesInBucket ( origin ) ;
2019-07-27 17:36:24 +03:00
2024-06-06 20:38:05 +03:00
if ( bucket . length ( ) < kMaxNodeLinks | | radius > cr : : sqrf ( 256.0f ) ) {
2023-04-11 22:32:28 +03:00
for ( const auto & path : m_paths ) {
if ( maxCount ! = - 1 & & static_cast < int > ( result . length ( ) ) > maxCount ) {
break ;
}
2023-06-24 02:36:51 +03:00
if ( origin . distanceSq ( path . origin ) < radiusSq ) {
2023-04-11 22:32:28 +03:00
result . push ( path . number ) ;
}
}
2020-11-23 00:06:18 +03:00
return result ;
2019-07-27 17:36:24 +03:00
}
for ( const auto & at : bucket ) {
if ( maxCount ! = - 1 & & static_cast < int > ( result . length ( ) ) > maxCount ) {
break ;
}
2023-06-24 02:36:51 +03:00
if ( origin . distanceSq ( m_paths [ at ] . origin ) < radiusSq ) {
2019-07-27 17:36:24 +03:00
result . push ( at ) ;
}
}
2020-11-23 00:06:18 +03:00
return result ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : add ( int type , const Vector & pos ) {
2023-05-02 09:42:43 +03:00
if ( game . isNullEntity ( m_editor ) & & ! analyzer . isAnalyzing ( ) ) {
2019-07-27 17:36:24 +03:00
return ;
}
int index = kInvalidNodeIndex ;
Path * path = nullptr ;
bool addNewNode = true ;
2023-05-02 09:42:43 +03:00
Vector newOrigin = pos ;
if ( newOrigin . empty ( ) ) {
if ( game . isNullEntity ( m_editor ) ) {
return ;
}
newOrigin = m_editor - > v . origin ;
}
2019-07-27 17:36:24 +03:00
if ( bots . hasBotsOnline ( ) ) {
bots . kickEveryone ( true ) ;
}
m_hasChanged = true ;
switch ( type ) {
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : Camp :
2023-01-22 19:12:03 +06:00
index = getEditorNearest ( ) ;
2019-08-29 23:31:54 +03:00
if ( index ! = kInvalidNodeIndex ) {
path = & m_paths [ index ] ;
if ( path - > flags & NodeFlag : : Camp ) {
path - > start = m_editor - > v . v_angle . get2d ( ) ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Done ) ; // play "done" sound...
2019-08-29 23:31:54 +03:00
return ;
}
}
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : CampEnd :
2023-01-22 19:12:03 +06:00
index = getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( index ! = kInvalidNodeIndex ) {
path = & m_paths [ index ] ;
if ( ! ( path - > flags & NodeFlag : : Camp ) ) {
2023-05-02 09:42:43 +03:00
msg ( " This is not camping node. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
path - > end = m_editor - > v . v_angle . get2d ( ) ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Done ) ; // play "done" sound...
2019-07-27 17:36:24 +03:00
}
return ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : JumpStart :
2024-01-19 00:03:45 +03:00
index = getEditorNearest ( 25.0f ) ;
2019-07-27 17:36:24 +03:00
if ( index ! = kInvalidNodeIndex & & m_paths [ index ] . number > = 0 ) {
2023-06-24 02:36:51 +03:00
const float distanceSq = m_editor - > v . origin . distanceSq ( m_paths [ index ] . origin ) ;
2019-07-27 17:36:24 +03:00
2024-01-19 00:03:45 +03:00
if ( distanceSq < cr : : sqrf ( 25.0f ) ) {
2019-07-27 17:36:24 +03:00
addNewNode = false ;
path = & m_paths [ index ] ;
path - > origin = ( path - > origin + m_learnPosition ) * 0.5f ;
}
}
else {
newOrigin = m_learnPosition ;
}
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : JumpEnd :
2024-01-19 00:03:45 +03:00
index = getEditorNearest ( 25.0f ) ;
2019-07-27 17:36:24 +03:00
if ( index ! = kInvalidNodeIndex & & m_paths [ index ] . number > = 0 ) {
2023-06-24 02:36:51 +03:00
const float distanceSq = m_editor - > v . origin . distanceSq ( m_paths [ index ] . origin ) ;
2019-07-27 17:36:24 +03:00
2024-01-19 00:03:45 +03:00
if ( distanceSq < cr : : sqrf ( 25.0f ) ) {
2019-07-27 17:36:24 +03:00
addNewNode = false ;
path = & m_paths [ index ] ;
int connectionFlags = 0 ;
for ( const auto & link : path - > links ) {
connectionFlags + = link . flags ;
}
if ( connectionFlags = = 0 ) {
path - > origin = ( path - > origin + m_editor - > v . origin ) * 0.5f ;
}
}
}
break ;
}
if ( addNewNode ) {
2023-05-02 09:42:43 +03:00
if ( analyzer . isAnalyzing ( ) ) {
for ( const auto & cp : m_paths ) {
if ( newOrigin . distanceSq ( cp . origin ) < cr : : sqrf ( 24.0f ) ) {
return ;
}
}
}
else {
auto nearest = getEditorNearest ( ) ;
2019-08-29 23:31:54 +03:00
2023-05-12 22:12:22 +03:00
// do not allow to place node "inside" node, make at leat 10 units range
2023-05-02 09:42:43 +03:00
if ( exists ( nearest ) & & newOrigin . distanceSq ( m_paths [ nearest ] . origin ) < cr : : sqrf ( 10.0f ) ) {
msg ( " Can't add node. It's way to near to %d node. Please move some units anywhere. " , nearest ) ;
return ;
}
2019-08-29 23:31:54 +03:00
}
2019-07-27 17:36:24 +03:00
// need to remove limit?
if ( m_paths . length ( ) > = kMaxNodes ) {
return ;
}
2022-01-16 01:57:21 +03:00
m_paths . emplace ( ) ;
2019-07-27 17:36:24 +03:00
2020-06-12 18:52:38 +03:00
index = length ( ) - 1 ;
2019-07-27 17:36:24 +03:00
path = & m_paths [ index ] ;
path - > number = index ;
path - > flags = 0 ;
// store the origin (location) of this node
path - > origin = newOrigin ;
2024-08-25 14:32:57 +03:00
path - > start . clear ( ) ;
path - > end . clear ( ) ;
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
path - > display = 0.0f ;
2023-06-24 03:23:22 +03:00
path - > light = kInvalidLightLevel ;
2019-07-27 17:36:24 +03:00
for ( auto & link : path - > links ) {
link . index = kInvalidNodeIndex ;
link . distance = 0 ;
link . flags = 0 ;
2024-08-25 14:32:57 +03:00
link . velocity . clear ( ) ;
2019-07-27 17:36:24 +03:00
}
2022-01-16 01:57:21 +03:00
// autosave nodes here and there
2024-04-25 15:03:39 +03:00
if ( ! analyzer . isAnalyzing ( ) & & cv_graph_auto_save_count & & + + m_autoSaveCount > = cv_graph_auto_save_count . as < int > ( ) ) {
2022-01-16 01:57:21 +03:00
if ( saveGraphData ( ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Nodes has been autosaved... " ) ;
2022-01-16 01:57:21 +03:00
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Can't autosave graph data... " ) ;
2022-01-16 01:57:21 +03:00
}
m_autoSaveCount = 0 ;
}
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
// store the last used node for the auto node code...
2023-05-02 09:42:43 +03:00
if ( ! analyzer . isAnalyzing ( ) ) {
m_lastNode = m_editor - > v . origin ;
}
2019-07-27 17:36:24 +03:00
}
2021-09-20 13:50:00 +03:00
if ( type = = NodeAddFlag : : JumpStart ) {
2019-07-27 17:36:24 +03:00
m_lastJumpNode = index ;
}
2021-09-20 13:50:00 +03:00
else if ( type = = NodeAddFlag : : JumpEnd ) {
2023-06-24 02:36:51 +03:00
const float distance = m_paths [ m_lastJumpNode ] . origin . distance ( m_editor - > v . origin ) ;
2019-07-27 17:36:24 +03:00
addPath ( m_lastJumpNode , index , distance ) ;
for ( auto & link : m_paths [ m_lastJumpNode ] . links ) {
if ( link . index = = index ) {
link . flags | = PathFlag : : Jump ;
link . velocity = m_learnVelocity ;
break ;
}
}
calculatePathRadius ( index ) ;
return ;
}
if ( ! path | | path - > number = = kInvalidNodeIndex ) {
return ;
}
2023-05-02 09:42:43 +03:00
if ( analyzer . isCrouch ( ) | | ( ! analyzer . isAnalyzing ( ) & & ( m_editor - > v . flags & FL_DUCKING ) ) ) {
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Crouch ; // set a crouch node
}
2023-05-02 09:42:43 +03:00
if ( ! analyzer . isAnalyzing ( ) & & m_editor - > v . movetype = = MOVETYPE_FLY ) {
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Ladder ;
}
else if ( m_isOnLadder ) {
path - > flags | = NodeFlag : : Ladder ;
}
switch ( type ) {
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : TOnly :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Crossing ;
path - > flags | = NodeFlag : : TerroristOnly ;
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : CTOnly :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Crossing ;
path - > flags | = NodeFlag : : CTOnly ;
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : NoHostage :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : NoHostage ;
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : Rescue :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Rescue ;
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : Camp :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Crossing ;
path - > flags | = NodeFlag : : Camp ;
2023-05-02 09:42:43 +03:00
if ( ! analyzer . isAnalyzing ( ) ) {
path - > start = m_editor - > v . v_angle ;
path - > end = m_editor - > v . v_angle ;
}
2019-07-27 17:36:24 +03:00
break ;
2021-09-20 13:50:00 +03:00
case NodeAddFlag : : Goal :
2019-07-27 17:36:24 +03:00
path - > flags | = NodeFlag : : Goal ;
break ;
}
2023-05-02 09:42:43 +03:00
// ladder nodes need careful connections
2019-07-27 17:36:24 +03:00
if ( path - > flags & NodeFlag : : Ladder ) {
2023-06-24 02:36:51 +03:00
float nearestDistance = kInfiniteDistance ;
2019-07-27 17:36:24 +03:00
int destIndex = kInvalidNodeIndex ;
2019-09-21 23:20:33 +03:00
TraceResult tr { } ;
2019-07-27 17:36:24 +03:00
// calculate all the paths to this new node
for ( const auto & calc : m_paths ) {
if ( calc . number = = index ) {
continue ; // skip the node that was just added
}
// other ladder nodes should connect to this
if ( calc . flags & NodeFlag : : Ladder ) {
// check if the node is reachable from the new one
game . testLine ( newOrigin , calc . origin , TraceIgnore : : Monsters , m_editor , & tr ) ;
2024-02-16 00:57:41 +03:00
if ( cr : : fequal ( tr . flFraction , 1.0f )
2024-05-05 01:06:56 +03:00
& & cr : : abs ( newOrigin . x - calc . origin . x ) < 64.0f
& & cr : : abs ( newOrigin . y - calc . origin . y ) < 64.0f
& & cr : : abs ( newOrigin . z - calc . origin . z ) < m_autoPathDistance ) {
2024-02-16 00:57:41 +03:00
2023-06-24 02:36:51 +03:00
const float distance = newOrigin . distance2d ( calc . origin ) ;
2019-07-27 17:36:24 +03:00
addPath ( index , calc . number , distance ) ;
addPath ( calc . number , index , distance ) ;
}
}
else {
2023-06-24 02:36:51 +03:00
const float distance = newOrigin . distance2d ( calc . origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distance < nearestDistance ) {
2023-05-02 09:42:43 +03:00
destIndex = calc . number ;
2023-06-24 02:36:51 +03:00
nearestDistance = distance ;
2023-05-02 09:42:43 +03:00
}
// check if the node is reachable from the new one
if ( isNodeReacheable ( newOrigin , calc . origin ) ) {
addPath ( index , calc . number , distance ) ;
2019-07-27 17:36:24 +03:00
}
}
}
if ( exists ( destIndex ) ) {
2023-05-02 09:42:43 +03:00
const float distance = newOrigin . distance2d ( m_paths [ destIndex ] . origin ) ;
if ( analyzer . isAnalyzing ( ) ) {
addPath ( index , destIndex , distance ) ;
addPath ( destIndex , index , distance ) ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
else {
// check if the node is reachable from the new one (one-way)
if ( isNodeReacheable ( newOrigin , m_paths [ destIndex ] . origin ) ) {
addPath ( index , destIndex , newOrigin . distance ( m_paths [ destIndex ] . origin ) ) ;
}
2019-07-27 17:36:24 +03:00
2023-05-02 09:42:43 +03:00
// check if the new one is reachable from the node (other way)
if ( isNodeReacheable ( m_paths [ destIndex ] . origin , newOrigin ) ) {
addPath ( destIndex , index , newOrigin . distance ( m_paths [ destIndex ] . origin ) ) ;
}
2019-07-27 17:36:24 +03:00
}
}
}
else {
// calculate all the paths to this new node
for ( const auto & calc : m_paths ) {
if ( calc . number = = index ) {
continue ; // skip the node that was just added
}
2023-05-02 09:42:43 +03:00
const float distance = calc . origin . distance2d ( newOrigin ) ;
2019-07-27 17:36:24 +03:00
// check if the node is reachable from the new one (one-way)
if ( isNodeReacheable ( newOrigin , calc . origin ) ) {
2023-05-02 09:42:43 +03:00
addPath ( index , calc . number , distance ) ;
2019-07-27 17:36:24 +03:00
}
// check if the new one is reachable from the node (other way)
if ( isNodeReacheable ( calc . origin , newOrigin ) ) {
2023-05-02 09:42:43 +03:00
addPath ( calc . number , index , distance ) ;
2019-07-27 17:36:24 +03:00
}
}
2023-05-02 09:42:43 +03:00
if ( ! analyzer . isAnalyzing ( ) ) {
clearConnections ( index ) ;
}
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Added ) ;
2019-07-27 17:36:24 +03:00
calculatePathRadius ( index ) ; // calculate the wayzone of this node
2023-05-02 09:42:43 +03:00
if ( analyzer . isAnalyzing ( ) ) {
analyzer . markOptimized ( index ) ;
}
2019-07-27 17:36:24 +03:00
}
void BotGraph : : erase ( int target ) {
m_hasChanged = true ;
if ( m_paths . empty ( ) ) {
return ;
}
if ( bots . hasBotsOnline ( ) ) {
bots . kickEveryone ( true ) ;
}
2023-01-22 19:12:03 +06:00
const int index = ( target = = kInvalidNodeIndex ) ? getEditorNearest ( ) : target ;
2019-07-27 17:36:24 +03:00
if ( ! exists ( index ) ) {
return ;
}
auto & path = m_paths [ index ] ;
// unassign paths that points to this nodes
for ( auto & connected : m_paths ) {
for ( auto & link : connected . links ) {
if ( link . index = = index ) {
link . index = kInvalidNodeIndex ;
link . flags = 0 ;
link . distance = 0 ;
2024-08-25 14:32:57 +03:00
link . velocity . clear ( ) ;
2019-07-27 17:36:24 +03:00
}
}
}
// relink nodes so the index will match path number
for ( auto & relink : m_paths ) {
// if pathnumber bigger than deleted node...
if ( relink . number > index ) {
- - relink . number ;
}
for ( auto & neighbour : relink . links ) {
if ( neighbour . index > index ) {
- - neighbour . index ;
}
}
}
m_paths . remove ( path ) ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Change ) ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : toggleFlags ( int toggleFlag ) {
// this function allow manually changing flags
2023-01-22 19:12:03 +06:00
int index = getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( index ! = kInvalidNodeIndex ) {
if ( m_paths [ index ] . flags & toggleFlag ) {
m_paths [ index ] . flags & = ~ toggleFlag ;
}
2020-10-03 16:39:09 +03:00
else {
2019-07-27 17:36:24 +03:00
if ( toggleFlag = = NodeFlag : : Sniper & & ! ( m_paths [ index ] . flags & NodeFlag : : Camp ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Cannot assign sniper flag to node %d. This is not camp node. " , index ) ;
2019-07-27 17:36:24 +03:00
return ;
}
m_paths [ index ] . flags | = toggleFlag ;
}
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Done ) ; // play "done" sound...
2019-07-27 17:36:24 +03:00
}
}
void BotGraph : : setRadius ( int index , float radius ) {
// this function allow manually setting the zone radius
2023-06-24 02:36:51 +03:00
const int node = exists ( index ) ? index : getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( node ! = kInvalidNodeIndex ) {
2023-04-02 12:17:12 +03:00
m_paths [ node ] . radius = radius ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Done ) ; // play "done" sound...
2019-07-27 17:36:24 +03:00
2023-05-02 09:42:43 +03:00
msg ( " Node %d has been set to radius %.2f. " , node , radius ) ;
2019-07-27 17:36:24 +03:00
}
}
bool BotGraph : : isConnected ( int a , int b ) {
// this function checks if node A has a connection to node B
2019-07-28 15:47:46 +03:00
if ( ! exists ( a ) | | ! exists ( b ) ) {
return false ;
}
2019-07-27 17:36:24 +03:00
for ( const auto & link : m_paths [ a ] . links ) {
if ( link . index = = b ) {
return true ;
}
}
return false ;
}
int BotGraph : : getFacingIndex ( ) {
2023-05-12 22:12:22 +03:00
// find the node the user is pointing at
2019-07-27 17:36:24 +03:00
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
Twin < int32_t , float > result { kInvalidNodeIndex , 5.32f } ;
2023-01-22 19:12:03 +06:00
auto nearestNode = getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
2019-08-31 22:55:16 +03:00
// check bounds from eyes of editor
const auto & editorEyes = m_editor - > v . origin + m_editor - > v . view_ofs ;
2019-07-27 17:36:24 +03:00
2019-08-31 22:55:16 +03:00
for ( const auto & path : m_paths ) {
2019-07-27 17:36:24 +03:00
2023-05-12 22:12:22 +03:00
// skip nearest node to editor, since this used mostly for adding / removing paths
2019-08-31 22:55:16 +03:00
if ( path . number = = nearestNode ) {
continue ;
}
2019-08-29 23:31:54 +03:00
2019-08-31 22:55:16 +03:00
const auto & to = path . origin - m_editor - > v . origin ;
auto angles = ( to . angles ( ) - m_editor - > v . v_angle ) . clampAngles ( ) ;
2019-08-29 23:31:54 +03:00
2023-05-12 22:12:22 +03:00
// skip the nodes that are too far away from us, and we're not looking at them directly
2024-08-06 09:14:23 +03:00
if ( to . lengthSq ( ) > cr : : sqrf ( cv_graph_draw_distance . as < float > ( ) ) | | cr : : abs ( angles . y ) > result . second ) {
2019-08-31 22:55:16 +03:00
continue ;
}
2023-03-13 15:39:15 +03:00
2023-05-12 22:12:22 +03:00
// check if visible, (we're not using visibility tables here, as they not valid at time of node editing)
2019-09-21 23:20:33 +03:00
TraceResult tr { } ;
2019-08-31 22:55:16 +03:00
game . testLine ( editorEyes , path . origin , TraceIgnore : : Everything , m_editor , & tr ) ;
2019-08-29 23:31:54 +03:00
2019-08-31 22:55:16 +03:00
if ( ! cr : : fequal ( tr . flFraction , 1.0f ) ) {
2019-08-29 23:31:54 +03:00
continue ;
}
2019-08-31 22:55:16 +03:00
const float bestAngle = angles . y ;
2019-07-27 17:36:24 +03:00
2019-08-31 22:55:16 +03:00
angles = - m_editor - > v . v_angle ;
angles . x = - angles . x ;
angles = ( angles + ( ( path . origin - Vector ( 0.0f , 0.0f , ( path . flags & NodeFlag : : Crouch ) ? 17.0f : 34.0f ) ) - editorEyes ) . angles ( ) ) . clampAngles ( ) ;
if ( angles . x > 0.0f ) {
continue ;
2019-07-27 17:36:24 +03:00
}
2019-08-31 22:55:16 +03:00
result = { path . number , bestAngle } ;
2019-07-27 17:36:24 +03:00
}
2019-08-31 22:55:16 +03:00
return result . first ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : pathCreate ( char dir ) {
// this function allow player to manually create a path from one node to another
2023-01-22 19:12:03 +06:00
int nodeFrom = getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( nodeFrom = = kInvalidNodeIndex ) {
2023-05-02 09:42:43 +03:00
msg ( " Unable to find nearest node in 50 units. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
int nodeTo = m_facingAtIndex ;
if ( ! exists ( nodeTo ) ) {
if ( exists ( m_cacheNodeIndex ) ) {
nodeTo = m_cacheNodeIndex ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Unable to find destination node. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
if ( nodeTo = = nodeFrom ) {
2023-05-02 09:42:43 +03:00
msg ( " Unable to connect node with itself. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
2023-06-24 02:36:51 +03:00
const float distance = m_paths [ nodeFrom ] . origin . distance ( m_paths [ nodeTo ] . origin ) ;
2019-07-27 17:36:24 +03:00
if ( dir = = PathConnection : : Outgoing ) {
addPath ( nodeFrom , nodeTo , distance ) ;
}
else if ( dir = = PathConnection : : Incoming ) {
addPath ( nodeTo , nodeFrom , distance ) ;
}
2023-05-13 00:19:07 +06:00
else if ( dir = = PathConnection : : Jumping ) {
if ( ! isConnected ( nodeFrom , nodeTo ) ) {
addPath ( nodeFrom , nodeTo , distance ) ;
}
2024-01-19 00:03:45 +03:00
2023-05-13 00:19:07 +06:00
for ( auto & link : m_paths [ nodeFrom ] . links ) {
if ( link . index = = nodeTo & & ! ( link . flags & PathFlag : : Jump ) ) {
link . flags | = PathFlag : : Jump ;
m_paths [ nodeFrom ] . radius = 0.0f ;
2024-01-19 00:03:45 +03:00
2023-05-13 00:19:07 +06:00
msg ( " Path added from %d to %d. " , nodeFrom , nodeTo ) ;
}
else if ( link . index = = nodeTo & & ( link . flags & PathFlag : : Jump ) ) {
2024-01-19 00:03:45 +03:00
msg ( " Denied path creation from %d to %d (path already exists). " , nodeFrom , nodeTo ) ;
2023-05-13 00:19:07 +06:00
}
}
}
2019-07-27 17:36:24 +03:00
else {
addPath ( nodeFrom , nodeTo , distance ) ;
addPath ( nodeTo , nodeFrom , distance ) ;
}
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Done ) ; // play "done" sound...
2019-07-27 17:36:24 +03:00
m_hasChanged = true ;
}
void BotGraph : : erasePath ( ) {
// this function allow player to manually remove a path from one node to another
2023-01-22 19:12:03 +06:00
int nodeFrom = getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( nodeFrom = = kInvalidNodeIndex ) {
2023-05-02 09:42:43 +03:00
msg ( " Unable to find nearest node in 50 units. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
int nodeTo = m_facingAtIndex ;
if ( ! exists ( nodeTo ) ) {
if ( exists ( m_cacheNodeIndex ) ) {
nodeTo = m_cacheNodeIndex ;
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Unable to find destination node. " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
2019-08-29 23:31:54 +03:00
// helper
auto destroy = [ ] ( PathLink & link ) - > void {
link . index = kInvalidNodeIndex ;
link . distance = 0 ;
link . flags = 0 ;
2024-08-25 14:32:57 +03:00
link . velocity . clear ( ) ;
2019-08-29 23:31:54 +03:00
} ;
2019-07-27 17:36:24 +03:00
for ( auto & link : m_paths [ nodeFrom ] . links ) {
if ( link . index = = nodeTo ) {
2019-08-29 23:31:54 +03:00
destroy ( link ) ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Change ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
// not found this way ? check for incoming connections then
2019-08-29 23:31:54 +03:00
cr : : swap ( nodeFrom , nodeTo ) ;
2019-07-27 17:36:24 +03:00
for ( auto & link : m_paths [ nodeFrom ] . links ) {
if ( link . index = = nodeTo ) {
2019-08-29 23:31:54 +03:00
destroy ( link ) ;
2023-05-02 09:42:43 +03:00
emitNotify ( NotifySound : : Change ) ;
2019-07-27 17:36:24 +03:00
return ;
}
}
2023-05-02 09:42:43 +03:00
msg ( " There is already no path on this node. " ) ;
2019-07-27 17:36:24 +03:00
}
2023-08-08 11:48:37 +03:00
void BotGraph : : resetPath ( int index ) {
int node = index ;
if ( ! exists ( node ) ) {
node = getEditorNearest ( ) ;
if ( ! exists ( node ) ) {
msg ( " Unable to find nearest node in 50 units. " ) ;
return ;
}
}
// helper
auto destroy = [ ] ( PathLink & link ) - > void {
link . index = kInvalidNodeIndex ;
link . distance = 0 ;
link . flags = 0 ;
2024-08-25 14:32:57 +03:00
link . velocity . clear ( ) ;
2023-08-08 11:48:37 +03:00
} ;
// clean all incoming
for ( auto & connected : m_paths ) {
for ( auto & link : connected . links ) {
if ( link . index = = node ) {
destroy ( link ) ;
}
}
}
// clean all outgoing connections
for ( auto & link : m_paths [ node ] . links ) {
destroy ( link ) ;
}
emitNotify ( NotifySound : : Change ) ;
// notify use something evil happened
msg ( " All paths for node #%d has been reset. " , node ) ;
}
2019-07-27 17:36:24 +03:00
void BotGraph : : cachePoint ( int index ) {
2023-06-24 02:36:51 +03:00
const int node = exists ( index ) ? index : getEditorNearest ( ) ;
2019-07-27 17:36:24 +03:00
if ( node = = kInvalidNodeIndex ) {
m_cacheNodeIndex = kInvalidNodeIndex ;
2023-05-02 09:42:43 +03:00
msg ( " Cached node cleared (nearby point not found in 50 units range). " ) ;
2019-07-27 17:36:24 +03:00
return ;
}
m_cacheNodeIndex = node ;
2023-05-02 09:42:43 +03:00
msg ( " Node %d has been put into memory. " , m_cacheNodeIndex ) ;
2019-07-27 17:36:24 +03:00
}
2020-08-23 11:08:27 +03:00
void BotGraph : : setAutoPathDistance ( const float distance ) {
m_autoPathDistance = distance ;
if ( cr : : fzero ( distance ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Autopathing is now disabled. " ) ;
2020-08-23 11:08:27 +03:00
}
else {
2023-05-02 09:42:43 +03:00
msg ( " Autopath distance is set to %.2f. " , distance ) ;
2020-08-23 11:08:27 +03:00
}
}
2020-08-31 14:52:12 +03:00
void BotGraph : : showStats ( ) {
int terrPoints = 0 ;
int ctPoints = 0 ;
int goalPoints = 0 ;
int rescuePoints = 0 ;
int campPoints = 0 ;
int sniperPoints = 0 ;
int noHostagePoints = 0 ;
for ( const auto & path : m_paths ) {
if ( path . flags & NodeFlag : : TerroristOnly ) {
+ + terrPoints ;
}
if ( path . flags & NodeFlag : : CTOnly ) {
+ + ctPoints ;
}
if ( path . flags & NodeFlag : : Goal ) {
+ + goalPoints ;
}
if ( path . flags & NodeFlag : : Rescue ) {
+ + rescuePoints ;
}
if ( path . flags & NodeFlag : : Camp ) {
+ + campPoints ;
}
if ( path . flags & NodeFlag : : Sniper ) {
+ + sniperPoints ;
}
if ( path . flags & NodeFlag : : NoHostage ) {
+ + noHostagePoints ;
}
}
2023-05-02 09:42:43 +03:00
msg ( " Nodes: %d - T Points: %d " , m_paths . length ( ) , terrPoints ) ;
msg ( " CT Points: %d - Goal Points: %d " , ctPoints , goalPoints ) ;
msg ( " Rescue Points: %d - Camp Points: %d " , rescuePoints , campPoints ) ;
msg ( " Block Hostage Points: %d - Sniper Points: %d " , noHostagePoints , sniperPoints ) ;
2020-08-31 14:52:12 +03:00
}
2022-09-21 14:47:36 +03:00
void BotGraph : : showFileInfo ( ) {
2023-05-02 09:42:43 +03:00
msg ( " header: " ) ;
msg ( " magic: %d " , m_graphHeader . magic ) ;
msg ( " version: %d " , m_graphHeader . version ) ;
msg ( " node_count: %d " , m_graphHeader . length ) ;
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 ?
2024-05-15 23:57:23 +03:00
msg ( " analyzed: %s " , isAnalyzed ( ) ? conf . translate ( " yes " ) : conf . translate ( " no " ) ) ; // display as string ?
2023-05-02 09:42:43 +03:00
msg ( " " ) ;
msg ( " extensions: " ) ;
msg ( " author: %s " , m_extenHeader . author ) ;
msg ( " modified_by: %s " , m_extenHeader . modified ) ;
msg ( " bsp_size: %d " , m_extenHeader . mapSize ) ;
}
void BotGraph : : emitNotify ( int32_t sound ) {
static HashMap < int32_t , String > notifySounds = {
{ NotifySound : : Added , " weapons/xbow_hit1.wav " } ,
{ NotifySound : : Change , " weapons/mine_activate.wav " } ,
{ NotifySound : : Done , " common/wpn_hudon.wav " }
} ;
// notify editor
if ( util . isPlayer ( m_editor ) & & ! m_silenceMessages ) {
game . playSound ( m_editor , notifySounds [ sound ] . chars ( ) ) ;
}
2022-09-21 14:47:36 +03:00
}
2024-01-30 14:37:14 +03:00
void BotGraph : : syncCollectOnline ( ) {
m_isOnlineCollected = true ; // once per server start
// path to graph files
auto graphFilesPath = bstor . buildPath ( BotFile : : Graph , false , true ) ;
// enumerate graph files
FileEnumerator enumerator { strings . joinPath ( graphFilesPath , " *.graph " ) } ;
// listing of graphs locally available
StringArray localGraphs { } ;
// collect all the files
while ( enumerator ) {
auto match = enumerator . getMatch ( ) ;
match = match . substr ( match . findLastOf ( kPathSeparator ) + 1 ) ;
match = match . substr ( 0 , match . findFirstOf ( " . " ) ) ;
localGraphs . emplace ( match ) ;
enumerator . next ( ) ;
}
// no graphs ? unbelievable
if ( localGraphs . empty ( ) ) {
return ;
}
2024-04-25 15:03:39 +03:00
String uploadUrlAddress = cv_graph_url_upload . as < StringRef > ( ) ;
2024-01-30 14:37:14 +03:00
// only allow to upload to non-https endpoint
if ( uploadUrlAddress . startsWith ( " https " ) ) {
return ;
}
String localFile = plat . tmpfname ( ) ;
2024-02-01 14:28:15 +03:00
// no temp file, no fun
if ( localFile . empty ( ) ) {
return ;
}
2024-01-30 14:37:14 +03:00
// don't forget remove temporary file
auto unlinkTemporary = [ & ] ( ) {
if ( plat . fileExists ( localFile . chars ( ) ) ) {
plat . removeFile ( localFile . chars ( ) ) ;
}
} ;
// write out our list of files into temporary
if ( File lc { localFile , " wt " } ) {
auto graphList = String : : join ( localGraphs , " , " ) ;
auto collectUrl = strings . format ( " %s://%s/collect/%u " , product . httpScheme , uploadUrlAddress , graphList . hash ( ) ) ;
lc . puts ( graphList . chars ( ) ) ;
lc . close ( ) ;
// upload to collection diff
if ( ! http . uploadFile ( collectUrl , localFile ) ) {
unlinkTemporary ( ) ;
return ;
}
unlinkTemporary ( ) ;
// download collection diff
if ( ! http . downloadFile ( collectUrl , localFile ) ) {
return ;
}
StringArray wanted { } ;
// decode answer
if ( lc . open ( localFile , " rt " ) ) {
2024-05-16 21:15:41 +03:00
String lines { } ;
2024-01-30 14:37:14 +03:00
if ( lc . getLine ( lines ) ) {
wanted = lines . split ( " , " ) ;
}
lc . close ( ) ;
}
unlinkTemporary ( ) ;
// if 'we're have something in diff, bailout
if ( wanted . empty ( ) ) {
return ;
}
localGraphs . clear ( ) ;
// convert graphs names into full paths
for ( const auto & wn : wanted ) {
if ( wn = = game . getMapName ( ) ) {
continue ; // skip current map always
}
localGraphs . emplace ( strings . joinPath ( graphFilesPath , wn ) + " .graph " ) ;
}
// try to upload everything database wants
for ( const auto & lg : localGraphs ) {
if ( ! plat . fileExists ( lg . chars ( ) ) ) {
continue ;
}
StorageHeader hdr { } ;
// read storage header and check if file NOT analyzed
if ( File gp { lg , " rb " } ) {
gp . read ( & hdr , sizeof ( StorageHeader ) ) ;
// check the magic, graph is NOT analyzed and have some viable nodes number
if ( hdr . magic = = kStorageMagic & & ! ( hdr . options & StorageOption : : Analyzed ) & & hdr . length > 48 ) {
String uploadUrl = strings . format ( " %s://%s " , product . httpScheme , uploadUrlAddress ) ;
// try to upload to database (no need check if it's succeed)
http . uploadFile ( uploadUrl , lg ) ;
}
gp . close ( ) ;
}
}
}
unlinkTemporary ( ) ;
}
void BotGraph : : collectOnline ( ) {
2024-04-25 15:03:39 +03:00
if ( m_isOnlineCollected | | ! cv_graph_auto_collect_db ) {
2024-01-30 14:37:14 +03:00
return ;
}
worker . enqueue ( [ this ] ( ) {
syncCollectOnline ( ) ;
} ) ;
}
2019-07-27 17:36:24 +03:00
void BotGraph : : calculatePathRadius ( int index ) {
// calculate "wayzones" for the nearest node (meaning a dynamic distance area to vary node origin)
auto & path = m_paths [ index ] ;
2024-05-16 21:15:41 +03:00
Vector start { } , direction { } ;
2019-07-27 17:36:24 +03:00
if ( ( path . flags & ( NodeFlag : : Ladder | NodeFlag : : Goal | NodeFlag : : Camp | NodeFlag : : Rescue | NodeFlag : : Crouch ) ) | | m_jumpLearnNode ) {
path . radius = 0.0f ;
return ;
}
for ( const auto & test : path . links ) {
if ( test . index ! = kInvalidNodeIndex & & ( m_paths [ test . index ] . flags & NodeFlag : : Ladder ) ) {
path . radius = 0.0f ;
return ;
}
}
2019-09-21 23:20:33 +03:00
TraceResult tr { } ;
2019-07-27 17:36:24 +03:00
bool wayBlocked = false ;
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
for ( int32_t scanDistance = 32 ; scanDistance < 128 ; scanDistance + = 16 ) {
2021-09-16 16:16:00 +03:00
auto scan = static_cast < float > ( scanDistance ) ;
2019-07-27 17:36:24 +03:00
start = path . origin ;
2021-09-16 16:16:00 +03:00
direction = Vector ( 0.0f , 0.0f , 0.0f ) . forward ( ) * scan ;
2019-07-27 17:36:24 +03:00
direction = direction . angles ( ) ;
2021-09-16 16:16:00 +03:00
path . radius = scan ;
2019-07-27 17:36:24 +03:00
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
for ( int32_t circleRadius = 0 ; circleRadius < 360 ; circleRadius + = 20 ) {
2019-08-12 14:16:28 +03:00
const auto & forward = direction . forward ( ) ;
2019-07-27 17:36:24 +03:00
2021-09-16 16:16:00 +03:00
auto radiusStart = start + forward * scan ;
auto radiusEnd = start + forward * scan ;
2019-07-27 17:36:24 +03:00
game . testHull ( radiusStart , radiusEnd , TraceIgnore : : Monsters , head_hull , nullptr , & tr ) ;
if ( tr . flFraction < 1.0f ) {
game . testLine ( radiusStart , radiusEnd , TraceIgnore : : Monsters , nullptr , & tr ) ;
2023-06-24 02:36:51 +03:00
if ( util . isDoorEntity ( tr . pHit ) ) {
2019-07-27 17:36:24 +03:00
path . radius = 0.0f ;
wayBlocked = true ;
break ;
}
wayBlocked = true ;
path . radius - = 16.0f ;
break ;
}
2021-09-16 16:16:00 +03:00
auto dropStart = start + forward * scan ;
auto dropEnd = dropStart - Vector ( 0.0f , 0.0f , scan + 60.0f ) ;
2019-07-27 17:36:24 +03:00
game . testHull ( dropStart , dropEnd , TraceIgnore : : Monsters , head_hull , nullptr , & tr ) ;
if ( tr . flFraction > = 1.0f ) {
wayBlocked = true ;
path . radius - = 16.0f ;
break ;
}
2021-09-16 16:16:00 +03:00
dropStart = start - forward * scan ;
dropEnd = dropStart - Vector ( 0.0f , 0.0f , scan + 60.0f ) ;
2019-07-27 17:36:24 +03:00
game . testHull ( dropStart , dropEnd , TraceIgnore : : Monsters , head_hull , nullptr , & tr ) ;
if ( tr . flFraction > = 1.0f ) {
wayBlocked = true ;
path . radius - = 16.0f ;
break ;
}
radiusEnd . z + = 34.0f ;
game . testHull ( radiusStart , radiusEnd , TraceIgnore : : Monsters , head_hull , nullptr , & tr ) ;
if ( tr . flFraction < 1.0f ) {
wayBlocked = true ;
path . radius - = 16.0f ;
break ;
}
2023-04-02 12:17:12 +03:00
direction . y = cr : : wrapAngle ( direction . y + static_cast < float > ( circleRadius ) ) ;
2019-07-27 17:36:24 +03:00
}
if ( wayBlocked ) {
break ;
}
}
path . radius - = 16.0f ;
if ( path . radius < 0.0f ) {
path . radius = 0.0f ;
}
}
2023-06-24 03:23:22 +03:00
void BotGraph : : syncInitLightLevels ( ) {
2023-07-18 15:59:15 +03:00
// this function get's the light level for each node on the map
2019-07-27 17:36:24 +03:00
// update light levels for all nodes
for ( auto & path : m_paths ) {
2024-05-05 01:06:56 +03:00
path . light = illum . getLightLevel ( path . origin + Vector { 0.0f , 0.0f , 16.0f } ) ;
2019-07-27 17:36:24 +03:00
}
2023-07-18 15:59:15 +03:00
m_lightChecked = true ;
2019-07-27 17:36:24 +03:00
// disable lightstyle animations on finish (will be auto-enabled on mapchange)
illum . enableAnimation ( false ) ;
2020-01-08 18:29:28 +03:00
}
2023-06-24 03:23:22 +03:00
void BotGraph : : initLightLevels ( ) {
2023-07-18 15:59:15 +03:00
// this function get's the light level for each node on the map
// no nodes ? no light levels, and only one-time init
if ( m_paths . empty ( ) | | m_lightChecked ) {
return ;
}
auto players = bots . countTeamPlayers ( ) ;
// do calculation if some-one is already playing on the server
if ( ! players . first & & ! players . second ) {
return ;
}
2023-06-24 03:23:22 +03:00
worker . enqueue ( [ this ] ( ) {
syncInitLightLevels ( ) ;
} ) ;
}
2020-01-08 18:29:28 +03:00
void BotGraph : : initNarrowPlaces ( ) {
// this function checks all nodes if they are inside narrow places. this is used to prevent
// bots to track hidden enemies in narrow places and prevent bots from throwing flashbangs or
// other grenades inside bad places.
// no nodes ?
if ( m_paths . empty ( ) | | m_narrowChecked ) {
return ;
}
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
constexpr int32_t kNarrowPlacesMinGraphVersion = 2 ;
2020-07-11 20:34:26 +03:00
// if version 2 or higher, narrow places already initialized and saved into file
2024-01-19 00:03:45 +03:00
if ( m_graphHeader . version > = kNarrowPlacesMinGraphVersion & & ! hasEditFlag ( GraphEdit : : On ) ) {
2020-07-11 20:34:26 +03:00
m_narrowChecked = true ;
return ;
}
2024-05-16 21:15:41 +03:00
TraceResult tr { } ;
2020-01-08 18:29:28 +03:00
const auto distance = 178.0f ;
const auto worldspawn = game . getStartEntity ( ) ;
const auto offset = Vector ( 0.0f , 0.0f , 16.0f ) ;
// check olny paths that have not too much connections
for ( auto & path : m_paths ) {
// skip any goals and camp points
if ( path . flags & ( NodeFlag : : Camp | NodeFlag : : Goal ) ) {
continue ;
}
int linkCount = 0 ;
for ( const auto & link : path . links ) {
if ( link . index = = kInvalidNodeIndex | | link . index = = path . number ) {
continue ;
}
if ( + + linkCount > kMaxNodeLinks / 2 ) {
break ;
}
}
// skip nodes with too much connections, this indicated we're not in narrow place
if ( linkCount > kMaxNodeLinks / 2 ) {
continue ;
}
int accumWeight = 0 ;
// we could use this one!
for ( const auto & link : path . links ) {
if ( link . index = = kInvalidNodeIndex | | link . index = = path . number ) {
continue ;
}
const Vector & ang = ( ( path . origin - m_paths [ link . index ] . origin ) . normalize ( ) * distance ) . angles ( ) ;
2024-05-16 21:15:41 +03:00
Vector forward { } , right { } , upward { } ;
2020-01-08 18:29:28 +03:00
ang . angleVectors ( & forward , & right , & upward ) ;
// helper lambda
2020-06-12 18:52:38 +03:00
auto directionCheck = [ & ] ( const Vector & to ) - > bool {
2020-01-08 18:29:28 +03:00
game . testLine ( path . origin + offset , to , TraceIgnore : : None , nullptr , & tr ) ;
// check if we're hit worldspawn entity
if ( tr . pHit = = worldspawn & & tr . flFraction < 1.0f ) {
2020-06-12 18:52:38 +03:00
return true ;
2020-01-08 18:29:28 +03:00
}
2020-06-12 18:52:38 +03:00
return false ;
2020-01-08 18:29:28 +03:00
} ;
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
2020-06-12 18:52:38 +03:00
if ( directionCheck ( - forward * distance ) ) {
accumWeight + = 1 ;
}
if ( directionCheck ( right * distance ) ) {
accumWeight + = 1 ;
}
if ( directionCheck ( - right * distance ) ) {
accumWeight + = 1 ;
}
2020-01-08 18:29:28 +03:00
2020-06-12 18:52:38 +03:00
if ( directionCheck ( upward * distance ) ) {
accumWeight + = 1 ;
}
2020-01-08 18:29:28 +03:00
}
path . flags & = ~ NodeFlag : : Narrow ;
if ( accumWeight > 1 ) {
path . flags | = NodeFlag : : Narrow ;
}
}
m_narrowChecked = true ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
void BotGraph : : populateNodes ( ) {
2019-07-27 17:36:24 +03:00
m_terrorPoints . clear ( ) ;
m_ctPoints . clear ( ) ;
m_goalPoints . clear ( ) ;
m_campPoints . clear ( ) ;
m_rescuePoints . clear ( ) ;
m_sniperPoints . clear ( ) ;
m_visitedGoals . clear ( ) ;
2025-01-17 22:43:35 +03:00
m_nodeNumbers . clear ( ) ;
2019-07-27 17:36:24 +03:00
for ( const auto & path : m_paths ) {
if ( path . flags & NodeFlag : : TerroristOnly ) {
m_terrorPoints . push ( path . number ) ;
}
else if ( path . flags & NodeFlag : : CTOnly ) {
m_ctPoints . push ( path . number ) ;
}
else if ( path . flags & NodeFlag : : Goal ) {
m_goalPoints . push ( path . number ) ;
}
else if ( path . flags & NodeFlag : : Camp ) {
m_campPoints . push ( path . number ) ;
}
else if ( path . flags & NodeFlag : : Sniper ) {
m_sniperPoints . push ( path . number ) ;
}
else if ( path . flags & NodeFlag : : Rescue ) {
m_rescuePoints . push ( path . number ) ;
}
2025-01-17 22:43:35 +03:00
m_nodeNumbers . push ( path . number ) ;
2019-07-27 17:36:24 +03:00
}
}
bool BotGraph : : convertOldFormat ( ) {
2023-05-02 09:42:43 +03:00
MemFile fp ( bstor . buildPath ( BotFile : : PodbotPWF , true ) ) ;
2023-04-13 03:05:35 +03:00
if ( ! fp ) {
2023-05-02 09:42:43 +03:00
if ( ! fp . open ( bstor . buildPath ( BotFile : : EbotEWP , true ) ) ) {
2023-04-13 03:05:35 +03:00
return false ;
}
}
2023-03-13 15:39:15 +03:00
2020-06-12 18:52:38 +03:00
PODGraphHeader header { } ;
2019-09-21 23:20:33 +03:00
plat . bzero ( & header , sizeof ( header ) ) ;
2019-07-27 17:36:24 +03:00
// save for faster access
2020-01-08 18:29:28 +03:00
auto map = game . getMapName ( ) ;
2019-07-27 17:36:24 +03:00
if ( fp ) {
if ( fp . read ( & header , sizeof ( header ) ) = = 0 ) {
return false ;
}
2023-06-26 10:28:01 +03:00
if ( strncmp ( header . header , kPodbotMagic , cr : : bufsize ( kPodbotMagic ) ) = = 0 ) {
2019-07-27 17:36:24 +03:00
if ( header . fileVersion ! = StorageVersion : : Podbot ) {
return false ;
}
2019-09-21 23:20:33 +03:00
else if ( ! strings . matches ( header . mapName , map ) ) {
2019-07-27 17:36:24 +03:00
return false ;
}
else {
if ( header . pointNumber = = 0 | | header . pointNumber > kMaxNodes ) {
return false ;
}
2020-09-26 18:41:53 +03:00
reset ( ) ;
2019-07-27 17:36:24 +03:00
for ( int i = 0 ; i < header . pointNumber ; + + i ) {
Path path { } ;
PODPath podpath { } ;
if ( fp . read ( & podpath , sizeof ( PODPath ) ) = = 0 ) {
return false ;
}
convertFromPOD ( path , podpath ) ;
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
// more checks of node quality
if ( path . number < 0 | | path . number > header . pointNumber ) {
return false ;
}
// add to node array
m_paths . push ( cr : : move ( path ) ) ;
}
2023-03-13 15:39:15 +03:00
fp . close ( ) ;
2023-03-13 15:20:13 +03:00
2023-03-13 15:39:15 +03:00
// save new format in case loaded older one
if ( ! m_paths . empty ( ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Converting old PWF to new format Graph. " ) ;
2023-03-13 15:20:13 +03:00
m_graphAuthor = header . author ;
// clean editor so graph will be saved with header's author
auto editor = m_editor ;
m_editor = nullptr ;
2023-03-13 15:39:15 +03:00
auto result = saveGraphData ( ) ;
2023-03-13 15:20:13 +03:00
m_editor = editor ;
return result ;
}
2019-07-27 17:36:24 +03:00
}
}
else {
return false ;
}
}
else {
return false ;
}
2023-03-13 15:20:13 +03:00
return false ;
2019-07-27 17:36:24 +03:00
}
bool BotGraph : : loadGraphData ( ) {
2020-02-08 00:03:52 +03:00
ExtenHeader exten { } ;
aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more
aim: reduce time between selecting grenade and throwing it away
aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar
aim: use direct enemy origin from visibility check, and not re-calculate it
aim: update enemy prediction, so it now depends on frame interval for a bot
aim: additional height offset are tweaked, and now used only for difficulty 4
nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain
nav: do not check banned nodes, when bucket sizes re too low
nav: cover nodes are now selected depending on total bots on server
nav: let bot enter pause task after long jump
nav: extend velocity by a little for a jump, like it was in first versions of bot
nav: stuck checking is now taken in account lower minimal speed if bot is ducking
fix: navigation reachability timers, so bots will have correct current node index while camping
fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable
fix: cover nodes are now calculated as they should
fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar
bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard
bot: minor refactoring and moving functions to correct source file
bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns
bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight
bot: camp buttons are now lightly depends on bot health
chat: welcome chat message from bots is now sent during first freeze time period
crlib: switch over to stdint.h and remove crlib-own types
crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
int32_t outOptions = 0 ;
2019-07-27 17:36:24 +03:00
2022-09-21 14:47:36 +03:00
m_graphHeader = { } ;
m_extenHeader = { } ;
2023-05-02 09:42:43 +03:00
// re-initialize paths
reset ( ) ;
2025-02-28 00:39:52 +03:00
// initialize compression
ULZ ulz { } ;
bstor . setUlzInstance ( & ulz ) ;
2019-07-27 17:36:24 +03:00
// check if loaded
2023-08-08 11:48:37 +03:00
const bool dataLoaded = bstor . load < Path > ( m_paths , & exten , & outOptions ) ;
2019-07-27 17:36:24 +03:00
if ( dataLoaded ) {
initBuckets ( ) ;
// add data to buckets
for ( const auto & path : m_paths ) {
addToBucket ( path . origin , path . number ) ;
}
2023-06-20 15:18:35 +03:00
StringRef author = exten . author ;
2019-07-27 17:36:24 +03:00
2023-06-20 15:18:35 +03:00
if ( ( outOptions & StorageOption : : Official ) | | author . startsWith ( " official " ) | | author . length ( ) < 2 ) {
2022-10-06 17:01:18 +03:00
m_graphAuthor . assign ( product . name ) ;
2019-07-27 17:36:24 +03:00
}
else {
2023-06-20 15:18:35 +03:00
m_graphAuthor . assign ( author ) ;
2022-09-21 14:47:36 +03:00
}
2022-10-03 11:20:06 +03:00
StringRef modified = exten . modified ;
2022-09-21 14:47:36 +03:00
2022-10-03 11:20:06 +03:00
if ( ! modified . empty ( ) & & ! modified . contains ( " (none) " ) ) {
2022-09-21 14:47:36 +03:00
m_graphModified . assign ( exten . modified ) ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
planner . init ( ) ; // initialize our little path planner
practice . load ( ) ; // load bots practice
vistab . load ( ) ; // load/initialize visibility
2022-09-21 14:47:36 +03:00
2023-05-02 09:42:43 +03:00
populateNodes ( ) ;
2024-05-05 01:06:56 +03:00
2020-02-08 00:03:52 +03:00
if ( exten . mapSize > 0 ) {
2025-02-28 00:39:52 +03:00
const int mapSize = getBspSize ( ) ;
2020-02-08 00:03:52 +03:00
2025-02-28 00:39:52 +03:00
if ( mapSize > 0 & & mapSize ! = exten . mapSize ) {
2023-07-27 01:46:55 +03:00
msg ( " Warning: Graph data is probably not for this map. Please check bots behaviour. " ) ;
2020-02-08 00:03:52 +03:00
}
}
2024-12-20 01:04:59 +03:00
// notify user about graph problems
if ( planner . isPathsCheckFailed ( ) ) {
ctrl . msg ( " Warning: Graph data has failed sanity check. " ) ;
ctrl . msg ( " Warning: Bots will use only shortest-path algo for path finding. " ) ;
ctrl . msg ( " Warning: This may significantly affect bots behavior on this map. " ) ;
}
2020-06-12 18:52:38 +03:00
cv_debug_goal . set ( kInvalidNodeIndex ) ;
2019-07-27 17:36:24 +03:00
2024-01-30 14:37:14 +03:00
// try to do graph collection, and push them to graph database automatically
collectOnline ( ) ;
2019-07-27 17:36:24 +03:00
return true ;
}
2023-05-02 09:42:43 +03:00
else {
analyzer . start ( ) ;
}
2019-07-27 17:36:24 +03:00
return false ;
}
2020-07-11 20:34:26 +03:00
bool BotGraph : : canDownload ( ) {
2024-04-25 15:03:39 +03:00
return ! cv_graph_url . as < StringRef > ( ) . empty ( ) ;
2020-07-11 20:34:26 +03:00
}
2019-07-27 17:36:24 +03:00
bool BotGraph : : saveGraphData ( ) {
2020-02-08 00:03:52 +03:00
auto options = StorageOption : : Graph | StorageOption : : Exten ;
2024-05-16 21:15:41 +03:00
String editorName { } ;
2019-07-27 17:36:24 +03:00
2022-09-21 14:47:36 +03:00
if ( game . isNullEntity ( m_editor ) & & ! m_graphAuthor . empty ( ) ) {
2022-10-06 17:01:18 +03:00
editorName = m_graphAuthor ;
2019-07-27 17:36:24 +03:00
2020-07-11 20:34:26 +03:00
if ( ! game . isDedicated ( ) ) {
options | = StorageOption : : Recovered ;
}
2019-07-27 17:36:24 +03:00
}
else if ( ! game . isNullEntity ( m_editor ) ) {
2022-10-06 17:01:18 +03:00
editorName = m_editor - > v . netname . chars ( ) ;
2019-07-27 17:36:24 +03:00
}
else {
2022-10-06 17:01:18 +03:00
editorName = product . name ;
2019-07-27 17:36:24 +03:00
}
2020-02-08 00:03:52 +03:00
2023-05-02 09:42:43 +03:00
// mark as analyzed
if ( analyzer . isAnalyzed ( ) ) {
options | = StorageOption : : Analyzed ;
}
2019-07-27 17:36:24 +03:00
// mark as official
2022-10-06 17:01:18 +03:00
if ( editorName . startsWith ( product . name ) ) {
2019-07-27 17:36:24 +03:00
options | = StorageOption : : Official ;
}
2020-02-08 00:03:52 +03:00
ExtenHeader exten { } ;
2022-09-21 14:47:36 +03:00
2022-10-10 01:51:32 +06:00
// only modify the author if no author currently assigned to graph file
2022-10-06 17:01:18 +03:00
if ( m_graphAuthor . empty ( ) | | strings . isEmpty ( m_extenHeader . author ) ) {
strings . copy ( exten . author , editorName . chars ( ) , cr : : bufsize ( exten . author ) ) ;
2022-09-21 14:47:36 +03:00
}
else {
strings . copy ( exten . author , m_extenHeader . author , cr : : bufsize ( exten . author ) ) ;
}
2022-10-06 14:50:58 +03:00
// only update modified by, if name differs
2022-10-06 17:01:18 +03:00
if ( m_graphAuthor ! = editorName & & ! strings . isEmpty ( m_extenHeader . author ) ) {
strings . copy ( exten . modified , editorName . chars ( ) , cr : : bufsize ( exten . author ) ) ;
2022-10-06 14:50:58 +03:00
}
2021-09-20 13:50:00 +03:00
exten . mapSize = getBspSize ( ) ;
2020-02-08 00:03:52 +03:00
2020-07-11 20:34:26 +03:00
// ensure narrow places saved into file
m_narrowChecked = false ;
2023-07-18 15:59:15 +03:00
m_lightChecked = false ;
2020-07-11 20:34:26 +03:00
initNarrowPlaces ( ) ;
2023-05-02 09:42:43 +03:00
return bstor . save < Path > ( m_paths , & exten , options ) ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : saveOldFormat ( ) {
2019-09-14 23:13:55 +03:00
PODGraphHeader header { } ;
2024-05-16 21:15:41 +03:00
String editorName { } ;
2023-05-12 20:00:06 +03:00
if ( game . isNullEntity ( m_editor ) & & ! m_graphAuthor . empty ( ) ) {
editorName = m_graphAuthor ;
}
else if ( ! game . isNullEntity ( m_editor ) ) {
editorName = m_editor - > v . netname . chars ( ) ;
}
else {
editorName = product . name ;
}
2019-09-21 23:20:33 +03:00
strings . copy ( header . header , kPodbotMagic , sizeof ( kPodbotMagic ) ) ;
2023-05-12 20:00:06 +03:00
strings . copy ( header . author , editorName . chars ( ) , cr : : bufsize ( header . author ) ) ;
2019-09-21 23:20:33 +03:00
strings . copy ( header . mapName , game . getMapName ( ) , cr : : bufsize ( header . mapName ) ) ;
2019-07-27 17:36:24 +03:00
header . mapName [ 31 ] = 0 ;
header . fileVersion = StorageVersion : : Podbot ;
2020-06-12 18:52:38 +03:00
header . pointNumber = length ( ) ;
2019-07-27 17:36:24 +03:00
2024-05-16 21:15:41 +03:00
File fp { } ;
2019-07-27 17:36:24 +03:00
// file was opened
2023-05-02 09:42:43 +03:00
if ( fp . open ( bstor . buildPath ( BotFile : : PodbotPWF ) , " wb " ) ) {
2019-07-27 17:36:24 +03:00
// write the node header to the file...
fp . write ( & header , sizeof ( header ) ) ;
// save the node paths...
for ( const auto & path : m_paths ) {
PODPath pod { } ;
2019-08-07 15:25:14 +03:00
convertToPOD ( path , pod ) ;
2019-07-27 17:36:24 +03:00
fp . write ( & pod , sizeof ( PODPath ) ) ;
}
fp . close ( ) ;
}
else {
logger . error ( " Error writing '%s.pwf' node file. " , game . getMapName ( ) ) ;
}
}
float BotGraph : : calculateTravelTime ( float maxSpeed , const Vector & src , const Vector & origin ) {
// this function returns 2D traveltime to a position
2021-09-20 13:50:00 +03:00
return origin . distance2d ( src ) / maxSpeed ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
bool BotGraph : : isNodeReacheableEx ( const Vector & src , const Vector & destination , const float maxHeight ) {
2019-09-21 23:20:33 +03:00
TraceResult tr { } ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
float distanceSq = destination . distanceSq ( src ) ;
2019-07-27 17:36:24 +03:00
2023-05-02 09:42:43 +03:00
if ( ( destination . z - src . z ) > = 45.0f ) {
return false ;
}
2019-07-27 17:36:24 +03:00
// is the destination not close enough?
2023-06-24 02:36:51 +03:00
if ( distanceSq > cr : : sqrf ( m_autoPathDistance ) ) {
2019-07-27 17:36:24 +03:00
return false ;
}
// check if we go through a func_illusionary, in which case return false
game . testHull ( src , destination , TraceIgnore : : Monsters , head_hull , m_editor , & tr ) ;
2023-06-20 15:18:35 +03:00
if ( tr . pHit & & tr . pHit - > v . classname . str ( ) = = " func_illusionary " ) {
return false ; // don't add path nodes through func_illusionaries
2019-07-27 17:36:24 +03:00
}
// check if this node is "visible"...
game . testLine ( src , destination , TraceIgnore : : Monsters , m_editor , & tr ) ;
2023-06-24 02:36:51 +03:00
const bool isDoor = util . isDoorEntity ( tr . pHit ) ;
2019-07-27 17:36:24 +03:00
// if node is visible from current position (even behind head)...
2023-06-24 02:36:51 +03:00
if ( tr . flFraction > = 1.0f | | isDoor ) {
2019-07-27 17:36:24 +03:00
// if it's a door check if nothing blocks behind
2023-06-24 02:36:51 +03:00
if ( isDoor ) {
2019-07-27 17:36:24 +03:00
game . testLine ( tr . vecEndPos , destination , TraceIgnore : : Monsters , tr . pHit , & tr ) ;
if ( tr . flFraction < 1.0f ) {
return false ;
}
}
// check for special case of both nodes being in water...
if ( engfuncs . pfnPointContents ( src ) = = CONTENTS_WATER & & engfuncs . pfnPointContents ( destination ) = = CONTENTS_WATER ) {
return true ; // then they're reachable each other
}
// is dest node higher than src? (45 is max jump height)
if ( destination . z > src . z + 45.0f ) {
Vector sourceNew = destination ;
Vector destinationNew = destination ;
destinationNew . z = destinationNew . z - 50.0f ; // straight down 50 units
game . testLine ( sourceNew , destinationNew , TraceIgnore : : Monsters , m_editor , & tr ) ;
// check if we didn't hit anything, if not then it's in mid-air
2023-04-02 12:17:12 +03:00
if ( tr . flFraction > = 1.0f ) {
2019-07-27 17:36:24 +03:00
return false ; // can't reach this one
}
}
// check if distance to ground drops more than step height at points between source and destination...
Vector direction = ( destination - src ) . normalize ( ) ; // 1 unit long
Vector check = src , down = src ;
down . z = down . z - 1000.0f ; // straight down 1000 units
game . testLine ( check , down , TraceIgnore : : Monsters , m_editor , & tr ) ;
float lastHeight = tr . flFraction * 1000.0f ; // height from ground
2023-06-24 02:36:51 +03:00
distanceSq = destination . distanceSq ( check ) ; // distance from goal
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
while ( distanceSq > cr : : sqrf ( 10.0f ) ) {
2019-07-27 17:36:24 +03:00
// move 10 units closer to the goal...
check = check + ( direction * 10.0f ) ;
down = check ;
down . z = down . z - 1000.0f ; // straight down 1000 units
game . testLine ( check , down , TraceIgnore : : Monsters , m_editor , & tr ) ;
2024-01-26 19:52:00 +03:00
const float height = tr . flFraction * 1000.0f ; // height from ground
2019-07-27 17:36:24 +03:00
// is the current height greater than the step height?
2023-05-02 09:42:43 +03:00
if ( height < lastHeight - maxHeight ) {
2019-07-27 17:36:24 +03:00
return false ; // can't get there without jumping...
}
lastHeight = height ;
2023-06-24 02:36:51 +03:00
distanceSq = destination . distanceSq ( check ) ; // distance from goal
2019-07-27 17:36:24 +03:00
}
return true ;
}
return false ;
}
2023-05-02 09:42:43 +03:00
bool BotGraph : : isNodeReacheable ( const Vector & src , const Vector & destination ) {
return isNodeReacheableEx ( src , destination , 45.0f ) ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
bool BotGraph : : isNodeReacheableWithJump ( const Vector & src , const Vector & destination ) {
2024-04-25 15:03:39 +03:00
return isNodeReacheableEx ( src , destination , cv_graph_analyze_max_jump_height . as < float > ( ) ) ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : frame ( ) {
// this function executes frame of graph operation code.
if ( game . isNullEntity ( m_editor ) ) {
return ; // this function is only valid with editor, and in graph enabled mode.
}
// keep the clipping mode enabled, or it can be turned off after new round has started
if ( graph . hasEditFlag ( GraphEdit : : Noclip ) & & util . isAlive ( m_editor ) ) {
m_editor - > v . movetype = MOVETYPE_NOCLIP ;
}
2023-06-24 02:36:51 +03:00
float nearestDistanceSq = kInfiniteDistance ;
2019-07-27 17:36:24 +03:00
int nearestIndex = kInvalidNodeIndex ;
// check if it's time to add jump node
if ( m_jumpLearnNode ) {
if ( ! m_endJumpPoint ) {
if ( m_editor - > v . button & IN_JUMP ) {
2021-09-20 13:50:00 +03:00
add ( NodeAddFlag : : JumpStart ) ;
2023-03-13 15:39:15 +03:00
2019-08-18 21:00:00 +03:00
m_timeJumpStarted = game . time ( ) ;
2019-07-27 17:36:24 +03:00
m_endJumpPoint = true ;
}
else {
m_learnVelocity = m_editor - > v . velocity ;
m_learnPosition = m_editor - > v . origin ;
}
}
2023-06-07 23:31:03 +03:00
else if ( ( ( m_editor - > v . flags & FL_ONGROUND ) | | m_editor - > v . movetype = = MOVETYPE_FLY ) & & m_timeJumpStarted + 0.1f < game . time ( ) ) {
2021-09-20 13:50:00 +03:00
add ( NodeAddFlag : : JumpEnd ) ;
2019-07-27 17:36:24 +03:00
m_jumpLearnNode = false ;
m_endJumpPoint = false ;
}
}
// check if it's a auto-add-node mode enabled
if ( hasEditFlag ( GraphEdit : : Auto ) & & ( m_editor - > v . flags & ( FL_ONGROUND | FL_PARTIALGROUND ) ) ) {
// find the distance from the last used node
2023-06-24 02:36:51 +03:00
float distanceSq = m_lastNode . distanceSq ( m_editor - > v . origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq > cr : : sqrf ( 128.0f ) ) {
2019-07-27 17:36:24 +03:00
// check that no other reachable nodes are nearby...
for ( const auto & path : m_paths ) {
if ( isNodeReacheable ( m_editor - > v . origin , path . origin ) ) {
2023-06-24 02:36:51 +03:00
distanceSq = path . origin . distanceSq ( m_editor - > v . origin ) ;
2019-07-27 17:36:24 +03:00
2023-06-24 02:36:51 +03:00
if ( distanceSq < nearestDistanceSq ) {
nearestDistanceSq = distanceSq ;
2019-07-27 17:36:24 +03:00
}
}
}
// make sure nearest node is far enough away...
2023-06-24 02:36:51 +03:00
if ( nearestDistanceSq > = cr : : sqrf ( 128.0f ) ) {
2021-09-20 13:50:00 +03:00
add ( NodeAddFlag : : Normal ) ; // place a node here
2019-07-27 17:36:24 +03:00
}
}
}
m_facingAtIndex = getFacingIndex ( ) ;
// reset the minimal distance changed before
2023-06-24 02:36:51 +03:00
nearestDistanceSq = kInfiniteDistance ;
2019-07-27 17:36:24 +03:00
// now iterate through all nodes in a map, and draw required ones
for ( auto & path : m_paths ) {
2023-06-24 02:36:51 +03:00
const float distanceSq = path . origin . distanceSq ( m_editor - > v . origin ) ;
2019-07-27 17:36:24 +03:00
2023-08-08 11:48:37 +03:00
// check if node is within a distance, and is visible
2024-04-25 15:03:39 +03:00
if ( distanceSq < cr : : sqrf ( cv_graph_draw_distance . as < float > ( ) )
2024-05-05 01:06:56 +03:00
& & ( ( util . isVisible ( path . origin , m_editor )
& & util . isInViewCone ( path . origin , m_editor ) ) | | ! util . isAlive ( m_editor ) | | distanceSq < cr : : sqrf ( 64.0f ) ) ) {
2024-02-16 00:57:41 +03:00
2019-07-27 17:36:24 +03:00
// check the distance
2023-06-24 02:36:51 +03:00
if ( distanceSq < nearestDistanceSq ) {
2019-07-27 17:36:24 +03:00
nearestIndex = path . number ;
2023-06-24 02:36:51 +03:00
nearestDistanceSq = distanceSq ;
2019-07-27 17:36:24 +03:00
}
2022-01-21 23:39:26 +03:00
if ( path . display + 1.0f < game . time ( ) ) {
2019-07-27 17:36:24 +03:00
float nodeHeight = 0.0f ;
// check the node height
if ( path . flags & NodeFlag : : Crouch ) {
nodeHeight = 36.0f ;
}
else {
nodeHeight = 72.0f ;
}
2023-06-24 02:36:51 +03:00
const float nodeHalfHeight = nodeHeight * 0.5f ;
2019-07-27 17:36:24 +03:00
// all nodes are by default are green
2020-06-12 18:52:38 +03:00
Color nodeColor { - 1 , - 1 , - 1 } ;
2019-07-27 17:36:24 +03:00
// colorize all other nodes
2023-01-18 22:32:38 +03:00
if ( path . flags & NodeFlag : : Goal ) {
2020-06-12 18:52:38 +03:00
nodeColor = { 128 , 0 , 255 } ;
2019-07-27 17:36:24 +03:00
}
else if ( path . flags & NodeFlag : : Ladder ) {
2020-06-12 18:52:38 +03:00
nodeColor = { 128 , 64 , 0 } ;
2019-07-27 17:36:24 +03:00
}
else if ( path . flags & NodeFlag : : Rescue ) {
2020-06-12 18:52:38 +03:00
nodeColor = { 255 , 255 , 255 } ;
2019-07-27 17:36:24 +03:00
}
2023-01-18 22:32:38 +03:00
else if ( path . flags & NodeFlag : : Camp ) {
if ( path . flags & NodeFlag : : TerroristOnly ) {
nodeColor = { 255 , 160 , 160 } ;
}
else if ( path . flags & NodeFlag : : CTOnly ) {
2023-03-20 09:42:55 +03:00
nodeColor = { 160 , 160 , 255 } ;
2023-01-18 22:32:38 +03:00
}
else {
nodeColor = { 0 , 255 , 255 } ;
}
}
else if ( path . flags & NodeFlag : : TerroristOnly ) {
nodeColor = { 255 , 0 , 0 } ;
}
else if ( path . flags & NodeFlag : : CTOnly ) {
nodeColor = { 0 , 0 , 255 } ;
}
2019-07-27 17:36:24 +03:00
else {
2020-06-12 18:52:38 +03:00
nodeColor = { 0 , 255 , 0 } ;
2023-03-13 15:39:15 +03:00
}
2019-07-27 17:36:24 +03:00
// colorize additional flags
2020-06-12 18:52:38 +03:00
Color nodeFlagColor { - 1 , - 1 , - 1 } ;
2019-07-27 17:36:24 +03:00
// check the colors
if ( path . flags & NodeFlag : : Sniper ) {
2020-06-12 18:52:38 +03:00
nodeFlagColor = { 130 , 87 , 0 } ;
2019-07-27 17:36:24 +03:00
}
else if ( path . flags & NodeFlag : : NoHostage ) {
2020-06-12 18:52:38 +03:00
nodeFlagColor = { 255 , 255 , 255 } ;
2019-07-27 17:36:24 +03:00
}
2023-01-18 22:32:38 +03:00
else if ( path . flags & NodeFlag : : Lift ) {
nodeFlagColor = { 255 , 0 , 255 } ;
2019-07-27 17:36:24 +03:00
}
int nodeWidth = 14 ;
if ( exists ( m_facingAtIndex ) & & path . number = = m_facingAtIndex ) {
nodeWidth * = 2 ;
}
// draw node without additional flags
if ( nodeFlagColor . red = = - 1 ) {
game . drawLine ( m_editor , path . origin - Vector ( 0 , 0 , nodeHalfHeight ) , path . origin + Vector ( 0 , 0 , nodeHalfHeight ) , nodeWidth + 1 , 0 , nodeColor , 250 , 0 , 10 ) ;
}
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
// draw node with flags
else {
game . drawLine ( m_editor , path . origin - Vector ( 0 , 0 , nodeHalfHeight ) , path . origin - Vector ( 0 , 0 , nodeHalfHeight - nodeHeight * 0.75f ) , nodeWidth , 0 , nodeColor , 250 , 0 , 10 ) ; // draw basic path
game . drawLine ( m_editor , path . origin - Vector ( 0 , 0 , nodeHalfHeight - nodeHeight * 0.75f ) , path . origin + Vector ( 0 , 0 , nodeHalfHeight ) , nodeWidth , 0 , nodeFlagColor , 250 , 0 , 10 ) ; // draw additional path
}
2019-08-18 21:00:00 +03:00
path . display = game . time ( ) ;
2019-07-27 17:36:24 +03:00
}
}
}
if ( nearestIndex = = kInvalidNodeIndex ) {
return ;
}
// draw arrow to a some importaint nodes
if ( exists ( m_findWPIndex ) | | exists ( m_cacheNodeIndex ) | | exists ( m_facingAtIndex ) ) {
// check for drawing code
2019-08-18 21:00:00 +03:00
if ( m_arrowDisplayTime + 0.5f < game . time ( ) ) {
2019-07-27 17:36:24 +03:00
// finding node - pink arrow
if ( m_findWPIndex ! = kInvalidNodeIndex ) {
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , m_editor - > v . origin , m_paths [ m_findWPIndex ] . origin , 10 , 0 , { 128 , 0 , 128 } , 200 , 0 , 5 , DrawLine : : Arrow ) ;
2019-07-27 17:36:24 +03:00
}
// cached node - yellow arrow
if ( m_cacheNodeIndex ! = kInvalidNodeIndex ) {
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , m_editor - > v . origin , m_paths [ m_cacheNodeIndex ] . origin , 10 , 0 , { 255 , 255 , 0 } , 200 , 0 , 5 , DrawLine : : Arrow ) ;
2019-07-27 17:36:24 +03:00
}
// node user facing at - white arrow
if ( m_facingAtIndex ! = kInvalidNodeIndex ) {
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , m_editor - > v . origin , m_paths [ m_facingAtIndex ] . origin , 10 , 0 , { 255 , 255 , 255 } , 200 , 0 , 5 , DrawLine : : Arrow ) ;
2019-07-27 17:36:24 +03:00
}
2019-08-18 21:00:00 +03:00
m_arrowDisplayTime = game . time ( ) ;
2019-07-27 17:36:24 +03:00
}
}
// draw a paths, camplines and danger directions for nearest node
2024-02-16 00:57:41 +03:00
if ( nearestDistanceSq < cr : : clamp ( m_paths [ nearestIndex ] . radius , cr : : sqrf ( 56.0f ) , cr : : sqrf ( 90.0f ) )
2024-05-05 01:06:56 +03:00
& & m_pathDisplayTime < game . time ( ) ) {
2024-02-16 00:57:41 +03:00
2022-01-21 23:39:26 +03:00
m_pathDisplayTime = game . time ( ) + 0.96f ;
2019-07-27 17:36:24 +03:00
2020-10-03 16:39:09 +03:00
// create path pointer for faster access
2023-06-24 02:36:51 +03:00
const auto & path = m_paths [ nearestIndex ] ;
2020-10-03 16:39:09 +03:00
2019-07-27 17:36:24 +03:00
// draw the camplines
if ( path . flags & NodeFlag : : Camp ) {
float height = 36.0f ;
// check if it's a source
if ( path . flags & NodeFlag : : Crouch ) {
height = 18.0f ;
}
2019-08-12 14:16:28 +03:00
const auto & source = Vector ( path . origin . x , path . origin . y , path . origin . z + height ) ; // source
const auto & start = path . origin + Vector ( path . start . x , path . start . y , 0.0f ) . forward ( ) * 500.0f ; // camp start
const auto & end = path . origin + Vector ( path . end . x , path . end . y , 0.0f ) . forward ( ) * 500.0f ; // camp end
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
// draw it now
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , source , start , 10 , 0 , { 255 , 0 , 0 } , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , source , end , 10 , 0 , { 255 , 0 , 0 } , 200 , 0 , 10 ) ;
2019-07-27 17:36:24 +03:00
}
// draw the connections
for ( const auto & link : path . links ) {
if ( link . index = = kInvalidNodeIndex ) {
continue ;
}
// jump connection
if ( link . flags & PathFlag : : Jump ) {
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , path . origin , m_paths [ link . index ] . origin , 5 , 0 , { 255 , 0 , 128 } , 200 , 0 , 10 ) ;
2019-07-27 17:36:24 +03:00
}
else if ( isConnected ( link . index , nearestIndex ) ) { // twoway connection
2020-06-12 18:52:38 +03:00
game . drawLine ( m_editor , path . origin , m_paths [ link . index ] . origin , 5 , 0 , { 255 , 255 , 0 } , 200 , 0 , 10 ) ;
2019-07-27 17:36:24 +03:00
}
else { // oneway connection
2023-01-18 22:32:38 +03:00
game . drawLine ( m_editor , path . origin , m_paths [ link . index ] . origin , 5 , 0 , { 255 , 255 , 255 } , 200 , 0 , 10 ) ;
2019-07-27 17:36:24 +03:00
}
}
// now look for oneway incoming connections
for ( const auto & connected : m_paths ) {
if ( isConnected ( connected . number , path . number ) & & ! isConnected ( path . number , connected . number ) ) {
2023-01-18 22:32:38 +03:00
game . drawLine ( m_editor , path . origin , connected . origin , 5 , 0 , { 0 , 192 , 96 } , 200 , 0 , 10 ) ;
2019-07-27 17:36:24 +03:00
}
}
// draw the radius circle
Vector origin = ( path . flags & NodeFlag : : Crouch ) ? path . origin : path . origin - Vector ( 0.0f , 0.0f , 18.0f ) ;
2025-01-17 22:43:35 +03:00
static Color radiusColor { 36 , 36 , 255 } ;
2019-07-27 17:36:24 +03:00
// if radius is nonzero, draw a full circle
if ( path . radius > 0.0f ) {
2023-06-24 02:36:51 +03:00
const float sqr = cr : : sqrtf ( cr : : sqrf ( path . radius ) * 0.5f ) ;
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
game . drawLine ( m_editor , origin + Vector ( path . radius , 0.0f , 0.0f ) , origin + Vector ( sqr , - sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( sqr , - sqr , 0.0f ) , origin + Vector ( 0.0f , - path . radius , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( 0.0f , - path . radius , 0.0f ) , origin + Vector ( - sqr , - sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( - sqr , - sqr , 0.0f ) , origin + Vector ( - path . radius , 0.0f , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( - path . radius , 0.0f , 0.0f ) , origin + Vector ( - sqr , sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( - sqr , sqr , 0.0f ) , origin + Vector ( 0.0f , path . radius , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( 0.0f , path . radius , 0.0f ) , origin + Vector ( sqr , sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( sqr , sqr , 0.0f ) , origin + Vector ( path . radius , 0.0f , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
}
else {
2023-06-24 02:36:51 +03:00
const float sqr = cr : : sqrtf ( 32.0f ) ;
2019-07-27 17:36:24 +03:00
game . drawLine ( m_editor , origin + Vector ( sqr , - sqr , 0.0f ) , origin + Vector ( - sqr , sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
game . drawLine ( m_editor , origin + Vector ( - sqr , - sqr , 0.0f ) , origin + Vector ( sqr , sqr , 0.0f ) , 5 , 0 , radiusColor , 200 , 0 , 10 ) ;
}
// draw the danger directions
if ( ! m_hasChanged ) {
2023-06-24 02:36:51 +03:00
const int dangerIndexT = practice . getIndex ( Team : : Terrorist , nearestIndex , nearestIndex ) ;
const int dangerIndexCT = practice . getIndex ( Team : : CT , nearestIndex , nearestIndex ) ;
2019-07-27 17:36:24 +03:00
2022-04-24 19:28:36 +06:00
if ( exists ( dangerIndexT ) ) {
game . drawLine ( m_editor , path . origin , m_paths [ dangerIndexT ] . origin , 15 , 0 , { 255 , 0 , 0 } , 200 , 0 , 10 , DrawLine : : Arrow ) ; // draw a red arrow to this index's danger point
}
if ( exists ( dangerIndexCT ) ) {
game . drawLine ( m_editor , path . origin , m_paths [ dangerIndexCT ] . origin , 15 , 0 , { 0 , 0 , 255 } , 200 , 0 , 10 , DrawLine : : Arrow ) ; // draw a blue arrow to this index's danger point
2019-07-27 17:36:24 +03:00
}
}
2022-01-21 23:39:26 +03:00
static int channel = 0 ;
2024-01-19 00:03:45 +03:00
auto sendHudMessage = [ & ] ( Color color , float x , float y , StringRef text ) {
static hudtextparms_t textParams { } ;
2024-01-19 14:33:09 +03:00
textParams . channel = channel + + ;
2024-01-19 00:03:45 +03:00
textParams . x = x ;
textParams . y = y ;
textParams . effect = 0 ;
textParams . r1 = textParams . r2 = static_cast < uint8_t > ( color . red ) ;
textParams . g1 = textParams . g2 = static_cast < uint8_t > ( color . green ) ;
textParams . b1 = textParams . b2 = static_cast < uint8_t > ( color . blue ) ;
textParams . a1 = textParams . a2 = static_cast < uint8_t > ( 1 ) ;
textParams . fadeinTime = 0.0f ;
textParams . fadeoutTime = 0.0f ;
2024-01-19 14:33:09 +03:00
textParams . holdTime = m_pathDisplayTime - game . time ( ) ;
2024-01-19 00:03:45 +03:00
textParams . fxTime = 0.0f ;
game . sendHudMessage ( m_editor , textParams , text ) ;
2022-01-21 23:39:26 +03:00
if ( channel > 3 ) {
channel = 0 ;
}
} ;
// very helpful stuff..
2023-08-08 11:48:37 +03:00
auto getNodeData = [ this ] ( StringRef type , int node ) - > String {
2024-05-16 21:15:41 +03:00
String message { } , flags { } ;
2019-07-27 17:36:24 +03:00
2023-04-02 12:17:12 +03:00
const auto & p = m_paths [ node ] ;
2019-07-27 17:36:24 +03:00
bool jumpPoint = false ;
// iterate through connections and find, if it's a jump path
2023-04-02 12:17:12 +03:00
for ( const auto & link : p . links ) {
2019-07-27 17:36:24 +03:00
// check if we got a valid connection
if ( link . index ! = kInvalidNodeIndex & & ( link . flags & PathFlag : : Jump ) ) {
jumpPoint = true ;
}
}
2023-04-02 12:17:12 +03:00
flags . assignf ( " %s%s%s%s%s%s%s%s%s%s%s%s " ,
2024-05-05 01:06:56 +03:00
( p . flags & NodeFlag : : Lift ) ? " LIFT " : " " ,
( p . flags & NodeFlag : : Crouch ) ? " CROUCH " : " " ,
( p . flags & NodeFlag : : Camp ) ? " CAMP " : " " ,
( p . flags & NodeFlag : : TerroristOnly ) ? " TERRORIST " : " " ,
( p . flags & NodeFlag : : CTOnly ) ? " CT " : " " ,
( p . flags & NodeFlag : : Sniper ) ? " SNIPER " : " " ,
( p . flags & NodeFlag : : Goal ) ? " GOAL " : " " ,
( p . flags & NodeFlag : : Ladder ) ? " LADDER " : " " ,
( p . flags & NodeFlag : : Rescue ) ? " RESCUE " : " " ,
( p . flags & NodeFlag : : DoubleJump ) ? " JUMPHELP " : " " ,
( p . flags & NodeFlag : : NoHostage ) ? " NOHOSTAGE " : " " , jumpPoint ? " JUMP " : " " ) ;
2019-07-27 17:36:24 +03:00
2022-01-21 23:39:26 +03:00
if ( flags . empty ( ) ) {
flags . assign ( " (none) " ) ;
2020-06-12 18:52:38 +03:00
}
2019-08-29 23:31:54 +03:00
2022-01-21 23:39:26 +03:00
// show the information about that point
message . assignf ( " %s node: \n "
2024-05-05 01:06:56 +03:00
" Node %d of %d, Radius: %.1f, Light: %s \n "
" Flags: %s \n "
2025-02-28 05:44:52 +03:00
" Origin: (%.1f, %.1f, %.1f) \n " ,
type , node , m_paths . length ( ) - 1 , p . radius ,
cr : : fequal ( p . light , kInvalidLightLevel ) ? " Invalid " : strings . format ( " %1.f " , p . light ) ,
flags , p . origin . x , p . origin . y , p . origin . z
) ;
2022-01-21 23:39:26 +03:00
return message ;
2019-08-29 23:31:54 +03:00
} ;
2019-07-27 17:36:24 +03:00
// display some information
2024-01-19 00:03:45 +03:00
sendHudMessage ( { 255 , 255 , 255 } , 0.0f , 0.025f , getNodeData ( " Current " , nearestIndex ) ) ;
2022-01-21 23:39:26 +03:00
// check if we need to show the cached point index
if ( m_cacheNodeIndex ! = kInvalidNodeIndex ) {
2024-01-19 00:03:45 +03:00
sendHudMessage ( { 255 , 255 , 255 } , 0.28f , 0.16f , getNodeData ( " Cached " , m_cacheNodeIndex ) ) ;
2022-01-21 23:39:26 +03:00
}
2019-07-27 17:36:24 +03:00
2022-01-21 23:39:26 +03:00
// check if we need to show the facing point index
if ( m_facingAtIndex ! = kInvalidNodeIndex ) {
2024-01-19 00:03:45 +03:00
sendHudMessage ( { 255 , 255 , 255 } , 0.28f , 0.025f , getNodeData ( " Facing " , m_facingAtIndex ) ) ;
2022-01-21 23:39:26 +03:00
}
String timeMessage = strings . format ( " Map: %s, Time: %s \n " , game . getMapName ( ) , util . getCurrentDateTime ( ) ) ;
2019-07-27 17:36:24 +03:00
// if node is not changed display experience also
if ( ! m_hasChanged ) {
2023-06-24 02:36:51 +03:00
const int dangerIndexCT = practice . getIndex ( Team : : CT , nearestIndex , nearestIndex ) ;
const int dangerIndexT = practice . getIndex ( Team : : Terrorist , nearestIndex , nearestIndex ) ;
2019-07-27 17:36:24 +03:00
2024-05-16 21:15:41 +03:00
String practiceText { } ;
2023-05-02 09:42:43 +03:00
practiceText . assignf ( " Node practice data (index / damage): \n "
2024-05-05 01:06:56 +03:00
" CT: %d / %d \n "
2025-02-28 05:44:52 +03:00
" T: %d / %d \n \n " ,
dangerIndexCT ,
dangerIndexCT ! = kInvalidNodeIndex ? practice . getDamage ( Team : : CT , nearestIndex , dangerIndexCT ) : 0 ,
dangerIndexT ,
dangerIndexT ! = kInvalidNodeIndex ? practice . getDamage ( Team : : Terrorist , nearestIndex , dangerIndexT ) : 0
) ;
2019-07-27 17:36:24 +03:00
2024-01-19 00:03:45 +03:00
sendHudMessage ( { 255 , 255 , 255 } , 0.0f , 0.16f , practiceText + timeMessage ) ;
2022-01-21 23:39:26 +03:00
}
else {
2024-01-19 00:03:45 +03:00
sendHudMessage ( { 255 , 255 , 255 } , 0.0f , 0.16f , timeMessage ) ;
2019-07-27 17:36:24 +03:00
}
}
}
bool BotGraph : : isConnected ( int index ) {
for ( const auto & path : m_paths ) {
if ( path . number = = index ) {
continue ;
}
for ( const auto & test : path . links ) {
if ( test . index = = index ) {
return true ;
}
}
}
return false ;
}
2024-12-20 01:04:59 +03:00
bool BotGraph : : checkNodes ( bool teleportPlayer , bool onlyPaths ) {
2019-07-27 17:36:24 +03:00
auto teleport = [ & ] ( const Path & path ) - > void {
if ( teleportPlayer ) {
engfuncs . pfnSetOrigin ( m_editor , path . origin ) ;
setEditFlag ( GraphEdit : : On | GraphEdit : : Noclip ) ;
}
} ;
2024-12-20 01:04:59 +03:00
const bool showErrors = ! onlyPaths ;
2019-07-27 17:36:24 +03:00
int terrPoints = 0 ;
int ctPoints = 0 ;
int goalPoints = 0 ;
int rescuePoints = 0 ;
for ( const auto & path : m_paths ) {
int connections = 0 ;
if ( path . number ! = static_cast < int > ( m_paths . index ( path ) ) ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d path differs from index %d. " , path . number , m_paths . index ( path ) ) ;
}
2019-07-27 17:36:24 +03:00
break ;
}
for ( const auto & test : path . links ) {
if ( test . index ! = kInvalidNodeIndex ) {
if ( test . index > length ( ) ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d connected with invalid node %d. " , path . number , test . index ) ;
}
2019-07-27 17:36:24 +03:00
return false ;
}
+ + connections ;
break ;
}
}
if ( connections = = 0 ) {
if ( ! isConnected ( path . number ) ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d isn't connected with any other node. " , path . number ) ;
}
2019-07-27 17:36:24 +03:00
return false ;
}
}
if ( path . flags & NodeFlag : : Camp ) {
2019-08-18 21:00:00 +03:00
if ( path . end . empty ( ) ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d camp-endposition not set. " , path . number ) ;
}
2019-07-27 17:36:24 +03:00
return false ;
}
}
else if ( path . flags & NodeFlag : : TerroristOnly ) {
+ + terrPoints ;
}
else if ( path . flags & NodeFlag : : CTOnly ) {
+ + ctPoints ;
}
else if ( path . flags & NodeFlag : : Goal ) {
+ + goalPoints ;
}
else if ( path . flags & NodeFlag : : Rescue ) {
+ + rescuePoints ;
}
for ( const auto & test : path . links ) {
if ( test . index ! = kInvalidNodeIndex ) {
if ( ! exists ( test . index ) ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d path index %d out of range. " , path . number , test . index ) ;
}
2019-07-27 17:36:24 +03:00
teleport ( path ) ;
return false ;
}
else if ( test . index = = path . number ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Node %d path index %d points to itself. " , path . number , test . index ) ;
}
2019-07-27 17:36:24 +03:00
teleport ( path ) ;
return false ;
}
}
}
}
if ( game . mapIs ( MapFlags : : HostageRescue ) ) {
if ( rescuePoints = = 0 ) {
2023-05-02 09:42:43 +03:00
msg ( " You didn't set a rescue point. " ) ;
2019-07-27 17:36:24 +03:00
return false ;
}
}
2024-12-20 01:04:59 +03:00
// only check paths, but not necessity of different nodes
if ( ! onlyPaths ) {
if ( terrPoints = = 0 ) {
msg ( " You didn't set any terrorist important point. " ) ;
return false ;
}
else if ( ctPoints = = 0 ) {
msg ( " You didn't set any CT important point. " ) ;
return false ;
}
else if ( goalPoints = = 0 ) {
msg ( " You didn't set any goal point. " ) ;
return false ;
}
2019-07-27 17:36:24 +03:00
}
// perform DFS instead of floyd-warshall, this shit speedup this process in a bit
2025-01-31 19:32:24 +03:00
const auto length = cr : : min ( static_cast < size_t > ( kMaxNodes ) , m_paths . length ( ) + 1 ) ;
2023-04-02 12:17:12 +03:00
// ensure valid capacity
2024-01-19 00:03:45 +03:00
assert ( length > 8 & & length < static_cast < size_t > ( kMaxNodes ) ) ;
2023-04-02 12:17:12 +03:00
2024-05-16 21:15:41 +03:00
PathWalk walk { } ;
2023-04-02 12:17:12 +03:00
walk . init ( length ) ;
2020-12-18 20:06:05 +03:00
2024-05-16 21:15:41 +03:00
Array < bool > visited { } ;
2023-04-02 12:17:12 +03:00
visited . resize ( length ) ;
2019-07-27 17:36:24 +03:00
// first check incoming connectivity, initialize the "visited" table
for ( auto & visit : visited ) {
visit = false ;
}
2020-11-23 00:06:18 +03:00
walk . add ( 0 ) ; // always check from node number 0
2019-07-27 17:36:24 +03:00
while ( ! walk . empty ( ) ) {
// pop a node from the stack
const int current = walk . first ( ) ;
walk . shift ( ) ;
visited [ current ] = true ;
for ( const auto & link : m_paths [ current ] . links ) {
int index = link . index ;
// skip this node as it's already visited
if ( exists ( index ) & & ! visited [ index ] ) {
visited [ index ] = true ;
2020-11-23 00:06:18 +03:00
walk . add ( index ) ;
2019-07-27 17:36:24 +03:00
}
}
}
for ( const auto & path : m_paths ) {
if ( ! visited [ path . number ] ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Path broken from node 0 to node %d. " , path . number ) ;
}
2019-07-27 17:36:24 +03:00
teleport ( path ) ;
return false ;
}
}
// then check outgoing connectivity
2024-05-16 21:15:41 +03:00
Array < IntArray > outgoingPaths { } ; // store incoming paths for speedup
2023-04-02 12:17:12 +03:00
outgoingPaths . resize ( length ) ;
2019-07-27 17:36:24 +03:00
2023-03-13 15:39:15 +03:00
for ( const auto & path : m_paths ) {
2023-04-02 12:17:12 +03:00
outgoingPaths [ path . number ] . resize ( length + 1 ) ;
2019-07-27 17:36:24 +03:00
for ( const auto & link : path . links ) {
if ( exists ( link . index ) ) {
outgoingPaths [ link . index ] . push ( path . number ) ;
}
}
}
// initialize the "visited" table
for ( auto & visit : visited ) {
visit = false ;
}
walk . clear ( ) ;
2020-11-23 00:06:18 +03:00
walk . add ( 0 ) ; // always check from node number 0
2019-07-27 17:36:24 +03:00
while ( ! walk . empty ( ) ) {
const int current = walk . first ( ) ; // pop a node from the stack
walk . shift ( ) ;
for ( auto & outgoing : outgoingPaths [ current ] ) {
if ( visited [ outgoing ] ) {
continue ; // skip this node as it's already visited
}
visited [ outgoing ] = true ;
2020-11-23 00:06:18 +03:00
walk . add ( outgoing ) ;
2019-07-27 17:36:24 +03:00
}
}
2023-03-13 15:39:15 +03:00
2019-07-27 17:36:24 +03:00
for ( const auto & path : m_paths ) {
if ( ! visited [ path . number ] ) {
2024-12-20 01:04:59 +03:00
if ( showErrors ) {
msg ( " Path broken from node %d to node 0. " , path . number ) ;
}
2019-07-27 17:36:24 +03:00
teleport ( path ) ;
return false ;
}
}
return true ;
}
void BotGraph : : setVisited ( int index ) {
if ( ! exists ( index ) ) {
return ;
}
if ( ! isVisited ( index ) & & ( m_paths [ index ] . flags & NodeFlag : : Goal ) ) {
m_visitedGoals . push ( index ) ;
}
}
void BotGraph : : clearVisited ( ) {
m_visitedGoals . clear ( ) ;
}
bool BotGraph : : isVisited ( int index ) {
for ( auto & visited : m_visitedGoals ) {
if ( visited = = index ) {
return true ;
}
}
return false ;
}
void BotGraph : : addBasic ( ) {
// this function creates basic node types on map
// first of all, if map contains ladder points, create it
2019-08-18 21:00:00 +03:00
game . searchEntities ( " classname " , " func_ladder " , [ & ] ( edict_t * ent ) {
2019-07-27 17:36:24 +03:00
Vector ladderLeft = ent - > v . absmin ;
Vector ladderRight = ent - > v . absmax ;
ladderLeft . z = ladderRight . z ;
2019-09-21 23:20:33 +03:00
TraceResult tr { } ;
2024-05-16 21:15:41 +03:00
Vector up { } , down { } , front { } , back { } ;
2019-07-27 17:36:24 +03:00
2023-05-02 09:42:43 +03:00
Vector diff = ( ( ladderLeft - ladderRight ) ^ nullptr ) * 15.0f ;
2021-09-16 16:09:51 +03:00
front = back = game . getEntityOrigin ( ent ) ;
2019-07-27 17:36:24 +03:00
front = front + diff ; // front
back = back - diff ; // back
up = down = front ;
down . z = ent - > v . absmax . z ;
game . testHull ( down , up , TraceIgnore : : Monsters , point_hull , nullptr , & tr ) ;
if ( engfuncs . pfnPointContents ( up ) = = CONTENTS_SOLID | | ! cr : : fequal ( tr . flFraction , 1.0f ) ) {
up = down = back ;
down . z = ent - > v . absmax . z ;
}
game . testHull ( down , up - Vector ( 0.0f , 0.0f , 1000.0f ) , TraceIgnore : : Monsters , point_hull , nullptr , & tr ) ;
up = tr . vecEndPos ;
Vector point = up + Vector ( 0.0f , 0.0f , 39.0f ) ;
m_isOnLadder = true ;
do {
if ( getNearestNoBuckets ( point , 50.0f ) = = kInvalidNodeIndex ) {
2021-09-20 13:50:00 +03:00
add ( NodeAddFlag : : NoHostage , point ) ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
point . z + = 160.0f ;
2019-07-27 17:36:24 +03:00
} while ( point . z < down . z - 40.0f ) ;
point = down + Vector ( 0.0f , 0.0f , 38.0f ) ;
if ( getNearestNoBuckets ( point , 50.0f ) = = kInvalidNodeIndex ) {
2021-09-20 13:50:00 +03:00
add ( NodeAddFlag : : NoHostage , point ) ;
2019-07-27 17:36:24 +03:00
}
m_isOnLadder = false ;
2019-08-18 21:00:00 +03:00
return EntitySearchResult : : Continue ;
} ) ;
2019-07-27 17:36:24 +03:00
2024-01-26 19:52:00 +03:00
auto autoCreateForEntity = [ ] ( int type , StringRef classname ) {
game . searchEntities ( " classname " , classname , [ & ] ( edict_t * ent ) {
2023-05-02 09:42:43 +03:00
Vector pos = game . getEntityOrigin ( ent ) ;
2024-05-16 21:15:41 +03:00
TraceResult tr { } ;
2023-05-02 09:42:43 +03:00
game . testLine ( pos , pos - Vector ( 0.0f , 0.0f , 999.0f ) , TraceIgnore : : Monsters , nullptr , & tr ) ;
tr . vecEndPos . z + = 36.0f ;
2019-07-27 17:36:24 +03:00
2023-05-02 09:42:43 +03:00
if ( graph . getNearestNoBuckets ( tr . vecEndPos , 50.0f ) = = kInvalidNodeIndex ) {
graph . add ( type , tr . vecEndPos ) ;
2019-07-27 17:36:24 +03:00
}
2019-08-18 21:00:00 +03:00
return EntitySearchResult : : Continue ;
} ) ;
2019-07-27 17:36:24 +03:00
} ;
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Normal , " info_player_deathmatch " ) ; // then terrortist spawnpoints
autoCreateForEntity ( NodeAddFlag : : Normal , " info_player_start " ) ; // then add ct spawnpoints
autoCreateForEntity ( NodeAddFlag : : Normal , " info_vip_start " ) ; // then vip spawnpoint
2023-06-24 02:36:51 +03:00
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Normal , " armoury_entity " ) ; // weapons on the map ?
2019-07-27 17:36:24 +03:00
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Rescue , " func_hostage_rescue " ) ; // hostage rescue zone
autoCreateForEntity ( NodeAddFlag : : Rescue , " info_hostage_rescue " ) ; // hostage rescue zone (same as above)
2019-07-27 17:36:24 +03:00
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Goal , " func_bomb_target " ) ; // bombspot zone
autoCreateForEntity ( NodeAddFlag : : Goal , " info_bomb_target " ) ; // bombspot zone (same as above)
2023-06-24 02:36:51 +03:00
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Goal , " hostage_entity " ) ; // hostage entities
2022-01-03 07:18:36 +01:00
autoCreateForEntity ( NodeAddFlag : : Goal , " monster_scientist " ) ; // hostage entities (same as above)
2023-06-24 02:36:51 +03:00
2021-09-20 13:50:00 +03:00
autoCreateForEntity ( NodeAddFlag : : Goal , " func_vip_safetyzone " ) ; // vip rescue (safety) zone
autoCreateForEntity ( NodeAddFlag : : Goal , " func_escapezone " ) ; // terrorist escape zone
2019-07-27 17:36:24 +03:00
}
2019-08-24 12:43:42 +03:00
void BotGraph : : setBombOrigin ( bool reset , const Vector & pos ) {
2019-07-27 17:36:24 +03:00
// this function stores the bomb position as a vector
2020-06-12 18:52:38 +03:00
if ( ! game . mapIs ( MapFlags : : Demolition ) | | ! bots . isBombPlanted ( ) ) {
return ;
}
2019-07-27 17:36:24 +03:00
if ( reset ) {
2024-08-25 14:32:57 +03:00
m_bombOrigin . clear ( ) ;
2019-07-27 17:36:24 +03:00
bots . setBombPlanted ( false ) ;
return ;
}
if ( ! pos . empty ( ) ) {
2019-08-24 12:43:42 +03:00
m_bombOrigin = pos ;
2019-07-27 17:36:24 +03:00
return ;
}
2020-06-12 18:52:38 +03:00
bool wasFound = false ;
2020-12-15 15:28:58 +03:00
auto bombModel = conf . getBombModelName ( ) ;
2023-03-13 15:39:15 +03:00
2019-08-18 21:00:00 +03:00
game . searchEntities ( " classname " , " grenade " , [ & ] ( edict_t * ent ) {
2020-12-15 15:28:58 +03:00
if ( util . isModel ( ent , bombModel ) ) {
2021-09-16 16:09:51 +03:00
m_bombOrigin = game . getEntityOrigin ( ent ) ;
2020-06-12 18:52:38 +03:00
wasFound = true ;
2019-08-18 21:00:00 +03:00
return EntitySearchResult : : Break ;
2019-07-27 17:36:24 +03:00
}
2019-08-18 21:00:00 +03:00
return EntitySearchResult : : Continue ;
} ) ;
2020-06-12 18:52:38 +03:00
if ( ! wasFound ) {
2024-08-25 14:32:57 +03:00
m_bombOrigin . clear ( ) ;
2020-06-12 18:52:38 +03:00
bots . setBombPlanted ( false ) ;
}
2019-07-27 17:36:24 +03:00
}
void BotGraph : : startLearnJump ( ) {
m_jumpLearnNode = true ;
}
void BotGraph : : setSearchIndex ( int index ) {
m_findWPIndex = index ;
if ( exists ( m_findWPIndex ) ) {
2023-05-02 09:42:43 +03:00
msg ( " Showing direction to node %d. " , m_findWPIndex ) ;
2019-07-27 17:36:24 +03:00
}
else {
m_findWPIndex = kInvalidNodeIndex ;
}
}
BotGraph : : BotGraph ( ) {
m_endJumpPoint = false ;
m_jumpLearnNode = false ;
m_hasChanged = false ;
2020-10-01 10:43:51 +03:00
m_narrowChecked = false ;
2023-07-18 15:59:15 +03:00
m_lightChecked = false ;
2019-07-27 17:36:24 +03:00
m_timeJumpStarted = 0.0f ;
m_lastJumpNode = kInvalidNodeIndex ;
m_cacheNodeIndex = kInvalidNodeIndex ;
m_findWPIndex = kInvalidNodeIndex ;
m_facingAtIndex = kInvalidNodeIndex ;
m_isOnLadder = false ;
m_editFlags = 0 ;
2020-07-11 20:34:26 +03:00
2019-07-27 17:36:24 +03:00
m_pathDisplayTime = 0.0f ;
m_arrowDisplayTime = 0.0f ;
m_autoPathDistance = 250.0f ;
m_editor = nullptr ;
}
void BotGraph : : initBuckets ( ) {
2023-04-11 22:32:28 +03:00
m_hashTable . clear ( ) ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : addToBucket ( const Vector & pos , int index ) {
2023-04-11 22:32:28 +03:00
m_hashTable [ locateBucket ( pos ) ] . emplace ( index ) ;
}
const Array < int32_t > & BotGraph : : getNodesInBucket ( const Vector & pos ) {
return m_hashTable [ locateBucket ( pos ) ] ;
2019-07-27 17:36:24 +03:00
}
2023-05-02 09:42:43 +03:00
bool BotGraph : : isAnalyzed ( ) const {
return ( m_graphHeader . options & StorageOption : : Analyzed ) ;
}
2019-07-27 17:36:24 +03:00
void BotGraph : : eraseFromBucket ( const Vector & pos , int index ) {
2023-04-11 22:32:28 +03:00
auto & data = m_hashTable [ locateBucket ( pos ) ] ;
2019-07-27 17:36:24 +03:00
for ( size_t i = 0 ; i < data . length ( ) ; + + i ) {
if ( data [ i ] = = index ) {
data . erase ( i , 1 ) ;
break ;
}
}
}
2023-04-11 22:32:28 +03:00
int BotGraph : : locateBucket ( const Vector & pos ) {
2024-05-16 21:15:41 +03:00
constexpr auto kWidth = 8192 ;
2019-07-27 17:36:24 +03:00
2023-04-11 22:32:28 +03:00
auto hash = [ & ] ( float axis , int32_t shift ) {
2024-05-16 21:15:41 +03:00
return ( ( static_cast < int > ( axis ) + kWidth ) & 0x007f80 ) > > shift ;
2019-07-27 17:36:24 +03:00
} ;
2023-04-11 22:32:28 +03:00
return hash ( pos . x , 15 ) + hash ( pos . y , 7 ) ;
2019-07-27 17:36:24 +03:00
}
void BotGraph : : unassignPath ( int from , int to ) {
auto & link = m_paths [ from ] . links [ to ] ;
link . index = kInvalidNodeIndex ;
link . distance = 0 ;
link . flags = 0 ;
2024-08-25 14:32:57 +03:00
link . velocity . clear ( ) ;
2019-07-27 17:36:24 +03:00
setEditFlag ( GraphEdit : : On ) ;
m_hasChanged = true ;
}
void BotGraph : : convertFromPOD ( Path & path , const PODPath & pod ) {
path . number = pod . number ;
path . flags = pod . flags ;
path . origin = pod . origin ;
path . start = Vector ( pod . csx , pod . csy , 0.0f ) ;
path . end = Vector ( pod . cex , pod . cey , 0.0f ) ;
2024-04-25 15:03:39 +03:00
if ( cv_graph_fixcamp ) {
2019-07-27 17:36:24 +03:00
convertCampDirection ( path ) ;
}
path . radius = pod . radius ;
2023-06-24 03:23:22 +03:00
path . light = kInvalidLightLevel ;
2019-07-27 17:36:24 +03:00
path . display = 0.0f ;
for ( int i = 0 ; i < kMaxNodeLinks ; + + i ) {
path . links [ i ] . index = pod . index [ i ] ;
path . links [ i ] . distance = pod . distance [ i ] ;
path . links [ i ] . flags = pod . conflags [ i ] ;
path . links [ i ] . velocity = pod . velocity [ i ] ;
}
2023-04-02 12:17:12 +03:00
path . vis . stand = 0 ;
path . vis . crouch = 0 ;
2019-07-27 17:36:24 +03:00
}
2019-08-07 15:25:14 +03:00
void BotGraph : : convertToPOD ( const Path & path , PODPath & pod ) {
2019-07-27 17:36:24 +03:00
pod . number = path . number ;
pod . flags = path . flags ;
pod . origin = path . origin ;
pod . radius = path . radius ;
pod . csx = path . start . x ;
pod . csy = path . start . y ;
pod . cex = path . end . x ;
pod . cey = path . end . y ;
for ( int i = 0 ; i < kMaxNodeLinks ; + + i ) {
pod . index [ i ] = path . links [ i ] . index ;
pod . distance [ i ] = path . links [ i ] . distance ;
pod . conflags [ i ] = path . links [ i ] . flags ;
pod . velocity [ i ] = path . links [ i ] . velocity ;
}
pod . vis . stand = path . vis . stand ;
pod . vis . crouch = path . vis . crouch ;
}
void BotGraph : : convertCampDirection ( Path & path ) {
// this function converts old vector based camp directions to angles, note that podbotmm graph
// are already saved with angles, and converting this stuff may result strange look directions.
if ( m_paths . empty ( ) ) {
return ;
}
2023-06-24 02:36:51 +03:00
const auto & offset = path . origin + Vector ( 0.0f , 0.0f , ( path . flags & NodeFlag : : Crouch ) ? 15.0f : 17.0f ) ;
2019-07-27 17:36:24 +03:00
path . start = ( Vector ( path . start . x , path . start . y , path . origin . z ) - offset ) . angles ( ) ;
path . end = ( Vector ( path . end . x , path . end . y , path . origin . z ) - offset ) . angles ( ) ;
path . start . x = - path . start . x ;
path . end . x = - path . end . x ;
path . start . clampAngles ( ) ;
path . end . clampAngles ( ) ;
}