Many small fixes to combat behaviour, navigation and perfomance.

This commit is contained in:
jeefo 2019-08-18 21:00:00 +03:00
commit f673f5cd0a
26 changed files with 1447 additions and 1330 deletions

View file

@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
combat.cpp \
control.cpp \
engine.cpp \
interface.cpp \
linkage.cpp \
navigate.cpp \
support.cpp \
graph.cpp \

File diff suppressed because it is too large Load diff

View file

@ -19,11 +19,11 @@ void BotUtils::stripTags (String &line) {
for (const auto &tag : m_tags) {
const size_t start = line.find (tag.first, 0);
if (start != String::kInvalidIndex) {
if (start != String::InvalidIndex) {
const size_t end = line.find (tag.second, start);
const size_t diff = end - start;
if (end != String::kInvalidIndex && end > start && diff < 32 && diff > 4) {
if (end != String::InvalidIndex && end > start && diff < 32 && diff > 2) {
line.erase (start, diff + tag.second.length ());
break;
}
@ -84,7 +84,7 @@ bool BotUtils::checkKeywords (const String &line, String &reply) {
for (const auto &keyword : factory.keywords) {
// check is keyword has occurred in message
if (line.find (keyword) != String::kInvalidIndex) {
if (line.find (keyword) != String::InvalidIndex) {
StringArray &usedReplies = factory.usedReplies;
if (usedReplies.length () >= factory.replies.length () / 4) {
@ -140,7 +140,7 @@ void Bot::prepareChatMessage (const String &message) {
size_t pos = message.find ('%');
// nothing found, bail out
if (pos == String::kInvalidIndex || pos >= message.length ()) {
if (pos == String::InvalidIndex || pos >= message.length ()) {
finishPreparation ();
return;
}
@ -181,7 +181,7 @@ void Bot::prepareChatMessage (const String &message) {
// get roundtime
auto getRoundTime = [] () -> String {
auto roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.timebase ());
auto roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.time ());
String roundTime;
roundTime.assignf ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59));
@ -235,7 +235,7 @@ void Bot::prepareChatMessage (const String &message) {
};
size_t replaceCounter = 0;
while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::kInvalidIndex) {
while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::InvalidIndex) {
// found one, let's do replace
switch (m_chatBuffer[pos + 1]) {
@ -295,7 +295,7 @@ bool Bot::isReplyingToChat () {
if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) {
// check is time to chat is good
if (m_sayTextBuffer.timeNextChat < game.timebase () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) {
if (m_sayTextBuffer.timeNextChat < game.time () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) {
String replyText;
if (rg.chance (m_sayTextBuffer.chatProbability + rg.int_ (20, 50)) && checkChatKeywords (replyText)) {
@ -303,7 +303,7 @@ bool Bot::isReplyingToChat () {
pushMsgQueue (BotMsg::Say);
m_sayTextBuffer.entityIndex = -1;
m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay;
m_sayTextBuffer.timeNextChat = game.time () + m_sayTextBuffer.chatDelay;
m_sayTextBuffer.sayText.clear ();
return true;
@ -323,7 +323,7 @@ void Bot::checkForChat () {
}
// bot chatting turned on?
if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.timebase () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.timebase () && !isReplyingToChat ()) {
if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.time () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.time () && !isReplyingToChat ()) {
if (conf.hasChatBank (Chat::Dead)) {
const auto &phrase = conf.pickRandomFromChatBank (Chat::Dead);
bool sayBufferExists = false;
@ -340,8 +340,8 @@ void Bot::checkForChat () {
prepareChatMessage (phrase);
pushMsgQueue (BotMsg::Say);
m_lastChatTime = game.timebase ();
bots.setLastChatTimestamp (game.timebase ());
m_lastChatTime = game.time ();
bots.setLastChatTimestamp (game.time ());
// add to ignore list
m_sayTextBuffer.lastUsedSentences.push (phrase);

View file

@ -92,7 +92,7 @@ bool Bot::checkBodyParts (edict_t *target) {
if (isEnemyHidden (target)) {
m_enemyParts = Visibility::None;
m_enemyOrigin = nullvec;
m_enemyOrigin = nullptr;
return false;
}
@ -178,7 +178,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
}
if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player)) {
m_seeEnemyTime = game.timebase ();
m_seeEnemyTime = game.time ();
m_lastEnemy = player;
m_lastEnemyOrigin = m_enemyOrigin;
@ -187,11 +187,21 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
return false;
}
void Bot::trackEnemies () {
if (lookupEnemies ()) {
m_states |= Sense::SeeingEnemy;
}
else {
m_states &= ~Sense::SeeingEnemy;
m_enemy = nullptr;
}
}
bool Bot::lookupEnemies () {
// this function tries to find the best suitable enemy for the bot
// do not search for enemies while we're blinded, or shooting disabled by user
if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.bool_ ()) {
if (m_enemyIgnoreTimer > game.time () || m_blindTime > game.time () || yb_ignore_enemies.bool_ ()) {
return false;
}
edict_t *player, *newEnemy = nullptr;
@ -203,18 +213,18 @@ bool Bot::lookupEnemies () {
if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) {
m_states &= ~Sense::SuspectEnemy;
}
else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.timebase () && util.isAlive (m_lastEnemy)) {
else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.time () && util.isAlive (m_lastEnemy)) {
m_states |= Sense::SuspectEnemy;
m_aimFlags |= AimFlags::LastEnemy;
}
m_enemyParts = Visibility::None;
m_enemyOrigin= nullvec;
m_enemyOrigin= nullptr;
if (!game.isNullEntity (m_enemy)) {
player = m_enemy;
// is player is alive
if (m_enemyUpdateTime > game.timebase () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) {
if (m_enemyUpdateTime > game.time () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) {
newEnemy = player;
}
}
@ -262,7 +272,7 @@ bool Bot::lookupEnemies () {
}
}
}
m_enemyUpdateTime = cr::clamp (game.timebase () + getFrameInterval () * 25.0f, 0.5f, 0.75f);
m_enemyUpdateTime = cr::clamp (game.time () + getFrameInterval () * 25.0f, 0.5f, 0.75f);
if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) {
newEnemy = shieldEnemy;
@ -277,7 +287,7 @@ bool Bot::lookupEnemies () {
// if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy
if (newEnemy == m_enemy) {
m_seeEnemyTime = game.timebase ();
m_seeEnemyTime = game.time ();
// zero out reaction time
m_actualReactionTime = 0.0f;
@ -287,7 +297,7 @@ bool Bot::lookupEnemies () {
return true;
}
else {
if (m_seeEnemyTime + 3.0f < game.timebase () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) {
if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) {
pushRadioMessage (Radio::EnemySpotted);
}
m_targetEntity = nullptr; // stop following when we see an enemy...
@ -302,7 +312,7 @@ bool Bot::lookupEnemies () {
if (usesSniper ()) {
m_enemySurpriseTime *= 0.5f;
}
m_enemySurpriseTime += game.timebase ();
m_enemySurpriseTime += game.time ();
// zero out reaction time
m_actualReactionTime = 0.0f;
@ -312,7 +322,7 @@ bool Bot::lookupEnemies () {
m_enemyReachableTimer = 0.0f;
// keep track of when we last saw an enemy
m_seeEnemyTime = game.timebase ();
m_seeEnemyTime = game.time ();
if (!(m_oldButtons & IN_ATTACK)) {
return true;
@ -324,10 +334,10 @@ bool Bot::lookupEnemies () {
continue;
}
if (other->m_seeEnemyTime + 2.0f < game.timebase () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) {
if (other->m_seeEnemyTime + 2.0f < game.time () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) {
other->m_lastEnemy = newEnemy;
other->m_lastEnemyOrigin = m_lastEnemyOrigin;
other->m_seeEnemyTime = game.timebase ();
other->m_seeEnemyTime = game.time ();
other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy);
other->m_aimFlags |= AimFlags::LastEnemy;
}
@ -343,9 +353,9 @@ bool Bot::lookupEnemies () {
m_enemy = nullptr;
// shoot at dying players if no new enemy to give some more human-like illusion
if (m_seeEnemyTime + 0.3f > game.timebase ()) {
if (m_seeEnemyTime + 0.3f > game.time ()) {
if (!usesSniper ()) {
m_shootAtDeadTime = game.timebase () + 0.4f;
m_shootAtDeadTime = game.time () + 0.4f;
m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy;
@ -353,7 +363,7 @@ bool Bot::lookupEnemies () {
}
return false;
}
else if (m_shootAtDeadTime > game.timebase ()) {
else if (m_shootAtDeadTime > game.time ()) {
m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy;
@ -364,7 +374,7 @@ bool Bot::lookupEnemies () {
// if no enemy visible check if last one shoot able through wall
if (yb_shoots_thru_walls.bool_ () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) {
m_seeEnemyTime = game.timebase ();
m_seeEnemyTime = game.time ();
m_states |= Sense::SuspectEnemy;
m_aimFlags |= AimFlags::LastEnemy;
@ -378,14 +388,14 @@ bool Bot::lookupEnemies () {
}
// check if bots should reload...
if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.timebase () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) {
if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.time () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) {
if (!m_reloadState) {
m_reloadState = Reload::Primary;
}
}
// is the bot using a sniper rifle or a zoomable rifle?
if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.timebase ()) {
if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.time ()) {
if (pev->fov < 90.0f) {
pev->button |= IN_ATTACK2;
}
@ -398,15 +408,15 @@ bool Bot::lookupEnemies () {
Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy)) {
return nullvec;
return nullptr;
}
if (m_aimErrorTime < game.timebase ()) {
if (m_aimErrorTime < game.time ()) {
const float error = distance / (cr::clamp (m_difficulty, 1, 4) * 1000.0f);
Vector &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins;
m_aimLastError = Vector (rg.float_ (mins.x * error, maxs.x * error), rg.float_ (mins.y * error, maxs.y * error), rg.float_ (mins.z * error, maxs.z * error));
m_aimErrorTime = game.timebase () + rg.float_ (0.5f, 1.0f);
m_aimErrorTime = game.time () + rg.float_ (0.5f, 1.0f);
}
return m_aimLastError;
}
@ -434,15 +444,10 @@ const Vector &Bot::getEnemyBodyOffset () {
else if (distance < 800.0f && usesSniper ()) {
m_enemyParts &= ~Visibility::Head;
}
// do not aim at head while enemy is soooo close enough to enemy when recoil aims at head automatically
else if (distance < kSprayDistance) {
m_enemyParts &= ~Visibility::Head;
}
Vector aimPos = m_enemy->v.origin;
if (m_difficulty > 2 && !(m_enemyParts & Visibility::Other)) {
aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos;
if (m_difficulty > 2) {
aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.75f);
}
// if we only suspect an enemy behind a wall take the worst skill
@ -450,16 +455,22 @@ const Vector &Bot::getEnemyBodyOffset () {
aimPos += getBodyOffsetError (distance);
}
else {
bool useBody = !usesPistol () && distance > kSprayDistance && distance < 2048.0f;
// now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
int headshotFreq[5] = { 20, 40, 60, 80, 100 };
// now check is our skill match to aim at head, else aim at enemy body
if (rg.chance (headshotFreq[m_difficulty]) || usesPistol ()) {
if (rg.chance (headshotFreq[m_difficulty]) && !useBody) {
aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance);
}
else {
aimPos.z += getEnemyBodyOffsetCorrection (distance);
if (useBody) {
aimPos.z += 4.5f;
}
}
}
else if (m_enemyParts & Visibility::Body) {
@ -496,7 +507,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
float result = -2.0f;
if (distance < kSprayDistance) {
return -9.0f;
return -16.0f;
}
else if (distance >= kDoubleSprayDistance) {
if (sniper) {
@ -655,7 +666,7 @@ bool Bot::needToPauseFiring (float distance) {
return false;
}
if (m_firePause > game.timebase ()) {
if (m_firePause > game.time ()) {
return true;
}
@ -678,16 +689,16 @@ bool Bot::needToPauseFiring (float distance) {
const float xPunch = cr::degreesToRadians (pev->punchangle.x);
const float yPunch = cr::degreesToRadians (pev->punchangle.y);
float interval = getFrameInterval ();
float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f;
const float interval = getFrameInterval ();
const float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f;
// check if we need to compensate recoil
if (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) {
if (m_firePause < game.timebase ()) {
m_firePause = rg.float_ (0.5f, 0.5f + 0.3f * tolerance);
if (m_firePause < game.time ()) {
m_firePause = rg.float_ (0.65f, 0.65f + 0.3f * tolerance);
}
m_firePause -= interval;
m_firePause += game.timebase ();
m_firePause += game.time ();
return true;
}
@ -700,7 +711,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// we want to fire weapon, don't reload now
if (!m_isReloading) {
m_reloadState = Reload::None;
m_reloadCheckTime = game.timebase () + 3.0f;
m_reloadCheckTime = game.time () + 3.0f;
}
// select this weapon if it isn't already selected
@ -730,7 +741,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// if we're have a glock or famas vary burst fire mode
checkBurstMode (distance);
if (hasShield () && m_shieldCheckTime < game.timebase () && getCurrentTaskId () != Task::Camp) // better shield gun usage
if (hasShield () && m_shieldCheckTime < game.time () && getCurrentTaskId () != Task::Camp) // better shield gun usage
{
if (distance >= 750.0f && !isShieldDrawn ()) {
pev->button |= IN_ATTACK2; // draw the shield
@ -738,11 +749,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEntity (m_enemy->v.origin)))) {
pev->button |= IN_ATTACK2; // draw out the shield
}
m_shieldCheckTime = game.timebase () + 1.0f;
m_shieldCheckTime = game.time () + 1.0f;
}
// is the bot holding a sniper rifle?
if (usesSniper () && m_zoomCheckTime < game.timebase ()) {
if (usesSniper () && m_zoomCheckTime < game.time ()) {
// should the bot switch to the long-range zoom?
if (distance > 1500.0f && pev->fov >= 40.0f) {
pev->button |= IN_ATTACK2;
@ -757,11 +768,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (distance <= 150.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2;
}
m_zoomCheckTime = game.timebase () + 0.25f;
m_zoomCheckTime = game.time () + 0.25f;
}
// else is the bot holding a zoomable rifle?
else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.timebase ()) {
else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.time ()) {
// should the bot switch to zoomed mode?
if (distance > 800.0f && pev->fov >= 90.0f) {
pev->button |= IN_ATTACK2;
@ -771,23 +782,23 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (distance <= 800.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2;
}
m_zoomCheckTime = game.timebase () + 0.5f;
m_zoomCheckTime = game.time () + 0.5f;
}
// we're should stand still before firing sniper weapons, else sniping is useless..
if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) {
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_navTimeset = game.timebase ();
m_navTimeset = game.time ();
if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) {
m_sniperStopTime = game.timebase () + 2.5f;
m_sniperStopTime = game.time () + 2.5f;
return;
}
}
// need to care for burst fire?
if (distance < kSprayDistance || m_blindTime > game.timebase ()) {
if (distance < kSprayDistance || m_blindTime > game.time ()) {
if (id == Weapon::Knife) {
if (distance < 64.0f) {
if (rg.chance (30) || hasShield ()) {
@ -813,7 +824,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
}
}
}
m_shootTime = game.timebase ();
m_shootTime = game.time ();
}
else {
if (needToPauseFiring (distance)) {
@ -822,13 +833,13 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// don't attack with knife over long distance
if (id == Weapon::Knife) {
m_shootTime = game.timebase ();
m_shootTime = game.time ();
return;
}
if (tab[choosen].primaryFireHold) {
m_shootTime = game.timebase ();
m_zoomCheckTime = game.timebase ();
m_shootTime = game.time ();
m_zoomCheckTime = game.time ();
pev->button |= IN_ATTACK; // use primary attack
}
@ -840,21 +851,22 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
const int offset = cr::abs <int> (m_difficulty * 25 / 20 - 5);
m_shootTime = game.timebase () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]);
m_zoomCheckTime = game.timebase ();
m_shootTime = game.time () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]);
m_zoomCheckTime = game.time ();
}
}
}
void Bot::fireWeapons () {
// this function will return true if weapon was fired, false otherwise
float distance = (m_lookAt - getEyesPos ()).length (); // how far away is the enemy?
// or if friend in line of fire, stop this too but do not update shoot time
if (!game.isNullEntity (m_enemy)) {
if (isFriendInLineOfFire (distance)) {
m_fightStyle = Fight::Strafe;
m_lastFightStyleCheck = game.timebase ();
m_lastFightStyleCheck = game.time ();
return;
}
@ -905,10 +917,10 @@ void Bot::fireWeapons () {
if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.timebase ()) {
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
m_isReloading = true;
m_reloadState = Reload::Primary;
m_reloadCheckTime = game.timebase ();
m_reloadCheckTime = game.time ();
if (rg.chance (cr::abs (m_difficulty * 25 - 100))) {
pushRadioMessage (Radio::NeedBackup);
@ -957,10 +969,14 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
}
void Bot::focusEnemy () {
if (game.isNullEntity (m_enemy)) {
return;
}
// aim for the head and/or body
m_lookAt = getEnemyBodyOffset ();
if (m_enemySurpriseTime > game.timebase () || game.isNullEntity (m_enemy)) {
if (m_enemySurpriseTime > game.time ()) {
return;
}
float distance = (m_lookAt - getEyesPos ()).length2d (); // how far away is the enemy scum?
@ -1010,7 +1026,7 @@ void Bot::attackMovement () {
}
float distance = (m_lookAt - getEyesPos ()).length2d (); // how far away is the enemy scum?
if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.timebase ()) {
if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.time ()) {
int approach;
if (m_currentWeapon == Weapon::Knife) {
@ -1048,10 +1064,10 @@ void Bot::attackMovement () {
if (usesSniper () || !(m_enemyParts & (Visibility::Body | Visibility::Head))) {
m_fightStyle = Fight::Stay;
m_lastFightStyleCheck = game.timebase ();
m_lastFightStyleCheck = game.time ();
}
else if (usesRifle () || usesSubmachine ()) {
if (m_lastFightStyleCheck + 3.0f < game.timebase ()) {
if (m_lastFightStyleCheck + 3.0f < game.time ()) {
int rand = rg.int_ (1, 100);
if (distance < 450.0f) {
@ -1073,23 +1089,15 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe;
}
}
m_lastFightStyleCheck = game.timebase ();
m_lastFightStyleCheck = game.time ();
}
}
else {
if (m_lastFightStyleCheck + 3.0f < game.timebase ()) {
if (rg.chance (50)) {
m_fightStyle = Fight::Strafe;
}
else {
m_fightStyle = Fight::Stay;
}
m_lastFightStyleCheck = game.timebase ();
}
m_fightStyle = Fight::Strafe;
}
if (m_fightStyle == Fight::Strafe || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 400.0f) || m_currentWeapon == Weapon::Knife) {
if (m_strafeSetTime < game.timebase ()) {
if (m_strafeSetTime < game.time ()) {
// to start strafing, we have to first figure out if the target is on the left side or right side
const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d ();
@ -1105,7 +1113,7 @@ void Bot::attackMovement () {
if (rg.chance (30)) {
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
}
m_strafeSetTime = game.timebase () + rg.float_ (0.5f, 3.0f);
m_strafeSetTime = game.time () + rg.float_ (0.5f, 3.0f);
}
if (m_combatStrafeDir == Dodge::Right) {
@ -1114,7 +1122,7 @@ void Bot::attackMovement () {
}
else {
m_combatStrafeDir = Dodge::Left;
m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f);
m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f);
}
}
else {
@ -1123,11 +1131,11 @@ void Bot::attackMovement () {
}
else {
m_combatStrafeDir = Dodge::Right;
m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f);
m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f);
}
}
if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.timebase () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) {
if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) {
pev->button |= IN_JUMP;
}
@ -1144,32 +1152,24 @@ void Bot::attackMovement () {
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
m_duckTime = game.timebase () + 0.5f;
m_duckTime = game.time () + 0.64f;
}
}
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_navTimeset = game.timebase ();
m_navTimeset = game.time ();
}
}
if (m_duckTime > game.timebase ()) {
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
}
if (m_moveSpeed > 0.0f && m_currentWeapon != Weapon::Knife) {
m_moveSpeed = getShiftSpeed ();
}
if (m_isReloading) {
m_moveSpeed = -pev->maxspeed;
m_duckTime = game.timebase () - 1.0f;
if (m_fightStyle == Fight::Stay || (m_duckTime > game.time () || m_sniperStopTime > game.time ())) {
if (m_moveSpeed > 0.0f) {
m_moveSpeed = 0.0f;
}
}
if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) {
Vector right, forward;
pev->v_angle.buildVectors (&forward, &right, nullptr);
pev->v_angle.angleVectors (&forward, &right, nullptr);
if (isDeadlyMove (pev->origin + (forward * m_moveSpeed * 0.2f) + (right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) {
m_strafeSpeed = -m_strafeSpeed;
@ -1499,7 +1499,7 @@ void Bot::decideFollowUser () {
void Bot::updateTeamCommands () {
// prevent spamming
if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) {
if (m_timeTeamOrder > game.time () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) {
return;
}
@ -1534,7 +1534,7 @@ void Bot::updateTeamCommands () {
else if (memberExists && yb_radio_mode.int_ () == 2) {
pushChatterMessage (Chatter::ScaredEmotion);
}
m_timeTeamOrder = game.timebase () + rg.float_ (15.0f, 30.0f);
m_timeTeamOrder = game.time () + rg.float_ (15.0f, 30.0f);
}
bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) {
@ -1562,13 +1562,19 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius
void Bot::checkReload () {
// check the reload state
if (getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PickupItem || getCurrentTaskId () == Task::ThrowFlashbang || getCurrentTaskId () == Task::ThrowSmoke || m_isUsingGrenade) {
auto task = getCurrentTaskId ();
// we're should not reload, while doing next tasks
bool uninterruptibleTask = (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::PickupItem || task == Task::ThrowExplosive || task == Task::ThrowFlashbang || task == Task::ThrowSmoke);
// do not check for reload
if (uninterruptibleTask || m_isUsingGrenade) {
m_reloadState = Reload::None;
return;
}
m_isReloading = false; // update reloading status
m_reloadCheckTime = game.timebase () + 3.0f;
m_reloadCheckTime = game.time () + 3.0f;
if (m_reloadState != Reload::None) {
int weaponIndex = 0;
@ -1611,7 +1617,7 @@ void Bot::checkReload () {
}
else {
// if we have enemy don't reload next weapon
if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.timebase ()) {
if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.time ()) {
m_reloadState = Reload::None;
return;
}

View file

@ -26,15 +26,15 @@ int BotControl::cmdAddBot () {
}
// if team is specified, modify args to set team
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex) {
if (m_args[alias].find ("_ct", 0) != String::InvalidIndex) {
m_args.set (team, "2");
}
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex) {
else if (m_args[alias].find ("_t", 0) != String::InvalidIndex) {
m_args.set (team, "1");
}
// if highskilled bot is requsted set personality to rusher and maxout difficulty
if (m_args[alias].find ("hs", 0) != String::kInvalidIndex) {
if (m_args[alias].find ("hs", 0) != String::InvalidIndex) {
m_args.set (difficulty, "4");
m_args.set (personality, "1");
}
@ -50,10 +50,10 @@ int BotControl::cmdKickBot () {
fixMissingArgs (max);
// if team is specified, kick from specified tram
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.kickFromTeam (Team::CT);
}
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.kickFromTeam (Team::Terrorist);
}
else {
@ -84,10 +84,10 @@ int BotControl::cmdKillBots () {
fixMissingArgs (max);
// if team is specified, kick from specified tram
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.killAllBots (Team::CT);
}
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.killAllBots (Team::Terrorist);
}
else {
@ -638,13 +638,13 @@ int BotControl::cmdNodePathCreate () {
graph.setEditFlag (GraphEdit::On);
// choose the direction for path creation
if (m_args[cmd].find ("_both", 0) != String::kInvalidIndex) {
if (m_args[cmd].find ("_both", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Bidirectional);
}
else if (m_args[cmd].find ("_in", 0) != String::kInvalidIndex) {
else if (m_args[cmd].find ("_in", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Incoming);
}
else if (m_args[cmd].find ("_out", 0) != String::kInvalidIndex) {
else if (m_args[cmd].find ("_out", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Outgoing);
}
else {
@ -1052,17 +1052,20 @@ int BotControl::menuClassSelect (int item) {
int BotControl::menuCommands (int item) {
showMenu (Menu::None); // reset menu display
Bot *bot = nullptr;
Bot *nearest = nullptr;
switch (item) {
case 1:
case 2:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true) && bot->m_hasC4 && !bot->hasHostage ()) {
if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->hasHostage ()) {
if (item == 1) {
bot->startDoubleJump (m_ent);
m_djump->startDoubleJump (m_ent);
}
else {
bot->resetDoubleJump ();
if (m_djump) {
m_djump->resetDoubleJump ();
m_djump = nullptr;
}
}
}
showMenu (Menu::Commands);
@ -1070,8 +1073,8 @@ int BotControl::menuCommands (int item) {
case 3:
case 4:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) {
bot->dropWeaponForUser (m_ent, item == 4 ? false : true);
if (util.findNearestPlayer (reinterpret_cast <void **> (&nearest), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) {
nearest->dropWeaponForUser (m_ent, item == 4 ? false : true);
}
showMenu (Menu::Commands);
break;
@ -1641,7 +1644,7 @@ void BotControl::showMenu (int id) {
Client &client = util.getClient (game.indexOfPlayer (m_ent));
if (id == Menu::None) {
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent)
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (0)
.writeChar (0)
.writeByte (0)
@ -1657,7 +1660,7 @@ void BotControl::showMenu (int id) {
MessageWriter msg;
while (strlen (text) >= 64) {
msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent)
msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (display.slots)
.writeChar (-1)
.writeByte (1);
@ -1669,7 +1672,7 @@ void BotControl::showMenu (int id) {
text += 64;
}
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent)
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (display.slots)
.writeChar (-1)
.writeByte (0)
@ -1773,6 +1776,8 @@ void BotControl::maintainAdminRights () {
BotControl::BotControl () {
m_ent = nullptr;
m_djump = nullptr;
m_isFromConsole = false;
m_isMenuFillCommand = false;
m_rapidOutput = false;
@ -1789,22 +1794,22 @@ BotControl::BotControl () {
m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion);
m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu);
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
m_cmds.emplace ("graph/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
m_cmds.emplace ("graph/g/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
// declare the menus
createMenus ();
}
void BotControl::handleEngineCommands () {
ctrl.collectArgs ();
ctrl.setIssuer (game.getLocalEntity ());
collectArgs ();
setIssuer (game.getLocalEntity ());
ctrl.setFromConsole (true);
ctrl.executeCommands ();
setFromConsole (true);
executeCommands ();
}
bool BotControl::handleClientCommands (edict_t *ent) {
ctrl.collectArgs ();
collectArgs ();
setIssuer (ent);
setFromConsole (true);
@ -1812,7 +1817,7 @@ bool BotControl::handleClientCommands (edict_t *ent) {
}
bool BotControl::handleMenuCommands (edict_t *ent) {
ctrl.collectArgs ();
collectArgs ();
setIssuer (ent);
setFromConsole (false);
@ -1827,16 +1832,15 @@ void BotControl::enableDrawModels (bool enable) {
entities.push ("info_vip_start");
for (auto &entity : entities) {
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity.chars ()))) {
game.searchEntities ("classname", entity, [&enable] (edict_t *ent) {
if (enable) {
ent->v.effects &= ~EF_NODRAW;
}
else {
ent->v.effects |= EF_NODRAW;
}
}
return EntitySearchResult::Continue;
});
}
}

View file

@ -129,6 +129,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
else if (strncmp (classname, "func_door", 9) == 0) {
m_mapFlags |= MapFlags::HasDoors;
}
else if (strncmp (classname, "func_button", 11) == 0) {
m_mapFlags |= MapFlags::HasButtons;
}
}
// next maps doesn't have map-specific entities, so determine it by name
@ -152,7 +155,7 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w
return; // reliability check
}
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, ent)
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent)
.writeByte (TE_BEAMPOINTS)
.writeCoord (end.x)
.writeCoord (end.y)
@ -285,7 +288,7 @@ const char *Game::getModName () {
name = engineModName;
size_t slash = name.findLastOf ("\\/");
if (slash != String::kInvalidIndex) {
if (slash != String::InvalidIndex) {
name = name.substr (slash + 1);
}
name = name.trim (" \\/");
@ -303,7 +306,7 @@ Vector Game::getAbsPos (edict_t *ent) {
// entity that has a bounding box has its center at the center of the bounding box itself.
if (isNullEntity (ent)) {
return nullvec;
return nullptr;
}
if (ent->v.origin.empty ()) {
@ -312,7 +315,7 @@ Vector Game::getAbsPos (edict_t *ent) {
return ent->v.origin;
}
void Game::registerCmd (const char *command, void func ()) {
void Game::registerEngineCommand (const char *command, void func ()) {
// this function tells the engine that a new server command is being declared, in addition
// to the standard ones, whose name is command_name. The engine is thus supposed to be aware
// that for every "command_name" server command it receives, it should call the function
@ -378,7 +381,7 @@ uint8 *Game::getVisibilitySet (Bot *bot, bool pvs) {
void Game::sendClientMessage (bool console, edict_t *ent, const char *message) {
// helper to sending the client message
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, ent)
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, ent)
.writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER)
.writeString (message);
}
@ -409,7 +412,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
const size_t space = args.find (' ', 0);
// if found space
if (space != String::kInvalidIndex) {
if (space != String::InvalidIndex) {
const auto quote = space + 1; // check for quote next to space
// check if we're got a quoted string
@ -430,7 +433,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
m_botArgs.clear (); // clear space for next cmd
};
if (str.find (';', 0) != String::kInvalidIndex) {
if (str.find (';', 0) != String::InvalidIndex) {
for (auto &part : str.split (";")) {
parsePartArgs (part);
}
@ -629,20 +632,40 @@ bool Game::loadCSBinary () {
}
bool Game::postload () {
// ensure we're have all needed directories
const char *mod = getModName ();
// create the needed paths
File::createPath (strings.format ("%s/addons/yapb/conf/lang", mod));
File::createPath (strings.format ("%s/addons/yapb/data/learned", mod));
File::createPath (strings.format ("%s/addons/yapb/data/graph", mod));
File::createPath (strings.format ("%s/addons/yapb/data/logs", mod));
// ensure we're have all needed directories
for (const auto &dir : StringArray { "conf/lang", "data/learned", "data/graph", "data/logs" }) {
File::createPath (strings.format ("%s/addons/yapb/%s", getModName (), dir.chars ()));
}
// set out user agent for http stuff
http.setUserAgent (strings.format ("%s/%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION));
// register bot cvars
game.registerCvars ();
// register server command(s)
registerEngineCommand ("yapb", [] () {
ctrl.handleEngineCommands ();
});
registerEngineCommand ("yb", [] () {
ctrl.handleEngineCommands ();
});
// register fake metamod command handler if we not! under mm
if (!(game.is (GameFlags::Metamod))) {
game.registerEngineCommand ("meta", [] () {
game.print ("You're launched standalone version of %s. Metamod is not installed or not enabled!", PRODUCT_SHORT_NAME);
});
}
// initialize weapons
conf.initWeapons ();
// print game detection info
auto printGame = [&] () {
auto displayCSVersion = [&] () {
String gameVersionStr;
StringArray gameVersionFlags;
@ -694,7 +717,7 @@ bool Game::postload () {
if (!m_gameLib.load (gamedll)) {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ());
}
printGame ();
displayCSVersion ();
}
else {
@ -703,7 +726,7 @@ bool Game::postload () {
if (!binaryLoaded && !is (GameFlags::Metamod)) {
logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ());
}
printGame ();
displayCSVersion ();
if (is (GameFlags::Metamod)) {
m_gameLib.unload ();
@ -742,7 +765,7 @@ void Game::detectDeathmatch () {
}
void Game::slowFrame () {
if (m_slowFrame > timebase ()) {
if (m_slowFrame > time ()) {
return;
}
ctrl.maintainAdminRights ();
@ -761,7 +784,36 @@ void Game::slowFrame () {
// display welcome message
util.checkWelcome ();
m_slowFrame = timebase () + 1.0f;
m_slowFrame = time () + 1.0f;
}
void Game::searchEntities (const String &field, const String &value, EntitySearch functor) {
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, field.chars (), value.chars ()))) {
if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) {
continue;
}
if (functor (ent) == EntitySearchResult::Break) {
break;
}
}
}
void Game::searchEntities (const Vector &position, const float radius, EntitySearch functor) {
edict_t *ent = nullptr;
const Vector &pos = position.empty () ? m_startEntity->v.origin : position;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityInSphere (ent, pos, radius))) {
if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) {
continue;
}
if (functor (ent) == EntitySearchResult::Break) {
break;
}
}
}
void LightMeasure::initializeLightstyles () {
@ -786,7 +838,7 @@ void LightMeasure::animateLight () {
}
// 'm' is normal light, 'a' is no light, 'z' is double bright
const int index = static_cast <int> (game.timebase () * 10.0f);
const int index = static_cast <int> (game.time () * 10.0f);
for (int j = 0; j < MAX_LIGHTSTYLES; ++j) {
if (!m_lightstyle[j].length) {

View file

@ -19,9 +19,9 @@ void BotGraph::initGraph () {
m_loadAttempts = 0;
m_editFlags = 0;
m_learnVelocity= nullvec;
m_learnPosition= nullvec;
m_lastNode= nullvec;
m_learnVelocity= nullptr;
m_learnPosition= nullptr;
m_lastNode= nullptr;
m_pathDisplayTime = 0.0f;
m_arrowDisplayTime = 0.0f;
@ -540,7 +540,7 @@ void BotGraph::add (int type, const Vector &pos) {
Path *path = nullptr;
bool addNewNode = true;
Vector newOrigin = pos == nullvec ? m_editor->v.origin : pos;
Vector newOrigin = pos.empty () ? m_editor->v.origin : pos;
if (bots.hasBotsOnline ()) {
bots.kickEveryone (true);
@ -624,8 +624,8 @@ void BotGraph::add (int type, const Vector &pos) {
path->origin = newOrigin;
addToBucket (newOrigin, index);
path->start = nullvec;
path->end = nullvec;
path->start = nullptr;
path->end = nullptr;
path->display = 0.0f;
path->light = 0.0f;
@ -634,7 +634,7 @@ void BotGraph::add (int type, const Vector &pos) {
link.index = kInvalidNodeIndex;
link.distance = 0;
link.flags = 0;
link.velocity = nullvec;
link.velocity = nullptr;
}
@ -804,7 +804,7 @@ void BotGraph::erase (int target) {
link.index = kInvalidNodeIndex;
link.flags = 0;
link.distance = 0;
link.velocity = nullvec;
link.velocity = nullptr;
}
}
}
@ -1037,9 +1037,8 @@ void BotGraph::calculatePathRadius (int index) {
for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) {
start = path.origin;
auto null = nullvec;
direction = null.forward () * scanDistance;
direction = Vector (0.0f, 0.0f, 0.0f).forward () * scanDistance;
direction = direction.angles ();
path.radius = scanDistance;
@ -1925,7 +1924,7 @@ void BotGraph::frame () {
if (m_editor->v.button & IN_JUMP) {
add (9);
m_timeJumpStarted = game.timebase ();
m_timeJumpStarted = game.time ();
m_endJumpPoint = true;
}
else {
@ -1933,7 +1932,7 @@ void BotGraph::frame () {
m_learnPosition = m_editor->v.origin;
}
}
else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) {
else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.time () && m_endJumpPoint) {
add (10);
m_jumpLearnNode = false;
@ -1946,7 +1945,7 @@ void BotGraph::frame () {
// find the distance from the last used node
float distance = (m_lastNode - m_editor->v.origin).lengthSq ();
if (distance > 16384.0f) {
if (distance > cr::square (128.0f)) {
// check that no other reachable nodes are nearby...
for (const auto &path : m_paths) {
if (isNodeReacheable (m_editor->v.origin, path.origin)) {
@ -1981,7 +1980,7 @@ void BotGraph::frame () {
nearestDistance = distance;
}
if (path.display + 0.8f < game.timebase ()) {
if (path.display + 0.8f < game.time ()) {
float nodeHeight = 0.0f;
// check the node height
@ -2045,7 +2044,7 @@ void BotGraph::frame () {
game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, nodeColor, 250, 0, 10); // draw basic path
game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), path.origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, nodeFlagColor, 250, 0, 10); // draw additional path
}
path.display = game.timebase ();
path.display = game.time ();
}
}
}
@ -2057,7 +2056,7 @@ void BotGraph::frame () {
// draw arrow to a some importaint nodes
if (exists (m_findWPIndex) || exists (m_cacheNodeIndex) || exists (m_facingAtIndex)) {
// check for drawing code
if (m_arrowDisplayTime + 0.5f < game.timebase ()) {
if (m_arrowDisplayTime + 0.5f < game.time ()) {
// finding node - pink arrow
if (m_findWPIndex != kInvalidNodeIndex) {
@ -2073,7 +2072,7 @@ void BotGraph::frame () {
if (m_facingAtIndex != kInvalidNodeIndex) {
game.drawLine (m_editor, m_editor->v.origin, m_paths[m_facingAtIndex].origin, 10, 0, Color (255, 255, 255), 200, 0, 5, DrawLine::Arrow);
}
m_arrowDisplayTime = game.timebase ();
m_arrowDisplayTime = game.time ();
}
}
@ -2081,8 +2080,8 @@ void BotGraph::frame () {
auto &path = m_paths[nearestIndex];
// draw a paths, camplines and danger directions for nearest node
if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) {
m_pathDisplayTime = game.timebase () + 1.0f;
if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.time ()) {
m_pathDisplayTime = game.time () + 1.0f;
// draw the camplines
if (path.flags & NodeFlag::Camp) {
@ -2214,7 +2213,7 @@ void BotGraph::frame () {
}
// draw entire message
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, m_editor)
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, m_editor)
.writeByte (TE_TEXTMESSAGE)
.writeByte (4) // channel
.writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // x
@ -2293,7 +2292,7 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
}
if (path.flags & NodeFlag::Camp) {
if (path.end == nullvec) {
if (path.end.empty ()) {
ctrl.msg ("Node %d Camp-Endposition not set!", path.number);
return false;
}
@ -2463,10 +2462,8 @@ bool BotGraph::isVisited (int index) {
void BotGraph::addBasic () {
// this function creates basic node types on map
edict_t *ent = nullptr;
// first of all, if map contains ladder points, create it
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) {
game.searchEntities ("classname", "func_ladder", [&] (edict_t *ent) {
Vector ladderLeft = ent->v.absmin;
Vector ladderRight = ent->v.absmax;
ladderLeft.z = ladderRight.z;
@ -2474,7 +2471,7 @@ void BotGraph::addBasic () {
TraceResult tr;
Vector up, down, front, back;
Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f;
const Vector &diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f;
front = back = game.getAbsPos (ent);
front = front + diff; // front
@ -2509,18 +2506,19 @@ void BotGraph::addBasic () {
add (3, point);
}
m_isOnLadder = false;
}
auto autoCreateForEntity = [](int type, const char *entity) {
edict_t *ent = nullptr;
return EntitySearchResult::Continue;
});
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity))) {
auto autoCreateForEntity = [] (int type, const char *entity) {
game.searchEntities ("classname", entity, [&] (edict_t *ent) {
const Vector &pos = game.getAbsPos (ent);
if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) {
graph.add (type, pos);
}
}
return EntitySearchResult::Continue;
});
};
autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints
@ -2583,7 +2581,7 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) {
// this function stores the bomb position as a vector
if (reset) {
m_bombPos= nullvec;
m_bombPos= nullptr;
bots.setBombPlanted (false);
return;
@ -2593,14 +2591,14 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) {
m_bombPos = pos;
return;
}
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) {
game.searchEntities ("classname", "grenade", [&] (edict_t *ent) {
if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) {
m_bombPos = game.getAbsPos (ent);
break;
return EntitySearchResult::Break;
}
}
return EntitySearchResult::Continue;
});
}
void BotGraph::startLearnJump () {
@ -2760,7 +2758,7 @@ void BotGraph::unassignPath (int from, int to) {
link.index = kInvalidNodeIndex;
link.distance = 0;
link.flags = 0;
link.velocity = nullvec;
link.velocity = nullptr;
setEditFlag (GraphEdit::On);
m_hasChanged = true;

View file

@ -109,32 +109,16 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
// server is enabled. Here is a good place to do our own game session initialization, and
// to register by the engine side the server commands we need to administrate our bots.
// register bot cvars
game.registerCvars ();
// register logger
logger.initialize (strings.format ("%slogs/yapb.log", graph.getDataDirectory (false)), [] (const char *msg) {
game.print (msg);
});
conf.initWeapons ();
// register server command(s)
game.registerCmd ("yapb", BotControl::handleEngineCommands);
game.registerCmd ("yb", BotControl::handleEngineCommands);
// set correct version string
yb_version.set (strings.format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, util.buildNumber ()));
// execute main config
conf.loadMainConfig ();
// register fake metamod command handler if we not! under mm
if (!(game.is (GameFlags::Metamod))) {
game.registerCmd ("meta", [] () {
game.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!");
});
}
conf.adjustWeaponPrices ();
if (game.is (GameFlags::Metamod)) {
@ -177,14 +161,8 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) {
auto bot = bots[pentTouched];
if (bot != nullptr && pentOther != bot->ent ()) {
if (util.isPlayer (pentOther)) {
bot->avoidIncomingPlayers (pentOther);
}
else {
bot->processBreakables (pentOther);
}
if (bot && pentOther != bot->ent () && !util.isPlayer (pentOther)) {
bot->checkBreakable (pentOther);
}
}
@ -397,9 +375,6 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
// for example if a new player joins the server, we should disconnect a bot, and if the
// player population decreases, we should fill the server with other bots.
// run periodic update of bot states
bots.frame ();
// update lightstyle animations
illum.animateLight ();
@ -430,7 +405,7 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
dllapi.pfnStartFrame ();
// run the bot ai
bots.slowFrame ();
bots.frame ();
};
functionTable->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) {
@ -504,7 +479,8 @@ CR_EXPORT int GetEntityAPI2_Post (gamefuncs_t *table, int *) {
// for the bots by the MOD side, remember). Post version called only by metamod.
// run the bot ai
bots.slowFrame ();
bots.frame ();
RETURN_META (MRES_IGNORED);
};
@ -578,17 +554,19 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) {
engfuncs.pfnLightStyle (style, val);
};
functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) {
// round starts in counter-strike 1.5
if ((game.is (GameFlags::Legacy)) && strcmp (value, "info_map_parameters") == 0) {
bots.initRound ();
}
if (game.is (GameFlags::Legacy)) {
functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) {
// round starts in counter-strike 1.5
if (strcmp (value, "info_map_parameters") == 0) {
bots.initRound ();
}
if (game.is (GameFlags::Metamod)) {
RETURN_META_VALUE (MRES_IGNORED, static_cast <edict_t *> (nullptr));
}
return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value);
};
if (game.is (GameFlags::Metamod)) {
RETURN_META_VALUE (MRES_IGNORED, static_cast <edict_t *> (nullptr));
}
return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value);
};
}
functionTable->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) {
// this function tells the engine that the entity pointed to by "entity", is emitting a sound
@ -601,7 +579,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) {
// SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL
// to handle this for them, such a job has to be done manually.
util.attachSoundsToClients (entity, sample, volume);
util.listenNoise (entity, sample, volume);
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
@ -863,7 +841,7 @@ CR_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMet
return TRUE; // tell metamod this plugin looks safe
}
CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) {
CR_EXPORT int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) {
// this function is called when metamod attempts to load the plugin. Since it's the place
// where we can tell if the plugin will be allowed to run or not, we wait until here to make
// our initialization stuff, like registering CVARs and dedicated server commands.
@ -880,6 +858,11 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g
nullptr, // pfnGetEngineFunctions_Post ()
};
if (now > Plugin_info.loadable) {
logger.error ("%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name);
return FALSE; // returning FALSE prevents metamod from attaching this plugin
}
// keep track of the pointers to engine function tables metamod gives us
gpMetaGlobals = pMGlobals;
memcpy (functionTable, &metamodFunctionTable, sizeof (metamod_funcs_t));
@ -888,10 +871,14 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g
return TRUE; // returning true enables metamod to attach this plugin
}
CR_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) {
CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
// this function is called when metamod unloads the plugin. A basic check is made in order
// to prevent unloading the plugin if its processing should not be interrupted.
if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED) {
logger.error ("%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
return FALSE; // returning FALSE prevents metamod from unloading this plugin
}
bots.kickEveryone (true); // kick all bots off this server
// save collected experience on shutdown

View file

@ -250,23 +250,15 @@ Bot *BotManager::findAliveBot () {
return nullptr;
}
void BotManager::slowFrame () {
// this function calls showframe function for all available at call moment bots
for (const auto &bot : m_bots) {
bot->slowFrame ();
}
}
void BotManager::frame () {
// this function calls periodic frame function for all available at call moment bots
// this function calls showframe function for all available at call moment bots
for (const auto &bot : m_bots) {
bot->frame ();
}
// select leader each team somewhere in round start
if (m_timeRoundStart + 5.0f > game.timebase () && m_timeRoundStart + 10.0f < game.timebase ()) {
if (m_timeRoundStart + 5.0f > game.time () && m_timeRoundStart + 10.0f < game.time ()) {
for (int team = 0; team < kGameTeamNum; ++team) {
selectLeaders (team, false);
}
@ -319,7 +311,7 @@ void BotManager::maintainQuota () {
}
// bot's creation update
if (!m_creationTab.empty () && m_maintainTime < game.timebase ()) {
if (!m_creationTab.empty () && m_maintainTime < game.time ()) {
const CreateQueue &last = m_creationTab.pop ();
const BotCreateResult callResult = create (last.name, last.difficulty, last.personality, last.team, last.member);
@ -342,11 +334,11 @@ void BotManager::maintainQuota () {
m_creationTab.clear ();
yb_quota.set (getBotCount ());
}
m_maintainTime = game.timebase () + 0.10f;
m_maintainTime = game.time () + 0.10f;
}
// now keep bot number up to date
if (m_quotaMaintainTime > game.timebase ()) {
if (m_quotaMaintainTime > game.time ()) {
return;
}
yb_quota.set (cr::clamp <int> (yb_quota.int_ (), 0, game.maxClients ()));
@ -410,7 +402,7 @@ void BotManager::maintainQuota () {
kickRandom (false, Team::Unassigned);
}
}
m_quotaMaintainTime = game.timebase () + 0.40f;
m_quotaMaintainTime = game.time () + 0.40f;
}
void BotManager::reset () {
@ -467,8 +459,8 @@ void BotManager::decrementQuota (int by) {
}
void BotManager::initQuota () {
m_maintainTime = game.timebase () + yb_join_delay.float_ ();
m_quotaMaintainTime = game.timebase () + yb_join_delay.float_ ();
m_maintainTime = game.time () + yb_join_delay.float_ ();
m_quotaMaintainTime = game.time () + yb_join_delay.float_ ();
m_creationTab.clear ();
}
@ -855,8 +847,8 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
m_difficulty = cr::clamp (difficulty, 0, 4);
m_basePing = rg.int_ (7, 14);
m_lastCommandTime = game.timebase () - 0.1f;
m_frameInterval = game.timebase ();
m_lastCommandTime = game.time () - 0.1f;
m_frameInterval = game.time ();
m_slowFrameTimestamp = 0.0f;
// stuff from jk_botti
@ -892,7 +884,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
// copy them over to the temp level variables
m_agressionLevel = m_baseAgressionLevel;
m_fearLevel = m_baseFearLevel;
m_nextEmotionUpdate = game.timebase () + 0.5f;
m_nextEmotionUpdate = game.time () + 0.5f;
// just to be sure
m_actMessageIndex = 0;
@ -906,7 +898,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
}
float Bot::getFrameInterval () {
return cr::fzero (m_thinkInterval) ? m_frameInterval : m_thinkInterval;
return m_frameInterval;
}
int BotManager::getHumansCount (bool ignoreSpectators) {
@ -977,10 +969,10 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
for (const auto &notify : bots) {
if (notify->m_notKilled && killerTeam == notify->m_team && killerTeam != victimTeam && killer != notify->ent () && notify->seesEntity (victim->v.origin)) {
if (!(killer->v.flags & FL_FAKECLIENT)) {
notify->processChatterMessage ("#Bot_NiceShotCommander");
notify->handleChatter ("#Bot_NiceShotCommander");
}
else {
notify->processChatterMessage ("#Bot_NiceShotPall");
notify->handleChatter ("#Bot_NiceShotPall");
}
break;
}
@ -991,9 +983,9 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
// notice nearby to victim teammates, that attacker is near
for (const auto &notify : bots) {
if (notify->m_seeEnemyTime + 2.0f < game.timebase () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) {
if (notify->m_seeEnemyTime + 2.0f < game.time () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) {
notify->m_actualReactionTime = 0.0f;
notify->m_seeEnemyTime = game.timebase ();
notify->m_seeEnemyTime = game.time ();
notify->m_enemy = killer;
notify->m_lastEnemy = killer;
notify->m_lastEnemyOrigin = killer->v.origin;
@ -1032,11 +1024,11 @@ void Bot::newRound () {
clearSearchNodes ();
clearRoute ();
m_pathOrigin= nullvec;
m_destOrigin= nullvec;
m_pathOrigin= nullptr;
m_destOrigin= nullptr;
m_path = nullptr;
m_currentTravelFlags = 0;
m_desiredVelocity= nullvec;
m_desiredVelocity= nullptr;
m_currentNodeIndex = kInvalidNodeIndex;
m_prevGoalIndex = kInvalidNodeIndex;
m_chosenGoalIndex = kInvalidNodeIndex;
@ -1053,13 +1045,10 @@ void Bot::newRound () {
m_oldButtons = pev->button;
m_rechoiceGoalCount = 0;
m_avoid = nullptr;
m_avoidTime = 0.0f;
for (i = 0; i < 5; ++i) {
m_previousNodes[i] = kInvalidNodeIndex;
}
m_navTimeset = game.timebase ();
m_navTimeset = game.time ();
m_team = game.getTeam (ent ());
m_isVIP = false;
@ -1093,9 +1082,9 @@ void Bot::newRound () {
m_minSpeed = 260.0f;
m_prevSpeed = 0.0f;
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
m_prevTime = game.timebase ();
m_lookUpdateTime = game.timebase ();
m_aimErrorTime = game.timebase ();
m_prevTime = game.time ();
m_lookUpdateTime = game.time ();
m_aimErrorTime = game.time ();
m_viewDistance = 4096.0f;
m_maxViewDistance = 4096.0f;
@ -1106,7 +1095,7 @@ void Bot::newRound () {
m_itemCheckTime = 0.0f;
m_breakableEntity = nullptr;
m_breakableOrigin= nullvec;
m_breakableOrigin= nullptr;
m_timeDoorOpen = 0.0f;
resetCollision ();
@ -1115,7 +1104,7 @@ void Bot::newRound () {
m_enemy = nullptr;
m_lastVictim = nullptr;
m_lastEnemy = nullptr;
m_lastEnemyOrigin= nullvec;
m_lastEnemyOrigin= nullptr;
m_trackingEdict = nullptr;
m_timeNextTracking = 0.0f;
@ -1139,9 +1128,9 @@ void Bot::newRound () {
m_aimFlags = 0;
m_liftState = 0;
m_aimLastError= nullvec;
m_position= nullvec;
m_liftTravelPos= nullvec;
m_aimLastError= nullptr;
m_position= nullptr;
m_liftTravelPos= nullptr;
setIdealReactionTimers (true);
@ -1157,8 +1146,8 @@ void Bot::newRound () {
m_reloadState = Reload::None;
m_reloadCheckTime = 0.0f;
m_shootTime = game.timebase ();
m_playerTargetTime = game.timebase ();
m_shootTime = game.time ();
m_playerTargetTime = game.time ();
m_firePause = 0.0f;
m_timeLastFired = 0.0f;
@ -1174,7 +1163,7 @@ void Bot::newRound () {
m_jumpFinished = false;
m_isStuck = false;
m_sayTextBuffer.timeNextChat = game.timebase ();
m_sayTextBuffer.timeNextChat = game.time ();
m_sayTextBuffer.entityIndex = -1;
m_sayTextBuffer.sayText.clear ();
@ -1189,10 +1178,10 @@ void Bot::newRound () {
m_currentWeapon = 0;
}
m_flashLevel = 100.0f;
m_checkDarkTime = game.timebase ();
m_checkDarkTime = game.time ();
m_knifeAttackTime = game.timebase () + rg.float_ (1.3f, 2.6f);
m_nextBuyTime = game.timebase () + rg.float_ (0.6f, 2.0f);
m_knifeAttackTime = game.time () + rg.float_ (1.3f, 2.6f);
m_nextBuyTime = game.time () + rg.float_ (0.6f, 2.0f);
m_buyPending = false;
m_inBombZone = false;
@ -1217,9 +1206,9 @@ void Bot::newRound () {
m_defendHostage = false;
m_headedTime = 0.0f;
m_timeLogoSpray = game.timebase () + rg.float_ (5.0f, 30.0f);
m_spawnTime = game.timebase ();
m_lastChatTime = game.timebase ();
m_timeLogoSpray = game.time () + rg.float_ (5.0f, 30.0f);
m_spawnTime = game.time ();
m_lastChatTime = game.time ();
m_timeCamping = 0.0f;
m_campDirection = 0;
@ -1227,7 +1216,7 @@ void Bot::newRound () {
m_campButtons = 0;
m_soundUpdateTime = 0.0f;
m_heardSoundTime = game.timebase ();
m_heardSoundTime = game.time ();
// clear its message queue
for (i = 0; i < 32; ++i) {
@ -1243,7 +1232,8 @@ void Bot::newRound () {
if (rg.chance (50)) {
pushChatterMessage (Chatter::NewRound);
}
m_thinkInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 90.0f)) * rg.float_ (0.95f, 1.05f);
m_updateInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 60.0f));
m_viewUpdateInterval = 1.0f / 30.0f;
}
void Bot::kill () {
@ -1344,15 +1334,6 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
}
if (plat.caseStrMatch (cmd, "say") || plat.caseStrMatch (cmd, "say_team")) {
if (strcmp (arg, "dropme") == 0 || strcmp (arg, "dropc4") == 0) {
Bot *bot = nullptr;
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), ent, 300.0f, true, true, true)) {
bot->dropWeaponForUser (ent, strings.isEmpty (strstr (arg, "c4")) ? false : true);
}
return;
}
bool alive = util.isAlive (ent);
int team = -1;
@ -1373,7 +1354,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
continue;
}
target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args ();
target->m_sayTextBuffer.timeNextChat = game.timebase () + target->m_sayTextBuffer.chatDelay;
target->m_sayTextBuffer.timeNextChat = game.time () + target->m_sayTextBuffer.chatDelay;
}
}
}
@ -1396,7 +1377,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
}
}
}
bots.setLastRadioTimestamp (target.team, game.timebase ());
bots.setLastRadioTimestamp (target.team, game.time ());
}
target.radio = 0;
}
@ -1419,59 +1400,61 @@ void BotManager::notifyBombDefuse () {
}
void BotManager::updateActiveGrenade () {
if (m_grenadeUpdateTime > game.timebase ()) {
if (m_grenadeUpdateTime > game.time ()) {
return;
}
edict_t *grenade = nullptr;
// clear previously stored grenades
m_activeGrenades.clear ();
m_activeGrenades.clear (); // clear previously stored grenades
// search the map for any type of grenade
while (!game.isNullEntity (grenade = engfuncs.pfnFindEntityByString (grenade, "classname", "grenade"))) {
game.searchEntities ("classname", "grenade", [&] (edict_t *e) {
// do not count c4 as a grenade
if (strcmp (STRING (grenade->v.model) + 9, "c4.mdl") == 0) {
continue;
if (strcmp (STRING (e->v.model) + 9, "c4.mdl") == 0) {
return EntitySearchResult::Continue;
}
m_activeGrenades.push (grenade);
}
m_grenadeUpdateTime = game.timebase () + 0.213f;
m_activeGrenades.push (e);
// continue iteration
return EntitySearchResult::Continue;
});
m_grenadeUpdateTime = game.time () + 0.25f;
}
void BotManager::updateIntrestingEntities () {
if (m_entityUpdateTime > game.timebase ()) {
if (m_entityUpdateTime > game.time ()) {
return;
}
// clear previously stored entities
m_intrestingEntities.clear ();
// search the map for entities
for (int i = kGameMaxPlayers - 1; i < globals->maxEntities; ++i) {
auto ent = game.entityOfIndex (i);
// search the map for any type of grenade
game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) {
auto classname = STRING (e->v.classname);
// only valid drawn entities
if (game.isNullEntity (ent) || ent->free || ent->v.classname == 0 || (ent->v.effects & EF_NODRAW)) {
continue;
}
auto classname = STRING (ent->v.classname);
// search for grenades, weaponboxes, weapons, items and armoury entities
if (strncmp ("weapon", classname, 6) == 0 || strncmp ("grenade", classname, 7) == 0 || strncmp ("item", classname, 4) == 0 || strncmp ("armoury", classname, 7) == 0) {
m_intrestingEntities.push (ent);
m_intrestingEntities.push (e);
}
// pickup some csdm stuff if we're running csdm
if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) {
m_intrestingEntities.push (ent);
m_intrestingEntities.push (e);
}
// add buttons
if (game.mapIs (MapFlags::HasButtons) && strncmp ("func_button", classname, 11) == 0) {
m_intrestingEntities.push (e);
}
// pickup some csdm stuff if we're running csdm
if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) {
m_intrestingEntities.push (ent);
m_intrestingEntities.push (e);
}
}
m_entityUpdateTime = game.timebase () + 0.5f;
// continue iteration
return EntitySearchResult::Continue;
});
m_entityUpdateTime = game.time () + 0.5f;
}
void BotManager::selectLeaders (int team, bool reset) {
@ -1606,14 +1589,14 @@ void BotManager::initRound () {
graph.updateGlobalPractice (); // update experience data on round start
// calculate the round mid/end in world time
m_timeRoundStart = game.timebase () + mp_freezetime.float_ ();
m_timeRoundStart = game.time () + mp_freezetime.float_ ();
m_timeRoundMid = m_timeRoundStart + mp_roundtime.float_ () * 60.0f * 0.5f;
m_timeRoundEnd = m_timeRoundStart + mp_roundtime.float_ () * 60.0f;
}
void BotManager::setBombPlanted (bool isPlanted) {
if (isPlanted) {
m_timeBombPlanted = game.timebase ();
m_timeBombPlanted = game.time ();
}
m_bombPlanted = isPlanted;
}
@ -1680,6 +1663,12 @@ void BotConfig::loadMainConfig () {
auto value = const_cast <char *> (keyval[1].trim ().trim ("\"").trim ().chars ());
if (needsToIgnoreVar (ignore, key) && !plat.caseStrMatch (value, cvar->string)) {
// preserve quota number if it's zero
if (plat.caseStrMatch (cvar->name, "yb_quota") && yb_quota.int_ () <= 0) {
engfuncs.pfnCvar_DirectSet (cvar, value);
continue;
}
game.print ("Bot CVAR '%s' differs from the stored in the config (%s/%s). Ignoring.", cvar->name, cvar->string, value);
// ensure cvar will have old value
@ -2183,6 +2172,8 @@ void BotConfig::clearUsedName (Bot *bot) {
}
void BotConfig::initWeapons () {
m_weapons.clear ();
// fill array with available weapons
m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true );
m_weapons.emplace (Weapon::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false );

View file

@ -23,7 +23,7 @@ void MessageDispatcher::netMsgTextMsg () {
auto notify = bots.findAliveBot ();
if (notify && notify->m_notKilled) {
notify->processChatterMessage (m_args[msg].chars_);
notify->handleChatter (m_args[msg].chars_);
}
}
@ -52,6 +52,14 @@ void MessageDispatcher::netMsgTextMsg () {
else if (cached & TextMsgCache::RestartRound) {
bots.updateTeamEconomics (Team::CT, true);
bots.updateTeamEconomics (Team::Terrorist, true);
extern ConVar mp_startmoney;
// set balance for all players
bots.forEach ([] (Bot *bot) {
bot->m_moneyAmount = mp_startmoney.int_ ();
return false;
});
}
else if (cached & TextMsgCache::TerroristWin) {
bots.setLastWinner (Team::Terrorist); // update last winner for economics
@ -157,7 +165,7 @@ void MessageDispatcher::netMsgCurWeapon () {
// ammo amount decreased ? must have fired a bullet...
if (m_args[id].long_ == m_bot->m_currentWeapon && m_bot->m_ammoInClip[m_args[id].long_] > m_args[clip].long_) {
m_bot->m_timeLastFired = game.timebase (); // remember the last bullet time
m_bot->m_timeLastFired = game.time (); // remember the last bullet time
}
m_bot->m_ammoInClip[m_args[id].long_] = m_args[clip].long_;
}
@ -201,7 +209,7 @@ void MessageDispatcher::netMsgDamage () {
// handle damage if any
if (m_args[armor].long_ > 0 || m_args[health].long_) {
m_bot->processDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_);
m_bot->takeDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_);
}
}
@ -237,7 +245,7 @@ void MessageDispatcher::netMsgStatusIcon () {
m_bot->m_inBuyZone = (m_args[enabled].long_ != 0);
// try to equip in buyzone
m_bot->processBuyzoneEntering (BuyState::PrimaryWeapon);
m_bot->enteredBuyZone (BuyState::PrimaryWeapon);
}
else if (cached & StatusIconCache::VipSafety) {
m_bot->m_inVIPZone = (m_args[enabled].long_ != 0);
@ -278,7 +286,7 @@ void MessageDispatcher::netMsgScreenFade () {
// screen completely faded ?
if (m_args[r].long_ >= 255 && m_args[g].long_ >= 255 && m_args[b].long_ >= 255 && m_args[alpha].long_ > 170) {
m_bot->processBlind (m_args[alpha].long_);
m_bot->takeBlind (m_args[alpha].long_);
}
}

File diff suppressed because it is too large Load diff

View file

@ -56,6 +56,17 @@ BotUtils::BotUtils () {
m_tags.emplace ("(", ")");
m_tags.emplace (")", "(");
// register noise cache
m_noiseCache["player/bhit"] = Noise::NeedHandle | Noise::HitFall;
m_noiseCache["player/head"] = Noise::NeedHandle | Noise::HitFall;
m_noiseCache["items/gunpi"] = Noise::NeedHandle | Noise::Pickup;
m_noiseCache["items/9mmcl"] = Noise::NeedHandle | Noise::Ammo;
m_noiseCache["weapons/zoo"] = Noise::NeedHandle | Noise::Zoom;
m_noiseCache["hostage/hos"] = Noise::NeedHandle | Noise::Hostage;
m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke;
m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke;
m_noiseCache["doors/doorm"] = Noise::NeedHandle | Noise::Door;
m_clients.resize (kGameMaxPlayers + 1);
}
@ -234,7 +245,7 @@ void BotUtils::checkWelcome () {
auto receiveEntity = game.getLocalEntity ();
if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0 && needToSendMsg) {
m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing
m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing
}
@ -243,11 +254,11 @@ void BotUtils::checkWelcome () {
game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ());
}
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, receiveEntity)
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity)
.writeByte (HUD_PRINTTALK)
.writeString (strings.format ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL));
MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullvec, receiveEntity)
MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity)
.writeByte (TE_TEXTMESSAGE)
.writeByte (1)
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
@ -312,91 +323,96 @@ bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDist
return true;
}
void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float volume) {
// this function called by the sound hooking code (in emit_sound) enters the played sound into
// the array associated with the entity
void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) {
// this function called by the sound hooking code (in emit_sound) enters the played sound into the array associated with the entity
if (game.isNullEntity (ent) || strings.isEmpty (sample)) {
if (game.isNullEntity (ent) || sample.empty ()) {
return;
}
const Vector &origin = game.getAbsPos (ent);
// something wrong with sound...
if (origin.empty ()) {
return;
}
int index = game.indexOfPlayer (ent);
auto noise = m_noiseCache[sample.substr (0, 11)];
if (index < 0 || index >= game.maxClients ()) {
float nearestDistance = kInfiniteDistance;
// we're not handling theese
if (!(noise & Noise::NeedHandle)) {
return;
}
// find nearest player to sound origin
auto findNearbyClient = [&origin] () {
float nearest = kInfiniteDistance;
Client *result = nullptr;
// loop through all players
for (int i = 0; i < game.maxClients (); ++i) {
const Client &client = m_clients[i];
for (auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) {
continue;
}
float distance = (client.origin - origin).length ();
auto distance = (client.origin - origin).lengthSq ();
// now find nearest player
if (distance < nearestDistance) {
index = i;
nearestDistance = distance;
if (distance < nearest) {
result = &client;
nearest = distance;
}
}
}
return result;
};
auto client = findNearbyClient ();
// in case of worst case
if (index < 0 || index >= game.maxClients ()) {
// update noise stats
auto registerNoise = [&origin, &client, &volume] (float distance, float lasting) {
client->hearingDistance = distance * volume;
client->timeSoundLasting = game.time () + lasting;
client->sound = origin;
};
// client wasn't found
if (!client) {
return;
}
Client &client = m_clients[index];
if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) {
// hit/fall sound?
client.hearingDistance = 768.0f * volume;
client.timeSoundLasting = game.timebase () + 0.5f;
client.sound = origin;
// hit/fall sound?
if (noise & Noise::HitFall) {
registerNoise (768.0f, 0.52f);
}
else if (strncmp ("items/gunpickup", sample, 15) == 0) {
// weapon pickup?
client.hearingDistance = 768.0f * volume;
client.timeSoundLasting = game.timebase () + 0.5f;
client.sound = origin;
// weapon pickup?
else if (noise & Noise::Pickup) {
registerNoise (768.0f, 0.45f);
}
else if (strncmp ("weapons/zoom", sample, 12) == 0) {
// sniper zooming?
client.hearingDistance = 512.0f * volume;
client.timeSoundLasting = game.timebase () + 0.1f;
client.sound = origin;
// sniper zooming?
else if (noise & Noise::Zoom) {
registerNoise (512.0f, 0.10f);
}
else if (strncmp ("items/9mmclip", sample, 13) == 0) {
// ammo pickup?
client.hearingDistance = 512.0f * volume;
client.timeSoundLasting = game.timebase () + 0.1f;
client.sound = origin;
// ammo pickup?
else if (noise & Noise::Ammo) {
registerNoise (512.0f, 0.25f);
}
else if (strncmp ("hostage/hos", sample, 11) == 0) {
// CT used hostage?
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = game.timebase () + 5.0f;
client.sound = origin;
// ct used hostage?
else if (noise & Noise::Hostage) {
registerNoise (1024.0f, 5.00f);
}
else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) {
// broke something?
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = game.timebase () + 2.0f;
client.sound = origin;
// broke something?
else if (noise & Noise::Broke) {
registerNoise (1024.0f, 2.00f);
}
else if (strncmp ("doors/doormove", sample, 14) == 0) {
// someone opened a door
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = game.timebase () + 3.0f;
client.sound = origin;
// someone opened a door
else if (noise & Noise::Door) {
registerNoise (1024.0f, 3.00f);
}
}
void BotUtils::simulateSoundUpdates (int playerIndex) {
void BotUtils::simulateNoise (int playerIndex) {
// this function tries to simulate playing of sounds to let the bots hear sounds which aren't
// captured through server sound hooking
@ -407,27 +423,28 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
float hearDistance = 0.0f;
float timeSound = 0.0f;
auto buttons = client.ent->v.button | client.ent->v.oldbuttons;
if (client.ent->v.oldbuttons & IN_ATTACK) // pressed attack button?
if (buttons & IN_ATTACK) // pressed attack button?
{
hearDistance = 2048.0f;
timeSound = game.timebase () + 0.3f;
timeSound = game.time () + 0.3f;
}
else if (client.ent->v.oldbuttons & IN_USE) // pressed used button?
else if (buttons & IN_USE) // pressed used button?
{
hearDistance = 512.0f;
timeSound = game.timebase () + 0.5f;
timeSound = game.time () + 0.5f;
}
else if (client.ent->v.oldbuttons & IN_RELOAD) // pressed reload button?
else if (buttons & IN_RELOAD) // pressed reload button?
{
hearDistance = 512.0f;
timeSound = game.timebase () + 0.5f;
timeSound = game.time () + 0.5f;
}
else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder?
{
if (cr::abs (client.ent->v.velocity.z) > 50.0f) {
hearDistance = 1024.0f;
timeSound = game.timebase () + 0.3f;
timeSound = game.time () + 0.3f;
}
}
else {
@ -436,7 +453,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
if (mp_footsteps.bool_ ()) {
// moves fast enough?
hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f);
timeSound = game.timebase () + 0.3f;
timeSound = game.time () + 0.3f;
}
}
@ -445,7 +462,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
}
// some sound already associated
if (client.timeSoundLasting > game.timebase ()) {
if (client.timeSoundLasting > game.time ()) {
if (client.hearingDistance <= hearDistance) {
// override it with new
client.hearingDistance = hearDistance;
@ -481,7 +498,7 @@ void BotUtils::updateClients () {
if (client.flags & ClientFlags::Alive) {
client.origin = player->v.origin;
simulateSoundUpdates (i);
simulateNoise (i);
}
}
else {
@ -576,7 +593,7 @@ void BotUtils::sendPings (edict_t *to) {
client.ping = getPingBitmask (client.ent, rg.int_ (5, 10), rg.int_ (15, 40));
}
msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullvec, to)
msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullptr, to)
.writeLong (client.ping)
.end ();
}