2015-06-04 11:52:48 +03:00
|
|
|
|
//
|
2014-09-09 18:29:42 +04:00
|
|
|
|
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
|
|
|
|
|
|
// Copyright (c) YaPB Development Team.
|
2014-07-30 14:17:46 +04:00
|
|
|
|
//
|
2014-09-09 18:29:42 +04:00
|
|
|
|
// This software is licensed under the BSD-style license.
|
|
|
|
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
2016-09-11 21:01:06 +03:00
|
|
|
|
// https://yapb.jeefo.net/license
|
2014-07-30 14:17:46 +04:00
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#include <core.h>
|
|
|
|
|
|
|
2015-06-18 21:37:39 +03:00
|
|
|
|
ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "2");
|
2014-07-30 14:17:46 +04:00
|
|
|
|
ConVar yb_ignore_enemies ("yb_ignore_enemies", "0");
|
2016-01-04 15:28:38 +03:00
|
|
|
|
ConVar yb_check_enemy_rendering ("yb_check_enemy_rendering", "0");
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, VT_NOREGISTER);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
int Bot::GetNearbyFriendsNearPosition(const Vector &origin, float radius)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2014-08-06 00:03:50 +04:00
|
|
|
|
int count = 0;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if ((client.origin - origin).GetLengthSquared () < GET_SQUARE (radius))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
int Bot::GetNearbyEnemiesNearPosition(const Vector &origin, float radius)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2014-08-06 00:03:50 +04:00
|
|
|
|
int count = 0;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if ((client.origin - origin).GetLengthSquared () < GET_SQUARE (radius))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-28 19:43:31 +03:00
|
|
|
|
bool Bot::IsEnemyHiddenByRendering (edict_t *enemy)
|
|
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (!yb_check_enemy_rendering.GetBool () || engine.IsNullEntity (enemy))
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
entvars_t &v = enemy->v;
|
2015-06-28 20:16:03 +03:00
|
|
|
|
|
2015-06-29 21:49:52 +03:00
|
|
|
|
bool enemyHasGun = (v.weapons & WEAPON_PRIMARY) || (v.weapons & WEAPON_SECONDARY);
|
2015-06-28 20:16:03 +03:00
|
|
|
|
bool enemyGunfire = (v.button & IN_ATTACK) || (v.oldbuttons & IN_ATTACK);
|
2015-06-28 19:43:31 +03:00
|
|
|
|
|
2015-06-28 20:16:03 +03:00
|
|
|
|
if ((v.renderfx == kRenderFxExplode || (v.effects & EF_NODRAW)) && (!enemyGunfire || !enemyHasGun))
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
|
2015-06-28 20:16:03 +03:00
|
|
|
|
if ((v.renderfx == kRenderFxExplode || (v.effects & EF_NODRAW)) && enemyGunfire && enemyHasGun)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return false;
|
|
|
|
|
|
|
2015-06-28 20:16:03 +03:00
|
|
|
|
if (v.renderfx != kRenderFxHologram && v.renderfx != kRenderFxExplode && v.rendermode != kRenderNormal)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
{
|
|
|
|
|
|
if (v.renderfx == kRenderFxGlowShell)
|
|
|
|
|
|
{
|
2016-07-01 09:32:43 +03:00
|
|
|
|
if (v.renderamt <= 20.0f && v.rendercolor.x <= 20.0f && v.rendercolor.y <= 20.0f && v.rendercolor.z <= 20.0f)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
{
|
2015-06-28 20:16:03 +03:00
|
|
|
|
if (!enemyGunfire || !enemyHasGun)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2015-06-28 20:16:03 +03:00
|
|
|
|
else if (!enemyGunfire && v.renderamt <= 60.0f && v.rendercolor.x <= 60.f && v.rendercolor.y <= 60.0f && v.rendercolor.z <= 60.0f)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (v.renderamt <= 20.0f)
|
|
|
|
|
|
{
|
2015-06-28 20:16:03 +03:00
|
|
|
|
if (!enemyGunfire || !enemyHasGun)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2015-06-28 20:16:03 +03:00
|
|
|
|
else if (!enemyGunfire && v.renderamt <= 60.0f)
|
2015-06-28 19:43:31 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
bool Bot::CheckVisibility (edict_t *target, Vector *origin, uint8 *bodyPart)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
// this function checks visibility of a bot target.
|
|
|
|
|
|
|
|
|
|
|
|
if (IsEnemyHiddenByRendering (target))
|
|
|
|
|
|
{
|
|
|
|
|
|
*bodyPart = 0;
|
2016-02-11 21:50:05 +03:00
|
|
|
|
origin->Zero ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
return false;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
}
|
2016-02-11 23:07:43 +03:00
|
|
|
|
TraceResult result;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
Vector eyes = EyePosition ();
|
|
|
|
|
|
Vector spot = target->v.origin;
|
|
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
*bodyPart = 0;
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (eyes, spot, TRACE_IGNORE_EVERYTHING, pev->pContainingEntity, &result);
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
if (result.flFraction >= 1.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
*bodyPart |= VISIBLE_BODY;
|
2016-02-11 23:07:43 +03:00
|
|
|
|
*origin = result.vecEndPos;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
if (m_difficulty > 3)
|
2015-07-12 17:18:20 +03:00
|
|
|
|
origin->z += 3.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
// check top of head
|
|
|
|
|
|
spot = spot + Vector (0, 0, 25.0f);
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (eyes, spot, TRACE_IGNORE_EVERYTHING, pev->pContainingEntity, &result);
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
if (result.flFraction >= 1.0f)
|
2015-07-12 17:18:20 +03:00
|
|
|
|
{
|
|
|
|
|
|
*bodyPart |= VISIBLE_HEAD;
|
2016-02-11 23:07:43 +03:00
|
|
|
|
*origin = result.vecEndPos;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2015-07-31 20:32:08 +03:00
|
|
|
|
if (m_difficulty > 3)
|
2015-07-12 17:18:20 +03:00
|
|
|
|
origin->z += 1.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (*bodyPart != 0)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
const float standFeet = 34.0f;
|
|
|
|
|
|
const float crouchFeet = 14.0f;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
if (target->v.flags & FL_DUCKING)
|
|
|
|
|
|
spot.z = target->v.origin.z - crouchFeet;
|
|
|
|
|
|
else
|
|
|
|
|
|
spot.z = target->v.origin.z - standFeet;
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (eyes, spot, TRACE_IGNORE_EVERYTHING, pev->pContainingEntity, &result);
|
2016-02-11 23:07:43 +03:00
|
|
|
|
|
|
|
|
|
|
if (result.flFraction >= 1.0f)
|
2015-07-12 17:18:20 +03:00
|
|
|
|
{
|
2016-02-11 23:07:43 +03:00
|
|
|
|
*bodyPart |= VISIBLE_OTHER;
|
|
|
|
|
|
*origin = result.vecEndPos;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
const float edgeOffset = 13.0f;
|
|
|
|
|
|
Vector dir = (target->v.origin - pev->origin).Normalize2D ();
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
Vector perp (-dir.y, dir.x, 0.0f);
|
|
|
|
|
|
spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (eyes, spot, TRACE_IGNORE_EVERYTHING, pev->pContainingEntity, &result);
|
2016-02-11 23:07:43 +03:00
|
|
|
|
|
|
|
|
|
|
if (result.flFraction >= 1.0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
*bodyPart |= VISIBLE_OTHER;
|
|
|
|
|
|
*origin = result.vecEndPos;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (eyes, spot, TRACE_IGNORE_EVERYTHING, pev->pContainingEntity, &result);
|
2016-02-11 23:07:43 +03:00
|
|
|
|
|
|
|
|
|
|
if (result.flFraction >= 1.0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
*bodyPart |= VISIBLE_OTHER;
|
|
|
|
|
|
*origin = result.vecEndPos;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2015-07-12 17:18:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::IsEnemyViewable (edict_t *player)
|
|
|
|
|
|
{
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (engine.IsNullEntity (player))
|
2015-07-12 17:18:20 +03:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
bool forceTrueIfVisible = false;
|
|
|
|
|
|
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (IsValidPlayer (pev->dmg_inflictor) && engine.GetTeam (pev->dmg_inflictor) != m_team)
|
2015-07-12 17:18:20 +03:00
|
|
|
|
forceTrueIfVisible = true;
|
|
|
|
|
|
|
|
|
|
|
|
if ((IsInViewCone (player->v.origin + pev->view_ofs) || forceTrueIfVisible) && CheckVisibility (player, &m_enemyOrigin, &m_visibility))
|
|
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_seeEnemyTime = engine.Time ();
|
2015-07-12 17:18:20 +03:00
|
|
|
|
m_lastEnemy = player;
|
|
|
|
|
|
m_lastEnemyOrigin = player->v.origin;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2015-07-12 17:18:20 +03:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::LookupEnemy (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function tries to find the best suitable enemy for the bot
|
|
|
|
|
|
|
|
|
|
|
|
// do not search for enemies while we're blinded, or shooting disabled by user
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_enemyIgnoreTimer > engine.Time () || m_blindTime > engine.Time () || yb_ignore_enemies.GetBool ())
|
2015-07-12 17:18:20 +03:00
|
|
|
|
return false;
|
|
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
edict_t *player, *newEnemy = nullptr;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
float nearestDistance = m_viewDistance;
|
2015-06-20 11:45:59 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// clear suspected flag
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_seeEnemyTime + 3.0f < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_states &= ~STATE_SUSPECT_ENEMY;
|
|
|
|
|
|
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (!engine.IsNullEntity (m_enemy) && m_enemyUpdateTime > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
player = m_enemy;
|
|
|
|
|
|
|
|
|
|
|
|
// is player is alive
|
|
|
|
|
|
if (IsAlive (player) && IsEnemyViewable (player))
|
|
|
|
|
|
newEnemy = player;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// the old enemy is no longer visible or
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (engine.IsNullEntity (newEnemy))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_enemyUpdateTime = engine.Time () + 0.5f;
|
2015-06-20 11:45:59 +03:00
|
|
|
|
|
|
|
|
|
|
// ignore shielded enemies, while we have real one
|
2016-09-11 21:01:06 +03:00
|
|
|
|
edict_t *shieldEnemy = nullptr;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// search the world for players...
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
player = client.ent;
|
2016-11-02 22:45:15 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// do some blind by smoke grenade
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_blindRecognizeTime < engine.Time () && IsBehindSmokeClouds (player))
|
2014-08-15 21:58:43 +04:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_blindRecognizeTime = engine.Time () + Random.Float (1.0f, 2.0f);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (Random.Int (0, 100) < 50)
|
2014-08-15 21:58:43 +04:00
|
|
|
|
ChatterMessage (Chatter_BehindSmoke);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
if (player->v.button & (IN_ATTACK | IN_ATTACK2))
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_blindRecognizeTime = engine.Time () - 0.1f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// see if bot can see the player...
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_blindRecognizeTime < engine.Time () && IsEnemyViewable (player))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-06-20 11:45:59 +03:00
|
|
|
|
if (IsEnemyProtectedByShield (player))
|
|
|
|
|
|
{
|
|
|
|
|
|
shieldEnemy = player;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2014-07-30 14:17:46 +04:00
|
|
|
|
float distance = (player->v.origin - pev->origin).GetLength ();
|
|
|
|
|
|
|
|
|
|
|
|
if (distance < nearestDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
nearestDistance = distance;
|
|
|
|
|
|
newEnemy = player;
|
|
|
|
|
|
|
|
|
|
|
|
// aim VIP first on AS maps...
|
2015-06-28 19:43:31 +03:00
|
|
|
|
if (IsPlayerVIP (newEnemy))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-06-20 11:45:59 +03:00
|
|
|
|
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (engine.IsNullEntity (newEnemy) && !engine.IsNullEntity (shieldEnemy))
|
2015-06-20 11:45:59 +03:00
|
|
|
|
newEnemy = shieldEnemy;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (IsValidPlayer (newEnemy))
|
|
|
|
|
|
{
|
|
|
|
|
|
g_botsCanPause = true;
|
2015-06-20 11:45:59 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_aimFlags |= AIM_ENEMY;
|
2015-06-20 11:45:59 +03:00
|
|
|
|
m_states |= STATE_SEEING_ENEMY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if (newEnemy == m_enemy)
|
|
|
|
|
|
{
|
|
|
|
|
|
// if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_seeEnemyTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// zero out reaction time
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_actualReactionTime = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_lastEnemy = newEnemy;
|
|
|
|
|
|
m_lastEnemyOrigin = newEnemy->v.origin;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (m_seeEnemyTime + 3.0 < engine.Time () && (m_hasC4 || HasHostage () || !engine.IsNullEntity (m_targetEntity)))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
RadioMessage (Radio_EnemySpotted);
|
|
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
m_targetEntity = nullptr; // stop following when we see an enemy...
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (Random.Int (0, 100) < m_difficulty * 25)
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_enemySurpriseTime = engine.Time () + m_actualReactionTime * 0.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_enemySurpriseTime = engine.Time () + m_actualReactionTime;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// zero out reaction time
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_actualReactionTime = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_enemy = newEnemy;
|
|
|
|
|
|
m_lastEnemy = newEnemy;
|
|
|
|
|
|
m_lastEnemyOrigin = newEnemy->v.origin;
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_enemyReachableTimer = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// keep track of when we last saw an enemy
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_seeEnemyTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-02-11 23:07:43 +03:00
|
|
|
|
if (!(pev->oldbuttons & IN_ATTACK))
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// now alarm all teammates who see this bot & don't have an actual enemy of the bots enemy should simulate human players seeing a teammate firing
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int j = 0; j < engine.MaxClients (); j++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[j];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
Bot *other = bots.GetBot (client.ent);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (other != nullptr && other->m_seeEnemyTime + 2.0f < engine.Time () && engine.IsNullEntity (other->m_lastEnemy) && IsVisible (pev->origin, other->GetEntity ()) && other->IsInViewCone (pev->origin))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
other->m_lastEnemy = newEnemy;
|
|
|
|
|
|
other->m_lastEnemyOrigin = m_lastEnemyOrigin;
|
|
|
|
|
|
other->m_seeEnemyTime = engine.Time ();
|
|
|
|
|
|
other->m_states |= (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY);
|
|
|
|
|
|
other->m_aimFlags |= AIM_LAST_ENEMY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-03-05 23:08:07 +03:00
|
|
|
|
else if (!engine.IsNullEntity (m_enemy))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
newEnemy = m_enemy;
|
|
|
|
|
|
m_lastEnemy = newEnemy;
|
|
|
|
|
|
|
|
|
|
|
|
if (!IsAlive (newEnemy))
|
|
|
|
|
|
{
|
2016-09-11 21:01:06 +03:00
|
|
|
|
m_enemy = nullptr;
|
2015-06-10 23:30:48 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// shoot at dying players if no new enemy to give some more human-like illusion
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_seeEnemyTime + 0.1f > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (!UsesSniper ())
|
|
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shootAtDeadTime = engine.Time () + 0.4f;
|
2016-02-11 23:07:43 +03:00
|
|
|
|
m_actualReactionTime = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_states |= STATE_SUSPECT_ENEMY;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-03-01 13:37:10 +03:00
|
|
|
|
else if (m_shootAtDeadTime > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_actualReactionTime = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_states |= STATE_SUSPECT_ENEMY;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if no enemy visible check if last one shoot able through wall
|
2015-06-04 11:52:48 +03:00
|
|
|
|
if (yb_shoots_thru_walls.GetBool () && m_difficulty >= 2 && IsShootableThruObstacle (newEnemy->v.origin))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_seeEnemyTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
m_states |= STATE_SUSPECT_ENEMY;
|
|
|
|
|
|
m_aimFlags |= AIM_LAST_ENEMY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
m_enemy = newEnemy;
|
|
|
|
|
|
m_lastEnemy = newEnemy;
|
|
|
|
|
|
m_lastEnemyOrigin = newEnemy->v.origin;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
return true;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check if bots should reload...
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if ((m_aimFlags <= AIM_PREDICT_PATH && m_seeEnemyTime + 3.0f < engine.Time () && !(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && engine.IsNullEntity (m_lastEnemy) && engine.IsNullEntity (m_enemy) && GetTaskId () != TASK_SHOOTBREAKABLE && GetTaskId () != TASK_PLANTBOMB && GetTaskId () != TASK_DEFUSEBOMB) || g_roundEnded)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (!m_reloadState)
|
|
|
|
|
|
m_reloadState = RELOAD_PRIMARY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// is the bot using a sniper rifle or a zoomable rifle?
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if ((UsesSniper () || UsesZoomableRifle ()) && m_zoomCheckTime + 1.0f < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (pev->fov < 90.0f) // let the bot zoom out
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
else
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_zoomCheckTime = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-11 14:19:52 +03:00
|
|
|
|
const Vector &Bot::GetAimPosition (void)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// the purpose of this function, is to make bot aiming not so ideal. it's mutate m_enemyOrigin enemy vector
|
|
|
|
|
|
// returned from visibility check function.
|
|
|
|
|
|
|
|
|
|
|
|
float distance = (m_enemy->v.origin - pev->origin).GetLength ();
|
|
|
|
|
|
|
|
|
|
|
|
// get enemy position initially
|
|
|
|
|
|
Vector targetOrigin = m_enemy->v.origin;
|
2015-08-15 18:09:15 +03:00
|
|
|
|
Vector randomize;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2015-06-09 15:45:34 +03:00
|
|
|
|
const Vector &adjust = Vector (Random.Float (m_enemy->v.mins.x * 0.5f, m_enemy->v.maxs.x * 0.5f), Random.Float (m_enemy->v.mins.y * 0.5f, m_enemy->v.maxs.y * 0.5f), Random.Float (m_enemy->v.mins.z * 0.5f, m_enemy->v.maxs.z * 0.5f));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// do not aim at head, at long distance (only if not using sniper weapon)
|
2015-06-04 11:52:48 +03:00
|
|
|
|
if ((m_visibility & VISIBLE_BODY) && !UsesSniper () && !UsesPistol () && (distance > (m_difficulty == 4 ? 2400.0 : 1200.0)))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_visibility &= ~VISIBLE_HEAD;
|
|
|
|
|
|
|
|
|
|
|
|
// if we only suspect an enemy behind a wall take the worst skill
|
|
|
|
|
|
if ((m_states & STATE_SUSPECT_ENEMY) && !(m_states & STATE_SEEING_ENEMY))
|
2015-06-04 11:52:48 +03:00
|
|
|
|
targetOrigin = targetOrigin + adjust;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// now take in account different parts of enemy body
|
|
|
|
|
|
if (m_visibility & (VISIBLE_HEAD | VISIBLE_BODY)) // visible head & body
|
|
|
|
|
|
{
|
2015-06-20 11:45:59 +03:00
|
|
|
|
int headshotFreq[5] = { 20, 40, 60, 80, 100 };
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// now check is our skill match to aim at head, else aim at enemy body
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if ((Random.Int (1, 100) < headshotFreq[m_difficulty]) || UsesPistol ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
targetOrigin = targetOrigin + m_enemy->v.view_ofs + Vector (0.0f, 0.0f, GetZOffset (distance));
|
|
|
|
|
|
else
|
2015-06-04 11:52:48 +03:00
|
|
|
|
targetOrigin = targetOrigin + Vector (0.0f, 0.0f, GetZOffset (distance));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else if (m_visibility & VISIBLE_BODY) // visible only body
|
2015-06-04 11:52:48 +03:00
|
|
|
|
targetOrigin = targetOrigin + Vector (0.0f, 0.0f, GetZOffset (distance));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else if (m_visibility & VISIBLE_OTHER) // random part of body is visible
|
|
|
|
|
|
targetOrigin = m_enemyOrigin;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
else if (m_visibility & VISIBLE_HEAD) // visible only head
|
|
|
|
|
|
targetOrigin = targetOrigin + m_enemy->v.view_ofs + Vector (0.0f, 0.0f, GetZOffset (distance));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else // something goes wrong, use last enemy origin
|
2015-06-04 11:52:48 +03:00
|
|
|
|
{
|
2014-07-30 14:17:46 +04:00
|
|
|
|
targetOrigin = m_lastEnemyOrigin;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
|
|
|
|
|
if (m_difficulty < 3)
|
|
|
|
|
|
randomize = adjust;
|
|
|
|
|
|
}
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_lastEnemyOrigin = targetOrigin;
|
|
|
|
|
|
}
|
2015-08-15 18:09:15 +03:00
|
|
|
|
const Vector &velocity = UsesSniper () ? Vector::GetZero () : 1.0f * m_frameInterval * (m_enemy->v.velocity - pev->velocity);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (m_difficulty < 3 && !randomize.IsZero ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-06-04 11:52:48 +03:00
|
|
|
|
float divOffs = (m_enemyOrigin - pev->origin).GetLength ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if (pev->fov < 40)
|
2015-06-04 11:52:48 +03:00
|
|
|
|
divOffs = divOffs / 2000;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else if (pev->fov < 90)
|
2015-06-04 11:52:48 +03:00
|
|
|
|
divOffs = divOffs / 1000;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2015-06-04 11:52:48 +03:00
|
|
|
|
divOffs = divOffs / 500;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// randomize the target position
|
2015-07-19 13:39:00 +03:00
|
|
|
|
m_enemyOrigin = divOffs * randomize;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2015-07-19 13:39:00 +03:00
|
|
|
|
m_enemyOrigin = targetOrigin;
|
|
|
|
|
|
|
2015-07-31 20:32:08 +03:00
|
|
|
|
if (distance >= 256.0f && m_difficulty < 4)
|
2015-07-19 13:39:00 +03:00
|
|
|
|
m_enemyOrigin += velocity;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
return m_enemyOrigin;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float Bot::GetZOffset (float distance)
|
|
|
|
|
|
{
|
2015-07-31 20:32:08 +03:00
|
|
|
|
if (m_difficulty < 3)
|
|
|
|
|
|
return 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
bool sniper = UsesSniper ();
|
|
|
|
|
|
bool pistol = UsesPistol ();
|
|
|
|
|
|
bool rifle = UsesRifle ();
|
|
|
|
|
|
|
|
|
|
|
|
bool zoomableRifle = UsesZoomableRifle ();
|
|
|
|
|
|
bool submachine = UsesSubmachineGun ();
|
|
|
|
|
|
bool shotgun = (m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3);
|
|
|
|
|
|
bool m249 = m_currentWeapon == WEAPON_M249;
|
|
|
|
|
|
|
|
|
|
|
|
const float BurstDistance = 300.0f;
|
|
|
|
|
|
const float DoubleBurstDistance = BurstDistance * 2;
|
|
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
float result = 3.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if (distance < 2800.0f && distance > DoubleBurstDistance)
|
|
|
|
|
|
{
|
2016-01-30 13:15:50 +03:00
|
|
|
|
if (sniper) result = 1.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else if (zoomableRifle) result = 4.5f;
|
|
|
|
|
|
else if (pistol) result = 6.5f;
|
|
|
|
|
|
else if (submachine) result = 5.5f;
|
|
|
|
|
|
else if (rifle) result = 5.5f;
|
|
|
|
|
|
else if (m249) result = 2.5f;
|
|
|
|
|
|
else if (shotgun) result = 10.5f;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (distance > BurstDistance && distance <= DoubleBurstDistance)
|
|
|
|
|
|
{
|
2016-01-30 13:15:50 +03:00
|
|
|
|
if (sniper) result = 2.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else if (zoomableRifle) result = 3.5f;
|
|
|
|
|
|
else if (pistol) result = 6.5f;
|
|
|
|
|
|
else if (submachine) result = 3.5f;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
else if (rifle) result = 1.6f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else if (m249) result = -1.0f;
|
|
|
|
|
|
else if (shotgun) result = 10.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (distance < BurstDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sniper) result = 4.5f;
|
|
|
|
|
|
else if (zoomableRifle) result = -5.0f;
|
|
|
|
|
|
else if (pistol) result = 4.5f;
|
|
|
|
|
|
else if (submachine) result = -4.5f;
|
|
|
|
|
|
else if (rifle) result = -4.5f;
|
|
|
|
|
|
else if (m249) result = -6.0f;
|
|
|
|
|
|
else if (shotgun) result = -5.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-20 11:45:59 +03:00
|
|
|
|
bool Bot::IsFriendInLineOfFire (float distance)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// bot can't hurt teammates, if friendly fire is not enabled...
|
2016-10-23 01:49:05 +03:00
|
|
|
|
if (!mp_friendlyfire.GetBool () || (g_gameFlags & GAME_CSDM))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
MakeVectors (pev->v_angle);
|
|
|
|
|
|
|
|
|
|
|
|
TraceResult tr;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (EyePosition (), EyePosition () + 10000.0f * pev->v_angle, TRACE_IGNORE_NONE, GetEntity (), &tr);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// check if we hit something
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (!engine.IsNullEntity (tr.pHit))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-05 23:08:07 +03:00
|
|
|
|
int playerIndex = engine.IndexOfEntity (tr.pHit) - 1;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// check valid range
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (playerIndex >= 0 && playerIndex < engine.MaxClients () && g_clients[playerIndex].team == m_team && (g_clients[playerIndex].flags & CF_ALIVE))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// search the world for players
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
edict_t *ent = client.ent;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
float friendDistance = (ent->v.origin - pev->origin).GetLength ();
|
2016-09-16 16:10:22 +03:00
|
|
|
|
float squareDistance = A_sqrtf (1089.0f + (friendDistance * friendDistance));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if (GetShootingConeDeviation (GetEntity (), &ent->v.origin) > (friendDistance * friendDistance) / (squareDistance * squareDistance) && friendDistance <= distance)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-07 19:43:16 +03:00
|
|
|
|
bool Bot::IsShootableThruObstacle (const Vector &dest)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-06-04 11:52:48 +03:00
|
|
|
|
// this function returns true if enemy can be shoot through some obstacle, false otherwise.
|
|
|
|
|
|
// credits goes to Immortal_BLG
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-07 19:43:16 +03:00
|
|
|
|
if (yb_shoots_thru_walls.GetInt () == 2)
|
|
|
|
|
|
return IsShootableThruObstacleEx (dest);
|
|
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
if (m_difficulty < 2)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
int penetratePower = GetWeaponPenetrationPower (m_currentWeapon);
|
|
|
|
|
|
|
|
|
|
|
|
if (penetratePower == 0)
|
|
|
|
|
|
return false;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
TraceResult tr;
|
|
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
float obstacleDistance = 0.0f;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (EyePosition (), dest, TRACE_IGNORE_MONSTERS, GetEntity (), &tr);
|
2015-07-12 17:18:20 +03:00
|
|
|
|
|
|
|
|
|
|
if (tr.fStartSolid)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
const Vector &source = tr.vecEndPos;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (dest, source, TRACE_IGNORE_MONSTERS, GetEntity (), &tr);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-01-04 18:26:06 +03:00
|
|
|
|
if (tr.flFraction != 1.0f)
|
2015-06-04 11:52:48 +03:00
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
if ((tr.vecEndPos - dest).GetLengthSquared () > GET_SQUARE (800.0f))
|
2015-06-04 11:52:48 +03:00
|
|
|
|
return false;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
if (tr.vecEndPos.z >= dest.z + 200.0f)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
obstacleDistance = (tr.vecEndPos - source).GetLength ();
|
2015-06-04 11:52:48 +03:00
|
|
|
|
}
|
2015-07-12 17:18:20 +03:00
|
|
|
|
}
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
if (obstacleDistance > 0.0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (penetratePower > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (obstacleDistance > 75.0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
obstacleDistance -= 75.0f;
|
|
|
|
|
|
penetratePower--;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
2015-07-12 17:18:20 +03:00
|
|
|
|
}
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-07 19:43:16 +03:00
|
|
|
|
bool Bot::IsShootableThruObstacleEx (const Vector &dest)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns if enemy can be shoot through some obstacle
|
|
|
|
|
|
|
|
|
|
|
|
if (m_difficulty < 2 || GetWeaponPenetrationPower (m_currentWeapon) == 0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
Vector source = EyePosition ();
|
|
|
|
|
|
Vector direction = (dest - source).Normalize (); // 1 unit long
|
2015-08-15 18:09:15 +03:00
|
|
|
|
Vector point;
|
2015-06-07 19:43:16 +03:00
|
|
|
|
|
|
|
|
|
|
int thikness = 0;
|
|
|
|
|
|
int numHits = 0;
|
|
|
|
|
|
|
|
|
|
|
|
TraceResult tr;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (source, dest, TRACE_IGNORE_EVERYTHING, GetEntity (), &tr);
|
2015-06-07 19:43:16 +03:00
|
|
|
|
|
2016-01-04 18:26:06 +03:00
|
|
|
|
while (tr.flFraction != 1.0f && numHits < 3)
|
2015-06-07 19:43:16 +03:00
|
|
|
|
{
|
|
|
|
|
|
numHits++;
|
|
|
|
|
|
thikness++;
|
|
|
|
|
|
|
|
|
|
|
|
point = tr.vecEndPos + direction;
|
|
|
|
|
|
|
|
|
|
|
|
while (POINT_CONTENTS (point) == CONTENTS_SOLID && thikness < 98)
|
|
|
|
|
|
{
|
|
|
|
|
|
point = point + direction;
|
|
|
|
|
|
thikness++;
|
|
|
|
|
|
}
|
2016-03-01 13:37:10 +03:00
|
|
|
|
engine.TestLine (point, dest, TRACE_IGNORE_EVERYTHING, GetEntity (), &tr);
|
2015-06-07 19:43:16 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (numHits < 3 && thikness < 98)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((dest - point).GetLengthSquared () < 13143)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-12 17:18:20 +03:00
|
|
|
|
bool Bot::DoFirePause (float distance)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// returns true if bot needs to pause between firing to compensate for punchangle & weapon spread
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_firePause > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if ((m_aimFlags & AIM_ENEMY) && !m_enemyOrigin.IsZero ())
|
2015-06-18 21:37:39 +03:00
|
|
|
|
{
|
|
|
|
|
|
if (IsEnemyProtectedByShield (m_enemy) && GetShootingConeDeviation (GetEntity (), &m_enemyOrigin) > 0.92f)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
float offset = 0.0f;
|
2016-09-16 16:10:22 +03:00
|
|
|
|
const float SprayDistance = 200.0f;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2016-09-16 16:10:22 +03:00
|
|
|
|
if (distance < SprayDistance)
|
2015-06-04 11:52:48 +03:00
|
|
|
|
return false;
|
2016-09-16 16:10:22 +03:00
|
|
|
|
else if (distance < 2 * SprayDistance)
|
2015-08-15 18:09:15 +03:00
|
|
|
|
offset = 10.0f;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
else
|
2015-08-15 18:09:15 +03:00
|
|
|
|
offset = 5.0f;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2015-06-24 15:38:48 +03:00
|
|
|
|
const float xPunch = DegreeToRadian (pev->punchangle.x);
|
|
|
|
|
|
const float yPunch = DegreeToRadian (pev->punchangle.y);
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2016-09-16 16:10:22 +03:00
|
|
|
|
float interval = m_thinkInterval;
|
|
|
|
|
|
|
|
|
|
|
|
if ((g_gameFlags & GAME_LEGACY) && Math::FltZero (interval))
|
|
|
|
|
|
interval = (1.0f / 30.0f) * Random.Float (0.95f, 1.05f);
|
|
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// check if we need to compensate recoil
|
2016-09-16 16:10:22 +03:00
|
|
|
|
if (tanf (A_sqrtf (fabsf (xPunch * xPunch) + fabsf (yPunch * yPunch))) * distance > offset + 30.0f + ((100 - (m_difficulty * 25)) / 100.f))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-16 16:10:22 +03:00
|
|
|
|
if (m_firePause < engine.Time () - interval)
|
|
|
|
|
|
m_firePause = engine.Time () + Random.Float (0.5f, 0.5f + 0.3f * ((100.0f - (m_difficulty * 25)) / 100.f));
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-04 10:51:52 +03:00
|
|
|
|
void Bot::FinishWeaponSelection (float distance, int index, int id, int choosen)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// we want to fire weapon, don't reload now
|
|
|
|
|
|
if (!m_isReloading)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_reloadCheckTime = engine.Time () + 3.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// select this weapon if it isn't already selected
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (m_currentWeapon != id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
SelectWeaponByName (g_weaponDefs[id].className);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// reset burst fire variables
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_firePause = 0.0f;
|
|
|
|
|
|
m_timeLastFired = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (tab[choosen].id != id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
choosen = 0;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found...
|
2016-04-04 10:51:52 +03:00
|
|
|
|
while (tab[choosen].id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (tab[choosen].id == id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
break;
|
|
|
|
|
|
|
2016-04-04 10:51:52 +03:00
|
|
|
|
choosen++;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if we're have a glock or famas vary burst fire mode
|
|
|
|
|
|
CheckBurstMode (distance);
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (HasShield () && m_shieldCheckTime < engine.Time () && GetTaskId () != TASK_CAMP) // better shield gun usage
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (distance >= 750.0f && !IsShieldDrawn ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2; // draw the shield
|
2016-04-04 10:51:52 +03:00
|
|
|
|
else if (IsShieldDrawn () || (!engine.IsNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !IsEnemyViewable (m_enemy))))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2; // draw out the shield
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shieldCheckTime = engine.Time () + 1.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (UsesSniper () && m_zoomCheckTime + 1.0f < engine.Time ()) // is the bot holding a sniper rifle?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (distance > 1500.0f && pev->fov >= 40.0f) // should the bot switch to the long-range zoom?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
else if (distance > 150.0f && pev->fov >= 90.0f) // else should the bot switch to the close-range zoom ?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
else if (distance <= 150.0f && pev->fov < 90.0f) // else should the bot restore the normal view ?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_zoomCheckTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (!engine.IsNullEntity (m_enemy) && (m_states & STATE_SEEING_ENEMY))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_moveSpeed = 0.0f;
|
|
|
|
|
|
m_strafeSpeed = 0.0f;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_navTimeset = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-06 23:37:58 +03:00
|
|
|
|
else if (m_difficulty < 4 && UsesZoomableRifle ()) // else is the bot holding a zoomable rifle?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (distance > 800.0f && pev->fov >= 90.0f) // should the bot switch to zoomed mode?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
else if (distance <= 800.0f && pev->fov < 90.0f) // else should the bot restore the normal view?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK2;
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_zoomCheckTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// need to care for burst fire?
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (distance < 256.0f || m_blindTime > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (id == WEAPON_KNIFE)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-06-14 12:55:49 +03:00
|
|
|
|
if (distance < 64.0f)
|
2016-04-04 10:51:52 +03:00
|
|
|
|
{
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (Random.Int (1, 100) < 30 || HasShield ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK; // use primary attack
|
|
|
|
|
|
else
|
|
|
|
|
|
pev->button |= IN_ATTACK2; // use secondary attack
|
|
|
|
|
|
}
|
2016-04-04 10:51:52 +03:00
|
|
|
|
}
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (tab[choosen].primaryFireHold && m_ammo[g_weaponDefs[tab[index].id].ammo1] > tab[index].minPrimaryAmmo) // if automatic weapon, just press attack
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK;
|
|
|
|
|
|
else // if not, toggle buttons
|
2016-04-04 10:51:52 +03:00
|
|
|
|
{
|
|
|
|
|
|
if ((pev->oldbuttons & IN_ATTACK) == 0)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shootTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
if (DoFirePause (distance))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// don't attack with knife over long distance
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (id == WEAPON_KNIFE)
|
2015-06-20 11:45:59 +03:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shootTime = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
2015-06-20 11:45:59 +03:00
|
|
|
|
}
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-04-04 10:51:52 +03:00
|
|
|
|
if (tab[choosen].primaryFireHold)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shootTime = engine.Time ();
|
|
|
|
|
|
m_zoomCheckTime = engine.Time ();
|
2016-04-04 10:51:52 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_ATTACK; // use primary attack
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
pev->button |= IN_ATTACK;
|
2015-06-07 19:43:16 +03:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_shootTime = engine.Time () + Random.Float (0.15f, 0.35f);
|
|
|
|
|
|
m_zoomCheckTime = engine.Time () - 0.09f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-04 10:51:52 +03:00
|
|
|
|
void Bot::FireWeapon (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function will return true if weapon was fired, false otherwise
|
|
|
|
|
|
float distance = (m_lookAt - EyePosition ()).GetLength (); // how far away is the enemy?
|
|
|
|
|
|
|
|
|
|
|
|
// if using grenade stop this
|
|
|
|
|
|
if (m_isUsingGrenade)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_shootTime = engine.Time () + 0.1f;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// or if friend in line of fire, stop this too but do not update shoot time
|
|
|
|
|
|
if (!engine.IsNullEntity (m_enemy))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsFriendInLineOfFire (distance))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_fightStyle = FIGHT_STRAFE;
|
|
|
|
|
|
m_lastFightStyleCheck = engine.Time ();
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
|
|
|
|
|
|
|
|
|
|
|
edict_t *enemy = m_enemy;
|
|
|
|
|
|
|
|
|
|
|
|
int selectId = WEAPON_KNIFE, selectIndex = 0, choosenWeapon = 0;
|
|
|
|
|
|
int weapons = pev->weapons;
|
|
|
|
|
|
|
|
|
|
|
|
// if jason mode use knife only
|
|
|
|
|
|
if (yb_jasonmode.GetBool ())
|
|
|
|
|
|
{
|
|
|
|
|
|
FinishWeaponSelection (distance, selectIndex, selectId, choosenWeapon);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// use knife if near and good difficulty (l33t dude!)
|
|
|
|
|
|
if (m_difficulty >= 3 && pev->health > 80.0f && !engine.IsNullEntity (enemy) && pev->health >= enemy->v.health && distance < 100.0f && !IsOnLadder () && !IsGroupOfEnemies (pev->origin))
|
|
|
|
|
|
{
|
|
|
|
|
|
FinishWeaponSelection (distance, selectIndex, selectId, choosenWeapon);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found...
|
|
|
|
|
|
while (tab[selectIndex].id)
|
|
|
|
|
|
{
|
|
|
|
|
|
// is the bot carrying this weapon?
|
|
|
|
|
|
if (weapons & (1 << tab[selectIndex].id))
|
|
|
|
|
|
{
|
|
|
|
|
|
// is enough ammo available to fire AND check is better to use pistol in our current situation...
|
|
|
|
|
|
if (m_ammoInClip[tab[selectIndex].id] > 0 && !IsWeaponBadInDistance (selectIndex, distance))
|
|
|
|
|
|
choosenWeapon = selectIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
selectIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
selectId = tab[choosenWeapon].id;
|
|
|
|
|
|
|
|
|
|
|
|
// if no available weapon...
|
|
|
|
|
|
if (choosenWeapon == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
selectIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found...
|
|
|
|
|
|
while (tab[selectIndex].id)
|
|
|
|
|
|
{
|
|
|
|
|
|
int id = tab[selectIndex].id;
|
|
|
|
|
|
|
|
|
|
|
|
// is the bot carrying this weapon?
|
|
|
|
|
|
if (weapons & (1 << id))
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( g_weaponDefs[id].ammo1 != -1 && g_weaponDefs[id].ammo1 < 32 && m_ammo[g_weaponDefs[id].ammo1] >= tab[selectIndex].minPrimaryAmmo)
|
|
|
|
|
|
{
|
|
|
|
|
|
// available ammo found, reload weapon
|
|
|
|
|
|
if (m_reloadState == RELOAD_NONE || m_reloadCheckTime > engine.Time ())
|
|
|
|
|
|
{
|
|
|
|
|
|
m_isReloading = true;
|
|
|
|
|
|
m_reloadState = RELOAD_PRIMARY;
|
|
|
|
|
|
m_reloadCheckTime = engine.Time ();
|
|
|
|
|
|
|
|
|
|
|
|
RadioMessage (Radio_NeedBackup);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
selectIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
selectId = WEAPON_KNIFE; // no available ammo, use knife!
|
|
|
|
|
|
}
|
|
|
|
|
|
FinishWeaponSelection (distance, selectIndex, selectId, choosenWeapon);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
bool Bot::IsWeaponBadInDistance (int weaponIndex, float distance)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function checks, is it better to use pistol instead of current primary weapon
|
|
|
|
|
|
// to attack our enemy, since current weapon is not very good in this situation.
|
|
|
|
|
|
|
2015-07-31 20:32:08 +03:00
|
|
|
|
if (m_difficulty < 2)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
int wid = g_weaponSelect[weaponIndex].id;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (wid == WEAPON_KNIFE)
|
2016-11-02 18:36:05 +03:00
|
|
|
|
return false;
|
2015-06-04 11:52:48 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
// check is ammo available for secondary weapon
|
|
|
|
|
|
if (m_ammoInClip[g_weaponSelect[GetBestSecondaryWeaponCarried ()].id] >= 1)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// better use pistol in short range distances, when using sniper weapons
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if ((wid == WEAPON_SCOUT || wid == WEAPON_AWP || wid == WEAPON_G3SG1 || wid == WEAPON_SG550) && distance < 500.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
// shotguns is too inaccurate at long distances, so weapon is bad
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if ((wid == WEAPON_M3 || wid == WEAPON_XM1014) && distance > 750.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::FocusEnemy (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// aim for the head and/or body
|
2016-01-14 23:32:38 +03:00
|
|
|
|
m_lookAt = GetAimPosition ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_enemySurpriseTime > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
2016-01-14 23:32:38 +03:00
|
|
|
|
float distance = (m_lookAt - EyePosition ()).GetLength2D (); // how far away is the enemy scum?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-20 11:45:59 +03:00
|
|
|
|
if (distance < 128.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (m_currentWeapon == WEAPON_KNIFE)
|
|
|
|
|
|
{
|
2016-01-26 21:18:59 +03:00
|
|
|
|
if (distance < 80.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_wantsToFire = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2016-01-26 21:18:59 +03:00
|
|
|
|
m_wantsToFire = true;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2015-06-20 11:45:59 +03:00
|
|
|
|
float dot = GetShootingConeDeviation (GetEntity (), &m_enemyOrigin);
|
|
|
|
|
|
|
|
|
|
|
|
if (dot < 0.90f)
|
|
|
|
|
|
m_wantsToFire = false;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2015-06-20 11:45:59 +03:00
|
|
|
|
float enemyDot = GetShootingConeDeviation (m_enemy, &pev->origin);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-06-20 11:45:59 +03:00
|
|
|
|
// enemy faces bot?
|
|
|
|
|
|
if (enemyDot >= 0.90f)
|
|
|
|
|
|
m_wantsToFire = true;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2015-06-20 11:45:59 +03:00
|
|
|
|
if (dot > 0.99f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_wantsToFire = true;
|
|
|
|
|
|
else
|
2015-06-20 11:45:59 +03:00
|
|
|
|
m_wantsToFire = false;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
2015-06-20 11:45:59 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::CombatFight (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// no enemy? no need to do strafing
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (engine.IsNullEntity (m_enemy))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
2016-01-14 23:32:38 +03:00
|
|
|
|
float distance = (m_lookAt - EyePosition ()).GetLength2D (); // how far away is the enemy scum?
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_timeWaypointMove < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
int approach;
|
|
|
|
|
|
|
2016-01-14 23:32:38 +03:00
|
|
|
|
if (m_currentWeapon == WEAPON_KNIFE) // knife?
|
|
|
|
|
|
approach = 100;
|
2016-03-09 23:32:45 +03:00
|
|
|
|
else if ((m_states & STATE_SUSPECT_ENEMY) && !(m_states & STATE_SEEING_ENEMY)) // if suspecting enemy stand still
|
2014-07-30 14:17:46 +04:00
|
|
|
|
approach = 49;
|
|
|
|
|
|
else if (m_isReloading || m_isVIP) // if reloading or vip back off
|
|
|
|
|
|
approach = 29;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
approach = static_cast <int> (pev->health * m_agressionLevel);
|
|
|
|
|
|
|
2015-06-07 19:43:16 +03:00
|
|
|
|
if (UsesSniper () && approach > 49)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
approach = 49;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
|
2015-06-04 11:52:48 +03:00
|
|
|
|
if (approach < 30 && !g_bombPlanted && (IsInViewCone (m_enemy->v.origin) || m_isVIP))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
m_moveSpeed = -pev->maxspeed;
|
|
|
|
|
|
|
|
|
|
|
|
TaskItem *task = GetTask ();
|
|
|
|
|
|
|
|
|
|
|
|
task->id = TASK_SEEKCOVER;
|
|
|
|
|
|
task->resume = true;
|
2015-08-15 18:09:15 +03:00
|
|
|
|
task->desire = TASKPRI_ATTACK + 1.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else if (approach < 50)
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_moveSpeed = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
m_moveSpeed = pev->maxspeed;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (distance < 96.0f && m_currentWeapon != WEAPON_KNIFE)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_moveSpeed = -pev->maxspeed;
|
|
|
|
|
|
|
|
|
|
|
|
if (UsesSniper ())
|
|
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STAY;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_lastFightStyleCheck = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else if (UsesRifle () || UsesSubmachineGun ())
|
|
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_lastFightStyleCheck + 3.0f < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-11 21:01:06 +03:00
|
|
|
|
int rand = Random.Int (1, 100);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (distance < 450.0f)
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STRAFE;
|
2015-08-15 18:09:15 +03:00
|
|
|
|
else if (distance < 1024.0f)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (rand < (UsesSubmachineGun () ? 50 : 30))
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STRAFE;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STAY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rand < (UsesSubmachineGun () ? 80 : 93))
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STAY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STRAFE;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_lastFightStyleCheck = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_lastFightStyleCheck + 3.0f < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (Random.Int (0, 100) < 50)
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STRAFE;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_fightStyle = FIGHT_STAY;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_lastFightStyleCheck = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (m_fightStyle == FIGHT_STRAFE || ((pev->button & IN_RELOAD) || m_isReloading) || (UsesPistol () && distance < 400.0f) || m_currentWeapon == WEAPON_KNIFE)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_strafeSetTime < engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// to start strafing, we have to first figure out if the target is on the left side or right side
|
|
|
|
|
|
MakeVectors (m_enemy->v.v_angle);
|
|
|
|
|
|
|
2015-06-14 12:55:49 +03:00
|
|
|
|
const Vector &dirToPoint = (pev->origin - m_enemy->v.origin).Normalize2D ();
|
|
|
|
|
|
const Vector &rightSide = g_pGlobals->v_right.Normalize2D ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if ((dirToPoint | rightSide) < 0)
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_combatStrafeDir = STRAFE_DIR_LEFT;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_combatStrafeDir = STRAFE_DIR_RIGHT;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (Random.Int (1, 100) < 30)
|
2016-03-09 22:34:24 +03:00
|
|
|
|
m_combatStrafeDir = (m_combatStrafeDir == STRAFE_DIR_LEFT ? STRAFE_DIR_RIGHT : STRAFE_DIR_LEFT);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2016-03-09 22:34:24 +03:00
|
|
|
|
m_strafeSetTime = engine.Time () + Random.Float (0.5f, 3.0f);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-09 22:34:24 +03:00
|
|
|
|
if (m_combatStrafeDir == STRAFE_DIR_RIGHT)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (!CheckWallOnLeft ())
|
2015-06-04 11:52:48 +03:00
|
|
|
|
m_strafeSpeed = -pev->maxspeed;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-03-09 22:34:24 +03:00
|
|
|
|
m_combatStrafeDir = STRAFE_DIR_LEFT;
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_strafeSetTime = engine.Time () + 1.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!CheckWallOnRight ())
|
2015-07-21 12:52:19 +03:00
|
|
|
|
m_strafeSpeed = pev->maxspeed;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-03-09 22:34:24 +03:00
|
|
|
|
m_combatStrafeDir = STRAFE_DIR_RIGHT;
|
2016-03-09 19:17:56 +03:00
|
|
|
|
m_strafeSetTime = engine.Time () + 1.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-11 21:01:06 +03:00
|
|
|
|
if (m_difficulty >= 3 && (m_jumpTime + 5.0f < engine.Time () && IsOnFloor () && Random.Int (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.GetLength2D () > 150.0f) && !UsesSniper ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button |= IN_JUMP;
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (m_moveSpeed > 0.0f && distance > 100.0f && m_currentWeapon != WEAPON_KNIFE)
|
|
|
|
|
|
m_moveSpeed = 0.0f;
|
2015-06-14 12:55:49 +03:00
|
|
|
|
|
|
|
|
|
|
if (m_currentWeapon == WEAPON_KNIFE)
|
|
|
|
|
|
m_strafeSpeed = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
2016-03-09 19:17:56 +03:00
|
|
|
|
else if (m_fightStyle == FIGHT_STAY)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if ((m_visibility & (VISIBLE_HEAD | VISIBLE_BODY)) && GetTaskId () != TASK_SEEKCOVER && GetTaskId () != TASK_HUNTENEMY && waypoints.IsDuckVisible (m_currentWaypointIndex, waypoints.FindNearest (m_enemy->v.origin)))
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_duckTime = engine.Time () + 0.5f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_moveSpeed = 0.0f;
|
|
|
|
|
|
m_strafeSpeed = 0.0f;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_navTimeset = engine.Time ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if (m_duckTime > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2015-08-15 18:09:15 +03:00
|
|
|
|
m_moveSpeed = 0.0f;
|
|
|
|
|
|
m_strafeSpeed = 0.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-07 19:43:16 +03:00
|
|
|
|
if (m_moveSpeed > 0.0f && m_currentWeapon != WEAPON_KNIFE)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
m_moveSpeed = GetWalkSpeed ();
|
|
|
|
|
|
|
|
|
|
|
|
if (m_isReloading)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_moveSpeed = -pev->maxspeed;
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_duckTime = engine.Time () - 1.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-04 11:52:48 +03:00
|
|
|
|
if (!IsInWater () && !IsOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
MakeVectors (pev->v_angle);
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (IsDeadlyDrop (pev->origin + (g_pGlobals->v_forward * m_moveSpeed * 0.2f) + (g_pGlobals->v_right * m_strafeSpeed * 0.2f) + (pev->velocity * m_frameInterval)))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
m_strafeSpeed = -m_strafeSpeed;
|
|
|
|
|
|
m_moveSpeed = -m_moveSpeed;
|
|
|
|
|
|
|
|
|
|
|
|
pev->button &= ~IN_JUMP;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-07-20 21:26:20 +03:00
|
|
|
|
IgnoreCollisionShortly ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::HasPrimaryWeapon (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns returns true, if bot has a primary weapon
|
|
|
|
|
|
|
|
|
|
|
|
return (pev->weapons & WEAPON_PRIMARY) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::HasSecondaryWeapon (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns returns true, if bot has a secondary weapon
|
|
|
|
|
|
|
|
|
|
|
|
return (pev->weapons & WEAPON_SECONDARY) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::HasShield (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns true, if bot has a tactical shield
|
|
|
|
|
|
|
|
|
|
|
|
return strncmp (STRING (pev->viewmodel), "models/shield/v_shield_", 23) == 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::IsShieldDrawn (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns true, is the tactical shield is drawn
|
|
|
|
|
|
|
|
|
|
|
|
if (!HasShield ())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
return pev->weaponanim == 6 || pev->weaponanim == 7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::IsEnemyProtectedByShield (edict_t *enemy)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns true, if enemy protected by the shield
|
|
|
|
|
|
|
2016-03-05 23:08:07 +03:00
|
|
|
|
if (engine.IsNullEntity (enemy) || IsShieldDrawn ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// check if enemy has shield and this shield is drawn
|
|
|
|
|
|
if (strncmp (STRING (enemy->v.viewmodel), "models/shield/v_shield_", 23) == 0 && (enemy->v.weaponanim == 6 || enemy->v.weaponanim == 7))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (::IsInViewCone (pev->origin, enemy))
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::UsesSniper (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function returns true, if returns if bot is using a sniper rifle
|
|
|
|
|
|
|
|
|
|
|
|
return m_currentWeapon == WEAPON_AWP || m_currentWeapon == WEAPON_G3SG1 || m_currentWeapon == WEAPON_SCOUT || m_currentWeapon == WEAPON_SG550;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::UsesRifle (void)
|
|
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
2014-07-30 14:17:46 +04:00
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
while (tab->id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (m_currentWeapon == tab->id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
break;
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
tab++;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (tab->id && count > 13)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::UsesPistol (void)
|
|
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
2014-07-30 14:17:46 +04:00
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found
|
2016-03-09 19:17:56 +03:00
|
|
|
|
while (tab->id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (m_currentWeapon == tab->id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
break;
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
tab++;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (tab->id && count < 7)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-09-17 20:36:42 +04:00
|
|
|
|
bool Bot::UsesCampGun (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return UsesSubmachineGun () || UsesRifle () || UsesSniper ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
bool Bot::UsesSubmachineGun (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_currentWeapon == WEAPON_MP5 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_UMP45;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::UsesZoomableRifle (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_currentWeapon == WEAPON_AUG || m_currentWeapon == WEAPON_SG552;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Bot::UsesBadPrimary (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3 || m_currentWeapon == WEAPON_UMP45 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Bot::CheckGrenades (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pev->weapons & (1 << WEAPON_EXPLOSIVE))
|
|
|
|
|
|
return WEAPON_EXPLOSIVE;
|
|
|
|
|
|
else if (pev->weapons & (1 << WEAPON_FLASHBANG))
|
|
|
|
|
|
return WEAPON_FLASHBANG;
|
|
|
|
|
|
else if (pev->weapons & (1 << WEAPON_SMOKE))
|
|
|
|
|
|
return WEAPON_SMOKE;
|
|
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::SelectBestWeapon (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// this function chooses best weapon, from weapons that bot currently own, and change
|
|
|
|
|
|
// current weapon to best one.
|
|
|
|
|
|
|
|
|
|
|
|
if (yb_jasonmode.GetBool ())
|
|
|
|
|
|
{
|
|
|
|
|
|
// if knife mode activated, force bot to use knife
|
|
|
|
|
|
SelectWeaponByName ("weapon_knife");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_isReloading)
|
|
|
|
|
|
return;
|
2015-06-07 19:43:16 +03:00
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
int selectIndex = 0;
|
|
|
|
|
|
int chosenWeaponIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found...
|
2016-03-09 19:17:56 +03:00
|
|
|
|
while (tab[selectIndex].id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// is the bot NOT carrying this weapon?
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (!(pev->weapons & (1 << tab[selectIndex].id)))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
selectIndex++; // skip to next weapon
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
int id = tab[selectIndex].id;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
bool ammoLeft = false;
|
|
|
|
|
|
|
|
|
|
|
|
// is the bot already holding this weapon and there is still ammo in clip?
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (tab[selectIndex].id == m_currentWeapon && (GetAmmoInClip () < 0 || GetAmmoInClip () >= tab[selectIndex].minPrimaryAmmo))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
ammoLeft = true;
|
|
|
|
|
|
|
|
|
|
|
|
// is no ammo required for this weapon OR enough ammo available to fire
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (g_weaponDefs[id].ammo1 < 0 || (g_weaponDefs[id].ammo1 < 32 && m_ammo[g_weaponDefs[id].ammo1] >= tab[selectIndex].minPrimaryAmmo))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
ammoLeft = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (ammoLeft)
|
|
|
|
|
|
chosenWeaponIndex = selectIndex;
|
|
|
|
|
|
|
|
|
|
|
|
selectIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chosenWeaponIndex %= NUM_WEAPONS + 1;
|
|
|
|
|
|
selectIndex = chosenWeaponIndex;
|
|
|
|
|
|
|
2016-03-09 19:17:56 +03:00
|
|
|
|
int id = tab[selectIndex].id;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// select this weapon if it isn't already selected
|
|
|
|
|
|
if (m_currentWeapon != id)
|
2016-03-09 19:17:56 +03:00
|
|
|
|
SelectWeaponByName (tab[selectIndex].weaponName);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
m_isReloading = false;
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::SelectPistol (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
int oldWeapons = pev->weapons;
|
|
|
|
|
|
|
|
|
|
|
|
pev->weapons &= ~WEAPON_PRIMARY;
|
|
|
|
|
|
SelectBestWeapon ();
|
|
|
|
|
|
|
|
|
|
|
|
pev->weapons = oldWeapons;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Bot::GetHighestWeapon (void)
|
|
|
|
|
|
{
|
2016-03-09 19:17:56 +03:00
|
|
|
|
WeaponSelect *tab = &g_weaponSelect[0];
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
int weapons = pev->weapons;
|
|
|
|
|
|
int num = 0;
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// loop through all the weapons until terminator is found...
|
2016-03-09 19:17:56 +03:00
|
|
|
|
while (tab->id)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// is the bot carrying this weapon?
|
2016-03-09 19:17:56 +03:00
|
|
|
|
if (weapons & (1 << tab->id))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
num = i;
|
|
|
|
|
|
|
|
|
|
|
|
i++;
|
2016-03-09 19:17:56 +03:00
|
|
|
|
tab++;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
return num;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::SelectWeaponByName (const char *name)
|
|
|
|
|
|
{
|
2016-03-01 22:52:17 +03:00
|
|
|
|
engine.IssueBotCommand (GetEntity (), name);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::SelectWeaponbyNumber (int num)
|
|
|
|
|
|
{
|
2016-03-01 22:52:17 +03:00
|
|
|
|
engine.IssueBotCommand (GetEntity (), g_weaponSelect[num].weaponName);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::AttachToUser (void)
|
|
|
|
|
|
{
|
2015-07-12 17:18:20 +03:00
|
|
|
|
// this function forces bot to follow user
|
2016-09-10 19:31:38 +03:00
|
|
|
|
Array <edict_t *> users;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
// search friends near us
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if (EntityIsVisible (client.origin) && !IsValidBot (client.ent))
|
|
|
|
|
|
users.Push (client.ent);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if (users.IsEmpty ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
m_targetEntity = users.GetRandomElement ();
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
ChatterMessage (Chatter_LeadOnSir);
|
2015-08-15 18:09:15 +03:00
|
|
|
|
PushTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, -1, 0.0f, true);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::CommandTeam (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// prevent spamming
|
2016-10-23 01:49:05 +03:00
|
|
|
|
if (m_timeTeamOrder > engine.Time () + 2.0f || (g_gameFlags & GAME_CSDM_FFA) || !yb_communication_type.GetInt ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
bool memberNear = false;
|
|
|
|
|
|
bool memberExists = false;
|
|
|
|
|
|
|
|
|
|
|
|
// search teammates seen by this bot
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
memberExists = true;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if (EntityIsVisible (client.origin))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
memberNear = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (memberNear) // has teammates ?
|
|
|
|
|
|
{
|
2015-06-06 16:37:15 +03:00
|
|
|
|
if (m_personality == PERSONALITY_RUSHER && yb_communication_type.GetInt () == 2)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
RadioMessage (Radio_StormTheFront);
|
2015-06-06 16:37:15 +03:00
|
|
|
|
else if (m_personality != PERSONALITY_RUSHER && yb_communication_type.GetInt () == 2)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
RadioMessage (Radio_Fallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (memberExists && yb_communication_type.GetInt () == 1)
|
|
|
|
|
|
RadioMessage (Radio_TakingFire);
|
|
|
|
|
|
else if (memberExists && yb_communication_type.GetInt () == 2)
|
|
|
|
|
|
ChatterMessage(Chatter_ScaredEmotion);
|
|
|
|
|
|
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_timeTeamOrder = engine.Time () + Random.Float (5.0f, 30.0f);
|
2014-07-30 14:17:46 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-10 23:41:55 +03:00
|
|
|
|
bool Bot::IsGroupOfEnemies (const Vector &location, int numEnemies, int radius)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
int numPlayers = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// search the world for enemy players...
|
2016-03-01 13:37:10 +03:00
|
|
|
|
for (int i = 0; i < engine.MaxClients (); i++)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
2016-09-10 19:31:38 +03:00
|
|
|
|
const Client &client = g_clients[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == GetEntity ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if ((client.ent->v.origin - location).GetLengthSquared () < GET_SQUARE (radius))
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
// don't target our teammates...
|
2016-09-10 19:31:38 +03:00
|
|
|
|
if (client.team == m_team)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
if (numPlayers++ > numEnemies)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Bot::CheckReload (void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// check the reload state
|
|
|
|
|
|
if (GetTaskId () == TASK_PLANTBOMB || GetTaskId () == TASK_DEFUSEBOMB || GetTaskId () == TASK_PICKUPITEM || GetTaskId () == TASK_THROWFLASHBANG || GetTaskId () == TASK_THROWSMOKE || m_isUsingGrenade)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-17 19:23:31 +03:00
|
|
|
|
m_isReloading = false; // update reloading status
|
2016-03-01 13:37:10 +03:00
|
|
|
|
m_reloadCheckTime = engine.Time () + 3.0f;
|
2014-07-30 14:17:46 +04:00
|
|
|
|
|
|
|
|
|
|
if (m_reloadState != RELOAD_NONE)
|
|
|
|
|
|
{
|
|
|
|
|
|
int weaponIndex = 0, maxClip = 0;
|
|
|
|
|
|
int weapons = pev->weapons;
|
|
|
|
|
|
|
|
|
|
|
|
if (m_reloadState == RELOAD_PRIMARY)
|
|
|
|
|
|
weapons &= WEAPON_PRIMARY;
|
|
|
|
|
|
else if (m_reloadState == RELOAD_SECONDARY)
|
|
|
|
|
|
weapons &= WEAPON_SECONDARY;
|
|
|
|
|
|
|
|
|
|
|
|
if (weapons == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_reloadState++;
|
|
|
|
|
|
|
|
|
|
|
|
if (m_reloadState > RELOAD_SECONDARY)
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < MAX_WEAPONS; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (weapons & (1 << i))
|
|
|
|
|
|
{
|
|
|
|
|
|
weaponIndex = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
InternalAssert (weaponIndex);
|
|
|
|
|
|
|
|
|
|
|
|
switch (weaponIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
case WEAPON_M249:
|
|
|
|
|
|
maxClip = 100;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_P90:
|
|
|
|
|
|
maxClip = 50;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_GALIL:
|
|
|
|
|
|
maxClip = 35;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_ELITE:
|
|
|
|
|
|
case WEAPON_MP5:
|
|
|
|
|
|
case WEAPON_TMP:
|
|
|
|
|
|
case WEAPON_MAC10:
|
|
|
|
|
|
case WEAPON_M4A1:
|
|
|
|
|
|
case WEAPON_AK47:
|
|
|
|
|
|
case WEAPON_SG552:
|
|
|
|
|
|
case WEAPON_AUG:
|
|
|
|
|
|
case WEAPON_SG550:
|
|
|
|
|
|
maxClip = 30;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_UMP45:
|
|
|
|
|
|
case WEAPON_FAMAS:
|
|
|
|
|
|
maxClip = 25;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_GLOCK:
|
|
|
|
|
|
case WEAPON_FIVESEVEN:
|
|
|
|
|
|
case WEAPON_G3SG1:
|
|
|
|
|
|
maxClip = 20;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_P228:
|
|
|
|
|
|
maxClip = 13;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_USP:
|
|
|
|
|
|
maxClip = 12;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_AWP:
|
|
|
|
|
|
case WEAPON_SCOUT:
|
|
|
|
|
|
maxClip = 10;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_M3:
|
|
|
|
|
|
maxClip = 8;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case WEAPON_DEAGLE:
|
|
|
|
|
|
case WEAPON_XM1014:
|
|
|
|
|
|
maxClip = 7;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-08-15 18:09:15 +03:00
|
|
|
|
if (m_ammoInClip[weaponIndex] < maxClip * 0.8f && g_weaponDefs[weaponIndex].ammo1 != -1 && g_weaponDefs[weaponIndex].ammo1 < 32 && m_ammo[g_weaponDefs[weaponIndex].ammo1] > 0)
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (m_currentWeapon != weaponIndex)
|
|
|
|
|
|
SelectWeaponByName (g_weaponDefs[weaponIndex].className);
|
2015-08-03 21:08:38 +03:00
|
|
|
|
|
2014-07-30 14:17:46 +04:00
|
|
|
|
pev->button &= ~IN_ATTACK;
|
|
|
|
|
|
|
|
|
|
|
|
if ((pev->oldbuttons & IN_RELOAD) == RELOAD_NONE)
|
|
|
|
|
|
pev->button |= IN_RELOAD; // press reload button
|
|
|
|
|
|
|
|
|
|
|
|
m_isReloading = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// if we have enemy don't reload next weapon
|
2016-03-01 13:37:10 +03:00
|
|
|
|
if ((m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) || m_seeEnemyTime + 5.0f > engine.Time ())
|
2014-07-30 14:17:46 +04:00
|
|
|
|
{
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_reloadState++;
|
|
|
|
|
|
|
|
|
|
|
|
if (m_reloadState > RELOAD_SECONDARY)
|
|
|
|
|
|
m_reloadState = RELOAD_NONE;
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|