* Fixed bots not camping in camp spots. Fixed chatter/radio message cycling. (need feedback). Fixed CTs unable to defuse bomb. Fixed backward jump path generation in waypoint editor. Fixed autoradius in waypoint editor. Fixed autoradius menu non closeable. Fixed bots version display on entering game. Fixed memory leak in DLL-loader. (non metamod). Fixed bots able to see through smoke. Fixed team-detection on non-standard modes. Fixed quota & autovacate management. Fixed bunch of warnings from static analyzers. Greatly imporoved grenade throwing. Grealty reduced bot CPU usage. * Fixed stack-corruption in memory-file reader. Fixed A* pathfinder not working correctly. Fixed 'Tried to write to uninitialized sizebuf_t error' on bot add/remove. Minor tweaks to camping and bot enemy aiming * Make clang happy. * Fixed VIP-dection on some maps. Fixed occupied waypoint checker. Small refactoring of code with clang-format. * Fixed clang compilation * Fixed compilation. * Debugging seek cover task. Some more code cleanup. * Fixed typos. * Fixes to attack movement. Revert Z component updates. * Fixes for aiming at enemy. Fixes for seek cover & enemy hunt tasks. More refactoring. * Making clang happy once again? Tweaked grenade timers. * Revised language comparer hasher * Fixed build. * Fixed build. * Optimized headshot offsets. Optimized aim errors and enemy searches. Get rid of preprocessor macroses. Added back yb_think_fps. Use with caution. * Minor refactoring of code. * Check if tracking entity is still alive. Do not duck in crouch-goal waypoints. Remove ancient hack with failed goals. * Get rid of c++14 stuff. Tweaked isOccupiedPoint. * Changed pickup check radius. * Fix compilation. * Fixed bots ignore breakables. Fixed A* pathfinder. Fixed searching for optimal waypoints. Fixed bot waypoint reachability functions. * Get rid of new/delete calls in pathfinder. Disallow access to yapb waypoint menu on hlds. Minor refactoring. * Updated linux/osx makefile * Spaces -> Tabs in makefile. Made G++ happy. * Updated makefile. * Fixed heap buffer overflow in config loader code. * Lowered CPU usage a bit, by using "waypoint buckets" for searching closest node. Do not traceline for doors on map, that have no doors. Get rid stack-based containers. * Remove win-only debug crap. * Refactored string class. * Fix OSX compiling. * Minor refactoring of corelib to use cpp move-semantic. * Use reference for active grenades searcher. * Use system's atan2f () as it's eror rate is a bit lower. Fixed bots continuously stays in throw smoke task. Fixed bots reaching camp-goal jumping or stays they for some time. Increased radius for searching targets for grenades. Tweaked bot difficulty levels. Improved sniper weapon handling. Trying to stand still while shooting. Increase retreat level only if sniper weapon is low on ammo. Fixed predict path enemy tracking timer is always true. Allow bots to process their tasks while on freezetime, so on small maps they already aiming enemies when freezetime ends. Fied bots endlessy trying to pickup weapons. Reduce surpise timers when holding sniper weapons. New aim-at-head position calculation. Shoot delay timers are now based on bot's difficulty. Prefer smoke grenades more than flashbangs. Fixed kill-all bot command not killing one random bot for first time use. Do not play with jump velocity, now using the same as in waypoints. Tweaked shift move, so zero move speed not overriden with shift speed. Radius waypoint searcher use waypoint bucket as well. Increase reachability radius for dest waypoint, if it's currenlty owned by other bot. Partially fixed bots choice to use unreachable waypoints. * Makes OSX clang happy? * Support for compiling on llvm-win32, makefile to be done. Increased default reachability time. * Fixed build. * Move level-initialization stuff from Spawn to ServerActivate, so bot will not check init-stuff every entity spawn. This should save few CPU cycles. * Fixed active grenades list not working after changelevel. Reworked items pickup code, so every bot is not firing sphere search every time, but instead we maintain our own list of intresting entities, so every bot is accessing this list. This should lower CPU usage more a little. * Precache should be done in spawn... * Do not use engfuncs in intresting entities. * Fixed GCC-8.2 warnings. Minor refactoring. * Added some safety checks to intresting entities. Get rid of stdc++ dependency for GCC & ICC under linux. * Remove -g from release make. Cosmetic changes. * Re-enabled debug overlay. * Remove test header... * Some static-analyzer warnings fixed. Support for X64 build for FWGS Xash3D Engine. * Reduced time between selecting grenade and throwing it away. Do not try to kill bots that already dead with kill command. Several fixes from static-analyzers. * Update CI. * Fixed bot's not added after the changelevel on Xash3D engine. * Revert commit that enables movement during freezetime. Everything goes bad, when there is no freezetime.... * Bots will try to not strafe while in combat if seeing enemy only partially. Do not use "shift" when considering stuck. * Weapon price for Elite is 800$ since CS 1.6... * Fixed bots at difficulty 0 can't shoot enemies. * Cosmetic change. * Fixed assert in ClientDisconnect when quitting game while meta unloaded yapb module. Consider freed entities as invalid. * Bigger distance for throwing he grenades. * Faster version of atan2f(). * Removed accidentally left SSE header. * Cosmetic changes to enums. * Tweaked difficulty levels. Bots on Android will have a difficulty level 2 by default. Fixed LTO builds under linux. * Do not consider Android CS as legacy. * Get rid of system's math functions. Just for fun) * Use SSE2 for sincos function. * Fixed failed during load wayponts still allows to add bots, thus causing bot to crash. Added ability to delete waypoint by number using "yb wp delete". Enabled Link Time Optimization for Linux and OSX. * Fixed CI Builds.
3244 lines
No EOL
103 KiB
C++
3244 lines
No EOL
103 KiB
C++
//
|
|
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
|
|
// Copyright (c) YaPB Development Team.
|
|
//
|
|
// This software is licensed under the BSD-style license.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://yapb.ru/license
|
|
//
|
|
|
|
#include <yapb.h>
|
|
|
|
ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0");
|
|
ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "0");
|
|
|
|
int Bot::searchGoal (void) {
|
|
|
|
// chooses a destination (goal) waypoint for a bot
|
|
if (!g_bombPlanted && m_team == TEAM_TERRORIST && (g_mapFlags & MAP_DE)) {
|
|
edict_t *pent = nullptr;
|
|
|
|
while (!engine.isNullEntity (pent = g_engfuncs.pfnFindEntityByString (pent, "classname", "weaponbox"))) {
|
|
if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0) {
|
|
int index = waypoints.getNearest (engine.getAbsPos (pent));
|
|
|
|
if (waypoints.exists (index)) {
|
|
return m_loosedBombWptIndex = index;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// forcing terrorist bot to not move to another bomb spot
|
|
if (m_inBombZone && !m_hasProgressBar && m_hasC4) {
|
|
return waypoints.getNearest (pev->origin, 768.0f, FLAG_GOAL);
|
|
}
|
|
}
|
|
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 *offensiveWpts = nullptr;
|
|
IntArray *defensiveWpts = nullptr;
|
|
|
|
switch (m_team) {
|
|
case TEAM_TERRORIST:
|
|
offensiveWpts = &waypoints.m_ctPoints;
|
|
defensiveWpts = &waypoints.m_terrorPoints;
|
|
break;
|
|
|
|
case TEAM_COUNTER:
|
|
default:
|
|
offensiveWpts = &waypoints.m_terrorPoints;
|
|
defensiveWpts = &waypoints.m_ctPoints;
|
|
break;
|
|
}
|
|
|
|
// terrorist carrying the C4?
|
|
if (m_hasC4 || m_isVIP) {
|
|
tactic = 3;
|
|
return getGoalProcess (tactic, defensiveWpts, offensiveWpts);
|
|
}
|
|
else if (m_team == TEAM_COUNTER && hasHostage ()) {
|
|
tactic = 2;
|
|
offensiveWpts = &waypoints.m_rescuePoints;
|
|
|
|
return getGoalProcess (tactic, defensiveWpts, offensiveWpts);
|
|
}
|
|
|
|
offensive = m_agressionLevel * 100.0f;
|
|
defensive = m_fearLevel * 100.0f;
|
|
|
|
if (g_mapFlags & (MAP_AS | MAP_CS)) {
|
|
if (m_team == TEAM_TERRORIST) {
|
|
defensive += 25.0f;
|
|
offensive -= 25.0f;
|
|
}
|
|
else if (m_team == TEAM_COUNTER) {
|
|
// on hostage maps force more bots to save hostages
|
|
if (g_mapFlags & MAP_CS) {
|
|
defensive -= 25.0f - m_difficulty * 0.5f;
|
|
offensive += 25.0f + m_difficulty * 5.0f;
|
|
}
|
|
else {
|
|
defensive -= 25.0f;
|
|
offensive += 25.0f;
|
|
}
|
|
}
|
|
}
|
|
else if ((g_mapFlags & MAP_DE) && m_team == TEAM_COUNTER) {
|
|
if (g_bombPlanted && taskId () != TASK_ESCAPEFROMBOMB && !waypoints.getBombPos ().empty ()) {
|
|
if (g_bombSayString) {
|
|
pushChatMessage (CHAT_BOMBPLANT);
|
|
g_bombSayString = false;
|
|
}
|
|
return m_chosenGoalIndex = getBombPoint ();
|
|
}
|
|
defensive += 25.0f + m_difficulty * 4.0f;
|
|
offensive -= 25.0f - m_difficulty * 0.5f;
|
|
|
|
if (m_personality != PERSONALITY_RUSHER) {
|
|
defensive += 10.0f;
|
|
}
|
|
}
|
|
else if ((g_mapFlags & MAP_DE) && m_team == TEAM_TERRORIST && g_timeRoundStart + 10.0f < engine.timebase ()) {
|
|
// send some terrorists to guard planted bomb
|
|
if (!m_defendedBomb && g_bombPlanted && taskId () != TASK_ESCAPEFROMBOMB && getBombTimeleft () >= 15.0) {
|
|
return m_chosenGoalIndex = getDefendPoint (waypoints.getBombPos ());
|
|
}
|
|
}
|
|
|
|
goalDesire = rng.getFloat (0.0f, 100.0f) + offensive;
|
|
forwardDesire = rng.getFloat (0.0f, 100.0f) + offensive;
|
|
campDesire = rng.getFloat (0.0f, 100.0f) + defensive;
|
|
backoffDesire = rng.getFloat (0.0f, 100.0f) + defensive;
|
|
|
|
if (!usesCampGun ()) {
|
|
campDesire *= 0.5f;
|
|
}
|
|
|
|
tacticChoice = backoffDesire;
|
|
tactic = 0;
|
|
|
|
if (campDesire > tacticChoice) {
|
|
tacticChoice = campDesire;
|
|
tactic = 1;
|
|
}
|
|
|
|
if (forwardDesire > tacticChoice) {
|
|
tacticChoice = forwardDesire;
|
|
tactic = 2;
|
|
}
|
|
|
|
if (goalDesire > tacticChoice) {
|
|
tactic = 3;
|
|
}
|
|
return getGoalProcess (tactic, defensiveWpts, offensiveWpts);
|
|
}
|
|
|
|
int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) {
|
|
int goalChoices[4] = { INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX };
|
|
|
|
if (tactic == 0 && !(*defensive).empty ()) { // careful goal
|
|
filterGoals (*defensive, goalChoices);
|
|
}
|
|
else if (tactic == 1 && !waypoints.m_campPoints.empty ()) // camp waypoint goal
|
|
{
|
|
// pickup sniper points if possible for sniping bots
|
|
if (!waypoints.m_sniperPoints.empty () && usesSniper ()) {
|
|
filterGoals (waypoints.m_sniperPoints, goalChoices);
|
|
}
|
|
else {
|
|
filterGoals (waypoints.m_campPoints, goalChoices);
|
|
}
|
|
}
|
|
else if (tactic == 2 && !(*offsensive).empty ()) { // offensive goal
|
|
filterGoals (*offsensive, goalChoices);
|
|
}
|
|
else if (tactic == 3 && !waypoints.m_goalPoints.empty ()) // map goal waypoint
|
|
{
|
|
// force bomber to select closest goal, if round-start goal was reset by something
|
|
if (m_hasC4 && g_timeRoundStart + 20.0f < engine.timebase ()) {
|
|
float minDist = 99999.0f;
|
|
int count = 0;
|
|
|
|
for (auto &point : waypoints.m_goalPoints) {
|
|
float distance = (waypoints[point].origin - pev->origin).lengthSq ();
|
|
|
|
if (distance > 1024.0f) {
|
|
continue;
|
|
}
|
|
if (distance < minDist) {
|
|
goalChoices[count] = point;
|
|
|
|
if (++count > 3) {
|
|
count = 0;
|
|
}
|
|
minDist = distance;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
if (goalChoices[i] == INVALID_WAYPOINT_INDEX) {
|
|
goalChoices[i] = waypoints.m_goalPoints.random ();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
filterGoals (waypoints.m_goalPoints, goalChoices);
|
|
}
|
|
}
|
|
|
|
if (!waypoints.exists (m_currentWaypointIndex)) {
|
|
m_currentWaypointIndex = changePointIndex (getNearestPoint ());
|
|
}
|
|
|
|
if (goalChoices[0] == INVALID_WAYPOINT_INDEX) {
|
|
return m_chosenGoalIndex = rng.getInt (0, waypoints.length () - 1);
|
|
}
|
|
bool isSorting = false;
|
|
|
|
do {
|
|
isSorting = false;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
int testIndex = goalChoices[i + 1];
|
|
|
|
if (testIndex < 0) {
|
|
break;
|
|
}
|
|
|
|
int goal1 = m_team == TEAM_TERRORIST ? (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i])->team0Value : (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i])->team1Value;
|
|
int goal2 = m_team == TEAM_TERRORIST ? (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i + 1])->team0Value : (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i + 1])->team1Value;
|
|
|
|
if (goal1 < goal2) {
|
|
goalChoices[i + 1] = goalChoices[i];
|
|
goalChoices[i] = testIndex;
|
|
|
|
isSorting = true;
|
|
}
|
|
}
|
|
} while (isSorting);
|
|
|
|
return m_chosenGoalIndex = goalChoices[0]; // return and store goal
|
|
}
|
|
|
|
void Bot::filterGoals (const IntArray &goals, int *result) {
|
|
// this function filters the goals, so new goal is not bot's old goal, and array of goals doesn't contains duplicate goals
|
|
|
|
int searchCount = 0;
|
|
|
|
for (int index = 0; index < 4; index++) {
|
|
int rand = goals.random ();
|
|
|
|
if (searchCount <= 8 && (m_prevGoalIndex == rand || ((result[0] == rand || result[1] == rand || result[2] == rand || result[3] == rand) && goals.length () > 4)) && !isOccupiedPoint (rand)) {
|
|
if (index > 0) {
|
|
index--;
|
|
}
|
|
searchCount++;
|
|
continue;
|
|
}
|
|
result[index] = rand;
|
|
}
|
|
}
|
|
|
|
bool Bot::hasActiveGoal (void) {
|
|
int goal = task ()->data;
|
|
|
|
if (goal == INVALID_WAYPOINT_INDEX) { // not decided about a goal
|
|
return false;
|
|
}
|
|
else if (goal == m_currentWaypointIndex) { // no nodes needed
|
|
return true;
|
|
}
|
|
else if (m_path.empty ()) { // no path calculated
|
|
return false;
|
|
}
|
|
return goal == m_path.back (); // got path - check if still valid
|
|
}
|
|
|
|
void Bot::resetCollision (void) {
|
|
m_collideTime = 0.0f;
|
|
m_probeTime = 0.0f;
|
|
|
|
m_collisionProbeBits = 0;
|
|
m_collisionState = COLLISION_NOTDECICED;
|
|
m_collStateIndex = 0;
|
|
|
|
for (int i = 0; i < MAX_COLLIDE_MOVES; i++) {
|
|
m_collideMoves[i] = 0;
|
|
}
|
|
}
|
|
|
|
void Bot::ignoreCollision (void) {
|
|
resetCollision ();
|
|
|
|
m_prevTime = engine.timebase () + 1.2f;
|
|
m_lastCollTime = engine.timebase () + 1.5f;
|
|
m_isStuck = false;
|
|
m_checkTerrain = false;
|
|
m_prevSpeed = m_moveSpeed;
|
|
m_prevOrigin = pev->origin;
|
|
}
|
|
|
|
void Bot::avoidIncomingPlayers (edict_t *touch) {
|
|
auto task = taskId ();
|
|
|
|
if (task == TASK_PLANTBOMB || task == TASK_DEFUSEBOMB || task == TASK_CAMP || m_moveSpeed <= 100.0f) {
|
|
return;
|
|
}
|
|
|
|
int ownId = engine.indexOfEntity (ent ());
|
|
int otherId = engine.indexOfEntity (touch);
|
|
|
|
if (ownId < otherId) {
|
|
return;
|
|
}
|
|
|
|
if (m_avoid) {
|
|
int currentId = engine.indexOfEntity (m_avoid);
|
|
|
|
if (currentId < otherId) {
|
|
return;
|
|
}
|
|
}
|
|
m_avoid = touch;
|
|
m_avoidTime = engine.timebase () + 0.33f + calcThinkInterval ();
|
|
}
|
|
|
|
bool Bot::doPlayerAvoidance (const Vector &normal) {
|
|
// avoid collision entity, got it form official csbot
|
|
|
|
if (m_avoidTime > engine.timebase () && isAlive (m_avoid)) {
|
|
|
|
Vector dir (cr::cosf (pev->v_angle.y), cr::sinf (pev->v_angle.y), 0.0f);
|
|
Vector lat (-dir.y, dir.x, 0.0f);
|
|
Vector to = Vector (m_avoid->v.origin.x - pev->origin.x, m_avoid->v.origin.y - pev->origin.y, 0.0f).normalize ();
|
|
|
|
float toProj = to.x * dir.x + to.y * dir.y;
|
|
float latProj = to.x * lat.x + to.y * lat.y;
|
|
|
|
const float c = 0.5f;
|
|
|
|
if (toProj > c) {
|
|
m_moveSpeed = -pev->maxspeed;
|
|
return true;
|
|
}
|
|
else if (toProj < -c) {
|
|
m_moveSpeed = pev->maxspeed;
|
|
return true;
|
|
}
|
|
|
|
if (latProj >= c) {
|
|
pev->button |= IN_MOVELEFT;
|
|
setStrafeSpeed (normal, pev->maxspeed);
|
|
return true;
|
|
}
|
|
else if (latProj <= -c) {
|
|
pev->button |= IN_MOVERIGHT;
|
|
setStrafeSpeed (normal, -pev->maxspeed);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
m_avoid = nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
|
m_isStuck = false;
|
|
TraceResult tr;
|
|
|
|
// Standing still, no need to check?
|
|
// FIXME: doesn't care for ladder movement (handled separately) should be included in some way
|
|
if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < engine.timebase () && m_seeEnemyTime + 0.8f < engine.timebase () && taskId () != TASK_ATTACK) {
|
|
|
|
// didn't we move enough previously?
|
|
if (movedDistance < 2.0f && m_prevSpeed >= 20.0f) {
|
|
m_prevTime = engine.timebase (); // then consider being stuck
|
|
m_isStuck = true;
|
|
|
|
if (cr::fzero (m_firstCollideTime)) {
|
|
m_firstCollideTime = engine.timebase () + 0.2f;
|
|
}
|
|
}
|
|
// not stuck yet
|
|
else {
|
|
// test if there's something ahead blocking the way
|
|
if (cantMoveForward (dirNormal, &tr) && !isOnLadder ()) {
|
|
if (m_firstCollideTime == 0.0f) {
|
|
m_firstCollideTime = engine.timebase () + 0.2f;
|
|
}
|
|
else if (m_firstCollideTime <= engine.timebase ()) {
|
|
m_isStuck = true;
|
|
}
|
|
}
|
|
else {
|
|
m_firstCollideTime = 0.0f;
|
|
}
|
|
}
|
|
|
|
// not stuck?
|
|
if (!m_isStuck) {
|
|
if (m_probeTime + 0.5f < engine.timebase ()) {
|
|
resetCollision (); // reset collision memory if not being stuck for 0.5 secs
|
|
}
|
|
else {
|
|
// remember to keep pressing duck if it was necessary ago
|
|
if (m_collideMoves[m_collStateIndex] == COLLISION_DUCK && (isOnFloor () || isInWater ())) {
|
|
pev->button |= IN_DUCK;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// bot is stuck!
|
|
|
|
Vector src;
|
|
Vector dst;
|
|
|
|
// not yet decided what to do?
|
|
if (m_collisionState == COLLISION_NOTDECICED) {
|
|
int bits = 0;
|
|
|
|
if (isOnLadder ()) {
|
|
bits |= PROBE_STRAFE;
|
|
}
|
|
else if (isInWater ()) {
|
|
bits |= (PROBE_JUMP | PROBE_STRAFE);
|
|
}
|
|
else {
|
|
bits |= (PROBE_STRAFE | (m_jumpStateTimer < engine.timebase () ? PROBE_JUMP : 0));
|
|
}
|
|
|
|
// collision check allowed if not flying through the air
|
|
if (isOnFloor () || isOnLadder () || isInWater ()) {
|
|
int state[MAX_COLLIDE_MOVES * 2 + 1];
|
|
int i = 0;
|
|
|
|
// first 4 entries hold the possible collision states
|
|
state[i++] = COLLISION_STRAFELEFT;
|
|
state[i++] = COLLISION_STRAFERIGHT;
|
|
state[i++] = COLLISION_JUMP;
|
|
state[i++] = COLLISION_DUCK;
|
|
|
|
if (bits & PROBE_STRAFE) {
|
|
state[i] = 0;
|
|
state[i + 1] = 0;
|
|
|
|
// to start strafing, we have to first figure out if the target is on the left side or right side
|
|
makeVectors (m_moveAngles);
|
|
|
|
Vector dirToPoint = (pev->origin - m_destOrigin).normalize2D ();
|
|
Vector rightSide = g_pGlobals->v_right.normalize2D ();
|
|
|
|
bool dirRight = false;
|
|
bool dirLeft = false;
|
|
bool blockedLeft = false;
|
|
bool blockedRight = false;
|
|
|
|
if ((dirToPoint | rightSide) > 0.0f) {
|
|
dirRight = true;
|
|
}
|
|
else {
|
|
dirLeft = true;
|
|
}
|
|
const Vector &testDir = m_moveSpeed > 0.0f ? g_pGlobals->v_forward : -g_pGlobals->v_forward;
|
|
|
|
// now check which side is blocked
|
|
src = pev->origin + g_pGlobals->v_right * 32.0f;
|
|
dst = src + testDir * 32.0f;
|
|
|
|
engine.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr);
|
|
|
|
if (tr.flFraction != 1.0f)
|
|
blockedRight = true;
|
|
|
|
src = pev->origin - g_pGlobals->v_right * 32.0f;
|
|
dst = src + testDir * 32.0f;
|
|
|
|
engine.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr);
|
|
|
|
if (tr.flFraction != 1.0f) {
|
|
blockedLeft = true;
|
|
}
|
|
|
|
if (dirLeft) {
|
|
state[i] += 5;
|
|
}
|
|
else {
|
|
state[i] -= 5;
|
|
}
|
|
|
|
if (blockedLeft) {
|
|
state[i] -= 5;
|
|
}
|
|
i++;
|
|
|
|
if (dirRight) {
|
|
state[i] += 5;
|
|
}
|
|
else {
|
|
state[i] -= 5;
|
|
}
|
|
|
|
if (blockedRight) {
|
|
state[i] -= 5;
|
|
}
|
|
}
|
|
|
|
// now weight all possible states
|
|
if (bits & PROBE_JUMP) {
|
|
state[i] = 0;
|
|
|
|
if (canJumpUp (dirNormal)) {
|
|
state[i] += 10;
|
|
}
|
|
|
|
if (m_destOrigin.z >= pev->origin.z + 18.0f) {
|
|
state[i] += 5;
|
|
}
|
|
|
|
if (seesEntity (m_destOrigin)) {
|
|
makeVectors (m_moveAngles);
|
|
|
|
src = eyePos ();
|
|
src = src + g_pGlobals->v_right * 15.0f;
|
|
|
|
engine.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (tr.flFraction >= 1.0f) {
|
|
src = eyePos ();
|
|
src = src - g_pGlobals->v_right * 15.0f;
|
|
|
|
engine.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (tr.flFraction >= 1.0f) {
|
|
state[i] += 5;
|
|
}
|
|
}
|
|
}
|
|
if (pev->flags & FL_DUCKING) {
|
|
src = pev->origin;
|
|
}
|
|
else {
|
|
src = pev->origin + Vector (0.0f, 0.0f, -17.0f);
|
|
}
|
|
dst = src + dirNormal * 30.0f;
|
|
engine.testLine (src, dst, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (tr.flFraction != 1.0f) {
|
|
state[i] += 10;
|
|
}
|
|
}
|
|
else {
|
|
state[i] = 0;
|
|
}
|
|
i++;
|
|
|
|
#if 0
|
|
if (bits & PROBE_DUCK)
|
|
{
|
|
state[i] = 0;
|
|
|
|
if (canDuckUnder (dirNormal)) {
|
|
state[i] += 10;
|
|
}
|
|
|
|
if ((m_destOrigin.z + 36.0f <= pev->origin.z) && seesEntity (m_destOrigin)) {
|
|
state[i] += 5;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
state[i] = 0;
|
|
i++;
|
|
|
|
// weighted all possible moves, now sort them to start with most probable
|
|
bool isSorting = false;
|
|
|
|
do {
|
|
isSorting = false;
|
|
for (i = 0; i < 3; i++) {
|
|
if (state[i + MAX_COLLIDE_MOVES] < state[i + MAX_COLLIDE_MOVES + 1]) {
|
|
int temp = state[i];
|
|
|
|
state[i] = state[i + 1];
|
|
state[i + 1] = temp;
|
|
|
|
temp = state[i + MAX_COLLIDE_MOVES];
|
|
|
|
state[i + MAX_COLLIDE_MOVES] = state[i + MAX_COLLIDE_MOVES + 1];
|
|
state[i + MAX_COLLIDE_MOVES + 1] = temp;
|
|
|
|
isSorting = true;
|
|
}
|
|
}
|
|
} while (isSorting);
|
|
|
|
for (i = 0; i < MAX_COLLIDE_MOVES; i++) {
|
|
m_collideMoves[i] = state[i];
|
|
}
|
|
|
|
m_collideTime = engine.timebase ();
|
|
m_probeTime = engine.timebase () + 0.5f;
|
|
m_collisionProbeBits = bits;
|
|
m_collisionState = COLLISION_PROBING;
|
|
m_collStateIndex = 0;
|
|
}
|
|
}
|
|
|
|
if (m_collisionState == COLLISION_PROBING) {
|
|
if (m_probeTime < engine.timebase ()) {
|
|
m_collStateIndex++;
|
|
m_probeTime = engine.timebase () + 0.5f;
|
|
|
|
if (m_collStateIndex > MAX_COLLIDE_MOVES) {
|
|
m_navTimeset = engine.timebase () - 5.0f;
|
|
resetCollision ();
|
|
}
|
|
}
|
|
|
|
if (m_collStateIndex < MAX_COLLIDE_MOVES) {
|
|
switch (m_collideMoves[m_collStateIndex]) {
|
|
case COLLISION_JUMP:
|
|
if (isOnFloor () || isInWater ()) {
|
|
pev->button |= IN_JUMP;
|
|
m_jumpStateTimer = engine.timebase () + rng.getFloat (0.7f, 1.5f);
|
|
}
|
|
break;
|
|
|
|
case COLLISION_DUCK:
|
|
if (isOnFloor () || isInWater ()) {
|
|
pev->button |= IN_DUCK;
|
|
}
|
|
break;
|
|
|
|
case COLLISION_STRAFELEFT:
|
|
pev->button |= IN_MOVELEFT;
|
|
setStrafeSpeed (dirNormal, -pev->maxspeed);
|
|
break;
|
|
|
|
case COLLISION_STRAFERIGHT:
|
|
pev->button |= IN_MOVERIGHT;
|
|
setStrafeSpeed (dirNormal, pev->maxspeed);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
doPlayerAvoidance (dirNormal);
|
|
}
|
|
|
|
bool Bot::processNavigation (void) {
|
|
// this function is a main path navigation
|
|
|
|
TraceResult tr, tr2;
|
|
|
|
// check if we need to find a waypoint...
|
|
if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) {
|
|
getValidPoint ();
|
|
m_waypointOrigin = m_currentPath->origin;
|
|
|
|
// if wayzone radios non zero vary origin a bit depending on the body angles
|
|
if (m_currentPath->radius > 0) {
|
|
makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f));
|
|
m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * rng.getFloat (0, m_currentPath->radius);
|
|
}
|
|
m_navTimeset = engine.timebase ();
|
|
}
|
|
m_destOrigin = m_waypointOrigin + pev->view_ofs;
|
|
|
|
float waypointDistance = (pev->origin - m_waypointOrigin).length ();
|
|
|
|
// this waypoint has additional travel flags - care about them
|
|
if (m_currentTravelFlags & PATHFLAG_JUMP) {
|
|
|
|
// bot is not jumped yet?
|
|
if (!m_jumpFinished) {
|
|
|
|
// if bot's on the ground or on the ladder we're free to jump. actually setting the correct velocity is cheating.
|
|
// pressing the jump button gives the illusion of the bot actual jumping.
|
|
if (isOnFloor () || isOnLadder ()) {
|
|
pev->velocity = m_desiredVelocity;
|
|
pev->button |= IN_JUMP;
|
|
|
|
m_jumpFinished = true;
|
|
m_checkTerrain = false;
|
|
m_desiredVelocity.nullify ();
|
|
}
|
|
}
|
|
else if (!yb_jasonmode.boolean () && m_currentWeapon == WEAPON_KNIFE && isOnFloor ()) {
|
|
selectBestWeapon ();
|
|
}
|
|
}
|
|
|
|
if (m_currentPath->flags & FLAG_LADDER) {
|
|
if (m_waypointOrigin.z >= (pev->origin.z + 16.0f)) {
|
|
m_waypointOrigin = m_currentPath->origin + Vector (0.0f, 0.0f, 16.0f);
|
|
}
|
|
else if (m_waypointOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) {
|
|
m_moveSpeed = waypointDistance;
|
|
|
|
if (m_moveSpeed < 150.0f) {
|
|
m_moveSpeed = 150.0f;
|
|
}
|
|
else if (m_moveSpeed > pev->maxspeed) {
|
|
m_moveSpeed = pev->maxspeed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// special lift handling (code merged from podbotmm)
|
|
if (m_currentPath->flags & FLAG_LIFT) {
|
|
bool liftClosedDoorExists = false;
|
|
|
|
// update waypoint time set
|
|
m_navTimeset = engine.timebase ();
|
|
|
|
// trace line to door
|
|
engine.testLine (pev->origin, m_currentPath->origin, TRACE_IGNORE_EVERYTHING, ent (), &tr2);
|
|
|
|
if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && pev->groundentity != tr2.pHit) {
|
|
if (m_liftState == LIFT_NO_NEARBY) {
|
|
m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE;
|
|
m_liftUsageTime = engine.timebase () + 7.0f;
|
|
}
|
|
liftClosedDoorExists = true;
|
|
}
|
|
|
|
// trace line down
|
|
engine.testLine (m_currentPath->origin, m_currentPath->origin + Vector (0.0f, 0.0f, -50.0f), TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
// if trace result shows us that it is a lift
|
|
if (!engine.isNullEntity (tr.pHit) && !m_path.empty () && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0) && !liftClosedDoorExists) {
|
|
if ((m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && tr.pHit->v.velocity.z == 0.0f) {
|
|
if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) {
|
|
m_liftEntity = tr.pHit;
|
|
m_liftState = LIFT_ENTERING_IN;
|
|
m_liftTravelPos = m_currentPath->origin;
|
|
m_liftUsageTime = engine.timebase () + 5.0f;
|
|
}
|
|
}
|
|
else if (m_liftState == LIFT_TRAVELING_BY) {
|
|
m_liftState = LIFT_LEAVING;
|
|
m_liftUsageTime = engine.timebase () + 7.0f;
|
|
}
|
|
}
|
|
else if (!m_path.empty ()) // no lift found at waypoint
|
|
{
|
|
if ((m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR) && m_path.hasNext ()) {
|
|
int nextNode = m_path.next ();
|
|
|
|
if (waypoints.exists (nextNode) && (waypoints[nextNode].flags & FLAG_LIFT)) {
|
|
engine.testLine (m_currentPath->origin, waypoints[nextNode].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (!engine.isNullEntity (tr.pHit) && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0)) {
|
|
m_liftEntity = tr.pHit;
|
|
}
|
|
}
|
|
m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE;
|
|
m_liftUsageTime = engine.timebase () + 15.0f;
|
|
}
|
|
}
|
|
|
|
// bot is going to enter the lift
|
|
if (m_liftState == LIFT_ENTERING_IN) {
|
|
m_destOrigin = m_liftTravelPos;
|
|
|
|
// check if we enough to destination
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
|
|
// need to wait our following teammate ?
|
|
bool needWaitForTeammate = false;
|
|
|
|
// if some bot is following a bot going into lift - he should take the same lift to go
|
|
for (int i = 0; i < engine.maxClients (); i++) {
|
|
Bot *bot = bots.getBot (i);
|
|
|
|
if (bot == nullptr || bot == this) {
|
|
continue;
|
|
}
|
|
|
|
if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->taskId () != TASK_FOLLOWUSER) {
|
|
continue;
|
|
}
|
|
|
|
if (bot->pev->groundentity == m_liftEntity && bot->isOnFloor ()) {
|
|
break;
|
|
}
|
|
|
|
bot->m_liftEntity = m_liftEntity;
|
|
bot->m_liftState = LIFT_ENTERING_IN;
|
|
bot->m_liftTravelPos = m_liftTravelPos;
|
|
|
|
needWaitForTeammate = true;
|
|
}
|
|
|
|
if (needWaitForTeammate) {
|
|
m_liftState = LIFT_WAIT_FOR_TEAMMATES;
|
|
m_liftUsageTime = engine.timebase () + 8.0f;
|
|
}
|
|
else {
|
|
m_liftState = LIFT_LOOKING_BUTTON_INSIDE;
|
|
m_liftUsageTime = engine.timebase () + 10.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// bot is waiting for his teammates
|
|
if (m_liftState == LIFT_WAIT_FOR_TEAMMATES) {
|
|
// need to wait our following teammate ?
|
|
bool needWaitForTeammate = false;
|
|
|
|
for (int i = 0; i < engine.maxClients (); i++) {
|
|
Bot *bot = bots.getBot (i);
|
|
|
|
if (bot == nullptr) {
|
|
continue; // skip invalid bots
|
|
}
|
|
|
|
if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->taskId () != TASK_FOLLOWUSER || bot->m_liftEntity != m_liftEntity) {
|
|
continue;
|
|
}
|
|
|
|
if (bot->pev->groundentity == m_liftEntity || !bot->isOnFloor ()) {
|
|
needWaitForTeammate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// need to wait for teammate
|
|
if (needWaitForTeammate) {
|
|
m_destOrigin = m_liftTravelPos;
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
}
|
|
}
|
|
|
|
// else we need to look for button
|
|
if (!needWaitForTeammate || m_liftUsageTime < engine.timebase ()) {
|
|
m_liftState = LIFT_LOOKING_BUTTON_INSIDE;
|
|
m_liftUsageTime = engine.timebase () + 10.0f;
|
|
}
|
|
}
|
|
|
|
// bot is trying to find button inside a lift
|
|
if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE) {
|
|
edict_t *button = getNearestButton (STRING (m_liftEntity->v.targetname));
|
|
|
|
// got a valid button entity ?
|
|
if (!engine.isNullEntity (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0f < engine.timebase () && m_liftEntity->v.velocity.z == 0.0f && isOnFloor ()) {
|
|
m_pickupItem = button;
|
|
m_pickupType = PICKUP_BUTTON;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
}
|
|
}
|
|
|
|
// is lift activated and bot is standing on it and lift is moving ?
|
|
if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE || m_liftState == LIFT_ENTERING_IN || m_liftState == LIFT_WAIT_FOR_TEAMMATES || m_liftState == LIFT_WAITING_FOR) {
|
|
if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT) || !engine.isNullEntity (m_targetEntity))) {
|
|
m_liftState = LIFT_TRAVELING_BY;
|
|
m_liftUsageTime = engine.timebase () + 14.0f;
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
}
|
|
}
|
|
}
|
|
|
|
// bots is currently moving on lift
|
|
if (m_liftState == LIFT_TRAVELING_BY) {
|
|
m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z);
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
}
|
|
}
|
|
|
|
// need to find a button outside the lift
|
|
if (m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) {
|
|
|
|
// button has been pressed, lift should come
|
|
if (m_buttonPushTime + 8.0f >= engine.timebase ()) {
|
|
if (waypoints.exists (m_prevWptIndex[0])) {
|
|
m_destOrigin = waypoints[m_prevWptIndex[0]].origin;
|
|
}
|
|
else {
|
|
m_destOrigin = pev->origin;
|
|
}
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
}
|
|
}
|
|
else if (!engine.isNullEntity (m_liftEntity)) {
|
|
edict_t *button = getNearestButton (STRING (m_liftEntity->v.targetname));
|
|
|
|
// if we got a valid button entity
|
|
if (!engine.isNullEntity (button)) {
|
|
// lift is already used ?
|
|
bool liftUsed = false;
|
|
|
|
// iterate though clients, and find if lift already used
|
|
for (int i = 0; i < engine.maxClients (); i++) {
|
|
const Client &client = g_clients[i];
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent () || engine.isNullEntity (client.ent->v.groundentity)) {
|
|
continue;
|
|
}
|
|
|
|
if (client.ent->v.groundentity == m_liftEntity) {
|
|
liftUsed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// lift is currently used
|
|
if (liftUsed) {
|
|
if (waypoints.exists (m_prevWptIndex[0])) {
|
|
m_destOrigin = waypoints[m_prevWptIndex[0]].origin;
|
|
}
|
|
else {
|
|
m_destOrigin = button->v.origin;
|
|
}
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
}
|
|
}
|
|
else {
|
|
m_pickupItem = button;
|
|
m_pickupType = PICKUP_BUTTON;
|
|
m_liftState = LIFT_WAITING_FOR;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_liftUsageTime = engine.timebase () + 20.0f;
|
|
}
|
|
}
|
|
else {
|
|
m_liftState = LIFT_WAITING_FOR;
|
|
m_liftUsageTime = engine.timebase () + 15.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// bot is waiting for lift
|
|
if (m_liftState == LIFT_WAITING_FOR) {
|
|
if (waypoints.exists (m_prevWptIndex[0])) {
|
|
if (!(waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT)) {
|
|
m_destOrigin = waypoints[m_prevWptIndex[0]].origin;
|
|
}
|
|
else if (waypoints.exists (m_prevWptIndex[1])) {
|
|
m_destOrigin = waypoints[m_prevWptIndex[1]].origin;
|
|
}
|
|
}
|
|
|
|
if ((pev->origin - m_destOrigin).lengthSq () < 100.0f) {
|
|
m_moveSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
m_aimFlags |= AIM_NAVPOINT;
|
|
|
|
resetCollision ();
|
|
}
|
|
}
|
|
|
|
// if bot is waiting for lift, or going to it
|
|
if (m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_ENTERING_IN) {
|
|
// bot fall down somewhere inside the lift's groove :)
|
|
if (pev->groundentity != m_liftEntity && waypoints.exists (m_prevWptIndex[0])) {
|
|
if ((waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT) && (m_currentPath->origin.z - pev->origin.z) > 50.0f && (waypoints[m_prevWptIndex[0]].origin.z - pev->origin.z) > 50.0f) {
|
|
m_liftState = LIFT_NO_NEARBY;
|
|
m_liftEntity = nullptr;
|
|
m_liftUsageTime = 0.0f;
|
|
|
|
clearSearchNodes ();
|
|
searchOptimalPoint ();
|
|
|
|
if (waypoints.exists (m_prevWptIndex[2])) {
|
|
searchShortestPath (m_currentWaypointIndex, m_prevWptIndex[2]);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!engine.isNullEntity (m_liftEntity) && !(m_currentPath->flags & FLAG_LIFT)) {
|
|
if (m_liftState == LIFT_TRAVELING_BY) {
|
|
m_liftState = LIFT_LEAVING;
|
|
m_liftUsageTime = engine.timebase () + 10.0f;
|
|
}
|
|
if (m_liftState == LIFT_LEAVING && m_liftUsageTime < engine.timebase () && pev->groundentity != m_liftEntity) {
|
|
m_liftState = LIFT_NO_NEARBY;
|
|
m_liftUsageTime = 0.0f;
|
|
|
|
m_liftEntity = nullptr;
|
|
}
|
|
}
|
|
|
|
if (m_liftUsageTime < engine.timebase () && m_liftUsageTime != 0.0f) {
|
|
m_liftEntity = nullptr;
|
|
m_liftState = LIFT_NO_NEARBY;
|
|
m_liftUsageTime = 0.0f;
|
|
|
|
clearSearchNodes ();
|
|
|
|
if (waypoints.exists (m_prevWptIndex[0])) {
|
|
if (!(waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT)) {
|
|
changePointIndex (m_prevWptIndex[0]);
|
|
}
|
|
else {
|
|
searchOptimalPoint ();
|
|
}
|
|
}
|
|
else {
|
|
searchOptimalPoint ();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// check if we are going through a door...
|
|
if (g_mapFlags & MAP_HAS_DOORS) {
|
|
engine.testLine (pev->origin, m_waypointOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
if (!engine.isNullEntity (tr.pHit) && engine.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) {
|
|
// if the door is near enough...
|
|
if ((engine.getAbsPos (tr.pHit) - pev->origin).lengthSq () < 2500.0f) {
|
|
ignoreCollision (); // don't consider being stuck
|
|
|
|
if (rng.getInt (1, 100) < 50) {
|
|
// do not use door directrly under xash, or we will get failed assert in gamedll code
|
|
if (g_gameFlags & GAME_XASH_ENGINE) {
|
|
pev->button |= IN_USE;
|
|
}
|
|
else {
|
|
MDLL_Use (tr.pHit, ent ()); // also 'use' the door randomly
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure we are always facing the door when going through it
|
|
m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_PATH);
|
|
m_canChooseAimDirection = false;
|
|
|
|
edict_t *button = getNearestButton (STRING (tr.pHit->v.targetname));
|
|
|
|
// check if we got valid button
|
|
if (!engine.isNullEntity (button)) {
|
|
m_pickupItem = button;
|
|
m_pickupType = PICKUP_BUTTON;
|
|
|
|
m_navTimeset = engine.timebase ();
|
|
}
|
|
|
|
// if bot hits the door, then it opens, so wait a bit to let it open safely
|
|
if (pev->velocity.length2D () < 2 && m_timeDoorOpen < engine.timebase ()) {
|
|
startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, engine.timebase () + 0.5f, false);
|
|
m_timeDoorOpen = engine.timebase () + 1.0f; // retry in 1 sec until door is open
|
|
|
|
edict_t *pent = nullptr;
|
|
|
|
if (m_tryOpenDoor++ > 2 && findNearestPlayer (reinterpret_cast <void **> (&pent), ent (), 256.0f, false, false, true, true, false)) {
|
|
m_seeEnemyTime = engine.timebase () - 0.5f;
|
|
|
|
m_states |= STATE_SEEING_ENEMY;
|
|
m_aimFlags |= AIM_ENEMY;
|
|
|
|
m_lastEnemy = pent;
|
|
m_enemy = pent;
|
|
m_lastEnemyOrigin = pent->v.origin;
|
|
|
|
m_tryOpenDoor = 0;
|
|
}
|
|
else
|
|
m_tryOpenDoor = 0;
|
|
}
|
|
}
|
|
}
|
|
float desiredDistance = 0.0f;
|
|
|
|
// initialize the radius for a special waypoint type, where the wpt is considered to be reached
|
|
if (m_currentPath->flags & FLAG_LIFT) {
|
|
desiredDistance = 50.0f;
|
|
}
|
|
else if ((pev->flags & FL_DUCKING) || (m_currentPath->flags & FLAG_GOAL)) {
|
|
desiredDistance = 25.0f;
|
|
}
|
|
else if (m_currentPath->flags & FLAG_LADDER) {
|
|
desiredDistance = 15.0f;
|
|
}
|
|
else if (m_currentTravelFlags & PATHFLAG_JUMP) {
|
|
desiredDistance = 0.0f;
|
|
}
|
|
else if (isOccupiedPoint (m_currentWaypointIndex)) {
|
|
desiredDistance = 120.0f;
|
|
}
|
|
else {
|
|
desiredDistance = m_currentPath->radius;
|
|
}
|
|
|
|
// check if waypoint has a special travelflag, so they need to be reached more precisely
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (m_currentPath->connectionFlags[i] != 0) {
|
|
desiredDistance = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// needs precise placement - check if we get past the point
|
|
if (desiredDistance < 22.0f && waypointDistance < 30.0f && (pev->origin + (pev->velocity * calcThinkInterval ()) - m_waypointOrigin).lengthSq () > cr::square (waypointDistance)) {
|
|
desiredDistance = waypointDistance + 1.0f;
|
|
}
|
|
|
|
if (waypointDistance < desiredDistance) {
|
|
|
|
// did we reach a destination waypoint?
|
|
if (task ()->data == m_currentWaypointIndex) {
|
|
// add goal values
|
|
if (m_chosenGoalIndex != INVALID_WAYPOINT_INDEX) {
|
|
int16 waypointValue;
|
|
|
|
int startIndex = m_chosenGoalIndex;
|
|
int goalIndex = m_currentWaypointIndex;
|
|
|
|
if (m_team == TEAM_TERRORIST) {
|
|
waypointValue = (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team0Value;
|
|
waypointValue += static_cast <int16> (pev->health * 0.5f);
|
|
waypointValue += static_cast <int16> (m_goalValue * 0.5f);
|
|
|
|
if (waypointValue < -MAX_GOAL_VALUE) {
|
|
waypointValue = -MAX_GOAL_VALUE;
|
|
}
|
|
else if (waypointValue > MAX_GOAL_VALUE) {
|
|
waypointValue = MAX_GOAL_VALUE;
|
|
}
|
|
(g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team0Value = waypointValue;
|
|
}
|
|
else {
|
|
waypointValue = (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team1Value;
|
|
waypointValue += static_cast <int16> (pev->health * 0.5f);
|
|
waypointValue += static_cast <int16> (m_goalValue * 0.5f);
|
|
|
|
if (waypointValue < -MAX_GOAL_VALUE) {
|
|
waypointValue = -MAX_GOAL_VALUE;
|
|
}
|
|
else if (waypointValue > MAX_GOAL_VALUE) {
|
|
waypointValue = MAX_GOAL_VALUE;
|
|
}
|
|
(g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team1Value = waypointValue;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else if (m_path.empty ()) {
|
|
return false;
|
|
}
|
|
int taskTarget = task ()->data;
|
|
|
|
if ((g_mapFlags & MAP_DE) && g_bombPlanted && m_team == TEAM_COUNTER && taskId () != TASK_ESCAPEFROMBOMB && taskTarget != INVALID_WAYPOINT_INDEX) {
|
|
const Vector &bombOrigin = isBombAudible ();
|
|
|
|
// bot within 'hearable' bomb tick noises?
|
|
if (!bombOrigin.empty ()) {
|
|
float distance = (bombOrigin - waypoints[taskTarget].origin).length ();
|
|
|
|
if (distance > 512.0f) {
|
|
if (rng.getInt (0, 100) < 50 && !waypoints.isVisited (taskTarget)) {
|
|
pushRadioMessage (RADIO_SECTOR_CLEAR);
|
|
}
|
|
waypoints.setVisited (taskTarget); // doesn't hear so not a good goal
|
|
}
|
|
}
|
|
else {
|
|
if (rng.getInt (0, 100) < 50 && !waypoints.isVisited (taskTarget)) {
|
|
pushRadioMessage (RADIO_SECTOR_CLEAR);
|
|
}
|
|
waypoints.setVisited (taskTarget); // doesn't hear so not a good goal
|
|
}
|
|
}
|
|
advanceMovement (); // do the actual movement checking
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Bot::searchShortestPath (int srcIndex, int destIndex) {
|
|
// this function finds the shortest path from source index to destination index
|
|
|
|
if (!waypoints.exists (srcIndex)){
|
|
logEntry (true, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex);
|
|
return;
|
|
}
|
|
else if (!waypoints.exists (destIndex)) {
|
|
logEntry (true, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex);
|
|
return;
|
|
}
|
|
clearSearchNodes ();
|
|
|
|
m_chosenGoalIndex = srcIndex;
|
|
m_goalValue = 0.0f;
|
|
|
|
m_path.push (srcIndex);
|
|
|
|
while (srcIndex != destIndex) {
|
|
srcIndex = *(waypoints.m_pathMatrix + (srcIndex * waypoints.length ()) + destIndex);
|
|
|
|
if (srcIndex < 0) {
|
|
m_prevGoalIndex = INVALID_WAYPOINT_INDEX;
|
|
task ()->data = INVALID_WAYPOINT_INDEX;
|
|
|
|
return;
|
|
}
|
|
m_path.push (srcIndex);
|
|
}
|
|
}
|
|
|
|
float gfunctionKillsDistT (int currentIndex, int parentIndex) {
|
|
// least kills and number of nodes to goal for a team
|
|
|
|
if (parentIndex == INVALID_WAYPOINT_INDEX) {
|
|
return 0.0f;
|
|
}
|
|
float cost = static_cast <float> ((g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team0Damage + g_highestDamageT);
|
|
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int neighbour = current.index[i];
|
|
|
|
if (neighbour != INVALID_WAYPOINT_INDEX) {
|
|
cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team0Damage;
|
|
}
|
|
}
|
|
|
|
if (current.flags & FLAG_CROUCH) {
|
|
cost *= 1.5f;
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
float gfunctionKillsDistCT (int currentIndex, int parentIndex) {
|
|
// least kills and number of nodes to goal for a team
|
|
|
|
if (parentIndex == INVALID_WAYPOINT_INDEX) {
|
|
return 0.0f;
|
|
}
|
|
float cost = static_cast <float> ((g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team1Damage + g_highestDamageCT);
|
|
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int neighbour = current.index[i];
|
|
|
|
if (neighbour != INVALID_WAYPOINT_INDEX) {
|
|
cost += static_cast <int> ((g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team1Damage);
|
|
}
|
|
}
|
|
|
|
if (current.flags & FLAG_CROUCH) {
|
|
cost *= 1.5f;
|
|
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
float gfunctionKillsDistCTWithHostage (int currentIndex, int parentIndex) {
|
|
// least kills and number of nodes to goal for a team
|
|
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
if (current.flags & FLAG_NOHOSTAGE) {
|
|
return 65355.0f;
|
|
}
|
|
else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) {
|
|
return gfunctionKillsDistCT (currentIndex, parentIndex) * 500.0f;
|
|
}
|
|
return gfunctionKillsDistCT (currentIndex, parentIndex);
|
|
}
|
|
|
|
float gfunctionKillsT (int currentIndex, int) {
|
|
// least kills to goal for a team
|
|
|
|
float cost = (g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team0Damage;
|
|
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int neighbour = current.index[i];
|
|
|
|
if (neighbour != INVALID_WAYPOINT_INDEX) {
|
|
cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team0Damage;
|
|
}
|
|
}
|
|
|
|
if (current.flags & FLAG_CROUCH) {
|
|
cost *= 1.5f;
|
|
}
|
|
return cost + 0.5f;
|
|
}
|
|
|
|
float gfunctionKillsCT (int currentIndex, int parentIndex) {
|
|
// least kills to goal for a team
|
|
|
|
if (parentIndex == INVALID_WAYPOINT_INDEX) {
|
|
return 0.0f;
|
|
}
|
|
float cost = (g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team1Damage;
|
|
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int neighbour = current.index[i];
|
|
|
|
if (neighbour != INVALID_WAYPOINT_INDEX) {
|
|
cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team1Damage;
|
|
}
|
|
}
|
|
|
|
if (current.flags & FLAG_CROUCH) {
|
|
cost *= 1.5f;
|
|
}
|
|
return cost + 0.5f;
|
|
}
|
|
|
|
float gfunctionKillsCTWithHostage (int currentIndex, int parentIndex) {
|
|
// least kills to goal for a team
|
|
|
|
if (parentIndex == INVALID_WAYPOINT_INDEX) {
|
|
return 0.0f;
|
|
}
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
if (current.flags & FLAG_NOHOSTAGE) {
|
|
return 65355.0f;
|
|
}
|
|
else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) {
|
|
return gfunctionKillsDistCT (currentIndex, parentIndex) * 500.0f;
|
|
}
|
|
return gfunctionKillsCT (currentIndex, parentIndex);
|
|
}
|
|
|
|
float gfunctionPathDist (int currentIndex, int parentIndex) {
|
|
if (parentIndex == INVALID_WAYPOINT_INDEX) {
|
|
return 0.0f;
|
|
}
|
|
Path &parent = waypoints[parentIndex];
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (parent.index[i] == currentIndex) {
|
|
// we don't like ladder or crouch point
|
|
if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) {
|
|
return parent.distances[i] * 1.5f;
|
|
}
|
|
return static_cast <float> (parent.distances[i]);
|
|
}
|
|
}
|
|
return 65355.0f;
|
|
}
|
|
|
|
float gfunctionPathDistWithHostage (int currentIndex, int parentIndex) {
|
|
Path ¤t = waypoints[currentIndex];
|
|
|
|
if (current.flags & FLAG_NOHOSTAGE) {
|
|
return 65355.0f;
|
|
}
|
|
else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) {
|
|
return gfunctionPathDist (currentIndex, parentIndex) * 500.0f;
|
|
}
|
|
return gfunctionPathDist (currentIndex, parentIndex);
|
|
}
|
|
|
|
float hfunctionPathDist (int index, int, int goalIndex) {
|
|
// square distance heuristic
|
|
|
|
Path &start = waypoints[index];
|
|
Path &goal = waypoints[goalIndex];
|
|
|
|
float x = cr::abs (start.origin.x - goal.origin.x);
|
|
float y = cr::abs (start.origin.y - goal.origin.y);
|
|
float z = cr::abs (start.origin.z - goal.origin.z);
|
|
|
|
switch (yb_debug_heuristic_type.integer ()) {
|
|
case 0:
|
|
default:
|
|
return cr::max (cr::max (x, y), z); // chebyshev distance
|
|
|
|
case 1:
|
|
return x + y + z; // manhattan distance
|
|
|
|
case 2:
|
|
return 0.0f; // no heuristic
|
|
|
|
case 3:
|
|
case 4:
|
|
// euclidean based distance
|
|
float euclidean = cr::powf (cr::powf (x, 2.0f) + cr::powf (y, 2.0f) + cr::powf (z, 2.0f), 0.5f);
|
|
|
|
if (yb_debug_heuristic_type.integer () == 4) {
|
|
return 1000.0f * (cr::ceilf (euclidean) - euclidean);
|
|
}
|
|
return euclidean;
|
|
}
|
|
}
|
|
|
|
float hfunctionPathDistWithHostage (int index, int startIndex, int goalIndex) {
|
|
// square distance heuristic with hostages
|
|
|
|
if (waypoints[startIndex].flags & FLAG_NOHOSTAGE) {
|
|
return 65355.0f;
|
|
}
|
|
return hfunctionPathDist (index, startIndex, goalIndex);
|
|
}
|
|
|
|
float hfunctionNone (int index, int startIndex, int goalIndex) {
|
|
return hfunctionPathDist (index, startIndex, goalIndex) / 128.0f * 10.0f;
|
|
}
|
|
|
|
void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= SEARCH_PATH_FASTEST */) {
|
|
// this function finds a path from srcIndex to destIndex
|
|
|
|
if (!waypoints.exists (srcIndex)) {
|
|
logEntry (true, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex);
|
|
return;
|
|
}
|
|
else if (!waypoints.exists (destIndex)) {
|
|
logEntry (true, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex);
|
|
return;
|
|
}
|
|
clearSearchNodes ();
|
|
|
|
m_chosenGoalIndex = srcIndex;
|
|
m_goalValue = 0.0f;
|
|
|
|
clearRoute ();
|
|
|
|
float (*gcalc) (int, int) = nullptr;
|
|
float (*hcalc) (int, int, int) = nullptr;
|
|
|
|
switch (pathType) {
|
|
default:
|
|
case SEARCH_PATH_FASTEST:
|
|
if ((g_mapFlags & MAP_CS) && hasHostage ()) {
|
|
gcalc = gfunctionPathDistWithHostage;
|
|
hcalc = hfunctionPathDistWithHostage;
|
|
}
|
|
else {
|
|
gcalc = gfunctionPathDist;
|
|
hcalc = hfunctionPathDist;
|
|
}
|
|
break;
|
|
|
|
case SEARCH_PATH_SAFEST_FASTER:
|
|
if (m_team == TEAM_TERRORIST) {
|
|
gcalc = gfunctionKillsDistT;
|
|
hcalc = hfunctionPathDist;
|
|
}
|
|
else if ((g_mapFlags & MAP_CS) && hasHostage ()) {
|
|
gcalc = gfunctionKillsDistCTWithHostage;
|
|
hcalc = hfunctionPathDistWithHostage;
|
|
}
|
|
else {
|
|
gcalc = gfunctionKillsDistCT;
|
|
hcalc = hfunctionPathDist;
|
|
}
|
|
break;
|
|
|
|
case SEARCH_PATH_SAFEST:
|
|
if (m_team == TEAM_TERRORIST) {
|
|
gcalc = gfunctionKillsT;
|
|
hcalc = hfunctionNone;
|
|
}
|
|
else if ((g_mapFlags & MAP_CS) && hasHostage ()) {
|
|
gcalc = gfunctionKillsCTWithHostage;
|
|
hcalc = hfunctionNone;
|
|
}
|
|
else {
|
|
gcalc = gfunctionKillsCT;
|
|
hcalc = hfunctionNone;
|
|
}
|
|
break;
|
|
}
|
|
auto srcRoute = &m_routes[srcIndex];
|
|
|
|
// put start node into open list
|
|
srcRoute->g = gcalc (srcIndex, INVALID_WAYPOINT_INDEX);
|
|
srcRoute->f = srcRoute->g + hcalc (srcIndex, srcIndex, destIndex);
|
|
srcRoute->state = ROUTE_OPEN;
|
|
|
|
m_routeQue.clear ();
|
|
m_routeQue.push (srcIndex, srcRoute->g);
|
|
|
|
while (!m_routeQue.empty ()) {
|
|
// remove the first node from the open list
|
|
int currentIndex = m_routeQue.pop ();
|
|
|
|
// safes us from bad waypoints...
|
|
if (m_routeQue.length () >= MAX_ROUTE_LENGTH - 1) {
|
|
logEntry (true, LL_ERROR, "A* Search for bots \"%s\" has tried to build path with at least %d nodes. Seems to be waypoints are broken.", STRING (pev->netname), m_routeQue.length ());
|
|
|
|
// bail out to shortest path
|
|
searchShortestPath (srcIndex, destIndex);
|
|
return;
|
|
}
|
|
|
|
// is the current node the goal node?
|
|
if (currentIndex == destIndex) {
|
|
|
|
// build the complete path
|
|
do {
|
|
m_path.push (currentIndex);
|
|
currentIndex = m_routes[currentIndex].parent;
|
|
|
|
} while (currentIndex != INVALID_WAYPOINT_INDEX);
|
|
|
|
m_path.reverse ();
|
|
return;
|
|
}
|
|
auto curRoute = &m_routes[currentIndex];
|
|
|
|
if (curRoute->state != ROUTE_OPEN) {
|
|
continue;
|
|
}
|
|
|
|
// put current node into CLOSED list
|
|
curRoute->state = ROUTE_CLOSED;
|
|
|
|
// now expand the current node
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int currentChild = waypoints[currentIndex].index[i];
|
|
|
|
if (currentChild == INVALID_WAYPOINT_INDEX) {
|
|
continue;
|
|
}
|
|
auto childRoute = &m_routes[currentChild];
|
|
|
|
// calculate the F value as F = G + H
|
|
float g = curRoute->g + gcalc (currentChild, currentIndex);
|
|
float h = hcalc (currentChild, srcIndex, destIndex);
|
|
float f = g + h;
|
|
|
|
if (childRoute->state == ROUTE_NEW || childRoute->f > f) {
|
|
// put the current child into open list
|
|
childRoute->parent = currentIndex;
|
|
childRoute->state = ROUTE_OPEN;
|
|
|
|
childRoute->g = g;
|
|
childRoute->f = f;
|
|
|
|
m_routeQue.push (currentChild, g);
|
|
}
|
|
}
|
|
}
|
|
searchShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead
|
|
}
|
|
|
|
void Bot::clearSearchNodes (void) {
|
|
m_path.clear ();
|
|
m_chosenGoalIndex = INVALID_WAYPOINT_INDEX;
|
|
}
|
|
|
|
void Bot::clearRoute (void) {
|
|
int maxWaypoints = waypoints.length ();
|
|
|
|
if (m_routes.capacity () < static_cast <size_t> (waypoints.length ())) {
|
|
m_routes.reserve (maxWaypoints);
|
|
}
|
|
|
|
for (int i = 0; i < maxWaypoints; i++) {
|
|
auto route = &m_routes[i];
|
|
|
|
route->g = route->f = 0.0f;
|
|
route->parent = INVALID_WAYPOINT_INDEX;
|
|
route->state = ROUTE_NEW;
|
|
}
|
|
m_routes.clear ();
|
|
}
|
|
|
|
int Bot::searchAimingPoint (const Vector &to) {
|
|
// return the most distant waypoint which is seen from the bot to the target and is within count
|
|
|
|
if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) {
|
|
m_currentWaypointIndex = changePointIndex (getNearestPoint ());
|
|
}
|
|
|
|
int destIndex = waypoints.getNearest (to);
|
|
int bestIndex = m_currentWaypointIndex;
|
|
|
|
if (destIndex == INVALID_WAYPOINT_INDEX) {
|
|
return INVALID_WAYPOINT_INDEX;
|
|
}
|
|
|
|
while (destIndex != m_currentWaypointIndex) {
|
|
destIndex = *(waypoints.m_pathMatrix + (destIndex * waypoints.length ()) + m_currentWaypointIndex);
|
|
|
|
if (destIndex < 0) {
|
|
break;
|
|
}
|
|
|
|
if (waypoints.isVisible (m_currentWaypointIndex, destIndex) && waypoints.isVisible (destIndex, m_currentWaypointIndex)) {
|
|
bestIndex = destIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bestIndex == m_currentWaypointIndex) {
|
|
return INVALID_WAYPOINT_INDEX;
|
|
}
|
|
return bestIndex;
|
|
}
|
|
|
|
bool Bot::searchOptimalPoint (void) {
|
|
// this function find a waypoint in the near of the bot if bot had lost his path of pathfinder needs
|
|
// to be restarted over again.
|
|
|
|
int busy = INVALID_WAYPOINT_INDEX;
|
|
|
|
float lessDist[3] = { 99999.0f, 99999.0f , 99999.0f };
|
|
int lessIndex[3] = { INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX , INVALID_WAYPOINT_INDEX };
|
|
|
|
auto &bucket = waypoints.getWaypointsInBucket (pev->origin);
|
|
int numToSkip = cr::clamp (rng.getInt (0, static_cast <int> (bucket.length () - 1)), 0, 5);
|
|
|
|
for (const int at : bucket) {
|
|
bool skip = !!(at == m_currentWaypointIndex);
|
|
|
|
// skip the current waypoint, if any
|
|
if (skip) {
|
|
continue;
|
|
}
|
|
|
|
// skip current and recent previous waypoints
|
|
for (int j = 0; j < numToSkip; j++) {
|
|
if (at == m_prevWptIndex[j]) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// skip waypoint from recent list
|
|
if (skip) {
|
|
continue;
|
|
}
|
|
|
|
// cts with hostages should not pick waypoints with no hostage flag
|
|
if ((g_mapFlags & MAP_CS) && m_team == TEAM_COUNTER && (waypoints[at].flags & FLAG_NOHOSTAGE) && hasHostage ()) {
|
|
continue;
|
|
}
|
|
|
|
// ignore non-reacheable waypoints...
|
|
if (!waypoints.isReachable (this, at)) {
|
|
continue;
|
|
}
|
|
|
|
// check if waypoint is already used by another bot...
|
|
if (g_timeRoundStart + 5.0f > engine.timebase () && isOccupiedPoint (at)) {
|
|
busy = at;
|
|
continue;
|
|
}
|
|
|
|
// if we're still here, find some close waypoints
|
|
float distance = (pev->origin - waypoints[at].origin).lengthSq ();
|
|
|
|
if (distance < lessDist[0]) {
|
|
lessDist[2] = lessDist[1];
|
|
lessIndex[2] = lessIndex[1];
|
|
|
|
lessDist[1] = lessDist[0];
|
|
lessIndex[1] = lessIndex[0];
|
|
|
|
lessDist[0] = distance;
|
|
lessIndex[0] = at;
|
|
}
|
|
else if (distance < lessDist[1]) {
|
|
lessDist[2] = lessDist[1];
|
|
lessIndex[2] = lessIndex[1];
|
|
|
|
lessDist[1] = distance;
|
|
lessIndex[1] = at;
|
|
}
|
|
else if (distance < lessDist[2]) {
|
|
lessDist[2] = distance;
|
|
lessIndex[2] = at;
|
|
}
|
|
}
|
|
int selected = INVALID_WAYPOINT_INDEX;
|
|
|
|
// now pick random one from choosen
|
|
int index = 0;
|
|
|
|
// choice from found
|
|
if (lessIndex[2] != INVALID_WAYPOINT_INDEX) {
|
|
index = rng.getInt (0, 2);
|
|
}
|
|
else if (lessIndex[1] != INVALID_WAYPOINT_INDEX) {
|
|
index = rng.getInt (0, 1);
|
|
}
|
|
else if (lessIndex[0] != INVALID_WAYPOINT_INDEX) {
|
|
index = 0;
|
|
}
|
|
selected = lessIndex[index];
|
|
|
|
// if we're still have no waypoint and have busy one (by other bot) pick it up
|
|
if (selected == INVALID_WAYPOINT_INDEX && busy != INVALID_WAYPOINT_INDEX) {
|
|
selected = busy;
|
|
}
|
|
|
|
// worst case... find atleast something
|
|
if (selected == INVALID_WAYPOINT_INDEX) {
|
|
selected = getNearestPoint ();
|
|
}
|
|
|
|
ignoreCollision ();
|
|
changePointIndex (selected);
|
|
|
|
return true;
|
|
}
|
|
|
|
float Bot::getReachTime (void) {
|
|
auto task = taskId ();
|
|
float estimatedTime = 0.0f;
|
|
|
|
switch (task) {
|
|
case TASK_PAUSE:
|
|
case TASK_CAMP:
|
|
case TASK_HIDE:
|
|
return 0.0f;
|
|
|
|
default:
|
|
estimatedTime = 2.8f; // time to reach next waypoint
|
|
}
|
|
|
|
// calculate 'real' time that we need to get from one waypoint to another
|
|
if (waypoints.exists (m_currentWaypointIndex) && waypoints.exists (m_prevWptIndex[0])) {
|
|
float distance = (waypoints[m_prevWptIndex[0]].origin - waypoints[m_currentWaypointIndex].origin).length ();
|
|
|
|
// caclulate estimated time
|
|
if (pev->maxspeed <= 0.0f) {
|
|
estimatedTime = 3.0f * distance / 240.0f;
|
|
}
|
|
else {
|
|
estimatedTime = 3.0f * distance / pev->maxspeed;
|
|
}
|
|
bool longTermReachability = (m_currentPath->flags & FLAG_CROUCH) || (m_currentPath->flags & FLAG_LADDER) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK);
|
|
|
|
// check for special waypoints, that can slowdown our movement
|
|
if (longTermReachability) {
|
|
estimatedTime *= 2.0f;
|
|
}
|
|
estimatedTime = cr::clamp (estimatedTime, 1.2f, longTermReachability ? 8.0f : 5.0f);
|
|
}
|
|
return estimatedTime;
|
|
}
|
|
|
|
void Bot::getValidPoint (void) {
|
|
// checks if the last waypoint the bot was heading for is still valid
|
|
|
|
auto rechoiceGoal = [&] (void) {
|
|
if (m_rechoiceGoalCount > 1) {
|
|
int newGoal = searchGoal ();
|
|
|
|
m_prevGoalIndex = newGoal;
|
|
m_chosenGoalIndex = newGoal;
|
|
task ()->data = newGoal;
|
|
|
|
// do path finding if it's not the current waypoint
|
|
if (newGoal != m_currentWaypointIndex) {
|
|
searchPath (m_currentWaypointIndex, newGoal, m_pathType);
|
|
}
|
|
m_rechoiceGoalCount = 0;
|
|
}
|
|
else {
|
|
searchOptimalPoint ();
|
|
m_rechoiceGoalCount++;
|
|
}
|
|
};
|
|
|
|
// if bot hasn't got a waypoint we need a new one anyway or if time to get there expired get new one as well
|
|
if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) {
|
|
clearSearchNodes ();
|
|
rechoiceGoal ();
|
|
|
|
m_waypointOrigin = m_currentPath->origin;
|
|
}
|
|
else if (m_navTimeset + getReachTime () < engine.timebase () && engine.isNullEntity (m_enemy)) {
|
|
if (m_team == TEAM_TERRORIST) {
|
|
int value = (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team0Damage;
|
|
value += 100;
|
|
|
|
if (value > MAX_DAMAGE_VALUE) {
|
|
value = MAX_DAMAGE_VALUE;
|
|
}
|
|
(g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team0Damage = static_cast <uint16> (value);
|
|
|
|
// affect nearby connected with victim waypoints
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypoints.exists (m_currentPath->index[i])) {
|
|
value = (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team0Damage;
|
|
value += 2;
|
|
|
|
if (value > MAX_DAMAGE_VALUE) {
|
|
value = MAX_DAMAGE_VALUE;
|
|
}
|
|
(g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team0Damage = static_cast <uint16> (value);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
int value = (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team1Damage;
|
|
value += 100;
|
|
|
|
if (value > MAX_DAMAGE_VALUE) {
|
|
value = MAX_DAMAGE_VALUE;
|
|
}
|
|
(g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team1Damage = static_cast <uint16> (value);
|
|
|
|
// affect nearby connected with victim waypoints
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypoints.exists (m_currentPath->index[i])) {
|
|
value = (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team1Damage;
|
|
value += 2;
|
|
|
|
if (value > MAX_DAMAGE_VALUE) {
|
|
value = MAX_DAMAGE_VALUE;
|
|
}
|
|
(g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team1Damage = static_cast <uint16> (value);
|
|
}
|
|
}
|
|
}
|
|
clearSearchNodes ();
|
|
rechoiceGoal ();
|
|
|
|
m_waypointOrigin = m_currentPath->origin;
|
|
}
|
|
}
|
|
|
|
int Bot::changePointIndex (int index) {
|
|
if (index == INVALID_WAYPOINT_INDEX) {
|
|
return 0;
|
|
}
|
|
m_prevWptIndex[4] = m_prevWptIndex[3];
|
|
m_prevWptIndex[3] = m_prevWptIndex[2];
|
|
m_prevWptIndex[2] = m_prevWptIndex[1];
|
|
m_prevWptIndex[0] = m_currentWaypointIndex;
|
|
|
|
m_currentWaypointIndex = index;
|
|
m_navTimeset = engine.timebase ();
|
|
|
|
m_currentPath = &waypoints[index];
|
|
m_waypointFlags = m_currentPath->flags;
|
|
|
|
return m_currentWaypointIndex; // to satisfy static-code analyzers
|
|
}
|
|
|
|
int Bot::getNearestPoint (void) {
|
|
// get the current nearest waypoint to bot with visibility checks
|
|
|
|
int index = INVALID_WAYPOINT_INDEX;
|
|
float minimum = cr::square (1024.0f);
|
|
|
|
auto &bucket = waypoints.getWaypointsInBucket (pev->origin);
|
|
|
|
for (const auto at : bucket) {
|
|
if (at == m_currentWaypointIndex) {
|
|
continue;
|
|
}
|
|
float distance = (waypoints[at].origin - pev->origin).lengthSq ();
|
|
|
|
if (distance < minimum) {
|
|
|
|
// if bot doing navigation, make sure waypoint really visible and not too high
|
|
if ((m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && waypoints.isVisible (m_currentWaypointIndex, at)) || waypoints.isReachable (this, at)) {
|
|
index = at;
|
|
minimum = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
// worst case, take any waypoint...
|
|
if (index == INVALID_WAYPOINT_INDEX) {
|
|
index = waypoints.getNearestNoBuckets (pev->origin);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
int Bot::getBombPoint (void) {
|
|
// this function finds the best goal (bomb) waypoint for CTs when searching for a planted bomb.
|
|
|
|
auto &goals = waypoints.m_goalPoints;
|
|
auto bomb = isBombAudible ();
|
|
|
|
if (goals.empty () || bomb.empty ()) {
|
|
return rng.getInt (0, waypoints.length () - 1); // reliability check
|
|
}
|
|
|
|
// take the nearest to bomb waypoints instead of goal if close enough
|
|
else if ((pev->origin - bomb).lengthSq () < cr::square (512.0f)) {
|
|
int waypoint = waypoints.getNearest (bomb, 80.0f);
|
|
m_bombSearchOverridden = true;
|
|
|
|
if (waypoint != INVALID_WAYPOINT_INDEX) {
|
|
return waypoint;
|
|
}
|
|
}
|
|
|
|
int goal = 0, count = 0;
|
|
float lastDistance = 999999.0f;
|
|
|
|
// find nearest goal waypoint either to bomb (if "heard" or player)
|
|
for (auto &point : goals) {
|
|
float distance = (waypoints[point].origin - bomb).lengthSq ();
|
|
|
|
// check if we got more close distance
|
|
if (distance < lastDistance) {
|
|
goal = point;
|
|
lastDistance = distance;
|
|
}
|
|
}
|
|
|
|
while (waypoints.isVisited (goal)) {
|
|
goal = goals.random ();
|
|
|
|
if (count++ >= static_cast <int> (goals.length ())) {
|
|
break;
|
|
}
|
|
}
|
|
return goal;
|
|
}
|
|
|
|
int Bot::getDefendPoint (const Vector &origin) {
|
|
// this function tries to find a good position which has a line of sight to a position,
|
|
// provides enough cover point, and is far away from the defending position
|
|
|
|
if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) {
|
|
m_currentWaypointIndex = changePointIndex (getNearestPoint ());
|
|
}
|
|
TraceResult tr;
|
|
|
|
int waypointIndex[MAX_PATH_INDEX];
|
|
int minDistance[MAX_PATH_INDEX];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
waypointIndex[i] = INVALID_WAYPOINT_INDEX;
|
|
minDistance[i] = 128;
|
|
}
|
|
|
|
int posIndex = waypoints.getNearest (origin);
|
|
int srcIndex = m_currentWaypointIndex;
|
|
|
|
// some of points not found, return random one
|
|
if (srcIndex == INVALID_WAYPOINT_INDEX || posIndex == INVALID_WAYPOINT_INDEX) {
|
|
return rng.getInt (0, waypoints.length () - 1);
|
|
}
|
|
|
|
// find the best waypoint now
|
|
for (int i = 0; i < waypoints.length (); i++) {
|
|
// exclude ladder & current waypoints
|
|
if ((waypoints[i].flags & FLAG_LADDER) || i == srcIndex || !waypoints.isVisible (i, posIndex) || isOccupiedPoint (i)) {
|
|
continue;
|
|
}
|
|
|
|
// use the 'real' pathfinding distances
|
|
int distance = waypoints.getPathDist (srcIndex, i);
|
|
|
|
// skip wayponts with distance more than 512 units
|
|
if (distance > 512) {
|
|
continue;
|
|
}
|
|
engine.testLine (waypoints[i].origin, waypoints[posIndex].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
// check if line not hit anything
|
|
if (tr.flFraction != 1.0f) {
|
|
continue;
|
|
}
|
|
|
|
for (int j = 0; j < MAX_PATH_INDEX; j++) {
|
|
if (distance > minDistance[j]) {
|
|
waypointIndex[j] = i;
|
|
minDistance[j] = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
// use statistic if we have them
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) {
|
|
Experience *exp = (g_experienceData + (waypointIndex[i] * waypoints.length ()) + waypointIndex[i]);
|
|
int experience = INVALID_WAYPOINT_INDEX;
|
|
|
|
if (m_team == TEAM_TERRORIST) {
|
|
experience = exp->team0Damage;
|
|
}
|
|
else {
|
|
experience = exp->team1Damage;
|
|
}
|
|
|
|
experience = (experience * 100) / (m_team == TEAM_TERRORIST ? g_highestDamageT : g_highestDamageCT);
|
|
minDistance[i] = (experience * 100) / 8192;
|
|
minDistance[i] += experience;
|
|
}
|
|
}
|
|
bool isOrderChanged = false;
|
|
|
|
// sort results waypoints for farest distance
|
|
do {
|
|
isOrderChanged = false;
|
|
|
|
// completely sort the data
|
|
for (int i = 0; i < 3 && waypointIndex[i] != INVALID_WAYPOINT_INDEX && waypointIndex[i + 1] != INVALID_WAYPOINT_INDEX && minDistance[i] > minDistance[i + 1]; i++) {
|
|
int index = waypointIndex[i];
|
|
|
|
waypointIndex[i] = waypointIndex[i + 1];
|
|
waypointIndex[i + 1] = index;
|
|
|
|
index = minDistance[i];
|
|
|
|
minDistance[i] = minDistance[i + 1];
|
|
minDistance[i + 1] = index;
|
|
|
|
isOrderChanged = true;
|
|
}
|
|
} while (isOrderChanged);
|
|
|
|
if (waypointIndex[0] == INVALID_WAYPOINT_INDEX) {
|
|
IntArray found;
|
|
|
|
for (int i = 0; i < waypoints.length (); i++) {
|
|
if ((waypoints[i].origin - origin).lengthSq () <= cr::square (1248.0f) && !waypoints.isVisible (i, posIndex) && !isOccupiedPoint (i)) {
|
|
found.push (i);
|
|
}
|
|
}
|
|
|
|
if (found.empty ()) {
|
|
return rng.getInt (0, waypoints.length () - 1); // most worst case, since there a evil error in waypoints
|
|
}
|
|
return found.random ();
|
|
}
|
|
int index = 0;
|
|
|
|
for (; index < MAX_PATH_INDEX; index++) {
|
|
if (waypointIndex[index] == INVALID_WAYPOINT_INDEX) {
|
|
break;
|
|
}
|
|
}
|
|
return waypointIndex[rng.getInt (0, static_cast <int> ((index - 1) * 0.5f))];
|
|
}
|
|
|
|
int Bot::getCoverPoint (float maxDistance) {
|
|
// this function tries to find a good cover waypoint if bot wants to hide
|
|
|
|
const float enemyMax = (m_lastEnemyOrigin - pev->origin).length ();
|
|
|
|
// do not move to a position near to the enemy
|
|
if (maxDistance > enemyMax) {
|
|
maxDistance = enemyMax;
|
|
}
|
|
|
|
if (maxDistance < 300.0f) {
|
|
maxDistance = 300.0f;
|
|
}
|
|
|
|
int srcIndex = m_currentWaypointIndex;
|
|
int enemyIndex = waypoints.getNearest (m_lastEnemyOrigin);
|
|
|
|
IntArray enemies;
|
|
enemies.reserve (MAX_ENGINE_PLAYERS);
|
|
|
|
int waypointIndex[MAX_PATH_INDEX];
|
|
int minDistance[MAX_PATH_INDEX];
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
waypointIndex[i] = INVALID_WAYPOINT_INDEX;
|
|
minDistance[i] = static_cast <int> (maxDistance);
|
|
}
|
|
|
|
if (enemyIndex == INVALID_WAYPOINT_INDEX) {
|
|
return INVALID_WAYPOINT_INDEX;
|
|
}
|
|
|
|
// now get enemies neigbouring points
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypoints[enemyIndex].index[i] != INVALID_WAYPOINT_INDEX) {
|
|
enemies.push (waypoints[enemyIndex].index[i]);
|
|
}
|
|
}
|
|
|
|
// ensure we're on valid point
|
|
changePointIndex (srcIndex);
|
|
|
|
// find the best waypoint now
|
|
for (int i = 0; i < waypoints.length (); i++) {
|
|
// exclude ladder, current waypoints and waypoints seen by the enemy
|
|
if ((waypoints[i].flags & FLAG_LADDER) || i == srcIndex || waypoints.isVisible (enemyIndex, i)) {
|
|
continue;
|
|
}
|
|
bool neighbourVisible = false; // now check neighbour waypoints for visibility
|
|
|
|
for (auto &enemy : enemies) {
|
|
if (waypoints.isVisible (enemy, i)) {
|
|
neighbourVisible = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// skip visible points
|
|
if (neighbourVisible) {
|
|
continue;
|
|
}
|
|
|
|
// use the 'real' pathfinding distances
|
|
int distances = waypoints.getPathDist (srcIndex, i);
|
|
int enemyDistance = waypoints.getPathDist (enemyIndex, i);
|
|
|
|
if (distances >= enemyDistance) {
|
|
continue;
|
|
}
|
|
|
|
for (int j = 0; j < MAX_PATH_INDEX; j++) {
|
|
if (distances < minDistance[j]) {
|
|
waypointIndex[j] = i;
|
|
minDistance[j] = distances;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// use statistic if we have them
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) {
|
|
Experience *exp = (g_experienceData + (waypointIndex[i] * waypoints.length ()) + waypointIndex[i]);
|
|
int experience = INVALID_WAYPOINT_INDEX;
|
|
|
|
if (m_team == TEAM_TERRORIST) {
|
|
experience = exp->team0Damage;
|
|
}
|
|
else {
|
|
experience = exp->team1Damage;
|
|
}
|
|
experience = (experience * 100) / MAX_DAMAGE_VALUE;
|
|
|
|
minDistance[i] = (experience * 100) / 8192;
|
|
minDistance[i] += experience;
|
|
}
|
|
}
|
|
bool isOrderChanged;
|
|
|
|
// sort resulting waypoints for nearest distance
|
|
do {
|
|
isOrderChanged = false;
|
|
|
|
for (int i = 0; i < 3 && waypointIndex[i] != INVALID_WAYPOINT_INDEX && waypointIndex[i + 1] != INVALID_WAYPOINT_INDEX && minDistance[i] > minDistance[i + 1]; i++) {
|
|
int index = waypointIndex[i];
|
|
|
|
waypointIndex[i] = waypointIndex[i + 1];
|
|
waypointIndex[i + 1] = index;
|
|
|
|
index = minDistance[i];
|
|
minDistance[i] = minDistance[i + 1];
|
|
minDistance[i + 1] = index;
|
|
|
|
isOrderChanged = true;
|
|
}
|
|
} while (isOrderChanged);
|
|
|
|
TraceResult tr;
|
|
|
|
// take the first one which isn't spotted by the enemy
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) {
|
|
engine.testLine (m_lastEnemyOrigin + Vector (0.0f, 0.0f, 36.0f), waypoints[waypointIndex[i]].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (tr.flFraction < 1.0f) {
|
|
return waypointIndex[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// if all are seen by the enemy, take the first one
|
|
if (waypointIndex[0] != INVALID_WAYPOINT_INDEX) {
|
|
return waypointIndex[0];
|
|
}
|
|
return INVALID_WAYPOINT_INDEX; // do not use random points
|
|
}
|
|
|
|
bool Bot::getNextBestPoint (void) {
|
|
// this function does a realtime post processing of waypoints return from the
|
|
// pathfinder, to vary paths and find the best waypoint on our way
|
|
|
|
assert (!m_path.empty ());
|
|
assert (m_path.hasNext ());
|
|
|
|
if (!isOccupiedPoint (m_path.first ())) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
int id = m_currentPath->index[i];
|
|
|
|
if (id != INVALID_WAYPOINT_INDEX && waypoints.isConnected (id, m_path.next ()) && waypoints.isConnected (m_currentWaypointIndex, id)) {
|
|
|
|
// don't use ladder waypoints as alternative
|
|
if (waypoints[id].flags & FLAG_LADDER) {
|
|
continue;
|
|
}
|
|
|
|
if (!isOccupiedPoint (id)) {
|
|
m_path.first () = id;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Bot::advanceMovement (void) {
|
|
// advances in our pathfinding list and sets the appropiate destination origins for this bot
|
|
|
|
getValidPoint (); // check if old waypoints is still reliable
|
|
|
|
// no waypoints from pathfinding?
|
|
if (m_path.empty ()) {
|
|
return false;
|
|
}
|
|
TraceResult tr;
|
|
|
|
m_path.shift (); // advance in list
|
|
m_currentTravelFlags = 0; // reset travel flags (jumping etc)
|
|
|
|
// we're not at the end of the list?
|
|
if (!m_path.empty ()) {
|
|
// if in between a route, postprocess the waypoint (find better alternatives)...
|
|
if (m_path.hasNext () && m_path.first () != m_path.back ()) {
|
|
getNextBestPoint ();
|
|
m_minSpeed = pev->maxspeed;
|
|
|
|
TaskID taskID = taskId ();
|
|
|
|
// only if we in normal task and bomb is not planted
|
|
if (taskID == TASK_NORMAL && g_timeRoundMid + 5.0f < engine.timebase () && m_timeCamping + 5.0f < engine.timebase () && !g_bombPlanted && m_personality != PERSONALITY_RUSHER && !m_hasC4 && !m_isVIP && m_loosedBombWptIndex == INVALID_WAYPOINT_INDEX && !hasHostage ()) {
|
|
m_campButtons = 0;
|
|
|
|
const int nextIndex = m_path.next ();
|
|
float kills = 0;
|
|
|
|
if (m_team == TEAM_TERRORIST) {
|
|
kills = (g_experienceData + (nextIndex * waypoints.length ()) + nextIndex)->team0Damage;
|
|
}
|
|
else {
|
|
kills = (g_experienceData + (nextIndex * waypoints.length ()) + nextIndex)->team1Damage;
|
|
}
|
|
|
|
// if damage done higher than one
|
|
if (kills > 1.0f && g_timeRoundMid > engine.timebase ()) {
|
|
switch (m_personality) {
|
|
case PERSONALITY_NORMAL:
|
|
kills *= 0.33f;
|
|
break;
|
|
|
|
default:
|
|
kills *= 0.5f;
|
|
break;
|
|
}
|
|
|
|
if (m_baseAgressionLevel < kills && hasPrimaryWeapon ()) {
|
|
startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (m_difficulty * 0.5f, static_cast <float> (m_difficulty)) * 5.0f, true);
|
|
startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, getDefendPoint (waypoints[nextIndex].origin), engine.timebase () + rng.getFloat (3.0f, 10.0f), true);
|
|
}
|
|
}
|
|
else if (g_botsCanPause && !isOnLadder () && !isInWater () && !m_currentTravelFlags && isOnFloor ()) {
|
|
if (static_cast <float> (kills) == m_baseAgressionLevel) {
|
|
m_campButtons |= IN_DUCK;
|
|
}
|
|
else if (rng.getInt (1, 100) > m_difficulty * 25) {
|
|
m_minSpeed = getShiftSpeed ();
|
|
}
|
|
}
|
|
|
|
// force terrorist bot to plant bomb
|
|
if (m_inBombZone && !m_hasProgressBar && m_hasC4) {
|
|
int newGoal = searchGoal ();
|
|
|
|
m_prevGoalIndex = newGoal;
|
|
m_chosenGoalIndex = newGoal;
|
|
|
|
// remember index
|
|
task ()->data = newGoal;
|
|
|
|
// do path finding if it's not the current waypoint
|
|
if (newGoal != m_currentWaypointIndex) {
|
|
searchPath (m_currentWaypointIndex, newGoal, m_pathType);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_path.empty ()) {
|
|
const int destIndex = m_path.first ();
|
|
|
|
// find out about connection flags
|
|
if (m_currentWaypointIndex != INVALID_WAYPOINT_INDEX) {
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
if (m_currentPath->index[i] == destIndex) {
|
|
m_currentTravelFlags = m_currentPath->connectionFlags[i];
|
|
m_desiredVelocity = m_currentPath->connectionVelocity[i];
|
|
m_jumpFinished = false;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if bot is going to jump
|
|
bool willJump = false;
|
|
float jumpDistance = 0.0f;
|
|
|
|
Vector src;
|
|
Vector dst;
|
|
|
|
// try to find out about future connection flags
|
|
if (m_path.hasNext ()) {
|
|
for (int i = 0; i < MAX_PATH_INDEX; i++) {
|
|
const int nextIndex = m_path.next ();
|
|
|
|
Path &path = waypoints[destIndex];
|
|
Path &next = waypoints[nextIndex];
|
|
|
|
if (path.index[i] == nextIndex && (path.connectionFlags[i] & PATHFLAG_JUMP)) {
|
|
src = path.origin;
|
|
dst = next.origin;
|
|
|
|
jumpDistance = (path.origin - next.origin).length ();
|
|
willJump = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// is there a jump waypoint right ahead and do we need to draw out the light weapon ?
|
|
if (willJump && m_currentWeapon != WEAPON_KNIFE && m_currentWeapon != WEAPON_SCOUT && !m_isReloading && !usesPistol () && (jumpDistance > 210.0f || (dst.z - 32.0f > src.z && jumpDistance > 150.0f)) && !(m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY))) {
|
|
selectWeaponByName ("weapon_knife"); // draw out the knife if we needed
|
|
}
|
|
|
|
// bot not already on ladder but will be soon?
|
|
if ((waypoints[destIndex].flags & FLAG_LADDER) && !isOnLadder ()) {
|
|
// get ladder waypoints used by other (first moving) bots
|
|
for (int c = 0; c < engine.maxClients (); c++) {
|
|
Bot *otherBot = bots.getBot (c);
|
|
|
|
// if another bot uses this ladder, wait 3 secs
|
|
if (otherBot != nullptr && otherBot != this && otherBot->m_notKilled && otherBot->m_currentWaypointIndex == destIndex) {
|
|
startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, engine.timebase () + 3.0f, false);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
changePointIndex (destIndex);
|
|
}
|
|
}
|
|
m_waypointOrigin = m_currentPath->origin;
|
|
|
|
// if wayzone radius non zero vary origin a bit depending on the body angles
|
|
if (m_currentPath->radius > 0.0f) {
|
|
makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f));
|
|
m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * rng.getFloat (0.0f, m_currentPath->radius);
|
|
}
|
|
|
|
if (isOnLadder ()) {
|
|
engine.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_waypointOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr);
|
|
|
|
if (tr.flFraction < 1.0f) {
|
|
m_waypointOrigin = m_waypointOrigin + (pev->origin - m_waypointOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f);
|
|
}
|
|
}
|
|
m_navTimeset = engine.timebase ();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bot::cantMoveForward (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.
|
|
|
|
// first do a trace from the bot's eyes forward...
|
|
Vector src = eyePos ();
|
|
Vector forward = src + normal * 24.0f;
|
|
|
|
makeVectors (Vector (0.0f, pev->angles.y, 0.0f));
|
|
|
|
auto checkDoor = [] (TraceResult *tr) {
|
|
if (!(g_mapFlags & MAP_HAS_DOORS)) {
|
|
return false;
|
|
}
|
|
return tr->flFraction < 1.0f && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0;
|
|
};
|
|
|
|
// trace from the bot's eyes straight forward...
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr->flFraction < 1.0f) {
|
|
if ((g_mapFlags & MAP_HAS_DOORS) && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) {
|
|
return false;
|
|
}
|
|
return true; // bot's head will hit something
|
|
}
|
|
|
|
// bot's head is clear, check at shoulder level...
|
|
// trace from the bot's shoulder left diagonal forward to the right shoulder...
|
|
src = eyePos () + Vector (0.0f, 0.0f, -16.0f) - g_pGlobals->v_right * -16.0f;
|
|
forward = eyePos () + Vector (0.0f, 0.0f, -16.0f) + g_pGlobals->v_right * 16.0f + normal * 24.0f;
|
|
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
|
|
// bot's head is clear, check at shoulder level...
|
|
// trace from the bot's shoulder right diagonal forward to the left shoulder...
|
|
src = eyePos () + Vector (0.0f, 0.0f, -16.0f) + g_pGlobals->v_right * 16.0f;
|
|
forward = eyePos () + Vector (0.0f, 0.0f, -16.0f) - g_pGlobals->v_right * -16.0f + normal * 24.0f;
|
|
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
|
|
// now check below waist
|
|
if (pev->flags & FL_DUCKING) {
|
|
src = pev->origin + Vector (0.0f, 0.0f, -19.0f + 19.0f);
|
|
forward = src + Vector (0.0f, 0.0f, 10.0f) + normal * 24.0f;
|
|
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
src = pev->origin;
|
|
forward = src + normal * 24.0f;
|
|
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
}
|
|
else {
|
|
// trace from the left waist to the right forward waist pos
|
|
src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - g_pGlobals->v_right * -16.0f;
|
|
forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + g_pGlobals->v_right * 16.0f + normal * 24.0f;
|
|
|
|
// trace from the bot's waist straight forward...
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
|
|
// trace from the left waist to the right forward waist pos
|
|
src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + g_pGlobals->v_right * 16.0f;
|
|
forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - g_pGlobals->v_right * -16.0f + normal * 24.0f;
|
|
|
|
engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (checkDoor (tr)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
}
|
|
return false; // bot can move forward, return false
|
|
}
|
|
|
|
#ifdef DEAD_CODE
|
|
bool Bot::canStrafeLeft (TraceResult *tr) {
|
|
// this function checks if bot can move sideways
|
|
|
|
makeVectors (pev->v_angle);
|
|
|
|
Vector src = pev->origin;
|
|
Vector left = src - g_pGlobals->v_right * -40.0f;
|
|
|
|
// trace from the bot's waist straight left...
|
|
TraceLine (src, left, true, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr->flFraction < 1.0f) {
|
|
return false; // bot's body will hit something
|
|
}
|
|
|
|
src = left;
|
|
left = left + g_pGlobals->v_forward * 40.0f;
|
|
|
|
// trace from the strafe pos straight forward...
|
|
TraceLine (src, left, true, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr->flFraction < 1.0f) {
|
|
return false; // bot's body will hit something
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Bot::canStrafeRight (TraceResult *tr) {
|
|
// this function checks if bot can move sideways
|
|
|
|
makeVectors (pev->v_angle);
|
|
|
|
Vector src = pev->origin;
|
|
Vector right = src + g_pGlobals->v_right * 40.0f;
|
|
|
|
// trace from the bot's waist straight right...
|
|
TraceLine (src, right, true, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr->flFraction < 1.0f) {
|
|
return false; // bot's body will hit something
|
|
}
|
|
src = right;
|
|
right = right + g_pGlobals->v_forward * 40.0f;
|
|
|
|
// trace from the strafe pos straight forward...
|
|
TraceLine (src, right, true, ent (), tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr->flFraction < 1.0f) {
|
|
return false; // bot's body will hit something
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Bot::canJumpUp (const Vector &normal) {
|
|
// this function check if bot can jump over some obstacle
|
|
|
|
TraceResult tr;
|
|
|
|
// can't jump if not on ground and not on ladder/swimming
|
|
if (!isOnFloor () && (isOnLadder () || !isInWater ())) {
|
|
return false;
|
|
}
|
|
|
|
// convert current view angle to vectors for traceline math...
|
|
makeVectors (Vector (0.0f, pev->angles.y, 0.0f));
|
|
|
|
// check for normal jump height first...
|
|
Vector src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
|
Vector dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
if (tr.flFraction < 1.0f) {
|
|
return doneCanJumpUp (normal);
|
|
}
|
|
else {
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// now check same height to one side of the bot...
|
|
src = pev->origin + g_pGlobals->v_right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return doneCanJumpUp (normal);
|
|
}
|
|
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now check same height on the other side of the bot...
|
|
src = pev->origin + (-g_pGlobals->v_right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f);
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return doneCanJumpUp (normal);
|
|
}
|
|
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
return tr.flFraction > 1.0f;
|
|
}
|
|
|
|
bool Bot::doneCanJumpUp (const Vector &normal) {
|
|
// use center of the body first... maximum duck jump height is 62, so check one unit above that (63)
|
|
Vector src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 63.0f);
|
|
Vector dest = src + normal * 32.0f;
|
|
|
|
TraceResult tr;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
else {
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, check duckjump
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// now check same height to one side of the bot...
|
|
src = pev->origin + g_pGlobals->v_right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 63.0f);
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now check same height on the other side of the bot...
|
|
src = pev->origin + (-g_pGlobals->v_right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 63.0f);
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at maximum jump height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now trace from jump height upward to check for obstructions...
|
|
src = dest;
|
|
dest.z = dest.z + 37.0f;
|
|
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
return tr.flFraction > 1.0f;
|
|
}
|
|
|
|
bool Bot::canDuckUnder (const Vector &normal) {
|
|
// this function check if bot can duck under obstacle
|
|
|
|
TraceResult tr;
|
|
Vector baseHeight;
|
|
|
|
// convert current view angle to vectors for TraceLine math...
|
|
makeVectors (Vector (0.0f, pev->angles.y, 0.0f));
|
|
|
|
// use center of the body first...
|
|
if (pev->flags & FL_DUCKING) {
|
|
baseHeight = pev->origin + Vector (0.0f, 0.0f, -17.0f);
|
|
}
|
|
else {
|
|
baseHeight = pev->origin;
|
|
}
|
|
|
|
Vector src = baseHeight;
|
|
Vector dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at duck height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now check same height to one side of the bot...
|
|
src = baseHeight + g_pGlobals->v_right * 16.0f;
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at duck height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
if (tr.flFraction < 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
// now check same height on the other side of the bot...
|
|
src = baseHeight + (-g_pGlobals->v_right * 16.0f);
|
|
dest = src + normal * 32.0f;
|
|
|
|
// trace a line forward at duck height...
|
|
engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// if trace hit something, return false
|
|
return tr.flFraction > 1.0f;
|
|
}
|
|
|
|
#ifdef DEAD_CODE
|
|
|
|
bool Bot::isBlockedLeft (void) {
|
|
TraceResult tr;
|
|
int direction = 48;
|
|
|
|
if (m_moveSpeed < 0.0f) {
|
|
direction = -48;
|
|
}
|
|
makeVectors (pev->angles);
|
|
|
|
// do a trace to the left...
|
|
engine.TestLine (pev->origin, g_pGlobals->v_forward * direction - g_pGlobals->v_right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// check if the trace hit something...
|
|
if ((g_mapFlags & MAP_HAS_DOORS) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Bot::isBlockedRight (void) {
|
|
TraceResult tr;
|
|
int direction = 48;
|
|
|
|
if (m_moveSpeed < 0) {
|
|
direction = -48;
|
|
}
|
|
makeVectors (pev->angles);
|
|
|
|
// do a trace to the right...
|
|
engine.TestLine (pev->origin, pev->origin + g_pGlobals->v_forward * direction + g_pGlobals->v_right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// check if the trace hit something...
|
|
if ((g_mapFlags & MAP_HAS_DOORS) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) {
|
|
return true; // bot's body will hit something
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Bot::checkWallOnLeft (void) {
|
|
TraceResult tr;
|
|
makeVectors (pev->angles);
|
|
|
|
engine.testLine (pev->origin, pev->origin - g_pGlobals->v_right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr.flFraction < 1.0f) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Bot::checkWallOnRight (void) {
|
|
TraceResult tr;
|
|
makeVectors (pev->angles);
|
|
|
|
// do a trace to the right...
|
|
engine.testLine (pev->origin, pev->origin + g_pGlobals->v_right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr);
|
|
|
|
// check if the trace hit something...
|
|
if (tr.flFraction < 1.0f) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Bot::isDeadlyMove (const Vector &to) {
|
|
// this function eturns if given location would hurt Bot with falling damage
|
|
|
|
Vector botPos = pev->origin;
|
|
TraceResult tr;
|
|
|
|
Vector move ((to - botPos).toYaw (), 0.0f, 0.0f);
|
|
makeVectors (move);
|
|
|
|
Vector direction = (to - botPos).normalize (); // 1 unit long
|
|
Vector check = botPos;
|
|
Vector down = botPos;
|
|
|
|
down.z = down.z - 1000.0f; // straight down 1000 units
|
|
|
|
engine.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr);
|
|
|
|
if (tr.flFraction > 0.036f) { // we're not on ground anymore?
|
|
tr.flFraction = 0.036f;
|
|
}
|
|
|
|
float lastHeight = tr.flFraction * 1000.0f; // height from ground
|
|
float distance = (to - check).length (); // distance from goal
|
|
|
|
while (distance > 16.0f) {
|
|
check = check + direction * 16.0f; // move 10 units closer to the goal...
|
|
|
|
down = check;
|
|
down.z = down.z - 1000.0f; // straight down 1000 units
|
|
|
|
engine.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr);
|
|
|
|
if (tr.fStartSolid) { // Wall blocking?
|
|
return false;
|
|
}
|
|
float height = tr.flFraction * 1000.0f; // height from ground
|
|
|
|
if (lastHeight < height - 100.0f) {// Drops more than 100 Units?
|
|
return true;
|
|
}
|
|
lastHeight = height;
|
|
distance = (to - check).length (); // distance from goal
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEAD_CODE
|
|
|
|
void Bot::changePitch (float speed) {
|
|
// this function turns a bot towards its ideal_pitch
|
|
|
|
float idealPitch = AngleNormalize (pev->idealpitch);
|
|
float curent = AngleNormalize (pev->v_angle.x);
|
|
|
|
// turn from the current v_angle pitch to the idealpitch by selecting
|
|
// the quickest way to turn to face that direction
|
|
|
|
// find the difference in the curent and ideal angle
|
|
float normalizePitch = AngleNormalize (idealPitch - curent);
|
|
|
|
if (normalizePitch > 0.0f) {
|
|
if (normalizePitch > speed) {
|
|
normalizePitch = speed;
|
|
}
|
|
}
|
|
else {
|
|
if (normalizePitch < -speed) {
|
|
normalizePitch = -speed;
|
|
}
|
|
}
|
|
|
|
pev->v_angle.x = AngleNormalize (curent + normalizePitch);
|
|
|
|
if (pev->v_angle.x > 89.9f) {
|
|
pev->v_angle.x = 89.9f;
|
|
}
|
|
|
|
if (pev->v_angle.x < -89.9f) {
|
|
pev->v_angle.x = -89.9f;
|
|
}
|
|
pev->angles.x = -pev->v_angle.x / 3;
|
|
}
|
|
|
|
void Bot::changeYaw (float speed) {
|
|
// this function turns a bot towards its ideal_yaw
|
|
|
|
float idealPitch = AngleNormalize (pev->ideal_yaw);
|
|
float curent = AngleNormalize (pev->v_angle.y);
|
|
|
|
// turn from the current v_angle yaw to the ideal_yaw by selecting
|
|
// the quickest way to turn to face that direction
|
|
|
|
// find the difference in the curent and ideal angle
|
|
float normalizePitch = AngleNormalize (idealPitch - curent);
|
|
|
|
if (normalizePitch > 0.0f) {
|
|
if (normalizePitch > speed) {
|
|
normalizePitch = speed;
|
|
}
|
|
}
|
|
else {
|
|
if (normalizePitch < -speed) {
|
|
normalizePitch = -speed;
|
|
}
|
|
}
|
|
pev->v_angle.y = AngleNormalize (curent + normalizePitch);
|
|
pev->angles.y = pev->v_angle.y;
|
|
}
|
|
|
|
#endif
|
|
|
|
int Bot::searchCampDir (void) {
|
|
// find a good waypoint to look at when camping
|
|
|
|
if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) {
|
|
m_currentWaypointIndex = changePointIndex (getNearestPoint ());
|
|
}
|
|
|
|
int count = 0, indices[3];
|
|
float distTab[3];
|
|
uint16 visibility[3];
|
|
|
|
int currentWaypoint = m_currentWaypointIndex;
|
|
|
|
for (int i = 0; i < waypoints.length (); i++) {
|
|
if (currentWaypoint == i || !waypoints.isVisible (currentWaypoint, i)) {
|
|
continue;
|
|
}
|
|
Path &path = waypoints[i];
|
|
|
|
if (count < 3) {
|
|
indices[count] = i;
|
|
|
|
distTab[count] = (pev->origin - path.origin).lengthSq ();
|
|
visibility[count] = path.vis.crouch + path.vis.stand;
|
|
|
|
count++;
|
|
}
|
|
else {
|
|
float distance = (pev->origin - path.origin).lengthSq ();
|
|
uint16 visBits = path.vis.crouch + path.vis.stand;
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
if (visBits >= visibility[j] && distance > distTab[j]) {
|
|
indices[j] = i;
|
|
|
|
distTab[j] = distance;
|
|
visibility[j] = visBits;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
count--;
|
|
|
|
if (count >= 0) {
|
|
return indices[rng.getInt (0, count)];
|
|
}
|
|
return rng.getInt (0, waypoints.length () - 1);
|
|
}
|
|
|
|
void Bot::processBodyAngles (void) {
|
|
// set the body angles to point the gun correctly
|
|
pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f);
|
|
pev->angles.y = pev->v_angle.y;
|
|
|
|
pev->angles.clampAngles ();
|
|
}
|
|
|
|
void Bot::processLookAngles (void) {
|
|
const float delta = cr::clamp (engine.timebase () - m_lookUpdateTime, cr::EQEPSILON, 0.05f);
|
|
m_lookUpdateTime = engine.timebase ();
|
|
|
|
// adjust all body and view angles to face an absolute vector
|
|
Vector direction = (m_lookAt - eyePos ()).toAngles ();
|
|
direction.x *= -1.0f; // invert for engine
|
|
|
|
direction.clampAngles ();
|
|
|
|
// lower skilled bot's have lower aiming
|
|
if (m_difficulty < 2) {
|
|
updateLookAnglesNewbie (direction, delta);
|
|
processBodyAngles ();
|
|
|
|
return;
|
|
}
|
|
|
|
if (m_difficulty > 3 && (m_aimFlags & AIM_ENEMY) && (m_wantsToFire || usesSniper ()) && yb_whose_your_daddy.boolean ()) {
|
|
pev->v_angle = direction;
|
|
processBodyAngles ();
|
|
|
|
return;
|
|
}
|
|
|
|
float accelerate = 3000.0f;
|
|
float stiffness = 200.0f;
|
|
float damping = 25.0f;
|
|
|
|
if ((m_aimFlags & (AIM_ENEMY | AIM_ENTITY | AIM_GRENADE)) && m_difficulty > 2) {
|
|
accelerate += 800.0f;
|
|
stiffness += 320.0f;
|
|
damping -= 8.0f;
|
|
}
|
|
m_idealAngles = pev->v_angle;
|
|
|
|
float angleDiffYaw = cr::angleDiff (direction.y, m_idealAngles.y);
|
|
float angleDiffPitch = cr::angleDiff (direction.x, m_idealAngles.x);
|
|
|
|
if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) {
|
|
m_lookYawVel = 0.0f;
|
|
m_idealAngles.y = direction.y;
|
|
}
|
|
else {
|
|
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);
|
|
|
|
m_lookPitchVel += delta * accel;
|
|
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);
|
|
|
|
pev->v_angle = m_idealAngles;
|
|
pev->v_angle.clampAngles ();
|
|
|
|
processBodyAngles ();
|
|
}
|
|
|
|
void Bot::updateLookAnglesNewbie (const Vector &direction, const float delta) {
|
|
Vector spring (13.0f, 13.0f, 0.0f);
|
|
Vector damperCoefficient (0.22f, 0.22f, 0.0f);
|
|
|
|
const float offset = cr::clamp (m_difficulty, 1, 4) * 25.0f;
|
|
|
|
Vector influence = Vector (0.25f, 0.17f, 0.0f) * (100.0f - offset) / 100.f;
|
|
Vector randomization = Vector (2.0f, 0.18f, 0.0f) * (100.0f - offset) / 100.f;
|
|
|
|
const float noTargetRatio = 0.3f;
|
|
const float offsetDelay = 1.2f;
|
|
|
|
Vector stiffness;
|
|
Vector randomize;
|
|
|
|
m_idealAngles = direction.make2D ();
|
|
m_idealAngles.clampAngles ();
|
|
|
|
if (m_aimFlags & (AIM_ENEMY | AIM_ENTITY)) {
|
|
m_playerTargetTime = engine.timebase ();
|
|
m_randomizedIdealAngles = m_idealAngles;
|
|
|
|
stiffness = spring * (0.2f + offset / 125.0f);
|
|
}
|
|
else {
|
|
// is it time for bot to randomize the aim direction again (more often where moving) ?
|
|
if (m_randomizeAnglesTime < engine.timebase () && ((pev->velocity.length () > 1.0f && m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) {
|
|
// is the bot standing still ?
|
|
if (pev->velocity.length () < 1.0f) {
|
|
randomize = randomization * 0.2f; // randomize less
|
|
}
|
|
else {
|
|
randomize = randomization;
|
|
}
|
|
// randomize targeted location a bit (slightly towards the ground)
|
|
m_randomizedIdealAngles = m_idealAngles + Vector (rng.getFloat (-randomize.x * 0.5f, randomize.x * 1.5f), rng.getFloat (-randomize.y, randomize.y), 0.0f);
|
|
|
|
// set next time to do this
|
|
m_randomizeAnglesTime = engine.timebase () + rng.getFloat (0.4f, offsetDelay);
|
|
}
|
|
float stiffnessMultiplier = noTargetRatio;
|
|
|
|
// take in account whether the bot was targeting someone in the last N seconds
|
|
if (engine.timebase () - (m_playerTargetTime + offsetDelay) < noTargetRatio * 10.0f) {
|
|
stiffnessMultiplier = 1.0f - (engine.timebase () - m_timeLastFired) * 0.1f;
|
|
|
|
// don't allow that stiffness multiplier less than zero
|
|
if (stiffnessMultiplier < 0.0f) {
|
|
stiffnessMultiplier = 0.5f;
|
|
}
|
|
}
|
|
|
|
// also take in account the remaining deviation (slow down the aiming in the last 10°)
|
|
stiffnessMultiplier *= m_angularDeviation.length () * 0.1f * 0.5f;
|
|
|
|
// but don't allow getting below a certain value
|
|
if (stiffnessMultiplier < 0.35f) {
|
|
stiffnessMultiplier = 0.35f;
|
|
}
|
|
stiffness = spring * stiffnessMultiplier; // increasingly slow aim
|
|
}
|
|
// compute randomized angle deviation this time
|
|
m_angularDeviation = m_randomizedIdealAngles - pev->v_angle;
|
|
m_angularDeviation.clampAngles ();
|
|
|
|
// spring/damper model aiming
|
|
m_aimSpeed.x = stiffness.x * m_angularDeviation.x - damperCoefficient.x * m_aimSpeed.x;
|
|
m_aimSpeed.y = stiffness.y * m_angularDeviation.y - damperCoefficient.y * m_aimSpeed.y;
|
|
|
|
// influence of y movement on x axis and vice versa (less influence than x on y since it's
|
|
// easier and more natural for the bot to "move its mouse" horizontally than vertically)
|
|
m_aimSpeed.x += cr::clamp (m_aimSpeed.y * influence.y, -50.0f, 50.0f);
|
|
m_aimSpeed.y += cr::clamp (m_aimSpeed.x * influence.x, -200.0f, 200.0f);
|
|
|
|
// move the aim cursor
|
|
pev->v_angle = pev->v_angle + delta * Vector (m_aimSpeed.x, m_aimSpeed.y, 0.0f);
|
|
pev->v_angle.clampAngles ();
|
|
}
|
|
|
|
void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) {
|
|
makeVectors (pev->angles);
|
|
|
|
const Vector &los = (moveDir - pev->origin).normalize2D ();
|
|
float dot = los | g_pGlobals->v_forward.make2D ();
|
|
|
|
if (dot > 0.0f && !checkWallOnRight ()) {
|
|
m_strafeSpeed = strafeSpeed;
|
|
}
|
|
else if (!checkWallOnLeft ()) {
|
|
m_strafeSpeed = -strafeSpeed;
|
|
}
|
|
}
|
|
|
|
int Bot::locatePlantedC4 (void) {
|
|
// this function tries to find planted c4 on the defuse scenario map and returns nearest to it waypoint
|
|
|
|
if (m_team != TEAM_TERRORIST || !(g_mapFlags & MAP_DE)) {
|
|
return INVALID_WAYPOINT_INDEX; // don't search for bomb if the player is CT, or it's not defusing bomb
|
|
}
|
|
|
|
edict_t *bombEntity = nullptr; // temporaly pointer to bomb
|
|
|
|
// search the bomb on the map
|
|
while (!engine.isNullEntity (bombEntity = g_engfuncs.pfnFindEntityByString (bombEntity, "classname", "grenade"))) {
|
|
if (strcmp (STRING (bombEntity->v.model) + 9, "c4.mdl") == 0) {
|
|
int nearestIndex = waypoints.getNearest (engine.getAbsPos (bombEntity));
|
|
|
|
if (waypoints.exists (nearestIndex)) {
|
|
return nearestIndex;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return INVALID_WAYPOINT_INDEX;
|
|
}
|
|
|
|
bool Bot::isOccupiedPoint (int index) {
|
|
if (!waypoints.exists (index)) {
|
|
return true;
|
|
}
|
|
for (int i = 0; i < engine.maxClients (); i++) {
|
|
const Client &client = g_clients[i];
|
|
|
|
if (!(client.flags & (CF_USED | CF_ALIVE)) || client.team != m_team || client.ent == ent ()) {
|
|
continue;
|
|
}
|
|
auto bot = bots.getBot (i);
|
|
|
|
if (bot == this) {
|
|
continue;
|
|
}
|
|
|
|
if (bot != nullptr) {
|
|
int occupyId = getShootingConeDeviation (bot->ent (), pev->origin) >= 0.7f ? bot->m_prevWptIndex[0] : bot->m_currentWaypointIndex;
|
|
|
|
if (bot != nullptr) {
|
|
if (index == occupyId) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
float length = (waypoints[index].origin - client.origin).lengthSq ();
|
|
|
|
if (length < cr::clamp (waypoints[index].radius, cr::square (32.0f), cr::square (90.0f))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
edict_t *Bot::getNearestButton (const char *targetName) {
|
|
// this function tries to find nearest to current bot button, and returns pointer to
|
|
// it's entity, also here must be specified the target, that button must open.
|
|
|
|
if (isEmptyStr (targetName)) {
|
|
return nullptr;
|
|
}
|
|
float nearestDistance = 99999.0f;
|
|
edict_t *searchEntity = nullptr, *foundEntity = nullptr;
|
|
|
|
// find the nearest button which can open our target
|
|
while (!engine.isNullEntity (searchEntity = g_engfuncs.pfnFindEntityByString (searchEntity, "target", targetName))) {
|
|
const Vector &pos = engine.getAbsPos (searchEntity);
|
|
|
|
// check if this place safe
|
|
if (!isDeadlyMove (pos)) {
|
|
float distance = (pev->origin - pos).lengthSq ();
|
|
|
|
// check if we got more close button
|
|
if (distance <= nearestDistance) {
|
|
nearestDistance = distance;
|
|
foundEntity = searchEntity;
|
|
}
|
|
}
|
|
}
|
|
return foundEntity;
|
|
} |