Merge branch 'master' into chatter-fixes-and-improvements
This commit is contained in:
commit
9e24eb6be4
15 changed files with 852 additions and 850 deletions
15
.gitattributes
vendored
15
.gitattributes
vendored
|
|
@ -1,7 +1,8 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.sln merge=union
|
||||
*.vcxproj merge=union
|
||||
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=lf
|
||||
* text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.sln merge=union
|
||||
*.vcxproj merge=union
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ void GraphAnalyze::optimize () {
|
|||
}
|
||||
cleanup ();
|
||||
|
||||
auto smooth = [] (const Array <int> &nodes) {
|
||||
auto smooth = [] (const Array <int> &nodes) {
|
||||
Vector result;
|
||||
|
||||
for (const auto &node : nodes) {
|
||||
|
|
@ -202,7 +202,7 @@ void GraphAnalyze::optimize () {
|
|||
|
||||
for (const auto &link : path.links) {
|
||||
if (graph.exists (link.index) && !m_optimizedNodes[link.index]
|
||||
&& !AStarAlgo::cantSkipNode (path.number, link.index, true)) {
|
||||
&& !AStarAlgo::cantSkipNode (path.number, link.index, true)) {
|
||||
|
||||
indexes.emplace (link.index);
|
||||
}
|
||||
|
|
@ -342,8 +342,8 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
|||
auto testPos = m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 18.0f } : nextPos;
|
||||
|
||||
if ((graph.isNodeReacheable (targetPos, testPos)
|
||||
&& graph.isNodeReacheable (testPos, targetPos)) || (graph.isNodeReacheableWithJump (testPos, targetPos)
|
||||
&& graph.isNodeReacheableWithJump (targetPos, testPos))) {
|
||||
&& graph.isNodeReacheable (testPos, targetPos)) || (graph.isNodeReacheableWithJump (testPos, targetPos)
|
||||
&& graph.isNodeReacheableWithJump (targetPos, testPos))) {
|
||||
graph.add (NodeAddFlag::Normal, m_isCrouch ? Vector { nextPos.x, nextPos.y, nextPos.z - 9.0f } : nextPos);
|
||||
}
|
||||
}
|
||||
|
|
@ -364,7 +364,7 @@ void GraphAnalyze::markGoals () {
|
|||
|
||||
auto updateNodeFlags = [] (int type, StringRef classname) {
|
||||
game.searchEntities ("classname", classname, [&] (edict_t *ent) {
|
||||
for (auto &path : graph) {
|
||||
for (auto &path : graph) {
|
||||
const auto &bb = path.origin + Vector (1.0f, 1.0f, 1.0f);
|
||||
|
||||
if (ent->v.absmin.x > bb.x || ent->v.absmin.y > bb.y) {
|
||||
|
|
|
|||
246
src/botlib.cpp
246
src/botlib.cpp
|
|
@ -97,9 +97,9 @@ void Bot::avoidGrenades () {
|
|||
auto model = pent->v.model.str (9);
|
||||
|
||||
if (m_preventFlashing < game.time ()
|
||||
&& m_personality == Personality::Rusher
|
||||
&& m_difficulty == Difficulty::Expert
|
||||
&& model == kFlashbangModelName) {
|
||||
&& m_personality == Personality::Rusher
|
||||
&& m_difficulty == Difficulty::Expert
|
||||
&& model == kFlashbangModelName) {
|
||||
|
||||
// don't look at flash bang
|
||||
if (!(m_states & Sense::SeeingEnemy)) {
|
||||
|
|
@ -175,13 +175,13 @@ void Bot::checkBreakable (edict_t *touch) {
|
|||
|
||||
void Bot::checkBreakablesAround () {
|
||||
if (!m_buyingFinished
|
||||
|| !cv_destroy_breakables_around
|
||||
|| usesKnife ()
|
||||
|| rg.chance (25)
|
||||
|| !game.hasBreakables ()
|
||||
|| m_seeEnemyTime + 4.0f > game.time ()
|
||||
|| !game.isNullEntity (m_enemy)
|
||||
|| !hasPrimaryWeapon ()) {
|
||||
|| !cv_destroy_breakables_around
|
||||
|| usesKnife ()
|
||||
|| rg.chance (25)
|
||||
|| !game.hasBreakables ()
|
||||
|| m_seeEnemyTime + 4.0f > game.time ()
|
||||
|| !game.isNullEntity (m_enemy)
|
||||
|| !hasPrimaryWeapon ()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -421,7 +421,7 @@ void Bot::updatePickups () {
|
|||
pickupType = Pickup::DroppedC4;
|
||||
}
|
||||
else if ((isWeaponBox || classname.startsWith ("armoury_entity") || (isCSDM && classname.startsWith ("csdm")))
|
||||
&& !m_isUsingGrenade) {
|
||||
&& !m_isUsingGrenade) {
|
||||
|
||||
allowPickup = true;
|
||||
pickupType = Pickup::Weapon;
|
||||
|
|
@ -438,15 +438,15 @@ void Bot::updatePickups () {
|
|||
const auto &secondaryProp = conf.getWeaponProp (secondary.id);
|
||||
|
||||
if (secondaryWeaponCarried < kPrimaryWeaponMinIndex
|
||||
&& (getAmmo (secondary.id) > 0.3 * secondaryProp.ammo1Max)
|
||||
&& model == "357ammobox.mdl") {
|
||||
&& (getAmmo (secondary.id) > 0.3 * secondaryProp.ammo1Max)
|
||||
&& model == "357ammobox.mdl") {
|
||||
|
||||
allowPickup = false;
|
||||
}
|
||||
else if (!m_isVIP &&
|
||||
primaryWeaponCarried >= kPrimaryWeaponMinIndex
|
||||
&& (getAmmo (primary.id) > 0.3 * primaryProp.ammo1Max)
|
||||
&& !m_isUsingGrenade && !hasShield ()) {
|
||||
primaryWeaponCarried >= kPrimaryWeaponMinIndex
|
||||
&& (getAmmo (primary.id) > 0.3 * primaryProp.ammo1Max)
|
||||
&& !m_isUsingGrenade && !hasShield ()) {
|
||||
|
||||
auto weaponType = conf.getWeaponType (primary.id);
|
||||
|
||||
|
|
@ -475,7 +475,7 @@ void Bot::updatePickups () {
|
|||
else if (m_healthValue >= 100.0f && model == "medkit.mdl") {
|
||||
allowPickup = false;
|
||||
}
|
||||
else if (pev->armorvalue >= 100.0f && (model == "kevlar.mdl"|| model == "battery.mdl" || model == "assault.mdl")) {
|
||||
else if (pev->armorvalue >= 100.0f && (model == "kevlar.mdl" || model == "battery.mdl" || model == "assault.mdl")) {
|
||||
allowPickup = false;
|
||||
}
|
||||
else if ((pev->weapons & cr::bit (Weapon::Flashbang)) && model == kFlashbangModelName) {
|
||||
|
|
@ -570,10 +570,10 @@ void Bot::updatePickups () {
|
|||
allowPickup = false;
|
||||
|
||||
if (!m_defendHostage && m_personality
|
||||
!= Personality::Rusher && m_difficulty >= Difficulty::Normal
|
||||
&& rg.chance (15)
|
||||
&& m_timeCamping + 15.0f < game.time ()
|
||||
&& numFriendsNear (pev->origin, 384.0f) < 3) {
|
||||
!= Personality::Rusher && m_difficulty >= Difficulty::Normal
|
||||
&& rg.chance (15)
|
||||
&& m_timeCamping + 15.0f < game.time ()
|
||||
&& numFriendsNear (pev->origin, 384.0f) < 3) {
|
||||
|
||||
const int index = findDefendNode (origin);
|
||||
|
||||
|
|
@ -640,7 +640,7 @@ void Bot::updatePickups () {
|
|||
if (allowPickup) {
|
||||
for (auto &client : util.getClients ()) {
|
||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
allowPickup = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -874,9 +874,9 @@ void Bot::showChatterIcon (bool show, bool disconnect) {
|
|||
void Bot::instantChatter (int type) {
|
||||
// this function sends instant chatter messages.
|
||||
if (!game.is (GameFlags::HasBotVoice)
|
||||
|| cv_radio_mode.as <int> () != 2
|
||||
|| !conf.hasChatterBank (type)
|
||||
|| !conf.hasChatterBank (Chatter::DiePain)) {
|
||||
|| cv_radio_mode.as <int> () != 2
|
||||
|| !conf.hasChatterBank (type)
|
||||
|| !conf.hasChatterBank (Chatter::DiePain)) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -1068,9 +1068,9 @@ void Bot::checkMsgQueue () {
|
|||
|
||||
if (m_radioSelect != -1) {
|
||||
if ((m_radioSelect != Radio::ReportingIn && m_forceRadio)
|
||||
|| cv_radio_mode.as <int> () != 2
|
||||
|| !conf.hasChatterBank (m_radioSelect)
|
||||
|| !game.is (GameFlags::HasBotVoice)) {
|
||||
|| cv_radio_mode.as <int> () != 2
|
||||
|| !conf.hasChatterBank (m_radioSelect)
|
||||
|| !game.is (GameFlags::HasBotVoice)) {
|
||||
|
||||
if (m_radioSelect < Radio::GoGoGo) {
|
||||
issueCommand ("radio1");
|
||||
|
|
@ -1340,8 +1340,8 @@ void Bot::buyStuff () {
|
|||
}
|
||||
|
||||
if (selectedWeapon->id == Weapon::Shield
|
||||
&& m_moneyAmount > limit[EcoLimit::ShieldGreater]
|
||||
&& rg.chance (disrespectEconomicsPct)) {
|
||||
&& m_moneyAmount > limit[EcoLimit::ShieldGreater]
|
||||
&& rg.chance (disrespectEconomicsPct)) {
|
||||
|
||||
ignoreWeapon = true;
|
||||
}
|
||||
|
|
@ -1443,8 +1443,8 @@ void Bot::buyStuff () {
|
|||
|
||||
case BuyState::ArmorVestHelm: // if armor is damaged and bot has some money, buy some armor
|
||||
if (pev->armorvalue < rg (50.0f, 80.0f)
|
||||
&& teamHasGoodEconomics
|
||||
&& (isPistolMode || (teamHasGoodEconomics && hasPrimaryWeapon ()))) {
|
||||
&& teamHasGoodEconomics
|
||||
&& (isPistolMode || (teamHasGoodEconomics && hasPrimaryWeapon ()))) {
|
||||
|
||||
// if bot is rich, buy kevlar + helmet, else buy a single kevlar
|
||||
if (m_moneyAmount > 1500 && !isWeaponRestricted (Weapon::ArmorHelm)) {
|
||||
|
|
@ -1458,9 +1458,9 @@ void Bot::buyStuff () {
|
|||
|
||||
case BuyState::SecondaryWeapon: // if bot has still some money, buy a better secondary weapon
|
||||
if (isPistolMode
|
||||
|| (isFirstRound && hasDefaultPistols && rg.chance (60))
|
||||
|| (hasDefaultPistols && bots.getLastWinner () == m_team && m_moneyAmount > rg (2000, 3000))
|
||||
|| (hasPrimaryWeapon () && hasDefaultPistols && m_moneyAmount > rg (7500, 9000))) {
|
||||
|| (isFirstRound && hasDefaultPistols && rg.chance (60))
|
||||
|| (hasDefaultPistols && bots.getLastWinner () == m_team && m_moneyAmount > rg (2000, 3000))
|
||||
|| (hasPrimaryWeapon () && hasDefaultPistols && m_moneyAmount > rg (7500, 9000))) {
|
||||
|
||||
do {
|
||||
pref--;
|
||||
|
|
@ -1571,10 +1571,10 @@ void Bot::buyStuff () {
|
|||
|
||||
case BuyState::DefusalKit: // if bot is CT and we're on a bomb map, randomly buy the defuse kit
|
||||
if (game.mapIs (MapFlags::Demolition)
|
||||
&& m_team == Team::CT
|
||||
&& rg.chance (80)
|
||||
&& m_moneyAmount > 200
|
||||
&& !isWeaponRestricted (Weapon::Defuser)) {
|
||||
&& m_team == Team::CT
|
||||
&& rg.chance (80)
|
||||
&& m_moneyAmount > 200
|
||||
&& !isWeaponRestricted (Weapon::Defuser)) {
|
||||
|
||||
if (isOldGame) {
|
||||
issueCommand ("buyequip;menuselect 6");
|
||||
|
|
@ -1612,7 +1612,7 @@ void Bot::updateEmotions () {
|
|||
if (m_nextEmotionUpdate > game.time ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (m_agressionLevel > m_baseAgressionLevel) {
|
||||
m_agressionLevel -= 0.05f;
|
||||
}
|
||||
|
|
@ -1642,11 +1642,11 @@ void Bot::overrideConditions () {
|
|||
|
||||
// check if we need to escape from bomb
|
||||
if (game.mapIs (MapFlags::Demolition)
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_isAlive
|
||||
&& tid != Task::EscapeFromBomb
|
||||
&& tid != Task::Camp
|
||||
&& isOutOfBombTimer ()) {
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_isAlive
|
||||
&& tid != Task::EscapeFromBomb
|
||||
&& tid != Task::Camp
|
||||
&& isOutOfBombTimer ()) {
|
||||
|
||||
completeTask (); // complete current task
|
||||
|
||||
|
|
@ -1663,8 +1663,8 @@ void Bot::overrideConditions () {
|
|||
const int nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
if (nearestToEnemyPoint != kInvalidNodeIndex
|
||||
&& nearestToEnemyPoint != m_currentNodeIndex
|
||||
&& cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
|
||||
&& nearestToEnemyPoint != m_currentNodeIndex
|
||||
&& cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
|
||||
|
||||
if (tid != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) {
|
||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + length / (m_moveSpeed * 2.0f), true);
|
||||
|
|
@ -1686,8 +1686,8 @@ void Bot::overrideConditions () {
|
|||
|
||||
// special handling for sniping
|
||||
if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))
|
||||
&& m_shootTime - 0.4f <= game.time ()
|
||||
&& m_sniperStopTime > game.time ()) {
|
||||
&& m_shootTime - 0.4f <= game.time ()
|
||||
&& m_sniperStopTime > game.time ()) {
|
||||
|
||||
ignoreCollision ();
|
||||
|
||||
|
|
@ -1699,11 +1699,11 @@ void Bot::overrideConditions () {
|
|||
|
||||
// special handling for reloading
|
||||
if (!bots.isRoundOver () &&
|
||||
tid == Task::Normal
|
||||
&& m_reloadState != Reload::None
|
||||
&& m_isReloading
|
||||
&& !isDucking ()
|
||||
&& !isInNarrowPlace ()) {
|
||||
tid == Task::Normal
|
||||
&& m_reloadState != Reload::None
|
||||
&& m_isReloading
|
||||
&& !isDucking ()
|
||||
&& !isInNarrowPlace ()) {
|
||||
|
||||
if (m_reloadState != Reload::None || m_isReloading) {
|
||||
const auto maxClip = conf.findWeaponById (m_currentWeapon).maxClip;
|
||||
|
|
@ -1901,8 +1901,8 @@ void Bot::setConditions () {
|
|||
|
||||
// don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman)
|
||||
if (m_soundUpdateTime < game.time ()
|
||||
&& m_blindTime < game.time ()
|
||||
&& m_seeEnemyTime + 1.0f < game.time ()) {
|
||||
&& m_blindTime < game.time ()
|
||||
&& m_seeEnemyTime + 1.0f < game.time ()) {
|
||||
|
||||
updateHearing ();
|
||||
m_soundUpdateTime = game.time () + 0.25f;
|
||||
|
|
@ -2037,14 +2037,14 @@ void Bot::filterTasks () {
|
|||
|
||||
// if half of the round is over, allow hunting
|
||||
if (getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& game.isNullEntity (m_enemy)
|
||||
&& !m_isVIP
|
||||
&& bots.getRoundMidTime () < game.time ()
|
||||
&& !m_hasHostage
|
||||
&& !m_isUsingGrenade
|
||||
&& m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin)
|
||||
&& m_personality != Personality::Careful
|
||||
&& !cv_ignore_enemies) {
|
||||
&& game.isNullEntity (m_enemy)
|
||||
&& !m_isVIP
|
||||
&& bots.getRoundMidTime () < game.time ()
|
||||
&& !m_hasHostage
|
||||
&& !m_isUsingGrenade
|
||||
&& m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin)
|
||||
&& m_personality != Personality::Careful
|
||||
&& !cv_ignore_enemies) {
|
||||
|
||||
float desireLevel = 4096.0f - ((1.0f - tempAgression) * m_lastEnemyOrigin.distance (pev->origin));
|
||||
|
||||
|
|
@ -2343,8 +2343,8 @@ void Bot::checkRadioQueue () {
|
|||
// check if line of sight to object is not blocked (i.e. visible)
|
||||
if (seesEntity (m_radioEntity->v.origin) || m_radioOrder == Radio::StickTogetherTeam) {
|
||||
if (game.isNullEntity (m_targetEntity)
|
||||
&& game.isNullEntity (m_enemy)
|
||||
&& rg.chance (m_personality == Personality::Careful ? 80 : 20)) {
|
||||
&& game.isNullEntity (m_enemy)
|
||||
&& rg.chance (m_personality == Personality::Careful ? 80 : 20)) {
|
||||
|
||||
int numFollowers = 0;
|
||||
|
||||
|
|
@ -2453,8 +2453,8 @@ void Bot::checkRadioQueue () {
|
|||
case Chatter::ScaredEmotion:
|
||||
case Chatter::PinnedDown:
|
||||
if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distanceSq < cr::sqrf (2048.0f) || !m_moveToC4)
|
||||
&& rg.chance (50)
|
||||
&& m_seeEnemyTime + 4.0f < game.time ()) {
|
||||
&& rg.chance (50)
|
||||
&& m_seeEnemyTime + 4.0f < game.time ()) {
|
||||
|
||||
m_fearLevel -= 0.1f;
|
||||
|
||||
|
|
@ -2737,9 +2737,9 @@ void Bot::checkRadioQueue () {
|
|||
|
||||
// check if it's a ct command
|
||||
if (game.getTeam (m_radioEntity) == Team::CT
|
||||
&& m_team == Team::CT
|
||||
&& util.isFakeClient (m_radioEntity)
|
||||
&& bots.getPlantedBombSearchTimestamp () < game.time ()) {
|
||||
&& m_team == Team::CT
|
||||
&& util.isFakeClient (m_radioEntity)
|
||||
&& bots.getPlantedBombSearchTimestamp () < game.time ()) {
|
||||
|
||||
float nearestDistanceSq = kInfiniteDistance;
|
||||
int bombPoint = kInvalidNodeIndex;
|
||||
|
|
@ -2836,8 +2836,8 @@ void Bot::tryHeadTowardRadioMessage () {
|
|||
}
|
||||
|
||||
if ((util.isFakeClient (m_radioEntity)
|
||||
&& rg.chance (25)
|
||||
&& m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) {
|
||||
&& rg.chance (25)
|
||||
&& m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) {
|
||||
|
||||
if (tid == Task::Pause || tid == Task::Camp) {
|
||||
getTask ()->time = game.time ();
|
||||
|
|
@ -2894,9 +2894,9 @@ void Bot::frame () {
|
|||
const Vector &bombPosition = graph.getBombOrigin ();
|
||||
|
||||
if (!m_hasProgressBar
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& pev->origin.distanceSq (bombPosition) < cr::sqrf (1540.0f)
|
||||
&& !isBombDefusing (bombPosition)) {
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& pev->origin.distanceSq (bombPosition) < cr::sqrf (1540.0f)
|
||||
&& !isBombDefusing (bombPosition)) {
|
||||
|
||||
m_itemIgnore = nullptr;
|
||||
m_itemCheckTime = game.time ();
|
||||
|
|
@ -2981,9 +2981,9 @@ void Bot::update () {
|
|||
}
|
||||
}
|
||||
else if (m_buyingFinished
|
||||
&& !(pev->maxspeed < 10.0f && tid != Task::PlantBomb && tid != Task::DefuseBomb)
|
||||
&& !cv_freeze_bots
|
||||
&& !graph.hasChanged ()) {
|
||||
&& !(pev->maxspeed < 10.0f && tid != Task::PlantBomb && tid != Task::DefuseBomb)
|
||||
&& !cv_freeze_bots
|
||||
&& !graph.hasChanged ()) {
|
||||
|
||||
botMovement = true;
|
||||
}
|
||||
|
|
@ -3055,9 +3055,9 @@ void Bot::checkSpawnConditions () {
|
|||
}
|
||||
|
||||
if (m_difficulty >= Difficulty::Normal
|
||||
&& rg.chance (m_personality == Personality::Rusher ? 99 : 50)
|
||||
&& !m_isReloading
|
||||
&& game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) {
|
||||
&& rg.chance (m_personality == Personality::Rusher ? 99 : 50)
|
||||
&& !m_isReloading
|
||||
&& game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) {
|
||||
|
||||
if (isKnifeMode ()) {
|
||||
dropCurrentWeapon ();
|
||||
|
|
@ -3165,15 +3165,15 @@ void Bot::logic () {
|
|||
pushChatterMessage (Chatter::VIPSpotted);
|
||||
}
|
||||
else if (!hasFriendNearby
|
||||
&& rg.chance (50)
|
||||
&& game.getTeam (m_enemy) != m_team
|
||||
&& isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) {
|
||||
&& rg.chance (50)
|
||||
&& game.getTeam (m_enemy) != m_team
|
||||
&& isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) {
|
||||
|
||||
pushChatterMessage (Chatter::ScaredEmotion);
|
||||
}
|
||||
else if (!hasFriendNearby
|
||||
&& rg.chance (40)
|
||||
&& (m_enemy->v.weapons & kSniperWeaponMask)) {
|
||||
&& rg.chance (40)
|
||||
&& (m_enemy->v.weapons & kSniperWeaponMask)) {
|
||||
|
||||
pushChatterMessage (Chatter::SniperWarning);
|
||||
}
|
||||
|
|
@ -3620,7 +3620,7 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
|
|||
if (victimIndex == kInvalidNodeIndex) {
|
||||
victimIndex = findNearestNode ();
|
||||
}
|
||||
|
||||
|
||||
if (m_healthValue > 20.0f) {
|
||||
if (victimTeam == Team::Terrorist || victimTeam == Team::CT) {
|
||||
practice.setDamage (victimIndex, victimIndex, victimIndex, cr::clamp (practice.getDamage (victimTeam, victimIndex, victimIndex), 0, kMaxDamageValue));
|
||||
|
|
@ -3824,8 +3824,8 @@ void Bot::runMovement () {
|
|||
translateInput ();
|
||||
|
||||
engfuncs.pfnRunPlayerMove (pev->pContainingEntity,
|
||||
getRpmAngles (), m_moveSpeed, m_strafeSpeed,
|
||||
0.0f, static_cast <uint16_t> (pev->button), static_cast <uint8_t> (pev->impulse), msecVal);
|
||||
getRpmAngles (), m_moveSpeed, m_strafeSpeed,
|
||||
0.0f, static_cast <uint16_t> (pev->button), static_cast <uint8_t> (pev->impulse), msecVal);
|
||||
|
||||
// save our own copy of old buttons, since bot bot code is not running every frame now
|
||||
m_oldButtons = pev->button;
|
||||
|
|
@ -3898,11 +3898,11 @@ void Bot::updateHearing () {
|
|||
// loop through all enemy clients to check for hearable stuff
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if (!(client.flags & ClientFlags::Used)
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.ent == ent ()
|
||||
|| client.team == m_team
|
||||
|| !client.ent
|
||||
|| client.noise.last < game.time ()) {
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.ent == ent ()
|
||||
|| client.team == m_team
|
||||
|| !client.ent
|
||||
|| client.noise.last < game.time ()) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
@ -3926,12 +3926,12 @@ void Bot::updateHearing () {
|
|||
if (util.isPlayer (m_hearedEnemy)) {
|
||||
// change to best weapon if heard something
|
||||
if (m_shootTime < game.time () - 5.0f
|
||||
&& isOnFloor ()
|
||||
&& m_currentWeapon != Weapon::C4
|
||||
&& m_currentWeapon != Weapon::Explosive
|
||||
&& m_currentWeapon != Weapon::Smoke
|
||||
&& m_currentWeapon != Weapon::Flashbang
|
||||
&& !isKnifeMode ()) {
|
||||
&& isOnFloor ()
|
||||
&& m_currentWeapon != Weapon::C4
|
||||
&& m_currentWeapon != Weapon::Explosive
|
||||
&& m_currentWeapon != Weapon::Smoke
|
||||
&& m_currentWeapon != Weapon::Flashbang
|
||||
&& !isKnifeMode ()) {
|
||||
|
||||
selectBestWeapon ();
|
||||
}
|
||||
|
|
@ -3995,10 +3995,10 @@ void Bot::updateHearing () {
|
|||
// check if heard enemy can be shoot through some obstacle
|
||||
else {
|
||||
if (cv_shoots_thru_walls
|
||||
&& m_lastEnemy == m_hearedEnemy
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
|
||||
&& m_seeEnemyTime + 3.0f > game.time ()
|
||||
&& isPenetrableObstacle (m_hearedEnemy->v.origin)) {
|
||||
&& m_lastEnemy == m_hearedEnemy
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
|
||||
&& m_seeEnemyTime + 3.0f > game.time ()
|
||||
&& isPenetrableObstacle (m_hearedEnemy->v.origin)) {
|
||||
|
||||
m_enemy = m_hearedEnemy;
|
||||
m_lastEnemy = m_hearedEnemy;
|
||||
|
|
@ -4022,11 +4022,11 @@ void Bot::enteredBuyZone (int buyState) {
|
|||
|
||||
// if bot is in buy zone, try to buy ammo for this weapon...
|
||||
if (m_seeEnemyTime + 12.0f < game.time ()
|
||||
&& m_lastEquipTime + 15.0f < game.time ()
|
||||
&& m_inBuyZone
|
||||
&& (bots.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as <float> () < game.time ())
|
||||
&& !bots.isBombPlanted ()
|
||||
&& m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) {
|
||||
&& m_lastEquipTime + 15.0f < game.time ()
|
||||
&& m_inBuyZone
|
||||
&& (bots.getRoundStartTime () + rg (10.0f, 20.0f) + mp_buytime.as <float> () < game.time ())
|
||||
&& !bots.isBombPlanted ()
|
||||
&& m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) {
|
||||
|
||||
m_ignoreBuyDelay = true;
|
||||
m_buyingFinished = false;
|
||||
|
|
@ -4095,7 +4095,7 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) {
|
|||
if (client.team == m_team) {
|
||||
|
||||
// if close enough, mark as progressing
|
||||
if (bombDistanceSq < distanceToBomb && ((client.ent->v.button | client.ent->v.oldbuttons) & IN_USE)) {
|
||||
if (bombDistanceSq < distanceToBomb && ((client.ent->v.button | client.ent->v.oldbuttons) & IN_USE)) {
|
||||
defusingInProgress = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -4107,18 +4107,18 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) {
|
|||
|
||||
float Bot::getShiftSpeed () {
|
||||
if (getCurrentTaskId () == Task::SeekCover
|
||||
|| (m_aimFlags & AimFlags::Enemy)
|
||||
|| isDucking ()
|
||||
|| (pev->button & IN_DUCK)
|
||||
|| (m_oldButtons & IN_DUCK)
|
||||
|| (m_currentTravelFlags & PathFlag::Jump)
|
||||
|| (m_pathFlags & NodeFlag::Ladder)
|
||||
|| isOnLadder ()
|
||||
|| isInWater ()
|
||||
|| isKnifeMode ()
|
||||
|| m_isStuck
|
||||
|| m_numEnemiesLeft <= 0
|
||||
|| !m_lostReachableNodeTimer.elapsed ()) {
|
||||
|| (m_aimFlags & AimFlags::Enemy)
|
||||
|| isDucking ()
|
||||
|| (pev->button & IN_DUCK)
|
||||
|| (m_oldButtons & IN_DUCK)
|
||||
|| (m_currentTravelFlags & PathFlag::Jump)
|
||||
|| (m_pathFlags & NodeFlag::Ladder)
|
||||
|| isOnLadder ()
|
||||
|| isInWater ()
|
||||
|| isKnifeMode ()
|
||||
|| m_isStuck
|
||||
|| m_numEnemiesLeft <= 0
|
||||
|| !m_lostReachableNodeTimer.elapsed ()) {
|
||||
|
||||
return pev->maxspeed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ void Bot::prepareChatMessage (StringRef message) {
|
|||
|
||||
// get bot's victim
|
||||
auto getMyVictim = [&] () -> String {;
|
||||
return humanizedName (game.indexOfPlayer (m_lastVictim));
|
||||
return humanizedName (game.indexOfPlayer (m_lastVictim));
|
||||
};
|
||||
|
||||
// get the game name alias
|
||||
|
|
@ -336,9 +336,9 @@ void Bot::checkForChat () {
|
|||
|
||||
// bot chatting turned on?
|
||||
if (rg.chance (cv_chat_percent.as <int> ())
|
||||
&& m_lastChatTime + rg (6.0f, 10.0f) < game.time ()
|
||||
&& bots.getLastChatTimestamp () + rg (2.5f, 5.0f) < game.time ()
|
||||
&& !isReplyingToChat ()) {
|
||||
&& m_lastChatTime + rg (6.0f, 10.0f) < game.time ()
|
||||
&& bots.getLastChatTimestamp () + rg (2.5f, 5.0f) < game.time ()
|
||||
&& !isReplyingToChat ()) {
|
||||
|
||||
if (conf.hasChatBank (Chat::Dead)) {
|
||||
StringRef phrase = conf.pickRandomFromChatBank (Chat::Dead);
|
||||
|
|
|
|||
109
src/combat.cpp
109
src/combat.cpp
|
|
@ -233,9 +233,9 @@ bool Bot::seesEnemy (edict_t *player) {
|
|||
}
|
||||
|
||||
if ((ignoreFieldOfView || isInViewCone (player->v.origin))
|
||||
&& frustum.check (m_viewFrustum, player)
|
||||
&& !isBehindSmokeClouds (player->v.origin)
|
||||
&& checkBodyParts (player)) {
|
||||
&& frustum.check (m_viewFrustum, player)
|
||||
&& !isBehindSmokeClouds (player->v.origin)
|
||||
&& checkBodyParts (player)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -275,7 +275,7 @@ bool Bot::lookupEnemies () {
|
|||
&& m_shootTime + 1.5f > game.time ();
|
||||
|
||||
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger))
|
||||
&& !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
&& !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
|
|
@ -287,9 +287,9 @@ bool Bot::lookupEnemies () {
|
|||
|
||||
// is player is alive
|
||||
if (m_enemyUpdateTime > game.time ()
|
||||
&& m_enemy->v.origin.distanceSq (pev->origin) < nearestDistanceSq
|
||||
&& util.isAlive (player)
|
||||
&& seesEnemy (player)) {
|
||||
&& m_enemy->v.origin.distanceSq (pev->origin) < nearestDistanceSq
|
||||
&& util.isAlive (player)
|
||||
&& seesEnemy (player)) {
|
||||
|
||||
newEnemy = player;
|
||||
}
|
||||
|
|
@ -336,10 +336,10 @@ bool Bot::lookupEnemies () {
|
|||
// search the world for players...
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if (!(client.flags & ClientFlags::Used)
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.team == m_team
|
||||
|| client.ent == ent ()
|
||||
|| !client.ent) {
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.team == m_team
|
||||
|| client.ent == ent ()
|
||||
|| !client.ent) {
|
||||
continue;
|
||||
}
|
||||
player = client.ent;
|
||||
|
|
@ -374,7 +374,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
}
|
||||
m_enemyUpdateTime = game.time () + (usesKnife () ? 1.25f : 0.85f);
|
||||
|
||||
|
||||
if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) {
|
||||
newEnemy = shieldEnemy;
|
||||
}
|
||||
|
|
@ -451,9 +451,9 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
|
||||
if (other->m_seeEnemyTime + 2.0f < game.time ()
|
||||
&& game.isNullEntity (other->m_lastEnemy)
|
||||
&& util.isVisible (pev->origin, other->ent ())
|
||||
&& other->isInViewCone (pev->origin)) {
|
||||
&& game.isNullEntity (other->m_lastEnemy)
|
||||
&& util.isVisible (pev->origin, other->ent ())
|
||||
&& other->isInViewCone (pev->origin)) {
|
||||
|
||||
other->m_lastEnemy = newEnemy;
|
||||
other->m_lastEnemyOrigin = newEnemy->v.origin;
|
||||
|
|
@ -496,8 +496,8 @@ bool Bot::lookupEnemies () {
|
|||
|
||||
// if no enemy visible check if last one shoot able through wall
|
||||
if (cv_shoots_thru_walls
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
|
||||
&& isPenetrableObstacle (newEnemy->v.origin)) {
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
|
||||
&& isPenetrableObstacle (newEnemy->v.origin)) {
|
||||
|
||||
m_seeEnemyTime = game.time ();
|
||||
|
||||
|
|
@ -514,13 +514,13 @@ bool Bot::lookupEnemies () {
|
|||
|
||||
// check if bots should reload...
|
||||
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 ()) {
|
||||
&& 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;
|
||||
|
|
@ -550,7 +550,7 @@ Vector Bot::getBodyOffsetError (float distance) {
|
|||
|
||||
m_aimLastError = Vector (rg (mins.x * hitError, maxs.x * hitError), rg (mins.y * hitError, maxs.y * hitError), rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f));
|
||||
|
||||
const auto &aimError = conf.getDifficultyTweaks (m_difficulty) ->aimError;
|
||||
const auto &aimError = conf.getDifficultyTweaks (m_difficulty)->aimError;
|
||||
m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z));
|
||||
|
||||
m_aimErrorTime = game.time () + rg (0.4f, 0.8f);
|
||||
|
|
@ -661,7 +661,7 @@ Vector Bot::getCustomHeight (float distance) {
|
|||
{ 0.0f, 0.0f, 0.0f }, // melee
|
||||
{ 0.5f, -0.1f, -1.5f }, // pistol
|
||||
{ 6.5f, 6.0f, -2.0f }, // shotgun
|
||||
{ 0.5f -7.5f, -9.5f }, // zoomrifle
|
||||
{ 0.5f - 7.5f, -9.5f }, // zoomrifle
|
||||
{ 0.5f, -7.5f, -9.5f }, // rifle
|
||||
{ 0.5f, -7.5f, -9.5f }, // smg
|
||||
{ 0.0f, -2.5f, -6.0f }, // sniper
|
||||
|
|
@ -714,7 +714,7 @@ bool Bot::isFriendInLineOfFire (float distance) {
|
|||
const auto friendDistanceSq = client.ent->v.origin.distanceSq (pev->origin);
|
||||
|
||||
if (friendDistanceSq <= distanceSq
|
||||
&& util.getConeDeviation (ent (), client.ent->v.origin) > friendDistanceSq / (friendDistanceSq + cr::sqrf (33.0f))) {
|
||||
&& util.getConeDeviation (ent (), client.ent->v.origin) > friendDistanceSq / (friendDistanceSq + cr::sqrf (33.0f))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1010,7 +1010,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
|
||||
// we're should stand still before firing sniper weapons, else sniping is useless..
|
||||
if (usesSniper () && (m_aimFlags & (AimFlags::Enemy | AimFlags::LastEnemy))
|
||||
&& !m_isReloading && pev->velocity.lengthSq () > 0.0f) {
|
||||
&& !m_isReloading && pev->velocity.lengthSq () > 0.0f) {
|
||||
|
||||
if (!cr::fzero (pev->velocity.x) || !cr::fzero (pev->velocity.y) || !cr::fzero (pev->velocity.z)) {
|
||||
m_moveSpeed = 0.0f;
|
||||
|
|
@ -1119,12 +1119,12 @@ void Bot::fireWeapons () {
|
|||
|
||||
// use knife if near and good difficulty (l33t dude!)
|
||||
if (cv_stab_close_enemies && m_difficulty >= Difficulty::Normal
|
||||
&& m_healthValue > 80.0f
|
||||
&& !game.isNullEntity (m_enemy)
|
||||
&& m_healthValue >= m_enemy->v.health
|
||||
&& distance < 100.0f
|
||||
&& !isOnLadder ()
|
||||
&& !isGroupOfEnemies (pev->origin)) {
|
||||
&& m_healthValue > 80.0f
|
||||
&& !game.isNullEntity (m_enemy)
|
||||
&& m_healthValue >= m_enemy->v.health
|
||||
&& distance < 100.0f
|
||||
&& !isOnLadder ()
|
||||
&& !isGroupOfEnemies (pev->origin)) {
|
||||
|
||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||
return;
|
||||
|
|
@ -1364,8 +1364,8 @@ void Bot::attackMovement () {
|
|||
|
||||
// fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles
|
||||
if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ())
|
||||
&& distance < pistolStrafeDistance
|
||||
&& isInViewCone (m_enemyOrigin))) {
|
||||
&& distance < pistolStrafeDistance
|
||||
&& isInViewCone (m_enemyOrigin))) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
m_lastFightStyleCheck = game.time ();
|
||||
|
|
@ -1446,10 +1446,10 @@ void Bot::attackMovement () {
|
|||
}
|
||||
|
||||
if (m_difficulty >= Difficulty::Normal
|
||||
&& (m_jumpTime + 5.0f < game.time ()
|
||||
&& isOnFloor ()
|
||||
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
|
||||
&& pev->velocity.length2d () > 150.0f) && !usesSniper ()) {
|
||||
&& (m_jumpTime + 5.0f < game.time ()
|
||||
&& isOnFloor ()
|
||||
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
|
||||
&& pev->velocity.length2d () > 150.0f) && !usesSniper ()) {
|
||||
|
||||
pev->button |= IN_JUMP;
|
||||
}
|
||||
|
|
@ -1461,14 +1461,14 @@ void Bot::attackMovement () {
|
|||
m_duckTime = game.time () + m_frameInterval * 3.0f;
|
||||
}
|
||||
else if ((distance > kSprayDistanceX2 && hasPrimaryWeapon ())
|
||||
&& isFullView
|
||||
&& getCurrentTaskId () != Task::SeekCover
|
||||
&& getCurrentTaskId () != Task::Hunt) {
|
||||
&& isFullView
|
||||
&& getCurrentTaskId () != Task::SeekCover
|
||||
&& getCurrentTaskId () != Task::Hunt) {
|
||||
|
||||
const int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
if (vistab.visible (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch)
|
||||
&& vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) {
|
||||
&& vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) {
|
||||
m_duckTime = game.time () + m_frameInterval * 3.0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -1846,11 +1846,11 @@ void Bot::checkReload () {
|
|||
|
||||
// we're should not reload, while doing next tasks
|
||||
const bool uninterruptibleTask = (tid == Task::PlantBomb
|
||||
|| tid == Task::DefuseBomb
|
||||
|| tid == Task::PickupItem
|
||||
|| tid == Task::ThrowExplosive
|
||||
|| tid == Task::ThrowFlashbang
|
||||
|| tid == Task::ThrowSmoke);
|
||||
|| tid == Task::DefuseBomb
|
||||
|| tid == Task::PickupItem
|
||||
|| tid == Task::ThrowExplosive
|
||||
|| tid == Task::ThrowFlashbang
|
||||
|| tid == Task::ThrowSmoke);
|
||||
|
||||
// do not check for reload
|
||||
if (uninterruptibleTask || m_isUsingGrenade || usesKnife ()) {
|
||||
|
|
@ -2130,10 +2130,10 @@ void Bot::checkGrenadesThrow () {
|
|||
|
||||
// special condition if we're have valid current enemy
|
||||
if (!isGrenadeMode && ((m_states & Sense::SeeingEnemy)
|
||||
&& util.isAlive (m_enemy)
|
||||
&& ((m_enemy->v.button | m_enemy->v.oldbuttons) & IN_ATTACK)
|
||||
&& util.isVisible (pev->origin, m_enemy))
|
||||
&& util.isInViewCone (pev->origin, m_enemy)) {
|
||||
&& util.isAlive (m_enemy)
|
||||
&& ((m_enemy->v.button | m_enemy->v.oldbuttons) & IN_ATTACK)
|
||||
&& util.isVisible (pev->origin, m_enemy))
|
||||
&& util.isInViewCone (pev->origin, m_enemy)) {
|
||||
|
||||
// do not throw away grenades if anyone is attacking us
|
||||
distanceSq = kInfiniteDistance;
|
||||
|
|
@ -2201,7 +2201,8 @@ void Bot::checkGrenadesThrow () {
|
|||
}
|
||||
break;
|
||||
|
||||
case Weapon::Flashbang: {
|
||||
case Weapon::Flashbang:
|
||||
{
|
||||
const int nearest = graph.getNearest (m_lastEnemy->v.velocity.get2d () + m_lastEnemy->v.origin);
|
||||
|
||||
if (nearest != kInvalidNodeIndex) {
|
||||
|
|
|
|||
|
|
@ -103,9 +103,9 @@ int BotControl::cmdFill () {
|
|||
return BotCommandResult::BadFormat;
|
||||
}
|
||||
bots.serverFill (arg <int> (team),
|
||||
hasArg (personality) ? arg <int> (personality) : -1,
|
||||
hasArg (difficulty) ? arg <int> (difficulty) : -1,
|
||||
hasArg (count) ? arg <int> (count) - 1 : -1);
|
||||
hasArg (personality) ? arg <int> (personality) : -1,
|
||||
hasArg (difficulty) ? arg <int> (difficulty) : -1,
|
||||
hasArg (count) ? arg <int> (count) - 1 : -1);
|
||||
|
||||
return BotCommandResult::Handled;
|
||||
}
|
||||
|
|
@ -1299,8 +1299,8 @@ int BotControl::menuCommands (int item) {
|
|||
case 1:
|
||||
case 2:
|
||||
if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false)
|
||||
&& !m_djump->m_hasC4
|
||||
&& !m_djump->m_hasHostage) {
|
||||
&& !m_djump->m_hasC4
|
||||
&& !m_djump->m_hasHostage) {
|
||||
|
||||
if (item == 1) {
|
||||
m_djump->startDoubleJump (m_ent);
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ int BotGraph::clearConnections (int index) {
|
|||
|
||||
// leave alone ladder connections and don't remove jump connections..
|
||||
if (((path.flags & NodeFlag::Ladder)
|
||||
&& (m_paths[prev.index].flags & NodeFlag::Ladder)) || (path.links[prev.number].flags & PathFlag::Jump)) {
|
||||
&& (m_paths[prev.index].flags & NodeFlag::Ladder)) || (path.links[prev.number].flags & PathFlag::Jump)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -217,8 +217,8 @@ int BotGraph::clearConnections (int index) {
|
|||
if (exists (top.index) && exists (sorted[0].index) && exists (sorted[1].index)) {
|
||||
|
||||
if ((sorted[1].angles - top.angles < 80.0f || 360.0f - (sorted[1].angles - top.angles) < 80.0f)
|
||||
&& (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder))
|
||||
&& !(path.links[sorted[0].number].flags & PathFlag::Jump)) {
|
||||
&& (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder))
|
||||
&& !(path.links[sorted[0].number].flags & PathFlag::Jump)) {
|
||||
|
||||
if ((sorted[1].distance + top.distance) * 1.1f / 2.0f < sorted[0].distance) {
|
||||
if (path.links[sorted[0].number].index == sorted[0].index) {
|
||||
|
|
@ -266,7 +266,7 @@ int BotGraph::clearConnections (int index) {
|
|||
|
||||
// leave alone ladder connections and don't remove jump connections..
|
||||
if (((path.flags & NodeFlag::Ladder)
|
||||
&& (m_paths[cur.index].flags & NodeFlag::Ladder)) || (path.links[cur.number].flags & PathFlag::Jump)) {
|
||||
&& (m_paths[cur.index].flags & NodeFlag::Ladder)) || (path.links[cur.number].flags & PathFlag::Jump)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -300,8 +300,8 @@ int BotGraph::clearConnections (int index) {
|
|||
else if (cur.distance < prev.distance * 1.1f) {
|
||||
// leave alone ladder connections and don't remove jump connections..
|
||||
if (((path.flags & NodeFlag::Ladder)
|
||||
&& (m_paths[prev.index].flags & NodeFlag::Ladder))
|
||||
|| (path.links[prev.number].flags & PathFlag::Jump)) {
|
||||
&& (m_paths[prev.index].flags & NodeFlag::Ladder))
|
||||
|| (path.links[prev.number].flags & PathFlag::Jump)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -349,8 +349,8 @@ int BotGraph::clearConnections (int index) {
|
|||
if (exists (top.index) && exists (sorted[0].index)) {
|
||||
|
||||
if ((top.angles - sorted[0].angles < 40.0f || (360.0f - top.angles - sorted[0].angles) < 40.0f)
|
||||
&& (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder))
|
||||
&& !(path.links[sorted[0].number].flags & PathFlag::Jump)) {
|
||||
&& (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder))
|
||||
&& !(path.links[sorted[0].number].flags & PathFlag::Jump)) {
|
||||
|
||||
if (top.distance * 1.1f < sorted[0].distance) {
|
||||
if (path.links[sorted[0].number].index == sorted[0].index) {
|
||||
|
|
@ -435,7 +435,7 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) {
|
|||
if (link.index == kInvalidNodeIndex) {
|
||||
link.index = static_cast <int16_t> (pathIndex);
|
||||
link.distance = integerDistance;
|
||||
|
||||
|
||||
msg ("Path added from %d to %d.", addIndex, pathIndex);
|
||||
return;
|
||||
}
|
||||
|
|
@ -832,9 +832,9 @@ void BotGraph::add (int type, const Vector &pos) {
|
|||
game.testLine (newOrigin, calc.origin, TraceIgnore::Monsters, m_editor, &tr);
|
||||
|
||||
if (cr::fequal (tr.flFraction, 1.0f)
|
||||
&& cr::abs (newOrigin.x - calc.origin.x) < 64.0f
|
||||
&& cr::abs (newOrigin.y - calc.origin.y) < 64.0f
|
||||
&& cr::abs (newOrigin.z - calc.origin.z) < m_autoPathDistance) {
|
||||
&& cr::abs (newOrigin.x - calc.origin.x) < 64.0f
|
||||
&& cr::abs (newOrigin.y - calc.origin.y) < 64.0f
|
||||
&& cr::abs (newOrigin.z - calc.origin.z) < m_autoPathDistance) {
|
||||
|
||||
const float distance = newOrigin.distance2d (calc.origin);
|
||||
|
||||
|
|
@ -1527,7 +1527,7 @@ void BotGraph::syncInitLightLevels () {
|
|||
|
||||
// update light levels for all nodes
|
||||
for (auto &path : m_paths) {
|
||||
path.light = illum.getLightLevel (path.origin + Vector { 0.0f, 0.0f, 16.0f} );
|
||||
path.light = illum.getLightLevel (path.origin + Vector { 0.0f, 0.0f, 16.0f });
|
||||
}
|
||||
m_lightChecked = true;
|
||||
|
||||
|
|
@ -1796,7 +1796,7 @@ bool BotGraph::loadGraphData () {
|
|||
vistab.load (); // load/initialize visibility
|
||||
|
||||
populateNodes ();
|
||||
|
||||
|
||||
if (exten.mapSize > 0) {
|
||||
int mapSize = getBspSize ();
|
||||
|
||||
|
|
@ -2093,8 +2093,8 @@ void BotGraph::frame () {
|
|||
|
||||
// check if node is within a distance, and is visible
|
||||
if (distanceSq < cr::sqrf (cv_graph_draw_distance.as <float> ())
|
||||
&& ((util.isVisible (path.origin, m_editor)
|
||||
&& util.isInViewCone (path.origin, m_editor)) || !util.isAlive (m_editor) || distanceSq < cr::sqrf (64.0f))) {
|
||||
&& ((util.isVisible (path.origin, m_editor)
|
||||
&& util.isInViewCone (path.origin, m_editor)) || !util.isAlive (m_editor) || distanceSq < cr::sqrf (64.0f))) {
|
||||
|
||||
// check the distance
|
||||
if (distanceSq < nearestDistanceSq) {
|
||||
|
|
@ -2211,7 +2211,7 @@ void BotGraph::frame () {
|
|||
|
||||
// draw a paths, camplines and danger directions for nearest node
|
||||
if (nearestDistanceSq < cr::clamp (m_paths[nearestIndex].radius, cr::sqrf (56.0f), cr::sqrf (90.0f))
|
||||
&& m_pathDisplayTime < game.time ()) {
|
||||
&& m_pathDisplayTime < game.time ()) {
|
||||
|
||||
m_pathDisplayTime = game.time () + 0.96f;
|
||||
|
||||
|
|
@ -2341,17 +2341,17 @@ void BotGraph::frame () {
|
|||
}
|
||||
}
|
||||
flags.assignf ("%s%s%s%s%s%s%s%s%s%s%s%s",
|
||||
(p.flags & NodeFlag::Lift) ? " LIFT" : "",
|
||||
(p.flags & NodeFlag::Crouch) ? " CROUCH" : "",
|
||||
(p.flags & NodeFlag::Camp) ? " CAMP" : "",
|
||||
(p.flags & NodeFlag::TerroristOnly) ? " TERRORIST" : "",
|
||||
(p.flags & NodeFlag::CTOnly) ? " CT" : "",
|
||||
(p.flags & NodeFlag::Sniper) ? " SNIPER" : "",
|
||||
(p.flags & NodeFlag::Goal) ? " GOAL" : "",
|
||||
(p.flags & NodeFlag::Ladder) ? " LADDER" : "",
|
||||
(p.flags & NodeFlag::Rescue) ? " RESCUE" : "",
|
||||
(p.flags & NodeFlag::DoubleJump) ? " JUMPHELP" : "",
|
||||
(p.flags & NodeFlag::NoHostage) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : "");
|
||||
(p.flags & NodeFlag::Lift) ? " LIFT" : "",
|
||||
(p.flags & NodeFlag::Crouch) ? " CROUCH" : "",
|
||||
(p.flags & NodeFlag::Camp) ? " CAMP" : "",
|
||||
(p.flags & NodeFlag::TerroristOnly) ? " TERRORIST" : "",
|
||||
(p.flags & NodeFlag::CTOnly) ? " CT" : "",
|
||||
(p.flags & NodeFlag::Sniper) ? " SNIPER" : "",
|
||||
(p.flags & NodeFlag::Goal) ? " GOAL" : "",
|
||||
(p.flags & NodeFlag::Ladder) ? " LADDER" : "",
|
||||
(p.flags & NodeFlag::Rescue) ? " RESCUE" : "",
|
||||
(p.flags & NodeFlag::DoubleJump) ? " JUMPHELP" : "",
|
||||
(p.flags & NodeFlag::NoHostage) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : "");
|
||||
|
||||
if (flags.empty ()) {
|
||||
flags.assign ("(none)");
|
||||
|
|
@ -2359,9 +2359,9 @@ void BotGraph::frame () {
|
|||
|
||||
// show the information about that point
|
||||
message.assignf (" %s node:\n"
|
||||
" Node %d of %d, Radius: %.1f, Light: %s\n"
|
||||
" Flags: %s\n"
|
||||
" Origin: (%.1f, %.1f, %.1f)\n", type, node, m_paths.length () - 1, p.radius, p.light == kInvalidLightLevel ? "Invalid" : strings.format ("%1.f", p.light), flags, p.origin.x, p.origin.y, p.origin.z);
|
||||
" Node %d of %d, Radius: %.1f, Light: %s\n"
|
||||
" Flags: %s\n"
|
||||
" Origin: (%.1f, %.1f, %.1f)\n", type, node, m_paths.length () - 1, p.radius, p.light == kInvalidLightLevel ? "Invalid" : strings.format ("%1.f", p.light), flags, p.origin.x, p.origin.y, p.origin.z);
|
||||
return message;
|
||||
};
|
||||
|
||||
|
|
@ -2386,8 +2386,8 @@ void BotGraph::frame () {
|
|||
|
||||
String practiceText;
|
||||
practiceText.assignf (" Node practice data (index / damage):\n"
|
||||
" CT: %d / %d\n"
|
||||
" T: %d / %d\n\n", dangerIndexCT, dangerIndexCT != kInvalidNodeIndex ? practice.getDamage (Team::CT, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != kInvalidNodeIndex ? practice.getDamage (Team::Terrorist, nearestIndex, dangerIndexT) : 0);
|
||||
" CT: %d / %d\n"
|
||||
" T: %d / %d\n\n", dangerIndexCT, dangerIndexCT != kInvalidNodeIndex ? practice.getDamage (Team::CT, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != kInvalidNodeIndex ? practice.getDamage (Team::Terrorist, nearestIndex, dangerIndexT) : 0);
|
||||
|
||||
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, practiceText + timeMessage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,43 +34,43 @@ plugin_info_t Plugin_info = {
|
|||
|
||||
// compilers can't create lambdas with vaargs, so put this one in it's own namespace
|
||||
namespace Hooks {
|
||||
void handler_engClientCommand (edict_t *ent, char const *format, ...) {
|
||||
// this function forces the client whose player entity is ent to issue a client command.
|
||||
// How it works is that clients all have a argv global string in their client DLL that
|
||||
// stores the command string; if ever that string is filled with characters, the client DLL
|
||||
// sends it to the engine as a command to be executed. When the engine has executed that
|
||||
// command, this argv string is reset to zero. Here is somehow a curious implementation of
|
||||
// ClientCommand: the engine sets the command it wants the client to issue in his argv, then
|
||||
// the client DLL sends it back to the engine, the engine receives it then executes the
|
||||
// command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have
|
||||
// no client DLL, be certain never to call this function upon a bot entity, else it will just
|
||||
// make the server crash. Since hordes of uncautious, not to say stupid, programmers don't
|
||||
// even imagine some players on their servers could be bots, this check is performed less than
|
||||
// sometimes actually by their side, that's why we strongly recommend to check it here too. In
|
||||
// case it's a bot asking for a client command, we handle it like we do for bot commands
|
||||
void handler_engClientCommand (edict_t *ent, char const *format, ...) {
|
||||
// this function forces the client whose player entity is ent to issue a client command.
|
||||
// How it works is that clients all have a argv global string in their client DLL that
|
||||
// stores the command string; if ever that string is filled with characters, the client DLL
|
||||
// sends it to the engine as a command to be executed. When the engine has executed that
|
||||
// command, this argv string is reset to zero. Here is somehow a curious implementation of
|
||||
// ClientCommand: the engine sets the command it wants the client to issue in his argv, then
|
||||
// the client DLL sends it back to the engine, the engine receives it then executes the
|
||||
// command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have
|
||||
// no client DLL, be certain never to call this function upon a bot entity, else it will just
|
||||
// make the server crash. Since hordes of uncautious, not to say stupid, programmers don't
|
||||
// even imagine some players on their servers could be bots, this check is performed less than
|
||||
// sometimes actually by their side, that's why we strongly recommend to check it here too. In
|
||||
// case it's a bot asking for a client command, we handle it like we do for bot commands
|
||||
|
||||
if (!game.isNullEntity (ent)) {
|
||||
if (bots[ent] || util.isFakeClient (ent) || (ent->v.flags & FL_DORMANT)) {
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands
|
||||
}
|
||||
return;
|
||||
if (!game.isNullEntity (ent)) {
|
||||
if (bots[ent] || util.isFakeClient (ent) || (ent->v.flags & FL_DORMANT)) {
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_IGNORED);
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
auto buffer = strings.chars ();
|
||||
|
||||
va_start (ap, format);
|
||||
vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap);
|
||||
va_end (ap);
|
||||
|
||||
engfuncs.pfnClientCommand (ent, buffer);
|
||||
}
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_IGNORED);
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
auto buffer = strings.chars ();
|
||||
|
||||
va_start (ap, format);
|
||||
vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap);
|
||||
va_end (ap);
|
||||
|
||||
engfuncs.pfnClientCommand (ent, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
||||
|
|
@ -488,7 +488,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
|
|||
}
|
||||
|
||||
if (entlink.needsBypass () && !game.is (GameFlags::Metamod)) {
|
||||
table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t *{
|
||||
table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t * {
|
||||
|
||||
if (entlink.isPaused ()) {
|
||||
entlink.enable ();
|
||||
|
|
@ -924,7 +924,7 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
|
|||
worker.shutdown ();
|
||||
|
||||
// kick all bots off this server
|
||||
bots.kickEveryone (true);
|
||||
bots.kickEveryone (true);
|
||||
|
||||
// save collected practice on shutdown
|
||||
practice.save ();
|
||||
|
|
|
|||
|
|
@ -896,8 +896,8 @@ void BotManager::listBots () {
|
|||
auto timelimitStr = cv_rotate_bots ? strings.format ("%-3.0f secs", bot->m_stayTime - game.time ()) : "unlimited";
|
||||
|
||||
ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%s",
|
||||
bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful",
|
||||
botTeam (bot->ent ()), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr);
|
||||
bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful",
|
||||
botTeam (bot->ent ()), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr);
|
||||
}
|
||||
ctrl.msg ("%d bots", m_bots.length ());
|
||||
}
|
||||
|
|
@ -1325,10 +1325,10 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
|||
// need to send congrats on well placed shot
|
||||
for (const auto ¬ify : bots) {
|
||||
if (notify->m_isAlive
|
||||
&& killerTeam == notify->m_team
|
||||
&& killerTeam != victimTeam
|
||||
&& killer != notify->ent ()
|
||||
&& notify->seesEntity (victim->v.origin)) {
|
||||
&& killerTeam == notify->m_team
|
||||
&& killerTeam != victimTeam
|
||||
&& killer != notify->ent ()
|
||||
&& notify->seesEntity (victim->v.origin)) {
|
||||
|
||||
if (!(killer->v.flags & FL_FAKECLIENT)) {
|
||||
notify->pushChatterMessage (Chatter::NiceShotCommander);
|
||||
|
|
@ -1346,13 +1346,13 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
|||
// notice nearby to victim teammates, that attacker is near
|
||||
for (const auto ¬ify : bots) {
|
||||
if (notify->m_difficulty >= Difficulty::Hard
|
||||
&& killerTeam != victimTeam
|
||||
&& notify->m_seeEnemyTime + 2.0f < game.time ()
|
||||
&& notify->m_isAlive
|
||||
&& notify->m_team == victimTeam
|
||||
&& game.isNullEntity (notify->m_enemy)
|
||||
&& game.isNullEntity (notify->m_lastEnemy)
|
||||
&& util.isVisible (killer->v.origin, notify->ent ())) {
|
||||
&& killerTeam != victimTeam
|
||||
&& notify->m_seeEnemyTime + 2.0f < game.time ()
|
||||
&& notify->m_isAlive
|
||||
&& notify->m_team == victimTeam
|
||||
&& game.isNullEntity (notify->m_enemy)
|
||||
&& game.isNullEntity (notify->m_lastEnemy)
|
||||
&& util.isVisible (killer->v.origin, notify->ent ())) {
|
||||
|
||||
// make bot look at last enemy position
|
||||
notify->m_actualReactionTime = 0.0f;
|
||||
|
|
@ -1890,10 +1890,10 @@ void BotManager::notifyBombDefuse () {
|
|||
const auto task = bot->getCurrentTaskId ();
|
||||
|
||||
if (!bot->m_defuseNotified
|
||||
&& bot->m_isAlive
|
||||
&& task != Task::MoveToPosition
|
||||
&& task != Task::DefuseBomb
|
||||
&& task != Task::EscapeFromBomb) {
|
||||
&& bot->m_isAlive
|
||||
&& task != Task::MoveToPosition
|
||||
&& task != Task::DefuseBomb
|
||||
&& task != Task::EscapeFromBomb) {
|
||||
|
||||
if (bot->m_team == Team::Terrorist && bot->pev->origin.distanceSq (bombPos) < cr::sqrf (512.0f)) {
|
||||
bot->clearSearchNodes ();
|
||||
|
|
@ -2263,7 +2263,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, flo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// define how much smoke a bot can see thru
|
||||
const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius;
|
||||
|
||||
|
|
|
|||
108
src/navigate.cpp
108
src/navigate.cpp
|
|
@ -302,7 +302,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
|
|||
postprocessGoals (graph.m_goalPoints, goalChoices);
|
||||
}
|
||||
}
|
||||
else if (tactic == GoalTactic::RescueHostage && !graph.m_rescuePoints.empty ()) {
|
||||
else if (tactic == GoalTactic::RescueHostage && !graph.m_rescuePoints.empty ()) {
|
||||
// force ct with hostage(s) to select closest rescue goal
|
||||
float nearestDistanceSq = kInfiniteDistance;
|
||||
int count = 0;
|
||||
|
|
@ -893,7 +893,7 @@ void Bot::moveToGoal () {
|
|||
|
||||
src.z += 12.0f;
|
||||
dst.z += 18.0f + 28.0f;
|
||||
|
||||
|
||||
game.testLine (src, dst, TraceIgnore::Everything, ent (), &tr);
|
||||
|
||||
if (tr.flFraction >= 0.95f) {
|
||||
|
|
@ -1037,7 +1037,7 @@ bool Bot::updateNavigation () {
|
|||
selectBestWeapon ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (m_pathFlags & NodeFlag::Ladder) {
|
||||
const float ladderDistance = pev->origin.distance (m_pathOrigin);
|
||||
|
||||
|
|
@ -1074,10 +1074,10 @@ bool Bot::updateNavigation () {
|
|||
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if (!(client.flags & ClientFlags::Used)
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| (client.ent->v.movetype != MOVETYPE_FLY)
|
||||
|| client.team != m_team
|
||||
|| client.ent == ent ()) {
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| (client.ent->v.movetype != MOVETYPE_FLY)
|
||||
|| client.team != m_team
|
||||
|| client.ent == ent ()) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1091,8 +1091,8 @@ bool Bot::updateNavigation () {
|
|||
|
||||
// someone is above or below us and is using the ladder already
|
||||
if (tr.pHit == client.ent
|
||||
&& cr::abs (pev->origin.z - client.ent->v.origin.z) > 15.0f
|
||||
&& (client.ent->v.movetype == MOVETYPE_FLY)) {
|
||||
&& cr::abs (pev->origin.z - client.ent->v.origin.z) > 15.0f
|
||||
&& (client.ent->v.movetype == MOVETYPE_FLY)) {
|
||||
|
||||
const auto numPreviousNode = rg (0, 2);
|
||||
|
||||
|
|
@ -1246,8 +1246,8 @@ bool Bot::updateNavigation () {
|
|||
|
||||
// needs precise placement - check if we get past the point
|
||||
if (desiredDistanceSq < cr::sqrf (22.0f)
|
||||
&& nodeDistanceSq < cr::sqrf (30.0f)
|
||||
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistanceSq) {
|
||||
&& nodeDistanceSq < cr::sqrf (30.0f)
|
||||
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistanceSq) {
|
||||
|
||||
desiredDistanceSq = nodeDistanceSq + 1.0f;
|
||||
}
|
||||
|
|
@ -1287,10 +1287,10 @@ bool Bot::updateNavigation () {
|
|||
const int taskTarget = getTask ()->data;
|
||||
|
||||
if (game.mapIs (MapFlags::Demolition)
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_team == Team::CT
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& taskTarget != kInvalidNodeIndex) {
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_team == Team::CT
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& taskTarget != kInvalidNodeIndex) {
|
||||
|
||||
const Vector &bombOrigin = isBombAudible ();
|
||||
|
||||
|
|
@ -1350,9 +1350,9 @@ bool Bot::updateLiftHandling () {
|
|||
game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Everything, ent (), &tr);
|
||||
|
||||
if (tr.flFraction < 1.0f
|
||||
&& util.isDoorEntity (tr.pHit)
|
||||
&& (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside)
|
||||
&& pev->groundentity != tr.pHit) {
|
||||
&& util.isDoorEntity (tr.pHit)
|
||||
&& (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside)
|
||||
&& pev->groundentity != tr.pHit) {
|
||||
|
||||
if (m_liftState == LiftState::None) {
|
||||
m_liftState = LiftState::LookingButtonOutside;
|
||||
|
|
@ -1372,8 +1372,8 @@ bool Bot::updateLiftHandling () {
|
|||
// if trace result shows us that it is a lift
|
||||
if (!game.isNullEntity (tr.pHit) && !m_pathWalk.empty () && isFunc (tr.pHit->v.classname.str ()) && !liftClosedDoorExists) {
|
||||
if ((m_liftState == LiftState::None
|
||||
|| m_liftState == LiftState::WaitingFor
|
||||
|| m_liftState == LiftState::LookingButtonOutside) && cr::fzero (tr.pHit->v.velocity.z)) {
|
||||
|| m_liftState == LiftState::WaitingFor
|
||||
|| m_liftState == LiftState::LookingButtonOutside) && cr::fzero (tr.pHit->v.velocity.z)) {
|
||||
|
||||
if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) {
|
||||
m_liftEntity = tr.pHit;
|
||||
|
|
@ -1449,10 +1449,10 @@ bool Bot::updateLiftHandling () {
|
|||
|
||||
for (const auto &bot : bots) {
|
||||
if (!bot->m_isAlive
|
||||
|| bot->m_team != m_team
|
||||
|| bot->m_targetEntity != ent ()
|
||||
|| bot->getCurrentTaskId () != Task::FollowUser
|
||||
|| bot->m_liftEntity != m_liftEntity) {
|
||||
|| bot->m_team != m_team
|
||||
|| bot->m_targetEntity != ent ()
|
||||
|| bot->getCurrentTaskId () != Task::FollowUser
|
||||
|| bot->m_liftEntity != m_liftEntity) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1484,10 +1484,10 @@ bool Bot::updateLiftHandling () {
|
|||
|
||||
// got a valid button entity ?
|
||||
if (!game.isNullEntity (button)
|
||||
&& pev->groundentity == m_liftEntity
|
||||
&& m_buttonPushTime + 1.0f < game.time ()
|
||||
&& cr::fzero (m_liftEntity->v.velocity.z)
|
||||
&& isOnFloor ()) {
|
||||
&& pev->groundentity == m_liftEntity
|
||||
&& m_buttonPushTime + 1.0f < game.time ()
|
||||
&& cr::fzero (m_liftEntity->v.velocity.z)
|
||||
&& isOnFloor ()) {
|
||||
|
||||
auto buttonWithLineOfSight = lookupButton (m_liftEntity->v.targetname.str (), false);
|
||||
|
||||
|
|
@ -1508,14 +1508,14 @@ bool Bot::updateLiftHandling () {
|
|||
|
||||
// is lift activated and bot is standing on it and lift is moving ?
|
||||
if (m_liftState == LiftState::LookingButtonInside
|
||||
|| m_liftState == LiftState::EnteringIn
|
||||
|| m_liftState == LiftState::WaitingForTeammates
|
||||
|| m_liftState == LiftState::WaitingFor) {
|
||||
|| m_liftState == LiftState::EnteringIn
|
||||
|| m_liftState == LiftState::WaitingForTeammates
|
||||
|| m_liftState == LiftState::WaitingFor) {
|
||||
|
||||
if (pev->groundentity == m_liftEntity
|
||||
&& !cr::fzero (m_liftEntity->v.velocity.z)
|
||||
&& isOnFloor ()
|
||||
&& ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) {
|
||||
&& !cr::fzero (m_liftEntity->v.velocity.z)
|
||||
&& isOnFloor ()
|
||||
&& ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) {
|
||||
|
||||
m_liftState = LiftState::TravelingBy;
|
||||
m_liftUsageTime = game.time () + 14.0f;
|
||||
|
|
@ -1555,10 +1555,10 @@ bool Bot::updateLiftHandling () {
|
|||
// iterate though clients, and find if lift already used
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if (!(client.flags & ClientFlags::Used)
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.team != m_team
|
||||
|| client.ent == ent ()
|
||||
|| game.isNullEntity (client.ent->v.groundentity)) {
|
||||
|| !(client.flags & ClientFlags::Alive)
|
||||
|| client.team != m_team
|
||||
|| client.ent == ent ()
|
||||
|| game.isNullEntity (client.ent->v.groundentity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1613,8 +1613,8 @@ bool Bot::updateLiftHandling () {
|
|||
// bot fall down somewhere inside the lift's groove :)
|
||||
if (pev->groundentity != m_liftEntity && graph.exists (m_previousNodes[0])) {
|
||||
if ((graph[m_previousNodes[0]].flags & NodeFlag::Lift)
|
||||
&& (m_path->origin.z - pev->origin.z) > 50.0f
|
||||
&& (graph[m_previousNodes[0]].origin.z - pev->origin.z) > 50.0f) {
|
||||
&& (m_path->origin.z - pev->origin.z) > 50.0f
|
||||
&& (graph[m_previousNodes[0]].origin.z - pev->origin.z) > 50.0f) {
|
||||
|
||||
m_liftState = LiftState::None;
|
||||
m_liftEntity = nullptr;
|
||||
|
|
@ -2129,8 +2129,8 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
|
||||
for (const auto &path : graph) {
|
||||
if (origin.distanceSq (path.origin) < cr::sqrf (kMaxDistance)
|
||||
&& vistab.visible (path.number, posIndex)
|
||||
&& !isOccupiedNode (path.number)) {
|
||||
&& vistab.visible (path.number, posIndex)
|
||||
&& !isOccupiedNode (path.number)) {
|
||||
|
||||
found.push (path.number);
|
||||
}
|
||||
|
|
@ -2379,13 +2379,13 @@ bool Bot::advanceMovement () {
|
|||
|
||||
// only if we in normal task and bomb is not planted
|
||||
if (tid == Task::Normal
|
||||
&& bots.getRoundMidTime () + 5.0f < game.time ()
|
||||
&& m_timeCamping + 5.0f < game.time ()
|
||||
&& !bots.isBombPlanted ()
|
||||
&& m_personality != Personality::Rusher
|
||||
&& !m_hasC4 && !m_isVIP
|
||||
&& m_loosedBombNodeIndex == kInvalidNodeIndex
|
||||
&& !m_hasHostage && !m_isCreature) {
|
||||
&& bots.getRoundMidTime () + 5.0f < game.time ()
|
||||
&& m_timeCamping + 5.0f < game.time ()
|
||||
&& !bots.isBombPlanted ()
|
||||
&& m_personality != Personality::Rusher
|
||||
&& !m_hasC4 && !m_isVIP
|
||||
&& m_loosedBombNodeIndex == kInvalidNodeIndex
|
||||
&& !m_hasHostage && !m_isCreature) {
|
||||
|
||||
m_campButtons = 0;
|
||||
|
||||
|
|
@ -2497,10 +2497,10 @@ bool Bot::advanceMovement () {
|
|||
|
||||
// is there a jump node right ahead and do we need to draw out the light weapon ?
|
||||
if (willJump && !usesKnife ()
|
||||
&& m_currentWeapon != Weapon::Scout
|
||||
&& !m_isReloading && !usesPistol ()
|
||||
&& (jumpDistanceSq > cr::sqrf (145.0f) || (dst.z - 32.0f > src.z && jumpDistanceSq > cr::sqrf (125.0f)))
|
||||
&& !(m_states & Sense::SeeingEnemy)) {
|
||||
&& m_currentWeapon != Weapon::Scout
|
||||
&& !m_isReloading && !usesPistol ()
|
||||
&& (jumpDistanceSq > cr::sqrf (145.0f) || (dst.z - 32.0f > src.z && jumpDistanceSq > cr::sqrf (125.0f)))
|
||||
&& !(m_states & Sense::SeeingEnemy)) {
|
||||
|
||||
selectWeaponById (Weapon::Knife); // draw out the knife if we needed
|
||||
}
|
||||
|
|
@ -3351,7 +3351,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
|
|||
m_planner->setG (Heuristic::gfunctionPathDist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_chosenGoalIndex = srcIndex;
|
||||
m_goalValue = 0.0f;
|
||||
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ bool FloydWarshallAlgo::load () {
|
|||
return true;
|
||||
}
|
||||
rebuild (); // rebuilds matrix
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
910
src/storage.cpp
910
src/storage.cpp
|
|
@ -1,464 +1,464 @@
|
|||
//
|
||||
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
|
||||
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
#include <yapb.h>
|
||||
|
||||
#if defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS)
|
||||
|
||||
template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *exten, int32_t *outOptions) {
|
||||
auto type = guessType <U> ();
|
||||
String filename = buildPath (storageToBotFile (type.option), true);
|
||||
|
||||
extern ConVar cv_debug, cv_graph_url;
|
||||
|
||||
// graphs can be downloaded...
|
||||
const bool isGraph = !!(type.option & StorageOption::Graph);
|
||||
const bool isDebug = cv_debug;
|
||||
|
||||
MemFile file (filename); // open the file
|
||||
data.clear ();
|
||||
|
||||
// resize data to fit the stuff
|
||||
auto resizeData = [&] (const size_t length) {
|
||||
data.resize (length); // for non-graph data the graph should be already loaded
|
||||
data.shrink (); // free up memory to minimum
|
||||
|
||||
// ensure we're have enough memory to decompress the data
|
||||
data.ensure (length + ULZ::Excess);
|
||||
};
|
||||
|
||||
// if graph & attempted to load multiple times, bail out, we're failed
|
||||
if (isGraph && ++m_retries > 2) {
|
||||
resetRetries ();
|
||||
|
||||
return error (isGraph, isDebug, file, "Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", type.name, filename);
|
||||
}
|
||||
|
||||
// downloader for graph
|
||||
auto download = [&] () -> bool {
|
||||
if (!graph.canDownload ()) {
|
||||
return false;
|
||||
}
|
||||
String lowercaseMapName = game.getMapName ();
|
||||
lowercaseMapName = lowercaseMapName.lowercase ();
|
||||
|
||||
auto downloadAddress = cv_graph_url.as <StringRef> ();
|
||||
|
||||
auto toDownload = buildPath (storageToBotFile (type.option), false);
|
||||
auto fromDownload = strings.format ("%s://%s/graph/%s.graph", product.httpScheme, downloadAddress, lowercaseMapName);
|
||||
|
||||
// try to download
|
||||
if (http.downloadFile (fromDownload, toDownload)) {
|
||||
ctrl.msg ("%s file '%s' successfully downloaded. Processing...", type.name, filename);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ctrl.msg ("Can't download '%s' from '%s' to '%s'... (%d).", filename, fromDownload, toDownload, http.getLastStatusCode ());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// tries to reload or open pwf file
|
||||
auto tryReload = [&] () -> bool {
|
||||
file.close ();
|
||||
|
||||
if (!isGraph) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (download ()) {
|
||||
return load (data, exten, outOptions);
|
||||
}
|
||||
|
||||
if (graph.convertOldFormat ()) {
|
||||
return load (data, exten, outOptions);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// no open no fun
|
||||
if (!file) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
||||
}
|
||||
|
||||
// read the header
|
||||
StorageHeader hdr {};
|
||||
file.read (&hdr, sizeof (StorageHeader));
|
||||
|
||||
// check the magic
|
||||
if (hdr.magic != kStorageMagic && hdr.magic != kStorageMagicUB) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Unable to read magic of %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
|
||||
// check the path-numbers
|
||||
if (!isGraph && hdr.length != graph.length ()) {
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", type.name, filename, hdr.length, graph.length ());
|
||||
}
|
||||
|
||||
// check the count
|
||||
if (hdr.length == 0 || hdr.length > kMaxNodes || hdr.length < kMaxNodeLinks) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", type.name, filename, hdr.length);
|
||||
}
|
||||
|
||||
// check the version
|
||||
if (hdr.version > type.version && isGraph) {
|
||||
ctrl.msg ("Graph version mismatch %s (filename: '%s'). Version number differs (got: '%d', need: '%d') Please, upgrade %s.", type.name, filename, hdr.version, type.version, product.name);
|
||||
}
|
||||
else if (hdr.version != type.version && !isGraph) {
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", type.name, filename, hdr.version, type.version);
|
||||
}
|
||||
|
||||
// save graph version
|
||||
if (isGraph) {
|
||||
graph.setGraphHeader (&hdr);
|
||||
}
|
||||
|
||||
// check the storage type
|
||||
if ((hdr.options & type.option) != type.option) {
|
||||
return error (isGraph, isDebug, file, "Incorrect storage format for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
const auto compressedSize = static_cast <size_t> (hdr.compressed);
|
||||
const auto numberNodes = static_cast <size_t> (hdr.length);
|
||||
|
||||
SmallArray <uint8_t> compressed (compressedSize + sizeof (uint8_t) * ULZ::Excess);
|
||||
|
||||
// graph is not resized upon load
|
||||
if (isGraph) {
|
||||
resizeData (numberNodes);
|
||||
}
|
||||
else {
|
||||
resizeData (hdr.uncompressed / sizeof (U));
|
||||
}
|
||||
|
||||
// read compressed data
|
||||
if (file.read (compressed.data (), sizeof (uint8_t), compressedSize) == compressedSize) {
|
||||
|
||||
// try to uncompress
|
||||
if (ulz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast <uint8_t *> (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) {
|
||||
return error (isGraph, isDebug, file, "Unable to decompress ULZ data for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
else {
|
||||
|
||||
if (outOptions) {
|
||||
outOptions = &hdr.options;
|
||||
}
|
||||
|
||||
// author of graph.. save
|
||||
if ((hdr.options & StorageOption::Exten) && exten != nullptr) {
|
||||
const auto extenSize = sizeof (ExtenHeader);
|
||||
const auto actuallyRead = file.read (exten, extenSize) * extenSize;
|
||||
|
||||
if (isGraph) {
|
||||
resetRetries ();
|
||||
|
||||
ExtenHeader extenHeader;
|
||||
strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author));
|
||||
|
||||
if (extenSize <= actuallyRead) {
|
||||
// write modified by, only if the name is different
|
||||
if (!strings.isEmpty (extenHeader.author)
|
||||
&& strncmp (extenHeader.author, exten->modified, cr::bufsize (extenHeader.author)) != 0) {
|
||||
|
||||
strings.copy (extenHeader.modified, exten->modified, cr::bufsize (exten->modified));
|
||||
}
|
||||
}
|
||||
else {
|
||||
strings.copy (extenHeader.modified, "(none)", cr::bufsize (exten->modified));
|
||||
}
|
||||
extenHeader.mapSize = exten->mapSize;
|
||||
|
||||
// tell graph about exten header
|
||||
graph.setExtenHeader (&extenHeader);
|
||||
}
|
||||
//
|
||||
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
|
||||
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
#include <yapb.h>
|
||||
|
||||
#if defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS)
|
||||
|
||||
template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *exten, int32_t *outOptions) {
|
||||
auto type = guessType <U> ();
|
||||
String filename = buildPath (storageToBotFile (type.option), true);
|
||||
|
||||
extern ConVar cv_debug, cv_graph_url;
|
||||
|
||||
// graphs can be downloaded...
|
||||
const bool isGraph = !!(type.option & StorageOption::Graph);
|
||||
const bool isDebug = cv_debug;
|
||||
|
||||
MemFile file (filename); // open the file
|
||||
data.clear ();
|
||||
|
||||
// resize data to fit the stuff
|
||||
auto resizeData = [&] (const size_t length) {
|
||||
data.resize (length); // for non-graph data the graph should be already loaded
|
||||
data.shrink (); // free up memory to minimum
|
||||
|
||||
// ensure we're have enough memory to decompress the data
|
||||
data.ensure (length + ULZ::Excess);
|
||||
};
|
||||
|
||||
// if graph & attempted to load multiple times, bail out, we're failed
|
||||
if (isGraph && ++m_retries > 2) {
|
||||
resetRetries ();
|
||||
|
||||
return error (isGraph, isDebug, file, "Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", type.name, filename);
|
||||
}
|
||||
|
||||
// downloader for graph
|
||||
auto download = [&] () -> bool {
|
||||
if (!graph.canDownload ()) {
|
||||
return false;
|
||||
}
|
||||
String lowercaseMapName = game.getMapName ();
|
||||
lowercaseMapName = lowercaseMapName.lowercase ();
|
||||
|
||||
auto downloadAddress = cv_graph_url.as <StringRef> ();
|
||||
|
||||
auto toDownload = buildPath (storageToBotFile (type.option), false);
|
||||
auto fromDownload = strings.format ("%s://%s/graph/%s.graph", product.httpScheme, downloadAddress, lowercaseMapName);
|
||||
|
||||
// try to download
|
||||
if (http.downloadFile (fromDownload, toDownload)) {
|
||||
ctrl.msg ("%s file '%s' successfully downloaded. Processing...", type.name, filename);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ctrl.msg ("Can't download '%s' from '%s' to '%s'... (%d).", filename, fromDownload, toDownload, http.getLastStatusCode ());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// tries to reload or open pwf file
|
||||
auto tryReload = [&] () -> bool {
|
||||
file.close ();
|
||||
|
||||
if (!isGraph) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (download ()) {
|
||||
return load (data, exten, outOptions);
|
||||
}
|
||||
|
||||
if (graph.convertOldFormat ()) {
|
||||
return load (data, exten, outOptions);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// no open no fun
|
||||
if (!file) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename);
|
||||
}
|
||||
|
||||
// read the header
|
||||
StorageHeader hdr {};
|
||||
file.read (&hdr, sizeof (StorageHeader));
|
||||
|
||||
// check the magic
|
||||
if (hdr.magic != kStorageMagic && hdr.magic != kStorageMagicUB) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Unable to read magic of %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
|
||||
// check the path-numbers
|
||||
if (!isGraph && hdr.length != graph.length ()) {
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", type.name, filename, hdr.length, graph.length ());
|
||||
}
|
||||
|
||||
// check the count
|
||||
if (hdr.length == 0 || hdr.length > kMaxNodes || hdr.length < kMaxNodeLinks) {
|
||||
if (tryReload ()) {
|
||||
return true;
|
||||
}
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", type.name, filename, hdr.length);
|
||||
}
|
||||
|
||||
// check the version
|
||||
if (hdr.version > type.version && isGraph) {
|
||||
ctrl.msg ("Graph version mismatch %s (filename: '%s'). Version number differs (got: '%d', need: '%d') Please, upgrade %s.", type.name, filename, hdr.version, type.version, product.name);
|
||||
}
|
||||
else if (hdr.version != type.version && !isGraph) {
|
||||
return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", type.name, filename, hdr.version, type.version);
|
||||
}
|
||||
|
||||
// save graph version
|
||||
if (isGraph) {
|
||||
graph.setGraphHeader (&hdr);
|
||||
}
|
||||
|
||||
// check the storage type
|
||||
if ((hdr.options & type.option) != type.option) {
|
||||
return error (isGraph, isDebug, file, "Incorrect storage format for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
const auto compressedSize = static_cast <size_t> (hdr.compressed);
|
||||
const auto numberNodes = static_cast <size_t> (hdr.length);
|
||||
|
||||
SmallArray <uint8_t> compressed (compressedSize + sizeof (uint8_t) * ULZ::Excess);
|
||||
|
||||
// graph is not resized upon load
|
||||
if (isGraph) {
|
||||
resizeData (numberNodes);
|
||||
}
|
||||
else {
|
||||
resizeData (hdr.uncompressed / sizeof (U));
|
||||
}
|
||||
|
||||
// read compressed data
|
||||
if (file.read (compressed.data (), sizeof (uint8_t), compressedSize) == compressedSize) {
|
||||
|
||||
// try to uncompress
|
||||
if (ulz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast <uint8_t *> (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) {
|
||||
return error (isGraph, isDebug, file, "Unable to decompress ULZ data for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
else {
|
||||
|
||||
if (outOptions) {
|
||||
outOptions = &hdr.options;
|
||||
}
|
||||
|
||||
// author of graph.. save
|
||||
if ((hdr.options & StorageOption::Exten) && exten != nullptr) {
|
||||
const auto extenSize = sizeof (ExtenHeader);
|
||||
const auto actuallyRead = file.read (exten, extenSize) * extenSize;
|
||||
|
||||
if (isGraph) {
|
||||
resetRetries ();
|
||||
|
||||
ExtenHeader extenHeader;
|
||||
strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author));
|
||||
|
||||
if (extenSize <= actuallyRead) {
|
||||
// write modified by, only if the name is different
|
||||
if (!strings.isEmpty (extenHeader.author)
|
||||
&& strncmp (extenHeader.author, exten->modified, cr::bufsize (extenHeader.author)) != 0) {
|
||||
|
||||
strings.copy (extenHeader.modified, exten->modified, cr::bufsize (exten->modified));
|
||||
}
|
||||
}
|
||||
else {
|
||||
strings.copy (extenHeader.modified, "(none)", cr::bufsize (exten->modified));
|
||||
}
|
||||
extenHeader.mapSize = exten->mapSize;
|
||||
|
||||
// tell graph about exten header
|
||||
graph.setExtenHeader (&extenHeader);
|
||||
}
|
||||
}
|
||||
|
||||
// for visibility tables load counts of stand/count numbers
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
file.read (&path.vis, sizeof (PathVis));
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
|
||||
file.close ();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return error (isGraph, isDebug, file, "Unable to read ULZ data for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename U> bool BotStorage::save (const SmallArray <U> &data, ExtenHeader *exten, int32_t passOptions) {
|
||||
auto type = guessType <U> ();
|
||||
|
||||
// append additional options
|
||||
if (passOptions != 0) {
|
||||
type.option |= passOptions;
|
||||
}
|
||||
const auto isGraph = !!(type.option & StorageOption::Graph);
|
||||
|
||||
// do not allow to save graph with less than 8 nodes
|
||||
if (isGraph && graph.length () < kMaxNodeLinks) {
|
||||
ctrl.msg ("Can't save graph data with less than %d nodes. Please add some more before saving.", kMaxNodeLinks);
|
||||
return false;
|
||||
}
|
||||
String filename = buildPath (storageToBotFile (type.option));
|
||||
|
||||
if (data.empty ()) {
|
||||
logger.error ("Unable to save %s file. Empty data. (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
else if (isGraph) {
|
||||
for (auto &path : graph) {
|
||||
path.display = 0.0f;
|
||||
path.light = illum.getLightLevel (path.origin);
|
||||
}
|
||||
}
|
||||
|
||||
// open the file
|
||||
File file (filename, "wb");
|
||||
|
||||
// no open no fun
|
||||
if (!file) {
|
||||
logger.error ("Unable to open %s file for writing (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
const auto rawLength = data.length () * sizeof (U);
|
||||
SmallArray <uint8_t> compressed (rawLength + sizeof (uint8_t) * ULZ::Excess);
|
||||
|
||||
// try to compress
|
||||
const auto compressedLength = static_cast <size_t> (ulz.compress (reinterpret_cast <uint8_t *> (data.data ()), static_cast <int32_t> (rawLength), reinterpret_cast <uint8_t *> (compressed.data ())));
|
||||
|
||||
if (compressedLength > 0) {
|
||||
StorageHeader hdr {};
|
||||
|
||||
hdr.magic = kStorageMagic;
|
||||
hdr.version = type.version;
|
||||
hdr.options = type.option;
|
||||
hdr.length = graph.length ();
|
||||
hdr.compressed = static_cast <int32_t> (compressedLength);
|
||||
hdr.uncompressed = static_cast <int32_t> (rawLength);
|
||||
|
||||
file.write (&hdr, sizeof (StorageHeader));
|
||||
|
||||
ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
|
||||
file.close ();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return error (isGraph, isDebug, file, "Unable to read ULZ data for %s (filename: '%s').", type.name, filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename U> bool BotStorage::save (const SmallArray <U> &data, ExtenHeader *exten, int32_t passOptions) {
|
||||
auto type = guessType <U> ();
|
||||
|
||||
// append additional options
|
||||
if (passOptions != 0) {
|
||||
type.option |= passOptions;
|
||||
}
|
||||
const auto isGraph = !!(type.option & StorageOption::Graph);
|
||||
|
||||
// do not allow to save graph with less than 8 nodes
|
||||
if (isGraph && graph.length () < kMaxNodeLinks) {
|
||||
ctrl.msg ("Can't save graph data with less than %d nodes. Please add some more before saving.", kMaxNodeLinks);
|
||||
return false;
|
||||
}
|
||||
String filename = buildPath (storageToBotFile (type.option));
|
||||
|
||||
if (data.empty ()) {
|
||||
logger.error ("Unable to save %s file. Empty data. (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
else if (isGraph) {
|
||||
for (auto &path : graph) {
|
||||
path.display = 0.0f;
|
||||
path.light = illum.getLightLevel (path.origin);
|
||||
}
|
||||
}
|
||||
|
||||
// open the file
|
||||
File file (filename, "wb");
|
||||
|
||||
// no open no fun
|
||||
if (!file) {
|
||||
logger.error ("Unable to open %s file for writing (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
const auto rawLength = data.length () * sizeof (U);
|
||||
SmallArray <uint8_t> compressed (rawLength + sizeof (uint8_t) * ULZ::Excess);
|
||||
|
||||
// try to compress
|
||||
const auto compressedLength = static_cast <size_t> (ulz.compress (reinterpret_cast <uint8_t *> (data.data ()), static_cast <int32_t> (rawLength), reinterpret_cast <uint8_t *> (compressed.data ())));
|
||||
|
||||
if (compressedLength > 0) {
|
||||
StorageHeader hdr {};
|
||||
|
||||
hdr.magic = kStorageMagic;
|
||||
hdr.version = type.version;
|
||||
hdr.options = type.option;
|
||||
hdr.length = graph.length ();
|
||||
hdr.compressed = static_cast <int32_t> (compressedLength);
|
||||
hdr.uncompressed = static_cast <int32_t> (rawLength);
|
||||
|
||||
file.write (&hdr, sizeof (StorageHeader));
|
||||
file.write (compressed.data (), sizeof (uint8_t), compressedLength);
|
||||
|
||||
// for visibility tables save counts of stand/count numbers
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
file.write (&path.vis, sizeof (PathVis));
|
||||
}
|
||||
}
|
||||
|
||||
// add extension
|
||||
if ((type.option & StorageOption::Exten) && exten != nullptr) {
|
||||
file.write (exten, sizeof (ExtenHeader));
|
||||
}
|
||||
extern ConVar cv_debug;
|
||||
|
||||
// notify only about graph
|
||||
if (isGraph || cv_debug) {
|
||||
ctrl.msg ("Successfully saved Bots %s data.", type.name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error ("Unable to compress %s data (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ...Args> bool BotStorage::error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) {
|
||||
auto result = strings.format (fmt, cr::forward <Args> (args)...);
|
||||
|
||||
// display error only for graph file
|
||||
if (isGraph || isDebug) {
|
||||
logger.error (result);
|
||||
}
|
||||
|
||||
// if graph reset paths
|
||||
if (isGraph) {
|
||||
bots.kickEveryone (true);
|
||||
graph.reset ();
|
||||
}
|
||||
file.close ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename U> BotStorage::SaveLoadData BotStorage::guessType () {
|
||||
if constexpr (cr::is_same <U, FloydWarshallAlgo::Matrix>::value) {
|
||||
return { "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, BotPractice::DangerSaveRestore>::value) {
|
||||
return { "Practice", StorageOption::Practice, StorageVersion::Practice };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, GraphVistable::VisStorage>::value) {
|
||||
return { "Vistable", StorageOption::Vistable, StorageVersion::Vistable };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, Path>::value) {
|
||||
return { "Graph", StorageOption::Graph, StorageVersion::Graph };
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
String BotStorage::buildPath (int32_t file, bool isMemoryLoad, bool withoutMapName) {
|
||||
using FilePath = Twin <String, String>;
|
||||
|
||||
static HashMap <int32_t, FilePath> paths = {
|
||||
{ BotFile::Vistable, FilePath (folders.train, "vis")},
|
||||
{ BotFile::Practice, FilePath (folders.train, "prc")},
|
||||
{ BotFile::Pathmatrix, FilePath (folders.train, "pmx")},
|
||||
{ BotFile::LogFile, FilePath (folders.logs, "txt")},
|
||||
{ BotFile::Graph, FilePath (folders.graph, "graph")},
|
||||
{ BotFile::PodbotPWF, FilePath (folders.podbot, "pwf")},
|
||||
{ BotFile::EbotEWP, FilePath (folders.ebot, "ewp")},
|
||||
};
|
||||
|
||||
static StringArray path;
|
||||
path.clear ();
|
||||
|
||||
// if not memory file we're don't need game dir
|
||||
if (isMemoryLoad) {
|
||||
path.emplace (getRunningPathVFS ());
|
||||
}
|
||||
else {
|
||||
path.emplace (getRunningPath ());
|
||||
}
|
||||
|
||||
// the datadir
|
||||
path.emplace (folders.data);
|
||||
|
||||
// append real filepath
|
||||
path.emplace (paths[file].first);
|
||||
|
||||
// if file is logfile use correct logfile name with date
|
||||
if (file == BotFile::LogFile) {
|
||||
time_t ticks = time (&ticks);
|
||||
tm timeinfo {};
|
||||
|
||||
plat.loctime (&timeinfo, &ticks);
|
||||
auto timebuf = strings.chars ();
|
||||
|
||||
strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo);
|
||||
path.emplace (strings.format ("%s_%s.%s", product.nameLower, timebuf, paths[file].second));
|
||||
}
|
||||
else if (!withoutMapName) {
|
||||
String mapName = game.getMapName ();
|
||||
path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second));
|
||||
}
|
||||
|
||||
// finally use correct path separators for us
|
||||
return String::join (path, kPathSeparator);
|
||||
}
|
||||
|
||||
int32_t BotStorage::storageToBotFile (int32_t options) {
|
||||
// converts storage option to storage filename
|
||||
|
||||
if (options & StorageOption::Graph) {
|
||||
return BotFile::Graph;
|
||||
}
|
||||
else if (options & StorageOption::Matrix) {
|
||||
return BotFile::Pathmatrix;
|
||||
}
|
||||
else if (options & StorageOption::Vistable) {
|
||||
return BotFile::Vistable;
|
||||
}
|
||||
else if (options & StorageOption::Practice) {
|
||||
return BotFile::Practice;
|
||||
}
|
||||
return BotFile::Graph;
|
||||
}
|
||||
|
||||
void BotStorage::unlinkFromDisk () {
|
||||
// this function removes graph file from the hard disk
|
||||
|
||||
StringArray unlinkable;
|
||||
bots.kickEveryone (true);
|
||||
|
||||
// if we're delete graph, delete all corresponding to it files
|
||||
unlinkable.emplace (buildPath (BotFile::Graph)); // graph itself
|
||||
unlinkable.emplace (buildPath (BotFile::Practice)); // corresponding to practice
|
||||
unlinkable.emplace (buildPath (BotFile::Vistable)); // corresponding to vistable
|
||||
unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix
|
||||
|
||||
for (const auto &item : unlinkable) {
|
||||
if (plat.fileExists (item.chars ())) {
|
||||
plat.removeFile (item.chars ());
|
||||
ctrl.msg ("File %s, has been deleted from the hard disk", item);
|
||||
}
|
||||
else {
|
||||
logger.error ("Unable to open %s", item);
|
||||
}
|
||||
}
|
||||
graph.reset (); // re-initialize points
|
||||
}
|
||||
|
||||
StringRef BotStorage::getRunningPath () {
|
||||
// this function get's relative path against bot library (bot library should reside in bin dir)
|
||||
|
||||
static String path;
|
||||
|
||||
// we're do not do relative (against bot's library) paths on android
|
||||
if (plat.android) {
|
||||
if (path.empty ()) {
|
||||
path = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// compute the full path to the our folder
|
||||
if (path.empty ()) {
|
||||
path = SharedLibrary::path (&bstor);
|
||||
|
||||
if (path.startsWith ("<unk")) {
|
||||
logger.fatal ("Unable to detect library path. Giving up...");
|
||||
}
|
||||
auto parts = path.substr (1).split (kPathSeparator);
|
||||
|
||||
parts.pop (); // remove library name
|
||||
parts.pop (); // remove bin directory
|
||||
|
||||
path = path.substr (0, 1) + String::join (parts, kPathSeparator);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
StringRef BotStorage::getRunningPathVFS () {
|
||||
static String path;
|
||||
|
||||
// we're do not do relative (against bot's library) paths on android
|
||||
if (plat.android) {
|
||||
if (path.empty ()) {
|
||||
path = strings.joinPath (folders.addons, folders.bot);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.empty ()) {
|
||||
path = getRunningPath ();
|
||||
|
||||
path = path.substr (path.find (game.getRunningModName ())); // skip to the game dir
|
||||
path = path.substr (path.find (kPathSeparator) + 1); // skip the game dir
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
#endif // BOT_STORAGE_EXPLICIT_INSTANTIATIONS
|
||||
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
file.write (&path.vis, sizeof (PathVis));
|
||||
}
|
||||
}
|
||||
|
||||
// add extension
|
||||
if ((type.option & StorageOption::Exten) && exten != nullptr) {
|
||||
file.write (exten, sizeof (ExtenHeader));
|
||||
}
|
||||
extern ConVar cv_debug;
|
||||
|
||||
// notify only about graph
|
||||
if (isGraph || cv_debug) {
|
||||
ctrl.msg ("Successfully saved Bots %s data.", type.name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error ("Unable to compress %s data (filename: '%s').", type.name, filename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ...Args> bool BotStorage::error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) {
|
||||
auto result = strings.format (fmt, cr::forward <Args> (args)...);
|
||||
|
||||
// display error only for graph file
|
||||
if (isGraph || isDebug) {
|
||||
logger.error (result);
|
||||
}
|
||||
|
||||
// if graph reset paths
|
||||
if (isGraph) {
|
||||
bots.kickEveryone (true);
|
||||
graph.reset ();
|
||||
}
|
||||
file.close ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename U> BotStorage::SaveLoadData BotStorage::guessType () {
|
||||
if constexpr (cr::is_same <U, FloydWarshallAlgo::Matrix>::value) {
|
||||
return { "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, BotPractice::DangerSaveRestore>::value) {
|
||||
return { "Practice", StorageOption::Practice, StorageVersion::Practice };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, GraphVistable::VisStorage>::value) {
|
||||
return { "Vistable", StorageOption::Vistable, StorageVersion::Vistable };
|
||||
}
|
||||
else if constexpr (cr::is_same <U, Path>::value) {
|
||||
return { "Graph", StorageOption::Graph, StorageVersion::Graph };
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
String BotStorage::buildPath (int32_t file, bool isMemoryLoad, bool withoutMapName) {
|
||||
using FilePath = Twin <String, String>;
|
||||
|
||||
static HashMap <int32_t, FilePath> paths = {
|
||||
{ BotFile::Vistable, FilePath (folders.train, "vis")},
|
||||
{ BotFile::Practice, FilePath (folders.train, "prc")},
|
||||
{ BotFile::Pathmatrix, FilePath (folders.train, "pmx")},
|
||||
{ BotFile::LogFile, FilePath (folders.logs, "txt")},
|
||||
{ BotFile::Graph, FilePath (folders.graph, "graph")},
|
||||
{ BotFile::PodbotPWF, FilePath (folders.podbot, "pwf")},
|
||||
{ BotFile::EbotEWP, FilePath (folders.ebot, "ewp")},
|
||||
};
|
||||
|
||||
static StringArray path;
|
||||
path.clear ();
|
||||
|
||||
// if not memory file we're don't need game dir
|
||||
if (isMemoryLoad) {
|
||||
path.emplace (getRunningPathVFS ());
|
||||
}
|
||||
else {
|
||||
path.emplace (getRunningPath ());
|
||||
}
|
||||
|
||||
// the datadir
|
||||
path.emplace (folders.data);
|
||||
|
||||
// append real filepath
|
||||
path.emplace (paths[file].first);
|
||||
|
||||
// if file is logfile use correct logfile name with date
|
||||
if (file == BotFile::LogFile) {
|
||||
time_t ticks = time (&ticks);
|
||||
tm timeinfo {};
|
||||
|
||||
plat.loctime (&timeinfo, &ticks);
|
||||
auto timebuf = strings.chars ();
|
||||
|
||||
strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo);
|
||||
path.emplace (strings.format ("%s_%s.%s", product.nameLower, timebuf, paths[file].second));
|
||||
}
|
||||
else if (!withoutMapName) {
|
||||
String mapName = game.getMapName ();
|
||||
path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second));
|
||||
}
|
||||
|
||||
// finally use correct path separators for us
|
||||
return String::join (path, kPathSeparator);
|
||||
}
|
||||
|
||||
int32_t BotStorage::storageToBotFile (int32_t options) {
|
||||
// converts storage option to storage filename
|
||||
|
||||
if (options & StorageOption::Graph) {
|
||||
return BotFile::Graph;
|
||||
}
|
||||
else if (options & StorageOption::Matrix) {
|
||||
return BotFile::Pathmatrix;
|
||||
}
|
||||
else if (options & StorageOption::Vistable) {
|
||||
return BotFile::Vistable;
|
||||
}
|
||||
else if (options & StorageOption::Practice) {
|
||||
return BotFile::Practice;
|
||||
}
|
||||
return BotFile::Graph;
|
||||
}
|
||||
|
||||
void BotStorage::unlinkFromDisk () {
|
||||
// this function removes graph file from the hard disk
|
||||
|
||||
StringArray unlinkable;
|
||||
bots.kickEveryone (true);
|
||||
|
||||
// if we're delete graph, delete all corresponding to it files
|
||||
unlinkable.emplace (buildPath (BotFile::Graph)); // graph itself
|
||||
unlinkable.emplace (buildPath (BotFile::Practice)); // corresponding to practice
|
||||
unlinkable.emplace (buildPath (BotFile::Vistable)); // corresponding to vistable
|
||||
unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix
|
||||
|
||||
for (const auto &item : unlinkable) {
|
||||
if (plat.fileExists (item.chars ())) {
|
||||
plat.removeFile (item.chars ());
|
||||
ctrl.msg ("File %s, has been deleted from the hard disk", item);
|
||||
}
|
||||
else {
|
||||
logger.error ("Unable to open %s", item);
|
||||
}
|
||||
}
|
||||
graph.reset (); // re-initialize points
|
||||
}
|
||||
|
||||
StringRef BotStorage::getRunningPath () {
|
||||
// this function get's relative path against bot library (bot library should reside in bin dir)
|
||||
|
||||
static String path;
|
||||
|
||||
// we're do not do relative (against bot's library) paths on android
|
||||
if (plat.android) {
|
||||
if (path.empty ()) {
|
||||
path = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// compute the full path to the our folder
|
||||
if (path.empty ()) {
|
||||
path = SharedLibrary::path (&bstor);
|
||||
|
||||
if (path.startsWith ("<unk")) {
|
||||
logger.fatal ("Unable to detect library path. Giving up...");
|
||||
}
|
||||
auto parts = path.substr (1).split (kPathSeparator);
|
||||
|
||||
parts.pop (); // remove library name
|
||||
parts.pop (); // remove bin directory
|
||||
|
||||
path = path.substr (0, 1) + String::join (parts, kPathSeparator);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
StringRef BotStorage::getRunningPathVFS () {
|
||||
static String path;
|
||||
|
||||
// we're do not do relative (against bot's library) paths on android
|
||||
if (plat.android) {
|
||||
if (path.empty ()) {
|
||||
path = strings.joinPath (folders.addons, folders.bot);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.empty ()) {
|
||||
path = getRunningPath ();
|
||||
|
||||
path = path.substr (path.find (game.getRunningModName ())); // skip to the game dir
|
||||
path = path.substr (path.find (kPathSeparator) + 1); // skip the game dir
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
#endif // BOT_STORAGE_EXPLICIT_INSTANTIATIONS
|
||||
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ void BotSupport::checkWelcome () {
|
|||
|
||||
// send the hud message
|
||||
game.sendHudMessage (receiveEnt, textParams,
|
||||
sendLegacyWelcome ? legacyWelcomeMessage.chars () : modernWelcomeMessage.chars ());
|
||||
sendLegacyWelcome ? legacyWelcomeMessage.chars () : modernWelcomeMessage.chars ());
|
||||
|
||||
m_welcomeReceiveTime = 0.0f;
|
||||
m_needToSendWelcome = false;
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ void Bot::normal_ () {
|
|||
|
||||
// if bomb planted and it's a CT calculate new path to bomb point if he's not already heading for
|
||||
if (!m_bombSearchOverridden
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_team == Team::CT
|
||||
&& getTask ()->data != kInvalidNodeIndex
|
||||
&& !(graph[getTask ()->data].flags & NodeFlag::Goal)
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb) {
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_team == Team::CT
|
||||
&& getTask ()->data != kInvalidNodeIndex
|
||||
&& !(graph[getTask ()->data].flags & NodeFlag::Goal)
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb) {
|
||||
|
||||
clearSearchNodes ();
|
||||
getTask ()->data = kInvalidNodeIndex;
|
||||
|
|
@ -77,10 +77,10 @@ void Bot::normal_ () {
|
|||
if (updateNavigation ()) {
|
||||
// if we're reached the goal, and there is not enemies, notify the team
|
||||
if (!bots.isBombPlanted ()
|
||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& (m_pathFlags & NodeFlag::Goal)
|
||||
&& rg.chance (15)
|
||||
&& numEnemiesNear (pev->origin, 650.0f) == 0) {
|
||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& (m_pathFlags & NodeFlag::Goal)
|
||||
&& rg.chance (15)
|
||||
&& numEnemiesNear (pev->origin, 650.0f) == 0) {
|
||||
|
||||
pushRadioMessage (Radio::SectorClear);
|
||||
}
|
||||
|
|
@ -90,12 +90,12 @@ void Bot::normal_ () {
|
|||
|
||||
// spray logo sometimes if allowed to do so
|
||||
if (!(m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))
|
||||
&& m_seeEnemyTime + 5.0f < game.time ()
|
||||
&& !m_reloadState && m_timeLogoSpray < game.time ()
|
||||
&& cv_spraypaints
|
||||
&& rg.chance (50)
|
||||
&& m_moveSpeed > getShiftSpeed ()
|
||||
&& game.isNullEntity (m_pickupItem)) {
|
||||
&& m_seeEnemyTime + 5.0f < game.time ()
|
||||
&& !m_reloadState && m_timeLogoSpray < game.time ()
|
||||
&& cv_spraypaints
|
||||
&& rg.chance (50)
|
||||
&& m_moveSpeed > getShiftSpeed ()
|
||||
&& game.isNullEntity (m_pickupItem)) {
|
||||
|
||||
if (!(game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_team == Team::CT)) {
|
||||
startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.time () + 1.0f, false);
|
||||
|
|
@ -126,7 +126,7 @@ void Bot::normal_ () {
|
|||
|
||||
// don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp
|
||||
if (campingAllowed
|
||||
&& (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) {
|
||||
&& (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) {
|
||||
campingAllowed = false;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ void Bot::normal_ () {
|
|||
// and reached a rescue point?
|
||||
if (m_pathFlags & NodeFlag::Rescue) {
|
||||
m_hostages.clear ();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_team == Team::Terrorist && rg.chance (75) && !game.mapIs (MapFlags::Demolition)) {
|
||||
const int index = findDefendNode (m_path->origin);
|
||||
|
|
@ -268,22 +268,22 @@ void Bot::normal_ () {
|
|||
const float shiftSpeed = getShiftSpeed ();
|
||||
|
||||
if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed && mp_footsteps)
|
||||
&& m_difficulty >= Difficulty::Normal
|
||||
&& (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy))
|
||||
&& numEnemiesNear (pev->origin, 768.0f) >= 1
|
||||
&& !isKnifeMode ()
|
||||
&& !bots.isBombPlanted ()) {
|
||||
&& m_difficulty >= Difficulty::Normal
|
||||
&& (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy))
|
||||
&& numEnemiesNear (pev->origin, 768.0f) >= 1
|
||||
&& !isKnifeMode ()
|
||||
&& !bots.isBombPlanted ()) {
|
||||
|
||||
m_moveSpeed = shiftSpeed;
|
||||
}
|
||||
|
||||
// bot hasn't seen anything in a long time and is asking his teammates to report in
|
||||
if (cv_radio_mode.as <int> () > 1
|
||||
&& bots.getLastRadio (m_team) != Radio::ReportInTeam
|
||||
&& bots.getRoundStartTime () + 20.0f < game.time ()
|
||||
&& m_askCheckTime < game.time () && rg.chance (15)
|
||||
&& m_seeEnemyTime + rg (45.0f, 80.0f) < game.time ()
|
||||
&& numFriendsNear (pev->origin, 1024.0f) == 0) {
|
||||
&& bots.getLastRadio (m_team) != Radio::ReportInTeam
|
||||
&& bots.getRoundStartTime () + 20.0f < game.time ()
|
||||
&& m_askCheckTime < game.time () && rg.chance (15)
|
||||
&& m_seeEnemyTime + rg (45.0f, 80.0f) < game.time ()
|
||||
&& numFriendsNear (pev->origin, 1024.0f) == 0) {
|
||||
|
||||
pushRadioMessage (Radio::ReportInTeam);
|
||||
|
||||
|
|
@ -943,8 +943,8 @@ void Bot::defuseBomb_ () {
|
|||
// bot is reloading and we close enough to start defusing
|
||||
if (m_isReloading && bombPos.distanceSq2d (pev->origin) < cr::sqrf (80.0f)) {
|
||||
if (m_numEnemiesLeft == 0
|
||||
|| timeToBlowUp < fullDefuseTime + 7.0f
|
||||
|| ((getAmmoInClip () > 8 && m_reloadState == Reload::Primary)|| (getAmmoInClip () > 5 && m_reloadState == Reload::Secondary))) {
|
||||
|| timeToBlowUp < fullDefuseTime + 7.0f
|
||||
|| ((getAmmoInClip () > 8 && m_reloadState == Reload::Primary) || (getAmmoInClip () > 5 && m_reloadState == Reload::Secondary))) {
|
||||
|
||||
const int weaponIndex = bestWeaponCarried ();
|
||||
|
||||
|
|
@ -1300,8 +1300,8 @@ void Bot::throwSmoke_ () {
|
|||
|
||||
void Bot::doublejump_ () {
|
||||
if (!util.isAlive (m_doubleJumpEntity)
|
||||
|| (m_aimFlags & AimFlags::Enemy)
|
||||
|| (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.time ())) {
|
||||
|| (m_aimFlags & AimFlags::Enemy)
|
||||
|| (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.time ())) {
|
||||
resetDoubleJump ();
|
||||
return;
|
||||
}
|
||||
|
|
@ -1648,7 +1648,7 @@ void Bot::pickupItem_ () {
|
|||
// check if hostage is with a human teammate (hack)
|
||||
for (auto &client : util.getClients ()) {
|
||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
return EntitySearchResult::Continue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,17 +79,17 @@ void Bot::checkDarkness () {
|
|||
const auto tid = getCurrentTaskId ();
|
||||
|
||||
if (!flashOn &&
|
||||
tid != Task::Camp
|
||||
&& tid != Task::Attack
|
||||
&& m_heardSoundTime + 3.0f < game.time ()
|
||||
&& m_flashLevel > 30
|
||||
&& ((skyColor > 50.0f && lightLevel < 10.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) {
|
||||
tid != Task::Camp
|
||||
&& tid != Task::Attack
|
||||
&& m_heardSoundTime + 3.0f < game.time ()
|
||||
&& m_flashLevel > 30
|
||||
&& ((skyColor > 50.0f && lightLevel < 10.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) {
|
||||
|
||||
pev->impulse = 100;
|
||||
}
|
||||
else if (flashOn
|
||||
&& (((lightLevel > 15.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f))
|
||||
|| tid == Task::Camp || tid == Task::Attack || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.time ())) {
|
||||
&& (((lightLevel > 15.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f))
|
||||
|| tid == Task::Camp || tid == Task::Attack || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.time ())) {
|
||||
|
||||
pev->impulse = 100;
|
||||
}
|
||||
|
|
@ -201,9 +201,9 @@ void Bot::updateLookAngles () {
|
|||
|
||||
// just force directioon
|
||||
if (m_difficulty == Difficulty::Expert
|
||||
&& (m_aimFlags & AimFlags::Enemy)
|
||||
&& (m_wantsToFire || usesSniper ())
|
||||
&& cv_whose_your_daddy) {
|
||||
&& (m_aimFlags & AimFlags::Enemy)
|
||||
&& (m_wantsToFire || usesSniper ())
|
||||
&& cv_whose_your_daddy) {
|
||||
|
||||
pev->v_angle = direction;
|
||||
pev->v_angle.clampAngles ();
|
||||
|
|
@ -300,8 +300,8 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) {
|
|||
else {
|
||||
// is it time for bot to randomize the aim direction again (more often where moving) ?
|
||||
if (m_randomizeAnglesTime < game.time ()
|
||||
&& ((pev->velocity.length () > 1.0f
|
||||
&& m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) {
|
||||
&& ((pev->velocity.length () > 1.0f
|
||||
&& m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) {
|
||||
|
||||
// is the bot standing still ?
|
||||
if (pev->velocity.length () < 1.0f) {
|
||||
|
|
@ -428,10 +428,10 @@ void Bot::setAimDirection () {
|
|||
|
||||
// don't switch view right away after loosing focus with current enemy
|
||||
if ((m_shootTime + 1.5f > game.time () || m_seeEnemyTime + 1.5 > game.time ())
|
||||
&& m_forgetLastVictimTimer.elapsed ()
|
||||
&& !m_lastEnemyOrigin.empty ()
|
||||
&& util.isAlive (m_lastEnemy)
|
||||
&& game.isNullEntity (m_enemy)) {
|
||||
&& m_forgetLastVictimTimer.elapsed ()
|
||||
&& !m_lastEnemyOrigin.empty ()
|
||||
&& util.isAlive (m_lastEnemy)
|
||||
&& game.isNullEntity (m_enemy)) {
|
||||
|
||||
flags |= AimFlags::LastEnemy;
|
||||
}
|
||||
|
|
@ -543,11 +543,11 @@ void Bot::setAimDirection () {
|
|||
|
||||
|
||||
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time ()
|
||||
&& !m_isStuck && !(pev->button & IN_DUCK)
|
||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
|
||||
&& m_pathWalk.hasNext () && !isOnLadder ()
|
||||
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
|
||||
&& !m_isStuck && !(pev->button & IN_DUCK)
|
||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
|
||||
&& m_pathWalk.hasNext () && !isOnLadder ()
|
||||
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
|
||||
|
||||
const auto nextPathIndex = m_pathWalk.next ();
|
||||
const auto nextPathX2 = m_pathWalk.nextX2 ();
|
||||
|
|
@ -573,8 +573,8 @@ void Bot::setAimDirection () {
|
|||
const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||
|
||||
if (graph.exists (dangerIndex)
|
||||
&& vistab.visible (m_currentNodeIndex, dangerIndex)
|
||||
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
||||
&& vistab.visible (m_currentNodeIndex, dangerIndex)
|
||||
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
||||
|
||||
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (512.0f)) {
|
||||
m_lookAt = destOrigin;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue