graph: reworked buckets so they can handle very large number of nodes

graph: reworked buckets so they can handle very large number of nodes
aim: bots should more respect headshot allow option (needs testing)
aim: incorporated never-finished changes from pr #204
nav: increased reachability timers a bit
nav: ensure buckets has enough nodes before use they
conf: introduced max recoil in difficulty config file
bot: overall fixes to jason mode, treat knife in hands and no weapons as jason mode too
bot: changed default difficulty level for bots to level 3
fix: knife attacks not working since last commit (fixes #429)
fix: hostage rescue not working since last commit (fixes #427)
refactor: use range loops for graph outside graph class when possible
This commit is contained in:
jeefo 2023-04-11 22:32:28 +03:00
commit 1a650c57ce
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
14 changed files with 426 additions and 347 deletions

View file

@ -103,7 +103,7 @@ int Bot::findBestGoal () {
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) {
// send some terrorists to guard planted bomb
if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) {
return pushToHistroy (m_chosenGoalIndex = graph.getNearest (graph.getBombOrigin ()));
return pushToHistroy (m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ()));
}
}
else if (game.mapIs (MapFlags::Escape)) {
@ -844,7 +844,7 @@ bool Bot::updateNavigation () {
m_desiredVelocity = nullptr;
}
}
else if (!cv_jasonmode.bool_ () && usesKnife () && isOnFloor ()) {
else if (!isKnifeMode () && m_switchedToKnifeDuringJump) {
selectBestWeapon ();
// if jump distance was big enough, cooldown a little
@ -852,6 +852,7 @@ bool Bot::updateNavigation () {
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.45f, false);
}
m_jumpDistance = 0.0f;
m_switchedToKnifeDuringJump = false;
}
}
@ -1041,7 +1042,7 @@ bool Bot::updateNavigation () {
desiredDistance = 0.0f;
}
else if (isOccupiedNode (m_path->number)) {
desiredDistance = 72.0f;
desiredDistance = 96.0f;
}
else {
desiredDistance = m_path->radius;
@ -1601,16 +1602,16 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
};
// square distance heuristic with hostages
auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float {
if (graph[startIndex].flags & NodeFlag::NoHostage) {
auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
if (graph[index].flags & NodeFlag::NoHostage) {
return 65355.0f;
}
return hfunctionPathDist (index, startIndex, goalIndex);
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex);
};
// none heuristic
auto hfunctionNone = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float {
return hfunctionPathDist (index, startIndex, goalIndex) / (128.0f * 10.0f);
auto hfunctionNone = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f);
};
if (!graph.exists (srcIndex)) {
@ -1673,7 +1674,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
// put start node into open list
srcRoute->g = gcalc (m_team, srcIndex, kInvalidNodeIndex);
srcRoute->f = srcRoute->g + hcalc (srcIndex, srcIndex, destIndex);
srcRoute->f = srcRoute->g + hcalc (srcIndex, kInvalidNodeIndex, destIndex);
srcRoute->state = RouteState::Open;
m_routeQue.clear ();
@ -1724,7 +1725,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
// calculate the F value as F = G + H
const float g = curRoute->g + gcalc (m_team, child.index, currentIndex);
const float h = hcalc (child.index, srcIndex, destIndex);
const float h = hcalc (child.index, kInvalidNodeIndex, destIndex);
const float f = g + h;
if (childRoute->state == RouteState::New || childRoute->f > f) {
@ -1747,14 +1748,13 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
void Bot::clearSearchNodes () {
m_pathWalk.clear ();
m_chosenGoalIndex = kInvalidNodeIndex;
}
void Bot::clearRoute () {
m_routes.resize (static_cast <size_t> (graph.length ()));
for (int i = 0; i < graph.length (); ++i) {
auto route = &m_routes[i];
for (const auto &path : graph) {
auto route = &m_routes[path.number];
route->g = route->f = 0.0f;
route->parent = kInvalidNodeIndex;
@ -1801,28 +1801,25 @@ bool Bot::findNextBestNode () {
// this function find a node in the near of the bot if bot had lost his path of pathfinder needs
// to be restarted over again.
int busy = kInvalidNodeIndex;
int busyIndex = kInvalidNodeIndex;
float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance };
int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex };
auto &bucket = graph.getNodesInBucket (pev->origin);
int numToSkip = cr::clamp (rg.get (0, 2), 0, static_cast <int> (bucket.length () / 2));
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
const auto &bucket = graph.getNodesInBucket (origin);
for (const int at : bucket) {
bool skip = !!(at == m_currentNodeIndex);
for (const auto &i : bucket) {
const auto &path = graph[i];
// skip the current node, if any
if (skip && numToSkip > 0) {
if (!graph.exists (path.number)) {
continue;
}
bool skip = !!(path.number == m_currentNodeIndex);
// skip current and recent previous nodes
for (int j = 0; j < numToSkip; ++j) {
if (at == m_previousNodes[j]) {
skip = true;
break;
}
// skip current or recent previous node
if (path.number == m_previousNodes[0]) {
skip = true;
}
// skip node from recent list
@ -1831,28 +1828,28 @@ bool Bot::findNextBestNode () {
}
// cts with hostages should not pick nodes with no hostage flag
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[at].flags & NodeFlag::NoHostage) && hasHostage ()) {
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[path.number].flags & NodeFlag::NoHostage) && hasHostage ()) {
continue;
}
// check we're have link to it
if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, at)) {
if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, path.number)) {
continue;
}
// ignore non-reacheable nodes...
if (!isReachableNode (at)) {
if (!isReachableNode (path.number)) {
continue;
}
// check if node is already used by another bot...
if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (at)) {
busy = at;
if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (path.number)) {
busyIndex = path.number;
continue;
}
// if we're still here, find some close nodes
float distance = pev->origin.distanceSq (graph[at].origin);
float distance = pev->origin.distanceSq (path.origin);
if (distance < lessDist[0]) {
lessDist[2] = lessDist[1];
@ -1862,18 +1859,18 @@ bool Bot::findNextBestNode () {
lessIndex[1] = lessIndex[0];
lessDist[0] = distance;
lessIndex[0] = at;
lessIndex[0] = path.number;
}
else if (distance < lessDist[1]) {
lessDist[2] = lessDist[1];
lessIndex[2] = lessIndex[1];
lessDist[1] = distance;
lessIndex[1] = at;
lessIndex[1] = path.number;
}
else if (distance < lessDist[2]) {
lessDist[2] = distance;
lessIndex[2] = at;
lessIndex[2] = path.number;
}
}
int selected = kInvalidNodeIndex;
@ -1894,8 +1891,8 @@ bool Bot::findNextBestNode () {
selected = lessIndex[index];
// if we're still have no node and have busy one (by other bot) pick it up
if (selected == kInvalidNodeIndex && busy != kInvalidNodeIndex) {
selected = busy;
if (selected == kInvalidNodeIndex && busyIndex != kInvalidNodeIndex) {
selected = busyIndex;
}
// worst case... find atleast something
@ -1909,26 +1906,27 @@ bool Bot::findNextBestNode () {
}
float Bot::getEstimatedNodeReachTime () {
float estimatedTime = 2.8f;
float estimatedTime = 6.0f;
// if just fired at enemy, increase reachability
if (m_shootTime + 0.15f < game.time ()) {
return estimatedTime;
}
// calculate 'real' time that we need to get from one node to another
if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) {
float distance = graph[m_previousNodes[0]].origin.distance (graph[m_currentNodeIndex].origin);
float distance = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin);
// caclulate estimated time
if (pev->maxspeed <= 0.0f) {
estimatedTime = 3.0f * distance / 240.0f;
}
else {
estimatedTime = 3.0f * distance / pev->maxspeed;
}
estimatedTime = 5.0f * (distance / cr::square (m_moveSpeed + 1.0f));
bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK);
// check for special nodes, that can slowdown our movement
if (longTermReachability) {
estimatedTime *= 2.0f;
}
estimatedTime = cr::clamp (estimatedTime, 2.0f, longTermReachability ? 8.0f : 5.0f);
estimatedTime = cr::clamp (estimatedTime, 3.0f, longTermReachability ? 8.0f : 6.0f);
}
return estimatedTime + m_frameInterval;
}
@ -1941,8 +1939,7 @@ void Bot::findValidNode () {
clearSearchNodes ();
findNextBestNode ();
}
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time () && game.isNullEntity (m_enemy)) {
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time ()) {
// increase danager for both teams
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
int damageValue = graph.getDangerDamage (team, m_currentNodeIndex, m_currentNodeIndex);
@ -1989,42 +1986,31 @@ int Bot::findNearestNode () {
// get the current nearest node to bot with visibility checks
int index = kInvalidNodeIndex;
float minimum = cr::square (1024.0f);
float minimumDistance = cr::square (1024.0f);
auto &bucket = graph.getNodesInBucket (pev->origin);
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
const auto &bucket = graph.getNodesInBucket (origin);
for (const auto at : bucket) {
if (at == m_currentNodeIndex) {
for (const auto &i : bucket) {
const auto &path = graph[i];
if (!graph.exists (path.number) || !graph.exists (m_currentNodeIndex) || path.number == m_currentNodeIndex) {
continue;
}
float distance = graph[at].origin.distanceSq (pev->origin);
const float distance = path.origin.distanceSq (pev->origin);
if (distance < minimum) {
// if bot doing navigation, make sure node really visible and not too high
if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at) && graph.isVisible (at, m_currentNodeIndex)) || isReachableNode (at)) {
index = at;
minimum = distance;
if (distance < minimumDistance) {
// if bot doing navigation, make sure node really visible and reacheable
if (graph.isVisible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) {
index = path.number;
minimumDistance = distance;
}
// @pr-419: temporarily disabled due to cpu usage
#if 0
else {
TraceResult tr {};
game.testHull (getEyesPos (), graph[at].origin, TraceIgnore::Monsters, head_hull, ent (), &tr);
if (tr.flFraction >= 1.0f && !tr.fStartSolid) {
index = at;
minimum = distance;
}
}
#endif
}
}
// worst case, take any node...
if (index == kInvalidNodeIndex) {
index = graph.getNearestNoBuckets (pev->origin);
index = graph.getNearestNoBuckets (origin);
}
return index;
}
@ -2109,14 +2095,14 @@ int Bot::findDefendNode (const Vector &origin) {
}
// find the best node now
for (int i = 0; i < graph.length (); ++i) {
for (const auto &path : graph) {
// exclude ladder & current nodes
if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex)) {
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || !graph.isVisible (path.number, posIndex)) {
continue;
}
// use the 'real' pathfinding distances
int distance = graph.getPathDist (srcIndex, i);
int distance = graph.getPathDist (srcIndex, path.number);
// skip wayponts too far
if (distance > kMaxDistance) {
@ -2124,10 +2110,10 @@ int Bot::findDefendNode (const Vector &origin) {
}
// skip occupied points
if (isOccupiedNode (i)) {
if (isOccupiedNode (path.number)) {
continue;
}
game.testLine (graph[i].origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr);
game.testLine (path.origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr);
// check if line not hit anything
if (!cr::fequal (tr.flFraction, 1.0f)) {
@ -2135,35 +2121,35 @@ int Bot::findDefendNode (const Vector &origin) {
}
if (distance > minDistance[0]) {
nodeIndex[0] = i;
nodeIndex[0] = path.number;
minDistance[0] = distance;
}
else if (distance > minDistance[1]) {
nodeIndex[1] = i;
nodeIndex[1] = path.number;
minDistance[1] = distance;
}
else if (distance > minDistance[2]) {
nodeIndex[2] = i;
nodeIndex[2] = path.number;
minDistance[2] = distance;
}
else if (distance > minDistance[3]) {
nodeIndex[3] = i;
nodeIndex[3] = path.number;
minDistance[3] = distance;
}
else if (distance > minDistance[4]) {
nodeIndex[4] = i;
nodeIndex[4] = path.number;
minDistance[4] = distance;
}
else if (distance > minDistance[5]) {
nodeIndex[5] = i;
nodeIndex[5] = path.number;
minDistance[5] = distance;
}
else if (distance > minDistance[6]) {
nodeIndex[6] = i;
nodeIndex[6] = path.number;
minDistance[6] = distance;
}
else if (distance > minDistance[7]) {
nodeIndex[7] = i;
nodeIndex[7] = path.number;
minDistance[7] = distance;
}
}
@ -2198,9 +2184,9 @@ int Bot::findDefendNode (const Vector &origin) {
if (nodeIndex[0] == kInvalidNodeIndex) {
IntArray found;
for (int i = 0; i < graph.length (); ++i) {
if (origin.distanceSq (graph[i].origin) < cr::square (static_cast <float> (kMaxDistance)) && !graph.isVisible (i, posIndex) && !isOccupiedNode (i)) {
found.push (i);
for (const auto &path : graph) {
if (origin.distanceSq (path.origin) < cr::square (static_cast <float> (kMaxDistance)) && graph.isVisible (path.number, posIndex) && !isOccupiedNode (path.number)) {
found.push (path.number);
}
}
@ -2261,15 +2247,15 @@ int Bot::findCoverNode (float maxDistance) {
changeNodeIndex (srcIndex);
// find the best node now
for (int i = 0; i < graph.length (); ++i) {
for (const auto &path : graph) {
// exclude ladder, current node and nodes seen by the enemy
if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || graph.isVisible (enemyIndex, i)) {
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || graph.isVisible (enemyIndex, path.number)) {
continue;
}
bool neighbourVisible = false; // now check neighbour nodes for visibility
for (auto &enemy : enemies) {
if (graph.isVisible (enemy, i)) {
if (graph.isVisible (enemy, path.number)) {
neighbourVisible = true;
break;
}
@ -2281,43 +2267,43 @@ int Bot::findCoverNode (float maxDistance) {
}
// use the 'real' pathfinding distances
int distance = graph.getPathDist (srcIndex, i);
int enemyDistance = graph.getPathDist (enemyIndex, i);
int distance = graph.getPathDist (srcIndex, path.number);
int enemyDistance = graph.getPathDist (enemyIndex, path.number);
if (distance >= enemyDistance) {
continue;
}
if (distance < minDistance[0]) {
nodeIndex[0] = i;
nodeIndex[0] = path.number;
minDistance[0] = distance;
}
else if (distance < minDistance[1]) {
nodeIndex[1] = i;
nodeIndex[1] = path.number;
minDistance[1] = distance;
}
else if (distance < minDistance[2]) {
nodeIndex[2] = i;
nodeIndex[2] = path.number;
minDistance[2] = distance;
}
else if (distance < minDistance[3]) {
nodeIndex[3] = i;
nodeIndex[3] = path.number;
minDistance[3] = distance;
}
else if (distance < minDistance[4]) {
nodeIndex[4] = i;
nodeIndex[4] = path.number;
minDistance[4] = distance;
}
else if (distance < minDistance[5]) {
nodeIndex[5] = i;
nodeIndex[5] = path.number;
minDistance[5] = distance;
}
else if (distance < minDistance[6]) {
nodeIndex[6] = i;
nodeIndex[6] = path.number;
minDistance[6] = distance;
}
else if (distance < minDistance[7]) {
nodeIndex[7] = i;
nodeIndex[7] = path.number;
minDistance[7] = distance;
}
}
@ -2326,7 +2312,7 @@ int Bot::findCoverNode (float maxDistance) {
for (int i = 0; i < kMaxNodeLinks; ++i) {
if (nodeIndex[i] != kInvalidNodeIndex) {
int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]);
practice = (practice * 100) / kMaxPracticeDamageValue;
practice = (practice * 100) / graph.getHighestDamageForTeam (m_team);
minDistance[i] = (practice * 100) / 8192;
minDistance[i] += practice;
@ -2380,15 +2366,16 @@ bool Bot::selectBestNextNode () {
}
for (auto &link : m_path->links) {
if (link.index != kInvalidNodeIndex && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) {
if (graph.exists (link.index) && m_pathWalk.first () != link.index && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) {
// don't use ladder nodes as alternative
if (graph[link.index].flags & NodeFlag::Ladder) {
if (graph[link.index].flags & (NodeFlag::Ladder | PathFlag::Jump)) {
continue;
}
if (graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f && !isOccupiedNode (link.index)) {
m_pathWalk.first () = link.index;
return true;
}
}
@ -2520,6 +2507,9 @@ bool Bot::advanceMovement () {
// is there a jump node right ahead and do we need to draw out the light weapon ?
if (willJump && !usesKnife () && m_currentWeapon != Weapon::Scout && !m_isReloading && !usesPistol () && (m_jumpDistance > 175.0f || (dst.z - 32.0f > src.z && m_jumpDistance > 135.0f)) && !(m_states & Sense::SeeingEnemy)) {
selectWeaponById (Weapon::Knife); // draw out the knife if we needed
// mark as switched
m_switchedToKnifeDuringJump = true;
}
// bot not already on ladder but will be soon?
@ -3100,16 +3090,13 @@ int Bot::getRandomCampDir () {
float distTab[kMaxNodesToSearch] {};
uint16_t visibility[kMaxNodesToSearch] {};
int currentNode = m_currentNodeIndex;
for (int i = 0; i < graph.length (); ++i) {
if (currentNode == i || !graph.isVisible (currentNode, i)) {
for (const auto &path : graph) {
if (m_currentNodeIndex == path.number || !graph.isVisible (m_currentNodeIndex, path.number)) {
continue;
}
const auto &path = graph[i];
if (count < kMaxNodesToSearch) {
indices[count] = i;
indices[count] = path.number;
distTab[count] = pev->origin.distanceSq (path.origin);
visibility[count] = path.vis.crouch + path.vis.stand;
@ -3122,7 +3109,7 @@ int Bot::getRandomCampDir () {
for (int j = 0; j < kMaxNodesToSearch; ++j) {
if (visBits >= visibility[j] && distance > distTab[j]) {
indices[j] = i;
indices[j] = path.number;
distTab[j] = distance;
visibility[j] = visBits;
@ -3179,12 +3166,12 @@ void Bot::updateLookAngles () {
accelerate += 600.0f;
}
stiffness += 100.0f;
damping += 5.0f;
damping -= 5.0f;
}
m_idealAngles = pev->v_angle;
float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x);
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x);
const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) {
m_lookYawVel = 0.0f;
@ -3332,11 +3319,6 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
continue;
}
// just in case, if something happend, and we're not updated yet
if (!util.isAlive (client.ent)) {
continue;
}
// do not check clients far away from us
if (pev->origin.distanceSq (client.origin) > cr::square (320.0f)) {
continue;
@ -3347,7 +3329,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
}
auto length = client.origin.distanceSq (graph[index].origin);
if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (80.0f), cr::square (140.0f))) {
if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (40.0f), cr::square (90.0f))) {
return true;
}
auto bot = bots[client.ent];
@ -3355,7 +3337,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
if (bot == nullptr || bot == this || !bot->m_notKilled) {
continue;
}
return index == bot->m_currentNodeIndex || bot->getTask ()->data == index;
return bot->m_currentNodeIndex == index;
}
return false;
}