aim: improved ladder handling view direction

aim: improved enemy prediction once again
nav: bots with hostages will try to take all hostages that are near with him instead of going directly to rescue zone
manager: fixed engine errors when removing bots with kickall with instant parameter
graph: strip http:// prefix from graph upload url, it should be always http for now
bot: improve handling of smoke grenades on ground (restored code from old yapb2 branch)
This commit is contained in:
jeefo 2023-06-23 19:52:46 +03:00
commit a49a4000c9
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
16 changed files with 269 additions and 133 deletions

@ -1 +1 @@
Subproject commit 67733ef6ffd51c538692f311e6cfb26affb3e50e
Subproject commit c4815b7445ae0fc509cba511deee0f2c67834704

View file

@ -395,6 +395,15 @@ CR_DECLARE_SCOPED_ENUM (Visibility,
None = 0
)
// goal tactic
CR_DECLARE_SCOPED_ENUM (GoalTactic,
Defensive = 0,
Camp,
Offensive,
Goal,
RescueHostage
)
// frustum sides
CR_DECLARE_SCOPED_ENUM (FrustumSide,
Top = 0,

View file

@ -99,8 +99,8 @@ private:
BinaryHeap <RouteTwin <float>> m_routeQue {};
Array <Route> m_routes {};
HeuristicFn m_hcalc;
HeuristicFn m_gcalc;
HeuristicFn m_hcalc {};
HeuristicFn m_gcalc {};
int m_length {};

View file

@ -54,6 +54,9 @@ public:
// check if entity is a vip
bool isPlayerVIP (edict_t *ent);
// check if entity is a hostage entity
bool isHostageEntity (edict_t *ent);
// nearest player search helper
bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false);

View file

@ -362,8 +362,9 @@ private:
Vector m_throw {}; // origin of node to throw grenades
Vector m_enemyOrigin {}; // target origin chosen for shooting
Vector m_grenade {}; // calculated vector for grenades
Vector m_entity {}; // origin of entities like buttons etc.
Vector m_lookAtSafe {}; // aiming vector when camping.
Vector m_entity {}; // origin of entities like buttons etc
Vector m_lookAtSafe {}; // aiming vector when camping
Vector m_lookAtPredict {}; // aiming vector when predicting
Vector m_desiredVelocity {}; // desired velocity for jump nodes
Vector m_breakableOrigin {}; // origin of breakable
@ -403,7 +404,7 @@ private:
bool canDuckUnder (const Vector &normal);
bool canJumpUp (const Vector &normal);
bool doneCanJumpUp (const Vector &normal, const Vector &right);
bool cantMoveForward (const Vector &normal, TraceResult *tr);
bool isBlockedForward (const Vector &normal, TraceResult *tr);
bool canStrafeLeft (TraceResult *tr);
bool canStrafeRight (TraceResult *tr);
bool isBlockedLeft ();
@ -730,7 +731,7 @@ public:
void pushChatterMessage (int message);
void tryHeadTowardRadioMessage ();
void kill ();
void kick ();
void kick (bool silent = false);
void resetDoubleJump ();
void startDoubleJump (edict_t *ent);
void sendBotToOrigin (const Vector &origin);

View file

@ -115,8 +115,8 @@ void Bot::avoidGrenades () {
float distanceMoved = pev->origin.distance (pent->v.origin + pent->v.velocity * m_frameInterval);
if (distanceMoved < distance && distance < cr::sqrf (500.0f)) {
const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d ();
const auto &rightSide = pev->v_angle.right ().normalize2d ();
const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d_apx ();
const auto &rightSide = pev->v_angle.right ().normalize2d_apx ();
if ((dirToPoint | rightSide) > 0.0f) {
m_needAvoidGrenade = -1;
@ -129,8 +129,14 @@ void Bot::avoidGrenades () {
}
}
else if ((pent->v.flags & FL_ONGROUND) && model == "smokegrenade.mdl") {
if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov - 7.0f) {
float distance = pent->v.origin.distance (pev->origin);
if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) {
const auto &entOrigin = game.getEntityOrigin (pent);
const auto &betweenUs = (entOrigin - pev->origin).normalize_apx ();
const auto &betweenNade = (entOrigin - pev->origin).normalize_apx ();
const auto &betweenResult = ((Vector (betweenNade.y, betweenNade.x, 0.0f) * 150.0f + entOrigin) - pev->origin).normalize_apx ();
if ((betweenNade | betweenUs) > (betweenNade | betweenResult) && util.isVisible (pent->v.origin, ent ())) {
const float distance = entOrigin.distance (pev->origin);
// shrink bot's viewing distance to smoke grenade's distance
if (m_viewDistance > distance) {
@ -143,6 +149,7 @@ void Bot::avoidGrenades () {
}
}
}
}
}
void Bot::checkBreakable (edict_t *touch) {
@ -383,7 +390,7 @@ void Bot::updatePickups () {
const bool isHostageRescueMap = game.mapIs (MapFlags::HostageRescue);
const bool isCSDM = game.is (GameFlags::CSDM);
if (isHostageRescueMap && (classname.startsWith ("hostage_entity") || classname.startsWith ("monster_scientist"))) {
if (isHostageRescueMap && util.isHostageEntity (ent)) {
allowPickup = true;
pickupType = Pickup::Hostage;
}
@ -794,12 +801,16 @@ void Bot::showChatterIcon (bool show, bool disconnect) {
int ownIndex = index ();
// do not respect timers while disconnecting bot
for (auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) {
continue;
}
// dormants not receiving messages
if (client.ent->v.flags & FL_DORMANT) {
continue;
}
// do not respect timers while disconnecting bot
if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && (disconnect || client.iconTimestamp[ownIndex] < game.time ())) {
sendBotVoice (false, client.ent, entindex ());
@ -2168,7 +2179,7 @@ bool Bot::reactOnEnemy () {
bool Bot::lastEnemyShootable () {
// don't allow shooting through walls
if (!(m_aimFlags & AimFlags::LastEnemy) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
if (!(m_aimFlags & (AimFlags::LastEnemy | AimFlags::PredictPath)) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
return false;
}
return util.getShootingCone (ent (), m_lastEnemyOrigin) >= 0.90f && isPenetrableObstacle (m_lastEnemyOrigin);

View file

@ -791,7 +791,7 @@ int BotControl::cmdNodeUpload () {
msg ("you may notice the game freezes a bit during upload and issue request creation. Please, be patient.");
msg ("\n");
String uploadUrl = cv_graph_url_upload.str ();
String uploadUrl = strings.format ("https://%s", cv_graph_url_upload.str ());
// try to upload the file
if (http.uploadFile (uploadUrl, bstor.buildPath (BotFile::Graph))) {

View file

@ -124,7 +124,7 @@ void Game::levelInitialize (edict_t *entities, int max) {
else if (classname == "func_vip_safetyzone" || classname == "info_vip_safetyzone") {
m_mapFlags |= MapFlags::Assassination; // assassination map
}
else if (classname == "hostage_entity" || classname == "monster_scientist") {
else if (util.isHostageEntity (ent)) {
m_mapFlags |= MapFlags::HostageRescue; // rescue map
}
else if (classname == "func_bomb_target" || classname == "info_bomb_target") {
@ -916,7 +916,7 @@ bool Game::postload () {
if (is (GameFlags::Metamod)) {
return true; // we should stop the attempt for loading the real gamedll, since metamod handle this for us
}
auto gamedll = strings.format ("%s/%s", plat.env ("XASH3D_GAMELIBDIR"), plat.hfp ? "libserver_hardfp.so" : "libserver.so");
auto gamedll = strings.format ("%s/%s", plat.env ("XASH3D_GAMELIBDIR"), "libserver.so");
if (!m_gameLib.load (gamedll)) {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getRunningModName ());
@ -1137,7 +1137,7 @@ void Game::printBotVersion () {
simdLevels.push ("4.2");
}
if (cpuflags.neon) {
simdLevels.push ("NEON");
simdLevels.push ("Neon");
}
botRuntimeFlags.push (strings.format ("SIMD: %s", String::join (simdLevels, " & ")));
}

View file

@ -9,7 +9,7 @@
ConVar cv_graph_fixcamp ("yb_graph_fixcamp", "0", "Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format.");
ConVar cv_graph_url ("yb_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);
ConVar cv_graph_url_upload ("yb_graph_url_upload", "http://yapb.jeefo.net/upload", "Specifies the URL to which bots will try to upload the graph file to database.", false, 0.0f, 0.0f);
ConVar cv_graph_url_upload ("yb_graph_url_upload", "yapb.jeefo.net/upload", "Specifies the URL to which bots will try to upload the graph file to database.", false, 0.0f, 0.0f);
ConVar cv_graph_auto_save_count ("yb_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 ("yb_graph_draw_distance", "400", "Maximum distance to draw graph nodes from editor viewport.", true, 64.0f, 3072.0f);

View file

@ -609,7 +609,7 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) {
if (instant) {
for (const auto &bot : m_bots) {
bot->kick ();
bot->kick (true);
}
}
m_addRequests.clear ();
@ -618,12 +618,26 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) {
void BotManager::kickFromTeam (Team team, bool removeAll) {
// this function remove random bot from specified team (if removeAll value = 1 then removes all players from team)
if (removeAll) {
const auto &counts = countTeamPlayers ();
m_quotaMaintainTime = game.time () + 3.0f;
m_addRequests.clear ();
if (team == Team::Terrorist) {
decrementQuota (counts.first);
}
else {
decrementQuota (counts.second);
}
}
for (const auto &bot : m_bots) {
if (team == bot->m_team) {
decrementQuota ();
bot->kick ();
bot->kick (removeAll);
if (!removeAll) {
decrementQuota ();
break;
}
}
@ -1524,6 +1538,11 @@ void Bot::resetPathSearchType () {
m_pathType = morale ? FindPath::Optimal : FindPath::Safe;
break;
}
// if debug goal - set the fastest
if (cv_debug_goal.int_ () != kInvalidNodeIndex) {
m_pathType = FindPath::Fast;
}
}
void Bot::kill () {
@ -1533,7 +1552,7 @@ void Bot::kill () {
bots.touchKillerEntity (this);
}
void Bot::kick () {
void Bot::kick (bool silent) {
// this function kick off one bot from the server.
auto username = pev->netname.chars ();
@ -1543,7 +1562,10 @@ void Bot::kick () {
markStale ();
game.serverCommand ("kick \"%s\"", username);
if (!silent) {
ctrl.msg ("Bot '%s' kicked.", username);
}
}
void Bot::markStale () {
@ -1559,7 +1581,7 @@ void Bot::markStale () {
// clear fakeclient bit
pev->flags &= ~FL_FAKECLIENT;
// make as not receiveing any messages
// make as not receiving any messages
pev->flags |= FL_DORMANT;
}
@ -1787,7 +1809,7 @@ void BotManager::updateInterestingEntities () {
}
// pickup some hostage if on cs_ maps
if (game.mapIs (MapFlags::HostageRescue) && classname.startsWith ("hostage")) {
if (game.mapIs (MapFlags::HostageRescue) && util.isHostageEntity (e)) {
m_interestingEntities.push (e);
}

View file

@ -27,18 +27,11 @@ int Bot::findBestGoal () {
return result;
}
}
int tactic = 0;
// path finding behavior depending on map type
float offensive = 0.0f;
float defensive = 0.0f;
float goalDesire = 0.0f;
float forwardDesire = 0.0f;
float campDesire = 0.0f;
float backoffDesire = 0.0f;
float tacticChoice = 0.0f;
IntArray *offensiveNodes = nullptr;
IntArray *defensiveNodes = nullptr;
@ -57,12 +50,51 @@ int Bot::findBestGoal () {
// terrorist carrying the C4?
if (m_hasC4 || m_isVIP) {
tactic = 3;
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
return findGoalPost (GoalTactic::Goal, defensiveNodes, offensiveNodes);
}
else if (m_team == Team::CT && m_hasHostage) {
tactic = 4;
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
bool hasMoreHostagesAround = false;
// try to search nearby-unused hostage, and if so, go to next goal
if (bots.hasInterestingEntities ()) {
const auto &interesting = bots.getInterestingEntities ();
// search world for hostages
for (const auto &ent : interesting) {
if (!util.isHostageEntity (ent)) {
continue;
}
bool hostageInUse = false;
// do not stole from bots (ignore humans, fuck them)
for (const auto &other : bots) {
if (!other->m_notKilled) {
continue;
}
for (const auto &hostage : other->m_hostages) {
if (hostage == ent) {
hostageInUse = true;
break;
}
}
}
// in-use, skip
if (hostageInUse) {
continue;
}
const auto &origin = game.getEntityOrigin (ent);
// too far, go to rescue point
if (origin.distanceSq2d (pev->origin) > 1024.0f) {
continue;
}
hasMoreHostagesAround = true;
break;
}
}
return findGoalPost (hasMoreHostagesAround ? GoalTactic::Goal : GoalTactic::RescueHostage, defensiveNodes, offensiveNodes);
}
auto difficulty = static_cast <float> (m_difficulty);
@ -110,39 +142,39 @@ int Bot::findBestGoal () {
}
else if (game.mapIs (MapFlags::Escape)) {
if (m_team == Team::Terrorist) {
offensive += 25.0f;
defensive -= 25.0f;
offensive += 25.0f + difficulty * 4.0f;
defensive -= 25.0f - difficulty * 0.5f;
}
else if (m_team == Team::CT) {
offensive -= 25.0f;
defensive += 25.0f;
offensive -= 25.0f - difficulty * 4.5f;
defensive += 25.0f + difficulty * 0.5f;
}
}
goalDesire = rg.get (0.0f, 100.0f) + offensive;
forwardDesire = rg.get (0.0f, 100.0f) + offensive;
campDesire = rg.get (0.0f, 100.0f) + defensive;
backoffDesire = rg.get (0.0f, 100.0f) + defensive;
float goalDesire = rg.get (0.0f, 100.0f) + offensive;
float forwardDesire = rg.get (0.0f, 100.0f) + offensive;
float campDesire = rg.get (0.0f, 100.0f) + defensive;
float backoffDesire = rg.get (0.0f, 100.0f) + defensive;
if (!usesCampGun ()) {
campDesire *= 0.5f;
}
tacticChoice = backoffDesire;
tactic = 0;
int tactic = GoalTactic::Defensive;
float tacticChoice = backoffDesire;
if (campDesire > tacticChoice) {
tacticChoice = campDesire;
tactic = 1;
tactic = GoalTactic::Camp;
}
if (forwardDesire > tacticChoice) {
tacticChoice = forwardDesire;
tactic = 2;
tactic = GoalTactic::Offensive;
}
if (goalDesire > tacticChoice) {
tactic = 3;
tactic = GoalTactic::Goal;
}
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
}
@ -209,10 +241,10 @@ int Bot::findBestGoalWhenBombAction () {
int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
if (tactic == 0 && !(*defensive).empty ()) { // careful goal
if (tactic == GoalTactic::Defensive && !(*defensive).empty ()) { // careful goal
postprocessGoals (*defensive, goalChoices);
}
else if (tactic == 1 && !graph.m_campPoints.empty ()) // camp node goal
else if (tactic == GoalTactic::Camp && !graph.m_campPoints.empty ()) // camp node goal
{
// pickup sniper points if possible for sniping bots
if (!graph.m_sniperPoints.empty () && usesSniper ()) {
@ -222,10 +254,10 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
postprocessGoals (graph.m_campPoints, goalChoices);
}
}
else if (tactic == 2 && !(*offensive).empty ()) { // offensive goal
else if (tactic == GoalTactic::Offensive && !(*offensive).empty ()) { // offensive goal
postprocessGoals (*offensive, goalChoices);
}
else if (tactic == 3 && !graph.m_goalPoints.empty ()) // map goal node
else if (tactic == GoalTactic::Goal && !graph.m_goalPoints.empty ()) // map goal node
{
// force bomber to select closest goal, if round-start goal was reset by something
if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ()) {
@ -258,7 +290,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
postprocessGoals (graph.m_goalPoints, goalChoices);
}
}
else if (tactic == 4 && !graph.m_rescuePoints.empty ()) {
else if (tactic == GoalTactic::RescueHostage && !graph.m_rescuePoints.empty ()) {
// force ct with hostage(s) to select closest rescue goal
float minDist = kInfiniteDistance;
int count = 0;
@ -316,11 +348,6 @@ void Bot::postprocessGoals (const IntArray &goals, int result[]) {
return true;
}
// too less to choice from just return all the goals
if (goals.length () < 4) {
return false;
}
// check if historical goal
for (const auto &hg : m_goalHist) {
if (hg == index) {
@ -336,6 +363,14 @@ void Bot::postprocessGoals (const IntArray &goals, int result[]) {
return isOccupiedNode (index);
};
// too less to choice from just return all the goals
if (goals.length () < 4) {
for (size_t i = 0; i < goals.length (); ++i) {
result[i] = goals[i];
}
return;
}
for (int index = 0; index < 4; ++index) {
auto goal = goals.random ();
@ -484,7 +519,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// not stuck yet
else {
// test if there's something ahead blocking the way
if (!isOnLadder () && cantMoveForward (dirNormal, &tr)) {
if (!isOnLadder () && isBlockedForward (dirNormal, &tr)) {
if (cr::fzero (m_firstCollideTime)) {
m_firstCollideTime = game.time () + 0.2f;
}
@ -528,6 +563,11 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
bits |= (CollisionProbe::Strafe | CollisionProbe::Jump);
}
// try to duck when graph analyzed
if (graph.isAnalyzed ()) {
bits |= CollisionProbe::Duck;
}
// collision check allowed if not flying through the air
if (isOnFloor () || isOnLadder () || isInWater ()) {
uint32_t state[kMaxCollideMoves * 2 + 1] {};
@ -657,7 +697,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
}
++i;
#if 0
if (bits & CollisionProbe::Duck) {
state[i] = 0;
@ -670,7 +709,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
}
}
else
#endif
state[i] = 0;
++i;
@ -909,24 +947,31 @@ bool Bot::updateNavigation () {
selectBestWeapon ();
}
}
#if 0
if (m_path->flags & NodeFlag::Ladder) {
}
#else
if ((m_pathFlags & NodeFlag::Ladder) || isOnLadder ()) {
if (graph.exists (m_previousNodes[0]) && (graph[m_previousNodes[0]].flags & NodeFlag::Ladder)) {
if (cr::abs (m_pathOrigin.z - pev->origin.z) > 5.0f) {
m_pathOrigin.z += pev->origin.z - m_pathOrigin.z;
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f);
if (m_pathOrigin.z >= (pev->origin.z + 16.0f)) {
m_pathOrigin = m_path->origin + kLadderOffset;
}
if (m_pathOrigin.z > (pev->origin.z + 16.0f)) {
m_pathOrigin = m_pathOrigin - Vector (0.0f, 0.0f, 16.0f);
else if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) {
m_moveSpeed = pev->origin.distance (m_pathOrigin);
if (m_moveSpeed < 150.0f) {
m_moveSpeed = 150.0f;
}
if (m_pathOrigin.z < (pev->origin.z - 16.0f)) {
m_pathOrigin = m_pathOrigin + Vector (0.0f, 0.0f, 16.0f);
else if (m_moveSpeed > pev->maxspeed) {
m_moveSpeed = pev->maxspeed;
}
}
m_destOrigin = m_pathOrigin;
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || (client.ent->v.movetype != MOVETYPE_FLY) || client.ent == nullptr || client.ent == ent ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || (client.ent->v.movetype != MOVETYPE_FLY) || client.ent == ent ()) {
continue;
}
TraceResult tr {};
@ -994,6 +1039,7 @@ bool Bot::updateNavigation () {
}
}
#endif
// special lift handling (code merged from podbotmm)
if (m_pathFlags & NodeFlag::Lift) {
@ -1043,30 +1089,29 @@ bool Bot::updateNavigation () {
}
// if bot hits the door, then it opens, so wait a bit to let it open safely
if (pev->velocity.length2d () < 10 && m_timeDoorOpen < game.time ()) {
if (pev->velocity.lengthSq2d () < cr::sqrf (10.0f) && m_timeDoorOpen < game.time ()) {
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.5f, false);
m_timeDoorOpen = game.time () + 1.0f; // retry in 1 sec until door is open
edict_t *pent = nullptr;
++m_tryOpenDoor;
if (++m_tryOpenDoor > 1 && util.findNearestPlayer (reinterpret_cast <void **> (&pent), ent (), 384.0f, false, false, true, true, false)) {
if (isPenetrableObstacle (pent->v.origin)) {
if (m_tryOpenDoor > 2 && util.isAlive (m_lastEnemy)) {
if (isPenetrableObstacle (m_lastEnemy->v.origin) && !cv_ignore_enemies.bool_ ()) {
m_seeEnemyTime = game.time ();
m_states |= Sense::SeeingEnemy | Sense::SuspectEnemy;
m_aimFlags |= AimFlags::Enemy;
m_lastEnemy = pent;
m_enemy = pent;
m_lastEnemyOrigin = pent->v.origin;
m_enemy = m_lastEnemy;
m_lastEnemyOrigin = m_lastEnemy->v.origin;
m_tryOpenDoor = 0;
}
else {
m_tryOpenDoor = 0;
}
}
else if (m_timeDoorOpen + 2.0f < game.time ()) {
else if (m_tryOpenDoor > 4) {
clearSearchNodes ();
clearTasks ();
m_tryOpenDoor = 0;
}
}
@ -2241,7 +2286,7 @@ bool Bot::advanceMovement () {
for (const auto &link : m_path->links) {
if (link.index == destIndex) {
m_currentTravelFlags = link.flags;
m_desiredVelocity = link.velocity;
m_desiredVelocity = link.velocity - link.velocity * m_frameInterval;
m_jumpFinished = false;
isCurrentJump = true;
@ -2371,7 +2416,7 @@ void Bot::setPathOrigin () {
}
}
bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) {
// checks if bot is blocked in his movement direction (excluding doors)
// use some TraceLines to determine if anything is blocking the current path of the bot.
@ -2398,11 +2443,12 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
}
return true; // bot's head will hit something
}
constexpr auto kVec00N16 = Vector (0.0f, 0.0f, -16.0f);
// bot's head is clear, check at shoulder level...
// trace from the bot's shoulder left diagonal forward to the right shoulder...
src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f;
forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f + normal * 24.0f;
src = getEyesPos () + kVec00N16 - right * -16.0f;
forward = getEyesPos () + kVec00N16 + right * 16.0f + normal * 24.0f;
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
@ -2413,8 +2459,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
// bot's head is clear, check at shoulder level...
// trace from the bot's shoulder right diagonal forward to the left shoulder...
src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f;
forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f + normal * 24.0f;
src = getEyesPos () + kVec00N16 + right * 16.0f;
forward = getEyesPos () + kVec00N16 - right * -16.0f + normal * 24.0f;
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
@ -2445,9 +2491,12 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
}
}
else {
constexpr auto kVec00N17 = Vector (0.0f, 0.0f, -17.0f);
constexpr auto kVec00N24 = Vector (0.0f, 0.0f, -24.0f);
// trace from the left waist to the right forward waist pos
src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f;
forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f + normal * 24.0f;
src = pev->origin + kVec00N17 - right * -16.0f;
forward = pev->origin + kVec00N17 + right * 16.0f + normal * 24.0f;
// trace from the bot's waist straight forward...
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
@ -2458,8 +2507,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
}
// trace from the left waist to the right forward waist pos
src = pev->origin + Vector (0.0f, 0.0f, -24.0f) + right * 16.0f;
forward = pev->origin + Vector (0.0f, 0.0f, -24.0f) - right * -16.0f + normal * 24.0f;
src = pev->origin + kVec00N24 + right * 16.0f;
forward = pev->origin + kVec00N24 - right * -16.0f + normal * 24.0f;
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
@ -2828,7 +2877,7 @@ bool Bot::isDeadlyMove (const Vector &to) {
if (tr.fStartSolid) {
return false;
}
float height = tr.flFraction * 1000.0f; // height from ground
const float height = tr.flFraction * 1000.0f; // height from ground
// drops more than 150 units?
if (lastHeight < height - 150.0f) {
@ -2862,7 +2911,6 @@ void Bot::changePitch (float speed) {
normalizePitch = -speed;
}
}
pev->v_angle.x = cr::wrapAngle (curent + normalizePitch);
if (pev->v_angle.x > 89.9f) {

View file

@ -204,7 +204,7 @@ bool BotSupport::isMonster (edict_t *ent) {
return false;
}
if (ent->v.classname.str ().startsWith ("hostage")) {
if (isHostageEntity (ent)) {
return false;
}
@ -226,6 +226,18 @@ bool BotSupport::isPlayerVIP (edict_t *ent) {
return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v';
}
bool BotSupport::isHostageEntity (edict_t *ent) {
if (game.isNullEntity (ent)) {
return false;
}
auto classHash = ent->v.classname.str ().hash ();
constexpr auto kHostageEntity = StringRef::fnv1a32 ("hostage_entity");
constexpr auto kMonsterScientist = StringRef::fnv1a32 ("monster_scientist");
return classHash == kHostageEntity || classHash == kMonsterScientist;
}
bool BotSupport::isFakeClient (edict_t *ent) {
if (bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) {
return true;

View file

@ -1471,7 +1471,7 @@ void Bot::pickupItem_ () {
auto tab = conf.getRawWeapons ();
if ((tab[weaponIndex].id == Weapon::Shield || weaponIndex >= kPrimaryWeaponMinIndex || hasShield ()) && niceWeapon) {
if ((weaponIndex >= kPrimaryWeaponMinIndex || tab[weaponIndex].id == Weapon::Shield || hasShield ()) && niceWeapon) {
selectWeaponByIndex (weaponIndex);
dropCurrentWeapon ();
}
@ -1561,9 +1561,7 @@ void Bot::pickupItem_ () {
// find the nearest 'unused' hostage within the area
game.searchEntities (pev->origin, 768.0f, [&] (edict_t *ent) {
auto classname = ent->v.classname.str ();
if (!classname.startsWith ("hostage_entity") && !classname.startsWith ("monster_scientist")) {
if (!util.isHostageEntity (ent)) {
return EntitySearchResult::Continue;
}

View file

@ -123,35 +123,37 @@ void Bot::updateAimDir () {
}
else if (flags & AimFlags::PredictPath) {
bool changePredictedEnemy = true;
bool predictFailed = false;
if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy) {
if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) {
changePredictedEnemy = false;
}
auto doFailPredict = [this] () {
auto doFailPredict = [this] () -> void {
if (m_timeNextTracking > game.time ()) {
return; // do not fail instantly
}
m_aimFlags &= ~AimFlags::PredictPath;
m_trackingEdict = nullptr;
m_lookAtPredict = nullptr;
};
if (changePredictedEnemy) {
auto isPredictedIndexApplicable = [this] () -> bool {
int pathLength = m_lastPredictLength;
int predictNode = m_lastPredictIndex;
if (predictNode != kInvalidNodeIndex) {
TraceResult tr;
game.testLine (getEyesPos (), graph[predictNode].origin, TraceIgnore::Everything, ent (), &tr);
if (tr.flFraction < 0.2f) {
pathLength = kInfiniteDistanceLong;
if (!vistab.visible (m_currentNodeIndex, predictNode)) {
predictNode = kInvalidNodeIndex;
}
}
return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ();
};
if (predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ()) {
m_lookAt = graph[predictNode].origin;
m_lookAtSafe = m_lookAt;
if (changePredictedEnemy) {
if (isPredictedIndexApplicable ()) {
m_lookAtPredict = graph[m_lastPredictIndex].origin;
m_timeNextTracking = game.time () + 0.25f;
m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f);
m_trackingEdict = m_lastEnemy;
// feel free to fire if shootable
@ -161,15 +163,16 @@ void Bot::updateAimDir () {
}
else {
doFailPredict ();
predictFailed = true;
}
}
else {
if (!isPredictedIndexApplicable ()) {
doFailPredict ();
}
}
if (predictFailed) {
doFailPredict ();
}
else {
m_lookAt = m_lookAtSafe;
if (!m_lookAtPredict.empty ()) {
m_lookAt = m_lookAtPredict;
}
}
else if (flags & AimFlags::Camp) {
@ -194,8 +197,9 @@ void Bot::updateAimDir () {
else {
m_lookAt = m_destOrigin;
}
const bool onLadder = (m_pathFlags & NodeFlag::Ladder);
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & NodeFlag::Ladder)) {
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !onLadder) {
auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
if (graph.exists (dangerIndex) && vistab.visible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
@ -211,8 +215,17 @@ void Bot::updateAimDir () {
}
}
// try look at next node if on ladder
if (onLadder && m_pathWalk.hasNext ()) {
auto nextPath = graph[m_pathWalk.next ()];
if ((nextPath.flags & NodeFlag::Ladder) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (120.0f) && nextPath.origin.z > m_pathOrigin.z + 45.0f) {
m_lookAt = nextPath.origin;
}
}
// don't look at bottom of node, if reached it
if (m_lookAt == m_destOrigin) {
if (m_lookAt == m_destOrigin && !onLadder) {
m_lookAt.z = getEyesPos ().z;
}
}
@ -230,14 +243,15 @@ void Bot::checkDarkness () {
}
// do not check every frame
if (m_checkDarkTime + 5.0f > game.time () || cr::fzero (m_path->light)) {
if (m_checkDarkTime > game.time () || cr::fzero (m_path->light)) {
return;
}
auto skyColor = illum.getSkyColor ();
auto flashOn = (pev->effects & EF_DIMLIGHT);
if (mp_flashlight.bool_ () && !m_hasNVG) {
auto task = Task ();
auto task = getCurrentTaskId ();
if (!flashOn && task != Task::Camp && task != Task::Attack && m_heardSoundTime + 3.0f < game.time () && m_flashLevel > 30 && ((skyColor > 50.0f && m_path->light < 10.0f) || (skyColor <= 50.0f && m_path->light < 40.0f))) {
pev->impulse = 100;
@ -257,7 +271,7 @@ void Bot::checkDarkness () {
issueCommand ("nightvision");
}
}
m_checkDarkTime = game.time ();
m_checkDarkTime = game.time () + rg.get (2.0f, 4.0f);
}
@ -359,12 +373,12 @@ void Bot::updateLookAngles () {
m_idealAngles.y = direction.y;
}
else {
float accel = cr::clamp (stiffness * angleDiffYaw - damping * m_lookYawVel, -accelerate, accelerate);
const float accel = cr::clamp (stiffness * angleDiffYaw - damping * m_lookYawVel, -accelerate, accelerate);
m_lookYawVel += delta * accel;
m_idealAngles.y += delta * m_lookYawVel;
}
float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate);
const float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate);
m_lookPitchVel += delta * accel;
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);

View file

@ -40,6 +40,9 @@
<ClInclude Include="..\ext\crlib\crlib\platform.h" />
<ClInclude Include="..\ext\crlib\crlib\random.h" />
<ClInclude Include="..\ext\crlib\crlib\simd.h" />
<ClInclude Include="..\ext\crlib\crlib\simd\neon.h" />
<ClInclude Include="..\ext\crlib\crlib\simd\sse2.h" />
<ClInclude Include="..\ext\crlib\crlib\simd\sse2neon.h" />
<ClInclude Include="..\ext\crlib\crlib\string.h" />
<ClInclude Include="..\ext\crlib\crlib\thread.h" />
<ClInclude Include="..\ext\crlib\crlib\timers.h" />

View file

@ -19,6 +19,9 @@
<Filter Include="inc\ext\linkage">
<UniqueIdentifier>{f6a0fc04-bdf5-479b-8e5a-85eae698541e}</UniqueIdentifier>
</Filter>
<Filter Include="inc\ext\crlib\simd">
<UniqueIdentifier>{01281138-9315-450e-be71-55e16a2e3019}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\inc\config.h">
@ -168,6 +171,18 @@
<ClInclude Include="..\ext\crlib\crlib\cpuflags.h">
<Filter>inc\ext\crlib</Filter>
</ClInclude>
<ClInclude Include="..\inc\constant.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\ext\crlib\crlib\simd\sse2.h">
<Filter>inc\ext\crlib\simd</Filter>
</ClInclude>
<ClInclude Include="..\ext\crlib\crlib\simd\sse2neon.h">
<Filter>inc\ext\crlib\simd</Filter>
</ClInclude>
<ClInclude Include="..\ext\crlib\crlib\simd\neon.h">
<Filter>inc\ext\crlib\simd</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\botlib.cpp">