nav: improve player avoidance once more
nav: try to repath our ways if stuck with other bot combat: a little improvement in knife usage control: enable/disable regame's round infinite when editing graph chatlib: replace say/say_team for older hlds to fix buffer overruns in gamelib Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
This commit is contained in:
parent
d965d7677f
commit
30013702c7
10 changed files with 181 additions and 23 deletions
|
|
@ -32,7 +32,8 @@ CR_DECLARE_SCOPED_ENUM (NetMsg,
|
||||||
Fashlight = 21,
|
Fashlight = 21,
|
||||||
ItemStatus = 22,
|
ItemStatus = 22,
|
||||||
ScoreInfo = 23,
|
ScoreInfo = 23,
|
||||||
ScoreAttrib = 24
|
ScoreAttrib = 24,
|
||||||
|
SayText = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// vgui menus (since latest steam updates is obsolete, but left for old cs)
|
// vgui menus (since latest steam updates is obsolete, but left for old cs)
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ private:
|
||||||
bool m_defuseNotified {}; // bot is notified about bomb defusion
|
bool m_defuseNotified {}; // bot is notified about bomb defusion
|
||||||
bool m_jumpSequence {}; // next path link will be jump link
|
bool m_jumpSequence {}; // next path link will be jump link
|
||||||
bool m_checkFall {}; // check bot fall
|
bool m_checkFall {}; // check bot fall
|
||||||
|
bool m_botMovement {}; // bot movement allowed ?
|
||||||
|
|
||||||
PathWalk m_pathWalk {}; // pointer to current node from path
|
PathWalk m_pathWalk {}; // pointer to current node from path
|
||||||
Dodge m_dodgeStrafeDir {}; // direction to strafe
|
Dodge m_dodgeStrafeDir {}; // direction to strafe
|
||||||
|
|
@ -367,6 +368,7 @@ private:
|
||||||
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
|
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
|
||||||
CountdownTimer m_lostReachableNodeTimer {}; // bot's issuing next node, probably he's lost
|
CountdownTimer m_lostReachableNodeTimer {}; // bot's issuing next node, probably he's lost
|
||||||
CountdownTimer m_fixFallTimer {}; // timer we're fixed fall last time
|
CountdownTimer m_fixFallTimer {}; // timer we're fixed fall last time
|
||||||
|
CountdownTimer m_repathTimer {}; // bots is going to repath his route
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int pickBestWeapon (Array <int> &vec, int moneySave);
|
int pickBestWeapon (Array <int> &vec, int moneySave);
|
||||||
|
|
@ -747,6 +749,7 @@ public:
|
||||||
void clearTasks ();
|
void clearTasks ();
|
||||||
void dropWeaponForUser (edict_t *user, bool discardC4);
|
void dropWeaponForUser (edict_t *user, bool discardC4);
|
||||||
void sendToChat (StringRef message, bool teamOnly);
|
void sendToChat (StringRef message, bool teamOnly);
|
||||||
|
void sendToChatLegacy (StringRef message, bool teamOnly);
|
||||||
void pushChatMessage (int type, bool isTeamSay = false);
|
void pushChatMessage (int type, bool isTeamSay = false);
|
||||||
void pushRadioMessage (int message);
|
void pushRadioMessage (int message);
|
||||||
void pushChatterMessage (int message);
|
void pushChatterMessage (int message);
|
||||||
|
|
|
||||||
|
|
@ -924,7 +924,7 @@ void Bot::instantChatter (int type) {
|
||||||
msg.start (MSG_ONE, msgs.id (NetMsg::SendAudio), nullptr, client.ent); // begin message
|
msg.start (MSG_ONE, msgs.id (NetMsg::SendAudio), nullptr, client.ent); // begin message
|
||||||
msg.writeByte (ownIndex);
|
msg.writeByte (ownIndex);
|
||||||
|
|
||||||
if (pev->deadflag & DEAD_DYING) {
|
if (pev->deadflag == DEAD_DYING) {
|
||||||
client.iconTimestamp[ownIndex] = game.time () + painSound.duration;
|
client.iconTimestamp[ownIndex] = game.time () + painSound.duration;
|
||||||
writeChatterSound (painSound);
|
writeChatterSound (painSound);
|
||||||
}
|
}
|
||||||
|
|
@ -1696,34 +1696,40 @@ void Bot::overrideConditions () {
|
||||||
// then start escape from bomb immediate
|
// then start escape from bomb immediate
|
||||||
startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true);
|
startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true);
|
||||||
}
|
}
|
||||||
constexpr float kReachEnemyWikKnifeDistanceSq = cr::sqrf (102.0f);
|
float reachEnemyWikKnifeDistanceSq = cr::sqrf (128.0f);
|
||||||
|
|
||||||
// special handling, if we have a knife in our hands
|
// special handling, if we have a knife in our hands
|
||||||
if (isKnifeMode () && (util.isPlayer (m_enemy) || (cv_attack_monsters && util.isMonster (m_enemy)))) {
|
if (isKnifeMode () && (util.isPlayer (m_enemy) || (cv_attack_monsters && util.isMonster (m_enemy)))) {
|
||||||
const float distanceSq2d = pev->origin.distanceSq2d (m_enemy->v.origin);
|
const auto distanceSq2d = pev->origin.distanceSq2d (m_enemy->v.origin);
|
||||||
|
const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
|
||||||
|
|
||||||
|
if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex) {
|
||||||
|
reachEnemyWikKnifeDistanceSq = graph[nearestToEnemyPoint].origin.distanceSq (m_enemy->v.origin);
|
||||||
|
reachEnemyWikKnifeDistanceSq += cr::sqrf (48.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// do nodes movement if enemy is not reachable with a knife
|
// do nodes movement if enemy is not reachable with a knife
|
||||||
if (distanceSq2d > kReachEnemyWikKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) {
|
if (distanceSq2d > reachEnemyWikKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) {
|
||||||
const int nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
|
|
||||||
|
|
||||||
if (nearestToEnemyPoint != kInvalidNodeIndex
|
if (nearestToEnemyPoint != kInvalidNodeIndex
|
||||||
&& nearestToEnemyPoint != m_currentNodeIndex
|
&& nearestToEnemyPoint != m_currentNodeIndex
|
||||||
&& cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
|
&& cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
|
||||||
|
|
||||||
|
const float taskTime = game.time () + distanceSq2d / cr::sqrf (m_moveSpeed) * 2.0f;
|
||||||
|
|
||||||
if (tid != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) {
|
if (tid != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) {
|
||||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + distanceSq2d / cr::sqrf (m_moveSpeed) * 2.0f, true);
|
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) {
|
if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) {
|
||||||
clearTask (Task::MoveToPosition);
|
clearTask (Task::MoveToPosition);
|
||||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + distanceSq2d / cr::sqrf (m_moveSpeed) * 2.0f, true);
|
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!m_isCreature
|
if (!m_isCreature
|
||||||
&& distanceSq2d <= kReachEnemyWikKnifeDistanceSq
|
&& distanceSq2d <= reachEnemyWikKnifeDistanceSq
|
||||||
&& (m_states & Sense::SeeingEnemy)
|
&& (m_states & Sense::SeeingEnemy)
|
||||||
&& tid == Task::MoveToPosition) {
|
&& tid == Task::MoveToPosition) {
|
||||||
|
|
||||||
|
|
@ -2364,7 +2370,7 @@ bool Bot::reactOnEnemy () {
|
||||||
if (m_isCreature && !game.isNullEntity (m_enemy)) {
|
if (m_isCreature && !game.isNullEntity (m_enemy)) {
|
||||||
m_isEnemyReachable = false;
|
m_isEnemyReachable = false;
|
||||||
|
|
||||||
if (pev->origin.distanceSq (m_enemy->v.origin) < cr::sqrf (128.0f)) {
|
if (pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (118.0f)) {
|
||||||
m_navTimeset = game.time ();
|
m_navTimeset = game.time ();
|
||||||
m_isEnemyReachable = true;
|
m_isEnemyReachable = true;
|
||||||
}
|
}
|
||||||
|
|
@ -2971,7 +2977,9 @@ void Bot::frame () {
|
||||||
|
|
||||||
// run bot command on twice speed
|
// run bot command on twice speed
|
||||||
if (m_commandDelay.time <= timestamp) {
|
if (m_commandDelay.time <= timestamp) {
|
||||||
runMovement ();
|
if (m_botMovement || pev->deadflag == DEAD_DYING) {
|
||||||
|
runMovement ();
|
||||||
|
}
|
||||||
m_commandDelay.time = timestamp + m_commandDelay.interval;
|
m_commandDelay.time = timestamp + m_commandDelay.interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3036,7 +3044,7 @@ void Bot::update () {
|
||||||
m_isCreature = isCreature ();
|
m_isCreature = isCreature ();
|
||||||
|
|
||||||
// is bot movement enabled
|
// is bot movement enabled
|
||||||
bool botMovement = false;
|
m_botMovement = false;
|
||||||
|
|
||||||
// for some unknown reason some bots have speed of 1.0 after respawn on csdm
|
// for some unknown reason some bots have speed of 1.0 after respawn on csdm
|
||||||
if (game.is (GameFlags::CSDM) && cr::fequal (pev->maxspeed, 1.0f) && tid == Task::Normal) {
|
if (game.is (GameFlags::CSDM) && cr::fequal (pev->maxspeed, 1.0f) && tid == Task::Normal) {
|
||||||
|
|
@ -3077,17 +3085,17 @@ void Bot::update () {
|
||||||
&& !cv_freeze_bots
|
&& !cv_freeze_bots
|
||||||
&& !graph.hasChanged ()) {
|
&& !graph.hasChanged ()) {
|
||||||
|
|
||||||
botMovement = true;
|
m_botMovement = true;
|
||||||
}
|
}
|
||||||
checkMsgQueue ();
|
checkMsgQueue ();
|
||||||
|
|
||||||
if (!m_isStale && botMovement) {
|
if (!m_isStale && m_botMovement) {
|
||||||
logic (); // execute main code
|
logic (); // execute main code
|
||||||
}
|
}
|
||||||
else if (pev->maxspeed < 10.0f) {
|
else if (pev->maxspeed < 10.0f) {
|
||||||
logicDuringFreezetime ();
|
logicDuringFreezetime ();
|
||||||
}
|
}
|
||||||
else if (!botMovement) {
|
else if (!m_botMovement) {
|
||||||
resetMovement ();
|
resetMovement ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4278,6 +4286,7 @@ float Bot::getShiftSpeed () {
|
||||||
if (getCurrentTaskId () == Task::SeekCover
|
if (getCurrentTaskId () == Task::SeekCover
|
||||||
|| (m_aimFlags & AimFlags::Enemy)
|
|| (m_aimFlags & AimFlags::Enemy)
|
||||||
|| isDucking ()
|
|| isDucking ()
|
||||||
|
|| m_isCreature
|
||||||
|| (pev->button & IN_DUCK)
|
|| (pev->button & IN_DUCK)
|
||||||
|| (m_oldButtons & IN_DUCK)
|
|| (m_oldButtons & IN_DUCK)
|
||||||
|| (m_currentTravelFlags & PathFlag::Jump)
|
|| (m_currentTravelFlags & PathFlag::Jump)
|
||||||
|
|
|
||||||
|
|
@ -249,8 +249,14 @@ void Bot::prepareChatMessage (StringRef message) {
|
||||||
size_t replaceCounter = 0;
|
size_t replaceCounter = 0;
|
||||||
|
|
||||||
while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::InvalidIndex) {
|
while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::InvalidIndex) {
|
||||||
|
const auto replacePosition = pos + 1;
|
||||||
|
|
||||||
|
if (replacePosition > m_chatBuffer.length ()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// found one, let's do replace
|
// found one, let's do replace
|
||||||
switch (m_chatBuffer[pos + 1]) {
|
switch (m_chatBuffer[replacePosition]) {
|
||||||
|
|
||||||
// the highest frag player
|
// the highest frag player
|
||||||
case 'f':
|
case 'f':
|
||||||
|
|
@ -294,6 +300,7 @@ void Bot::prepareChatMessage (StringRef message) {
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
m_chatBuffer.replace ("%g", graph.getAuthor ());
|
m_chatBuffer.replace ("%g", graph.getAuthor ());
|
||||||
|
break;
|
||||||
};
|
};
|
||||||
++replaceCounter;
|
++replaceCounter;
|
||||||
}
|
}
|
||||||
|
|
@ -375,11 +382,97 @@ void Bot::checkForChat () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Bot::sendToChat (StringRef message, bool teamOnly) {
|
void Bot::sendToChat (StringRef message, bool teamOnly) {
|
||||||
// this function prints saytext message to all players
|
// this function prints saytext message to all players
|
||||||
|
|
||||||
if (m_isCreature || message.empty () || !cv_chat) {
|
if (m_isCreature || message.empty () || !cv_chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
issueCommand ("%s \"%s\"", teamOnly ? "say_team" : "say", message);
|
|
||||||
|
// special handling for legacy games
|
||||||
|
if (game.is (GameFlags::Legacy)) {
|
||||||
|
sendToChatLegacy (message, teamOnly);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueCommand ("%s \"%s\"", teamOnly ? "say_team" : "say", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bot::sendToChatLegacy (StringRef message, bool teamOnly) {
|
||||||
|
// this function prints saytext message to all players for legacy games (< cs 1.6)
|
||||||
|
|
||||||
|
// note: for some reason using regular say & say_team for sending chat messages on hlds on a legacy games
|
||||||
|
// causes buffer overruns somewhere in gamedll Host_Say function, thus crashing the game randomly.
|
||||||
|
// so this function mimics what legacy gamedll is doing in their Host_Say.
|
||||||
|
|
||||||
|
bool dedicatedSend = false;
|
||||||
|
|
||||||
|
auto sendChatMsg = [&] (const Client &client, String chatMsg) {
|
||||||
|
auto rcv = bots[client.ent];
|
||||||
|
|
||||||
|
if (rcv != nullptr) {
|
||||||
|
rcv->m_sayTextBuffer.entityIndex = m_index;
|
||||||
|
|
||||||
|
rcv->m_sayTextBuffer.sayText = message;
|
||||||
|
rcv->m_sayTextBuffer.timeNextChat = game.time () + rcv->m_sayTextBuffer.chatDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((client.flags & ClientFlags::Alive) && m_isAlive)
|
||||||
|
|| (!(client.flags & ClientFlags::Alive) && m_isAlive)
|
||||||
|
|| (!(client.flags & ClientFlags::Alive) && !m_isAlive)) {
|
||||||
|
|
||||||
|
MessageWriter (MSG_ONE, msgs.id (NetMsg::SayText), nullptr, client.ent)
|
||||||
|
.writeByte (m_index)
|
||||||
|
.writeString (chatMsg.chars ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.isDedicated () && !dedicatedSend) {
|
||||||
|
game.print ("%s", chatMsg.trim ());
|
||||||
|
}
|
||||||
|
dedicatedSend = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (teamOnly) {
|
||||||
|
StringRef teamName {};
|
||||||
|
|
||||||
|
if (m_team == Team::Terrorist) {
|
||||||
|
teamName = "(Terrorist)";
|
||||||
|
}
|
||||||
|
else if (m_team == Team::CT) {
|
||||||
|
teamName = "(Counter-Terrorist)";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &client : util.getClients ()) {
|
||||||
|
if (!(client.flags & ClientFlags::Used) || client.team2 != m_team || client.ent == ent ()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String chatMsg {};
|
||||||
|
|
||||||
|
if (m_isAlive) {
|
||||||
|
chatMsg.appendf ("%c%s %c%s%c : %s\n", 0x01, teamName, 0x03, pev->netname.chars (), 0x01, message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
chatMsg.appendf ("%c*DEAD*%s %c%s%c : %s\n", 0x01, teamName, 0x03, pev->netname.chars (), 0x01, message);
|
||||||
|
}
|
||||||
|
sendChatMsg (client, chatMsg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &client : util.getClients ()) {
|
||||||
|
if (!(client.flags & ClientFlags::Used) || client.ent == ent ()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String chatMsg {};
|
||||||
|
|
||||||
|
if (m_isAlive) {
|
||||||
|
chatMsg.appendf ("%c%s : %s\n", 0x02, pev->netname.chars (), message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
chatMsg.appendf ("%c*DEAD* %c%s%c : %s\n", 0x01, 0x03, pev->netname.chars (), 0x01, message);
|
||||||
|
}
|
||||||
|
sendChatMsg (client, chatMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1520,6 +1520,10 @@ void Bot::attackMovement () {
|
||||||
|
|
||||||
if (usesKnife () && isEnemyCone) {
|
if (usesKnife () && isEnemyCone) {
|
||||||
m_fightStyle = Fight::Strafe;
|
m_fightStyle = Fight::Strafe;
|
||||||
|
|
||||||
|
if (distanceSq > cr::sqrf (100.0f)) {
|
||||||
|
m_fightStyle = Fight::None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_fightStyle == Fight::Strafe) {
|
if (m_fightStyle == Fight::Strafe) {
|
||||||
|
|
@ -1584,7 +1588,7 @@ void Bot::attackMovement () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not move if inside "corridor"
|
// do not move if inside "corridor"
|
||||||
if (wallOnRight && wallOnLeft) {
|
if (wallOnRight && wallOnLeft && !usesKnife ()) {
|
||||||
m_strafeSpeed = 0.0f;
|
m_strafeSpeed = 0.0f;
|
||||||
m_moveSpeed = 0.0f;
|
m_moveSpeed = 0.0f;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,14 @@ int BotControl::cmdNodeOn () {
|
||||||
mp_roundtime.set (9);
|
mp_roundtime.set (9);
|
||||||
mp_freezetime.set (0);
|
mp_freezetime.set (0);
|
||||||
mp_timelimit.set (0);
|
mp_timelimit.set (0);
|
||||||
|
|
||||||
|
if (game.is (GameFlags::ReGameDLL)) {
|
||||||
|
ConVarRef mp_round_infinite ("mp_round_infinite");
|
||||||
|
|
||||||
|
if (mp_round_infinite.exists ()) {
|
||||||
|
mp_round_infinite.set ("1");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BotCommandResult::Handled;
|
return BotCommandResult::Handled;
|
||||||
}
|
}
|
||||||
|
|
@ -527,6 +535,13 @@ int BotControl::cmdNodeOff () {
|
||||||
mp_freezetime.set (m_graphSaveVarValues.freezetime);
|
mp_freezetime.set (m_graphSaveVarValues.freezetime);
|
||||||
mp_timelimit.set (m_graphSaveVarValues.timelimit);
|
mp_timelimit.set (m_graphSaveVarValues.timelimit);
|
||||||
|
|
||||||
|
if (game.is (GameFlags::ReGameDLL)) {
|
||||||
|
ConVarRef mp_round_infinite ("mp_round_infinite");
|
||||||
|
|
||||||
|
if (mp_round_infinite.exists ()) {
|
||||||
|
mp_round_infinite.set ("0");
|
||||||
|
}
|
||||||
|
}
|
||||||
msg ("Graph editor has been disabled.");
|
msg ("Graph editor has been disabled.");
|
||||||
}
|
}
|
||||||
else if (arg <StringRef> (option) == "models") {
|
else if (arg <StringRef> (option) == "models") {
|
||||||
|
|
|
||||||
|
|
@ -1409,6 +1409,8 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
||||||
// mark bot as "spawned", and reset it to new-round state when it dead (for csdm/zombie only)
|
// mark bot as "spawned", and reset it to new-round state when it dead (for csdm/zombie only)
|
||||||
if (victimBot != nullptr) {
|
if (victimBot != nullptr) {
|
||||||
victimBot->spawned ();
|
victimBot->spawned ();
|
||||||
|
|
||||||
|
victimBot->m_isAlive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is this message about a bot who killed somebody?
|
// is this message about a bot who killed somebody?
|
||||||
|
|
@ -1559,6 +1561,7 @@ void Bot::newRound () {
|
||||||
m_forgetLastVictimTimer.invalidate ();
|
m_forgetLastVictimTimer.invalidate ();
|
||||||
m_lostReachableNodeTimer.invalidate ();
|
m_lostReachableNodeTimer.invalidate ();
|
||||||
m_fixFallTimer.invalidate ();
|
m_fixFallTimer.invalidate ();
|
||||||
|
m_repathTimer.invalidate ();
|
||||||
|
|
||||||
for (auto &timer : m_chatterTimes) {
|
for (auto &timer : m_chatterTimes) {
|
||||||
timer = kMaxChatterRepeatInterval;
|
timer = kMaxChatterRepeatInterval;
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,7 @@ MessageDispatcher::MessageDispatcher () {
|
||||||
// we're need next messages IDs but we're won't handle them, so they will be removed from wanted list as soon as they get engine IDs
|
// we're need next messages IDs but we're won't handle them, so they will be removed from wanted list as soon as they get engine IDs
|
||||||
addWanted ("BotVoice", NetMsg::BotVoice, nullptr);
|
addWanted ("BotVoice", NetMsg::BotVoice, nullptr);
|
||||||
addWanted ("SendAudio", NetMsg::SendAudio, nullptr);
|
addWanted ("SendAudio", NetMsg::SendAudio, nullptr);
|
||||||
|
addWanted ("SayText", NetMsg::SayText, nullptr);
|
||||||
|
|
||||||
// register text msg cache
|
// register text msg cache
|
||||||
m_textMsgCache["#CTs_Win"] = TextMsgCache::NeedHandle | TextMsgCache::CounterWin;
|
m_textMsgCache["#CTs_Win"] = TextMsgCache::NeedHandle | TextMsgCache::CounterWin;
|
||||||
|
|
|
||||||
|
|
@ -476,8 +476,6 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
m_hindrance = nullptr;
|
m_hindrance = nullptr;
|
||||||
float distanceSq = cr::sqrf (pev->maxspeed);
|
float distanceSq = cr::sqrf (pev->maxspeed);
|
||||||
|
|
||||||
const auto ownPrio = bots.getPlayerPriority (ent ());
|
|
||||||
|
|
||||||
auto clearCamp = [&] (edict_t *ent) {
|
auto clearCamp = [&] (edict_t *ent) {
|
||||||
auto bot = bots[ent];
|
auto bot = bots[ent];
|
||||||
|
|
||||||
|
|
@ -496,6 +494,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const auto ownPrio = bots.getPlayerPriority (ent ());
|
||||||
|
|
||||||
// find nearest player to bot
|
// find nearest player to bot
|
||||||
for (const auto &client : util.getClients ()) {
|
for (const auto &client : util.getClients ()) {
|
||||||
|
|
@ -532,6 +531,27 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||||
if (game.isNullEntity (m_hindrance)) {
|
if (game.isNullEntity (m_hindrance)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we're stuck with a hindrance, probably we're in bad place, find path to single goal for both bots
|
||||||
|
if (m_isStuck) {
|
||||||
|
auto other = bots[m_hindrance];
|
||||||
|
|
||||||
|
if (other != nullptr) {
|
||||||
|
m_prevGoalIndex = other->m_prevGoalIndex;
|
||||||
|
m_chosenGoalIndex = other->m_chosenGoalIndex;
|
||||||
|
|
||||||
|
auto destIndex = m_chosenGoalIndex;
|
||||||
|
|
||||||
|
if (!graph.exists (destIndex)) {
|
||||||
|
destIndex = m_prevGoalIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graph.exists (destIndex)) {
|
||||||
|
findPath (m_currentNodeIndex, destIndex, other->m_pathType);
|
||||||
|
other->findPath (m_currentNodeIndex, destIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 6.0f : 2.0f);
|
const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 6.0f : 2.0f);
|
||||||
|
|
||||||
// use our movement angles, try to predict where we should be next frame
|
// use our movement angles, try to predict where we should be next frame
|
||||||
|
|
@ -576,8 +596,11 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
||||||
|
|
||||||
const auto tid = getCurrentTaskId ();
|
const auto tid = getCurrentTaskId ();
|
||||||
|
|
||||||
|
// minimal speed for consider stuck
|
||||||
|
const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4;
|
||||||
|
|
||||||
// standing still, no need to check?
|
// standing still, no need to check?
|
||||||
if ((m_moveSpeed >= 10 || m_strafeSpeed >= 10)
|
if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed)
|
||||||
&& m_lastCollTime < game.time ()
|
&& m_lastCollTime < game.time ()
|
||||||
&& tid != Task::Attack
|
&& tid != Task::Attack
|
||||||
&& tid != Task::Camp) {
|
&& tid != Task::Camp) {
|
||||||
|
|
@ -1327,6 +1350,11 @@ bool Bot::updateNavigation () {
|
||||||
desiredDistanceSq = 0.0f;
|
desiredDistanceSq = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if just recalculated path, assume reached current node
|
||||||
|
if (!m_repathTimer.elapsed () && !pathHasFlags) {
|
||||||
|
desiredDistanceSq = cr::sqrf (72.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// needs precise placement - check if we get past the point
|
// needs precise placement - check if we get past the point
|
||||||
if (desiredDistanceSq < cr::sqrf (20.0f) && nodeDistanceSq < cr::sqrf (30.0f)) {
|
if (desiredDistanceSq < cr::sqrf (20.0f) && nodeDistanceSq < cr::sqrf (30.0f)) {
|
||||||
const auto predictRangeSq = m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval);
|
const auto predictRangeSq = m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval);
|
||||||
|
|
@ -3492,6 +3520,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
|
||||||
}
|
}
|
||||||
clearSearchNodes ();
|
clearSearchNodes ();
|
||||||
|
|
||||||
|
m_repathTimer.start (0.5f);
|
||||||
m_chosenGoalIndex = srcIndex;
|
m_chosenGoalIndex = srcIndex;
|
||||||
m_goalValue = 0.0f;
|
m_goalValue = 0.0f;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -793,7 +793,7 @@ void Bot::moveToPos_ () {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ensureDestIndexOK = [&] (int &index) {
|
auto ensureDestIndexOK = [&] (int &index) {
|
||||||
if (isOccupiedNode (index)) {
|
if (!m_position.empty () && isOccupiedNode (index)) {
|
||||||
index = findDefendNode (m_position);
|
index = findDefendNode (m_position);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue