mirror of
https://github.com/ENSL/NS.git
synced 2024-11-25 05:51:11 +00:00
f6796ad025
* Reworked how bot hearing and sight combine * Reworked target prioritisation * Reworked when a bot should ignore a nearby enemy and engage * Overall result is bots should have a better balance of engaging enemies and getting on with their tasks
1321 lines
No EOL
35 KiB
C++
1321 lines
No EOL
35 KiB
C++
|
|
#include "AvHAIWeaponHelper.h"
|
|
#include "AvHAIPlayerUtil.h"
|
|
#include "AvHAIMath.h"
|
|
#include "AvHAINavigation.h"
|
|
#include "AvHAIHelper.h"
|
|
#include "AvHAITactical.h"
|
|
#include "AvHAIPlayerManager.h"
|
|
|
|
#include "AvHGamerules.h"
|
|
#include "AvHAlienWeaponConstants.h"
|
|
#include "AvHAlienWeapons.h"
|
|
#include "AvHMarineEquipmentConstants.h"
|
|
#include "AvHServerUtil.h"
|
|
|
|
extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular)
|
|
extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles
|
|
|
|
int GetPlayerCurrentWeaponClipAmmo(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->m_iClip;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int GetPlayerCurrentWeaponMaxClipAmmo(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxClip();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int GetPlayerCurrentWeaponReserveAmmo(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
float GetProjectileVelocityForWeapon(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_GORGE_SPIT:
|
|
return (float)BALANCE_VAR(kSpitVelocity);
|
|
case WEAPON_LERK_SPORES:
|
|
return (float)BALANCE_VAR(kShootCloudVelocity);
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
return (float)BALANCE_VAR(kAcidRocketVelocity);
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
return (float)BALANCE_VAR(kBileBombVelocity);
|
|
case WEAPON_MARINE_GRENADE:
|
|
case WEAPON_MARINE_GL:
|
|
return (float)BALANCE_VAR(kGrenadeForce);
|
|
default:
|
|
return 0.0f; // Hitscan. We don't bother with bile bomb as it's so short range that it doesn't really need leading the target
|
|
}
|
|
}
|
|
|
|
bool CanInterruptWeaponReload(AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
case WEAPON_MARINE_GL:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float GetReloadTimeForWeapon(AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_MARINE_PISTOL:
|
|
case WEAPON_MARINE_MG:
|
|
return 3.0f;
|
|
case WEAPON_MARINE_HMG:
|
|
return 6.3f;
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
return 0.22f;
|
|
case WEAPON_MARINE_GL:
|
|
return 1.5f;
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
float GetEnergyCostForWeapon(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_SKULK_BITE:
|
|
return BALANCE_VAR(kBiteEnergyCost);
|
|
case WEAPON_SKULK_PARASITE:
|
|
return BALANCE_VAR(kParasiteEnergyCost);
|
|
case WEAPON_SKULK_LEAP:
|
|
return BALANCE_VAR(kLeapEnergyCost);
|
|
case WEAPON_SKULK_XENOCIDE:
|
|
return BALANCE_VAR(kDivineWindEnergyCost);
|
|
|
|
case WEAPON_GORGE_SPIT:
|
|
return BALANCE_VAR(kSpitEnergyCost);
|
|
case WEAPON_GORGE_HEALINGSPRAY:
|
|
return BALANCE_VAR(kHealingSprayEnergyCost);
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
return BALANCE_VAR(kBileBombEnergyCost);
|
|
case WEAPON_GORGE_WEB:
|
|
return BALANCE_VAR(kWebEnergyCost);
|
|
|
|
case WEAPON_LERK_BITE:
|
|
return BALANCE_VAR(kBite2EnergyCost);
|
|
case WEAPON_LERK_SPORES:
|
|
return BALANCE_VAR(kSporesEnergyCost);
|
|
case WEAPON_LERK_UMBRA:
|
|
return BALANCE_VAR(kUmbraEnergyCost);
|
|
case WEAPON_LERK_PRIMALSCREAM:
|
|
return BALANCE_VAR(kPrimalScreamEnergyCost);
|
|
|
|
case WEAPON_FADE_SWIPE:
|
|
return BALANCE_VAR(kSwipeEnergyCost);
|
|
case WEAPON_FADE_BLINK:
|
|
return BALANCE_VAR(kBlinkEnergyCost);
|
|
case WEAPON_FADE_METABOLIZE:
|
|
return BALANCE_VAR(kMetabolizeEnergyCost);
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
return BALANCE_VAR(kAcidRocketEnergyCost);
|
|
|
|
case WEAPON_ONOS_GORE:
|
|
return BALANCE_VAR(kClawsEnergyCost);
|
|
case WEAPON_ONOS_DEVOUR:
|
|
return BALANCE_VAR(kDevourEnergyCost);
|
|
case WEAPON_ONOS_STOMP:
|
|
return BALANCE_VAR(kStompEnergyCost);
|
|
case WEAPON_ONOS_CHARGE:
|
|
return BALANCE_VAR(kChargeEnergyCost);
|
|
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
void InterruptReload(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->Button |= IN_ATTACK;
|
|
}
|
|
|
|
AvHAIWeapon UTIL_GetPlayerPrimaryWeapon(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* Weapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[1]);
|
|
|
|
if (Weapon)
|
|
{
|
|
return (AvHAIWeapon)Weapon->m_iId;
|
|
}
|
|
|
|
return WEAPON_INVALID;
|
|
}
|
|
|
|
AvHAIWeapon UTIL_GetPlayerSecondaryWeapon(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* Weapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[2]);
|
|
|
|
if (Weapon)
|
|
{
|
|
return (AvHAIWeapon)Weapon->m_iId;
|
|
}
|
|
|
|
return WEAPON_INVALID;
|
|
}
|
|
|
|
bool IsHitscanWeapon(AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_MARINE_MG:
|
|
case WEAPON_MARINE_HMG:
|
|
case WEAPON_MARINE_PISTOL:
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
case WEAPON_SKULK_PARASITE:
|
|
case WEAPON_MARINE_WELDER:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float GetTimeUntilPlayerNextRefire(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* WeaponRef = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
|
|
|
if (!WeaponRef) { return 0.0f; }
|
|
|
|
return WeaponRef->m_flNextPrimaryAttack;
|
|
}
|
|
|
|
AvHAIWeapon GetBotMarineSecondaryWeapon(const AvHAIPlayer* pBot)
|
|
{
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_PISTOL))
|
|
{
|
|
return WEAPON_MARINE_PISTOL;
|
|
}
|
|
|
|
return WEAPON_INVALID;
|
|
}
|
|
|
|
int UTIL_GetPlayerPrimaryMaxAmmoReserve(AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[1]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxAmmo1();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerPrimaryAmmoReserve(AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[1]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerSecondaryMaxAmmoReserve(AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxAmmo1();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerSecondaryAmmoReserve(AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BotGetSecondaryWeaponAmmoReserve(AvHAIPlayer* pBot)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(pBot->Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return pBot->Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerPrimaryWeaponClipAmmo(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[1]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->m_iClip;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BotGetSecondaryWeaponClipAmmo(const AvHAIPlayer* pBot)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(pBot->Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->m_iClip;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerPrimaryWeaponMaxClipSize(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[1]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxClip();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerSecondaryWeaponClipAmmo(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->m_iClip;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UTIL_GetPlayerSecondaryWeaponMaxClipSize(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxClip();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BotGetSecondaryWeaponMaxClipSize(const AvHAIPlayer* pBot)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(pBot->Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxClip();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BotGetSecondaryWeaponMaxAmmoReserve(AvHAIPlayer* pBot)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(pBot->Player->m_rgpPlayerItems[2]);
|
|
|
|
if (theBasePlayerWeapon)
|
|
{
|
|
return theBasePlayerWeapon->iMaxClip();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
float GetMaxIdealWeaponRange(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_LERK_PRIMALSCREAM:
|
|
return UTIL_MetresToGoldSrcUnits(100.0f);
|
|
case WEAPON_LERK_SPORES:
|
|
case WEAPON_LERK_UMBRA:
|
|
case WEAPON_MARINE_GL:
|
|
case WEAPON_MARINE_MG:
|
|
case WEAPON_MARINE_PISTOL:
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
case WEAPON_SKULK_PARASITE:
|
|
case WEAPON_SKULK_LEAP:
|
|
case WEAPON_ONOS_CHARGE:
|
|
case WEAPON_GORGE_SPIT:
|
|
return UTIL_MetresToGoldSrcUnits(50.0f);
|
|
case WEAPON_MARINE_HMG:
|
|
case WEAPON_MARINE_GRENADE:
|
|
return UTIL_MetresToGoldSrcUnits(10.0f);
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
case WEAPON_ONOS_STOMP:
|
|
return UTIL_MetresToGoldSrcUnits(8.0f);
|
|
case WEAPON_SKULK_XENOCIDE:
|
|
return (float)BALANCE_VAR(kDivineWindRadius) * 0.8f;
|
|
case WEAPON_ONOS_GORE:
|
|
return (float)BALANCE_VAR(kClawsRange) + 20.0f;
|
|
case WEAPON_ONOS_DEVOUR:
|
|
return (float)BALANCE_VAR(kDevourRange);
|
|
case WEAPON_FADE_SWIPE:
|
|
return (float)BALANCE_VAR(kSwipeRange) + 30.0f;
|
|
case WEAPON_SKULK_BITE:
|
|
return (float)BALANCE_VAR(kBiteRange) + 20.0f;
|
|
case WEAPON_LERK_BITE:
|
|
return (float)BALANCE_VAR(kBite2Range) + 20.0f;
|
|
case WEAPON_GORGE_HEALINGSPRAY:
|
|
return (float)BALANCE_VAR(kHealingSprayRange) * 0.5f;
|
|
case WEAPON_MARINE_WELDER:
|
|
return (float)BALANCE_VAR(kWelderRange) + 10.0f;
|
|
default:
|
|
return max_player_use_reach;
|
|
}
|
|
}
|
|
|
|
float GetMinIdealWeaponRange(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_MARINE_GL:
|
|
case WEAPON_MARINE_GRENADE:
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
return UTIL_MetresToGoldSrcUnits(5.0f);
|
|
case WEAPON_SKULK_LEAP:
|
|
return UTIL_MetresToGoldSrcUnits(3.0f);
|
|
case WEAPON_MARINE_MG:
|
|
case WEAPON_MARINE_PISTOL:
|
|
case WEAPON_MARINE_HMG:
|
|
case WEAPON_SKULK_PARASITE:
|
|
return UTIL_MetresToGoldSrcUnits(5.0f);
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
return UTIL_MetresToGoldSrcUnits(2.0f);
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
case WEAPON_ONOS_STOMP:
|
|
return UTIL_MetresToGoldSrcUnits(2.0f);
|
|
default:
|
|
return max_player_use_reach * 0.5f;
|
|
}
|
|
}
|
|
|
|
bool IsMeleeWeapon(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_MARINE_KNIFE:
|
|
case WEAPON_SKULK_BITE:
|
|
case WEAPON_FADE_SWIPE:
|
|
case WEAPON_ONOS_GORE:
|
|
case WEAPON_ONOS_DEVOUR:
|
|
case WEAPON_LERK_BITE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool WeaponCanBeReloaded(const AvHAIWeapon CheckWeapon)
|
|
{
|
|
switch (CheckWeapon)
|
|
{
|
|
case WEAPON_MARINE_GL:
|
|
case WEAPON_MARINE_HMG:
|
|
case WEAPON_MARINE_MG:
|
|
case WEAPON_MARINE_PISTOL:
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
return true;
|
|
default:
|
|
return false;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
Vector UTIL_GetGrenadeThrowTarget(edict_t* Player, const Vector TargetLocation, const float ExplosionRadius, bool bPrecise)
|
|
{
|
|
if (UTIL_PlayerHasLOSToLocation(Player, TargetLocation, UTIL_MetresToGoldSrcUnits(10.0f)))
|
|
{
|
|
return TargetLocation;
|
|
}
|
|
|
|
if (UTIL_PointIsDirectlyReachable(Player->v.origin, TargetLocation))
|
|
{
|
|
Vector Orientation = UTIL_GetVectorNormal(Player->v.origin - TargetLocation);
|
|
|
|
Vector NewSpot = TargetLocation + (Orientation * UTIL_MetresToGoldSrcUnits(1.5f));
|
|
|
|
NewSpot = UTIL_ProjectPointToNavmesh(NewSpot);
|
|
|
|
if (NewSpot != ZERO_VECTOR)
|
|
{
|
|
NewSpot.z += 10.0f;
|
|
}
|
|
|
|
return NewSpot;
|
|
}
|
|
|
|
vector<bot_path_node> CheckPath;
|
|
CheckPath.clear();
|
|
|
|
dtStatus Status = FindPathClosestToPoint(GetBaseNavProfile(ALL_NAV_PROFILE), Player->v.origin, TargetLocation, CheckPath, ExplosionRadius);
|
|
|
|
if (dtStatusSucceed(Status))
|
|
{
|
|
Vector FurthestPointVisible = UTIL_GetFurthestVisiblePointOnPath(GetPlayerEyePosition(Player), CheckPath, bPrecise);
|
|
|
|
if (vDist3DSq(FurthestPointVisible, TargetLocation) <= sqrf(ExplosionRadius))
|
|
{
|
|
return FurthestPointVisible;
|
|
}
|
|
|
|
Vector ThrowDir = UTIL_GetVectorNormal(FurthestPointVisible - Player->v.origin);
|
|
|
|
Vector LineEnd = FurthestPointVisible + (ThrowDir * UTIL_MetresToGoldSrcUnits(5.0f));
|
|
|
|
Vector ClosestPointInTrajectory = vClosestPointOnLine(FurthestPointVisible, LineEnd, TargetLocation);
|
|
|
|
ClosestPointInTrajectory = UTIL_ProjectPointToNavmesh(ClosestPointInTrajectory);
|
|
ClosestPointInTrajectory.z += 10.0f;
|
|
|
|
if (vDist2DSq(ClosestPointInTrajectory, TargetLocation) < sqrf(ExplosionRadius) && UTIL_PlayerHasLOSToLocation(Player, ClosestPointInTrajectory, UTIL_MetresToGoldSrcUnits(10.0f)) && UTIL_PointIsDirectlyReachable(ClosestPointInTrajectory, TargetLocation))
|
|
{
|
|
return ClosestPointInTrajectory;
|
|
}
|
|
else
|
|
{
|
|
return ZERO_VECTOR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ZERO_VECTOR;
|
|
}
|
|
}
|
|
|
|
AvHAIWeapon BotAlienChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target)
|
|
{
|
|
if (FNullEnt(target))
|
|
{
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
|
|
return BotAlienChooseBestWeaponForStructure(pBot, target);
|
|
}
|
|
|
|
AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target)
|
|
{
|
|
|
|
if (FNullEnt(target))
|
|
{
|
|
if (IsPlayerReloading(pBot->Player))
|
|
{
|
|
return GetPlayerCurrentWeapon(pBot->Player);
|
|
}
|
|
|
|
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)
|
|
{
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0)
|
|
{
|
|
return GetBotMarineSecondaryWeapon(pBot);
|
|
}
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
|
|
if (IsEdictPlayer(target))
|
|
{
|
|
return MarineGetBestWeaponForPlayerTarget(pBot, dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(target)));
|
|
}
|
|
else
|
|
{
|
|
return BotMarineChooseBestWeaponForStructure(pBot, target);
|
|
}
|
|
}
|
|
|
|
bool BotAnyWeaponNeedsReloading(AvHAIPlayer* pBot)
|
|
{
|
|
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { return true; }
|
|
if (UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerSecondaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) > 0) { return true; }
|
|
|
|
return false;
|
|
}
|
|
|
|
AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target)
|
|
{
|
|
AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(target);
|
|
|
|
if (StructureType == STRUCTURE_NONE)
|
|
{
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
|
|
{
|
|
return WEAPON_GORGE_BILEBOMB;
|
|
}
|
|
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET) && (StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType)))
|
|
{
|
|
return WEAPON_FADE_ACIDROCKET;
|
|
}
|
|
|
|
// If we have xenocide, then choose it if we have lots of good targets in blast radius
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_SKULK_XENOCIDE))
|
|
{
|
|
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
|
|
|
int NumEnemyTargetsInArea = AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, target->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, nullptr, AVH_USER3_NONE);
|
|
|
|
AvHTeam* EnemyTeamRef = GetGameRules()->GetTeam(EnemyTeam);
|
|
|
|
if (EnemyTeamRef)
|
|
{
|
|
AvHAIDeployableStructureType StructureSearchType = (EnemyTeamRef->GetTeamType() == AVH_CLASS_TYPE_MARINE) ? SEARCH_ALL_MARINE_STRUCTURES : SEARCH_ALL_ALIEN_STRUCTURES;
|
|
|
|
DeployableSearchFilter SearchFilter;
|
|
SearchFilter.DeployableTypes = StructureSearchType;
|
|
SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
|
SearchFilter.DeployableTeam = EnemyTeam;
|
|
SearchFilter.ReachabilityTeam = TEAM_IND;
|
|
SearchFilter.ReachabilityFlags = AI_REACHABILITY_NONE;
|
|
|
|
NumEnemyTargetsInArea += AITAC_GetNumDeployablesNearLocation(target->v.origin, &SearchFilter);
|
|
}
|
|
|
|
if (NumEnemyTargetsInArea > 2)
|
|
{
|
|
return WEAPON_SKULK_XENOCIDE;
|
|
}
|
|
}
|
|
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
|
|
AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target)
|
|
{
|
|
AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(target);
|
|
|
|
if (StructureType == STRUCTURE_NONE || StructureType == STRUCTURE_ALIEN_HIVE || IsDamagingStructure(StructureType) || UTIL_IsStructureElectrified(target))
|
|
{
|
|
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)
|
|
{
|
|
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
}
|
|
else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0)
|
|
{
|
|
return GetBotMarineSecondaryWeapon(pBot);
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
}
|
|
|
|
AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
|
|
if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0))
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
|
|
AvHAIWeapon MarineGetBestWeaponForPlayerTarget(AvHAIPlayer* pBot, AvHPlayer* Target)
|
|
{
|
|
AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
AvHAIWeapon SecondaryWeapon = UTIL_GetPlayerSecondaryWeapon(pBot->Player);
|
|
AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player);
|
|
|
|
float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, Target->pev->origin);
|
|
|
|
bool bHasAmmoForPrimary = (PrimaryWeapon != WEAPON_INVALID && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0);
|
|
bool bHasAmmoForSecondary = (SecondaryWeapon != WEAPON_INVALID && UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) > 0);
|
|
|
|
if (PrimaryWeapon != WEAPON_INVALID && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)
|
|
{
|
|
if (PrimaryWeapon == WEAPON_MARINE_GL)
|
|
{
|
|
if (DistToEnemy > sqrf(BALANCE_VAR(kGrenadeRadius)) || !bHasAmmoForSecondary)
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
if (bHasAmmoForSecondary)
|
|
{
|
|
return SecondaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
}
|
|
}
|
|
else if (PrimaryWeapon == WEAPON_MARINE_SHOTGUN)
|
|
{
|
|
float MaxDist = (IsPlayerMarine(Target) || Target->GetUser3() > AVH_USER3_ALIEN_PLAYER3) ? UTIL_MetresToGoldSrcUnits(15.0f) : UTIL_MetresToGoldSrcUnits(8.0f);
|
|
|
|
// Give a little extra leeway if the bot is currently holding a shotgun. Helps prevent rapid switching if the enemy is right on the edge of the max distance
|
|
if (CurrentWeapon == PrimaryWeapon)
|
|
{
|
|
MaxDist *= 1.25f;
|
|
}
|
|
|
|
if (DistToEnemy < sqrf(MaxDist) || !bHasAmmoForSecondary)
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
if (bHasAmmoForSecondary)
|
|
{
|
|
return SecondaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
}
|
|
|
|
bool bEnemyIsRanged = IsPlayerMarine(Target) || ((GetPlayerCurrentWeapon(Target) == WEAPON_FADE_ACIDROCKET || GetPlayerCurrentWeapon(Target) == WEAPON_LERK_SPORES) && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)));
|
|
|
|
if (bEnemyIsRanged)
|
|
{
|
|
if (bHasAmmoForSecondary)
|
|
{
|
|
return SecondaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
}
|
|
|
|
if (DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
|
|
{
|
|
if (bHasAmmoForPrimary)
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
else if (bHasAmmoForSecondary)
|
|
{
|
|
return SecondaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
}
|
|
|
|
if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)
|
|
{
|
|
return PrimaryWeapon;
|
|
}
|
|
else if (UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) > 0)
|
|
{
|
|
return SecondaryWeapon;
|
|
}
|
|
else
|
|
{
|
|
return WEAPON_MARINE_KNIFE;
|
|
}
|
|
|
|
}
|
|
|
|
AvHAIWeapon GorgeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|
{
|
|
// Apparently I only imagined bile bomb doing damage to marine armour. Leaving it commented out in case we want to enable it again in future
|
|
/*if (Target->v.armorvalue > 0.0f && PlayerHasWeapon(pBot->Edict, WEAPON_GORGE_BILEBOMB) && vDist2DSq(pBot->Edict->v.origin, Target->v.origin) < sqrf(GetMaxIdealWeaponRange(WEAPON_GORGE_BILEBOMB)))
|
|
{
|
|
return WEAPON_GORGE_BILEBOMB;
|
|
}*/
|
|
|
|
return WEAPON_GORGE_SPIT;
|
|
}
|
|
|
|
AvHAIWeapon SkulkGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|
{
|
|
if (FNullEnt(Target) || IsPlayerDead(Target))
|
|
{
|
|
return WEAPON_SKULK_BITE;
|
|
}
|
|
|
|
// If we have xenocide, then choose it if we have lots of good targets in blast radius
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_SKULK_XENOCIDE))
|
|
{
|
|
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
|
float XenocideRadius = GetMaxIdealWeaponRange(WEAPON_SKULK_XENOCIDE);
|
|
|
|
// Add one to include the target themselves
|
|
int NumEnemyTargetsInArea = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, Target->v.origin, XenocideRadius, Target) + 1;
|
|
|
|
if (NumEnemyTargetsInArea <= 2)
|
|
{
|
|
|
|
AvHTeam* EnemyTeamRef = GetGameRules()->GetTeam(EnemyTeam);
|
|
|
|
if (EnemyTeamRef)
|
|
{
|
|
AvHAIDeployableStructureType StructureSearchType = (EnemyTeamRef->GetTeamType() == AVH_CLASS_TYPE_MARINE) ? SEARCH_ALL_MARINE_STRUCTURES : SEARCH_ALL_ALIEN_STRUCTURES;
|
|
|
|
DeployableSearchFilter SearchFilter;
|
|
SearchFilter.DeployableTypes = StructureSearchType;
|
|
SearchFilter.MaxSearchRadius = XenocideRadius;
|
|
SearchFilter.DeployableTeam = EnemyTeam;
|
|
|
|
NumEnemyTargetsInArea += AITAC_GetNumDeployablesNearLocation(Target->v.origin, &SearchFilter);
|
|
}
|
|
}
|
|
|
|
if (NumEnemyTargetsInArea > 2)
|
|
{
|
|
return WEAPON_SKULK_XENOCIDE;
|
|
}
|
|
}
|
|
|
|
if (!IsPlayerParasited(Target))
|
|
{
|
|
float DistFromTarget = vDist2DSq(pBot->Edict->v.origin, Target->v.origin);
|
|
|
|
if (DistFromTarget >= sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
|
|
{
|
|
Vector EnemyFacing = UTIL_GetForwardVector2D(Target->v.angles);
|
|
Vector BotFacing = UTIL_GetVectorNormal2D(Target->v.origin - pBot->Edict->v.origin);
|
|
|
|
float Dot = UTIL_GetDotProduct2D(EnemyFacing, BotFacing);
|
|
|
|
// Only use parasite if the enemy is facing towards us. Means we don't ruin the element of surprise if sneaking up on an enemy
|
|
if (Dot < 0.0f)
|
|
{
|
|
return WEAPON_SKULK_PARASITE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return WEAPON_SKULK_BITE;
|
|
|
|
}
|
|
|
|
AvHAIWeapon LerkGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|
{
|
|
if (!IsPlayerBuffed(pBot->Edict) && PlayerHasWeapon(pBot->Player, WEAPON_LERK_PRIMALSCREAM) && GetPlayerEnergy(pBot->Edict) > (GetEnergyCostForWeapon(WEAPON_LERK_PRIMALSCREAM) * 1.25f))
|
|
{
|
|
int NumAllies = AITAC_GetNumPlayersOnTeamWithLOS(pBot->Player->GetTeam(), Target->v.origin, UTIL_MetresToGoldSrcUnits(15.0f), pBot->Edict);
|
|
|
|
if (NumAllies > 0)
|
|
{
|
|
return WEAPON_LERK_PRIMALSCREAM;
|
|
}
|
|
}
|
|
|
|
float DistFromEnemy = vDist2DSq(pBot->Edict->v.origin, Target->v.origin);
|
|
|
|
if (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && PlayerHasWeapon(pBot->Player, WEAPON_LERK_UMBRA) && GetPlayerEnergy(pBot->Edict) > (GetEnergyCostForWeapon(WEAPON_LERK_UMBRA) * 1.25f))
|
|
{
|
|
int NumAllies = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Target->v.origin, BALANCE_VAR(kUmbraCloudRadius), false, pBot->Edict, AVH_USER3_NONE);
|
|
|
|
if (NumAllies > 0)
|
|
{
|
|
return WEAPON_LERK_UMBRA;
|
|
}
|
|
}
|
|
|
|
if (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && GetPlayerEnergy(pBot->Edict) > GetEnergyCostForWeapon(WEAPON_LERK_SPORES) && !IsAreaAffectedBySpores(Target->v.origin))
|
|
{
|
|
return WEAPON_LERK_SPORES;
|
|
}
|
|
|
|
return WEAPON_LERK_BITE;
|
|
}
|
|
|
|
AvHAIWeapon OnosGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|
{
|
|
if (FNullEnt(Target) || IsPlayerDead(Target))
|
|
{
|
|
return WEAPON_ONOS_GORE;
|
|
}
|
|
|
|
float DistFromTarget = vDist2DSq(pBot->Edict->v.origin, Target->v.origin);
|
|
|
|
if (DistFromTarget > sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))
|
|
{
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_ONOS_CHARGE) && UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, Target->v.origin))
|
|
{
|
|
return WEAPON_ONOS_CHARGE;
|
|
}
|
|
|
|
return WEAPON_ONOS_GORE;
|
|
}
|
|
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_ONOS_STOMP) && !IsPlayerStunned(Target) && DistFromTarget > sqrf(UTIL_MetresToGoldSrcUnits(2.0f)) && DistFromTarget < sqrf(UTIL_MetresToGoldSrcUnits(8.0f)))
|
|
{
|
|
return WEAPON_ONOS_STOMP;
|
|
}
|
|
|
|
AvHAIWeapon AttackWeapon = WEAPON_ONOS_GORE;
|
|
|
|
if (!IsPlayerDigesting(pBot->Edict))
|
|
{
|
|
AttackWeapon = WEAPON_ONOS_DEVOUR;
|
|
}
|
|
|
|
float AttackWeaponRange = GetMaxIdealWeaponRange(AttackWeapon);
|
|
|
|
BotAttackResult WeaponAttackResult = PerformAttackLOSCheck(pBot, AttackWeapon, Target);
|
|
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_ONOS_CHARGE) && UTIL_PointIsDirectlyReachable(pBot->Edict->v.origin, Target->v.origin) && WeaponAttackResult == ATTACK_OUTOFRANGE)
|
|
{
|
|
return WEAPON_ONOS_CHARGE;
|
|
}
|
|
|
|
return AttackWeapon;
|
|
}
|
|
|
|
AvHAIWeapon FadeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target)
|
|
{
|
|
if (FNullEnt(Target) || IsPlayerDead(Target))
|
|
{
|
|
return WEAPON_FADE_SWIPE;
|
|
}
|
|
|
|
if (!PlayerHasWeapon(pBot->Player, WEAPON_FADE_ACIDROCKET))
|
|
{
|
|
return WEAPON_FADE_SWIPE;
|
|
}
|
|
|
|
float DistFromTarget = vDist2DSq(pBot->Edict->v.origin, Target->v.origin);
|
|
|
|
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
|
|
|
int NumEnemyAllies = AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, Target->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, nullptr, AVH_USER3_NONE);
|
|
|
|
if (NumEnemyAllies > 2)
|
|
{
|
|
return WEAPON_FADE_ACIDROCKET;
|
|
}
|
|
|
|
AvHPlayer* EnemyPlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(Target));
|
|
|
|
if (EnemyPlayerRef && PlayerHasWeapon(EnemyPlayerRef, WEAPON_MARINE_SHOTGUN))
|
|
{
|
|
if (DistFromTarget > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)))
|
|
{
|
|
return WEAPON_FADE_ACIDROCKET;
|
|
}
|
|
}
|
|
|
|
return WEAPON_FADE_SWIPE;
|
|
|
|
}
|
|
|
|
void BotReloadCurrentWeapon(AvHAIPlayer* pBot)
|
|
{
|
|
AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player);
|
|
|
|
if (!WeaponCanBeReloaded(CurrentWeapon)) { return; }
|
|
|
|
if (!IsPlayerReloading(pBot->Player))
|
|
{
|
|
if (gpGlobals->time - pBot->LastUseTime > 1.0f)
|
|
{
|
|
pBot->Button |= IN_RELOAD;
|
|
pBot->LastUseTime = gpGlobals->time;
|
|
}
|
|
}
|
|
}
|
|
|
|
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const edict_t* Target)
|
|
{
|
|
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return ATTACK_INVALIDTARGET; }
|
|
|
|
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
|
|
|
// Don't need aiming or special LOS checks for primal scream as it's AoE buff
|
|
if (Weapon == WEAPON_LERK_PRIMALSCREAM)
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
// Add a LITTLE bit of give to avoid edge cases where the bot is a smidge out of range
|
|
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon) - 5.0f;
|
|
|
|
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
|
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, Target->v.origin) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, Target->v.origin))
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
}
|
|
|
|
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
|
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, Target->v.origin) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, Target->v.origin) || fabsf(Target->v.origin.z - Target->v.origin.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
TraceResult hit;
|
|
|
|
Vector StartTrace = pBot->CurrentEyePosition;
|
|
|
|
Vector AttackDir = UTIL_GetVectorNormal(UTIL_GetCentreOfEntity(Target) - StartTrace);
|
|
|
|
Vector EndTrace = pBot->CurrentEyePosition + (AttackDir * MaxWeaponRange);
|
|
|
|
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &hit);
|
|
|
|
if (FNullEnt(hit.pHit)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (hit.pHit != Target)
|
|
{
|
|
if (vDist3DSq(pBot->CurrentEyePosition, Target->v.origin) > sqrf(MaxWeaponRange))
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_BLOCKED;
|
|
}
|
|
}
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const Vector TargetLocation)
|
|
{
|
|
if (!TargetLocation) { return ATTACK_INVALIDTARGET; }
|
|
|
|
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
|
|
|
// Don't need aiming or special LOS checks for primal scream as it's AoE buff
|
|
if (Weapon == WEAPON_LERK_PRIMALSCREAM)
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
// Add a LITTLE bit of give to avoid edge cases where the bot is a smidge out of range
|
|
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon) - 5.0f;
|
|
|
|
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
|
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation))
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
}
|
|
|
|
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
|
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation) || fabsf(TargetLocation.z - TargetLocation.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
TraceResult hit;
|
|
|
|
Vector StartTrace = pBot->CurrentEyePosition;
|
|
|
|
Vector AttackDir = UTIL_GetVectorNormal(TargetLocation - StartTrace);
|
|
|
|
Vector EndTrace = pBot->CurrentEyePosition + (AttackDir * MaxWeaponRange);
|
|
|
|
if (vDist3DSq(StartTrace, EndTrace) < vDist3DSq(StartTrace, TargetLocation)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &hit);
|
|
|
|
return (hit.flFraction >= 1.0f) ? ATTACK_SUCCESS : ATTACK_BLOCKED;
|
|
}
|
|
|
|
BotAttackResult PerformAttackLOSCheck(AvHAIPlayer* pBot, const AvHAIWeapon Weapon, const Vector TargetLocation, const edict_t* Target)
|
|
{
|
|
if (!TargetLocation) { return ATTACK_INVALIDTARGET; }
|
|
|
|
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
|
|
|
// Don't need aiming or special LOS checks for primal scream as it's AoE buff
|
|
if (Weapon == WEAPON_LERK_PRIMALSCREAM)
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
// Add a LITTLE bit of give to avoid edge cases where the bot is a smidge out of range
|
|
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon) - 5.0f;
|
|
|
|
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
|
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation))
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
}
|
|
|
|
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
|
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
|
{
|
|
if (vDist3DSq(pBot->Edict->v.origin, TargetLocation) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, TargetLocation) || fabsf(TargetLocation.z - TargetLocation.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
TraceResult hit;
|
|
|
|
Vector StartTrace = pBot->CurrentEyePosition;
|
|
|
|
Vector AttackDir = UTIL_GetVectorNormal(TargetLocation - StartTrace);
|
|
|
|
Vector EndTrace = pBot->CurrentEyePosition + (AttackDir * MaxWeaponRange);
|
|
|
|
if (vDist3DSq(StartTrace, EndTrace) < vDist3DSq(StartTrace, TargetLocation)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &hit);
|
|
|
|
return (hit.flFraction >= 1.0f || hit.pHit == Target) ? ATTACK_SUCCESS : ATTACK_BLOCKED;
|
|
|
|
}
|
|
|
|
BotAttackResult PerformAttackLOSCheck(const Vector Location, const AvHAIWeapon Weapon, const edict_t* Target)
|
|
{
|
|
if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return ATTACK_INVALIDTARGET; }
|
|
|
|
if (Weapon == WEAPON_NONE) { return ATTACK_NOWEAPON; }
|
|
|
|
float MaxWeaponRange = GetMaxIdealWeaponRange(Weapon);
|
|
|
|
// Don't need aiming or special LOS checks for Xenocide as it's an AOE attack, just make sure we're close enough and don't have a wall in the way
|
|
if (Weapon == WEAPON_SKULK_XENOCIDE)
|
|
{
|
|
if (vDist3DSq(Location, Target->v.origin) <= sqrf(MaxWeaponRange) && UTIL_QuickTrace(nullptr, Location, Target->v.origin))
|
|
{
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
}
|
|
|
|
// For charge and stomp, we can go through stuff so don't need to check for being blocked
|
|
if (Weapon == WEAPON_ONOS_CHARGE || Weapon == WEAPON_ONOS_STOMP)
|
|
{
|
|
if (vDist3DSq(Location, Target->v.origin) > sqrf(MaxWeaponRange)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (!UTIL_QuickTrace(nullptr, Location, Target->v.origin) || fabsf(Target->v.origin.z - Target->v.origin.z) > 50.0f) { return ATTACK_OUTOFRANGE; }
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
bool bIsMeleeWeapon = IsMeleeWeapon(Weapon);
|
|
|
|
TraceResult hit;
|
|
|
|
Vector StartTrace = Location;
|
|
|
|
Vector AttackDir = UTIL_GetVectorNormal(UTIL_GetCentreOfEntity(Target) - StartTrace);
|
|
|
|
Vector EndTrace = Location + (AttackDir * MaxWeaponRange);
|
|
|
|
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, nullptr, &hit);
|
|
|
|
if (FNullEnt(hit.pHit)) { return ATTACK_OUTOFRANGE; }
|
|
|
|
if (hit.pHit != Target)
|
|
{
|
|
if (vDist3DSq(Location, Target->v.origin) > sqrf(MaxWeaponRange))
|
|
{
|
|
return ATTACK_OUTOFRANGE;
|
|
}
|
|
else
|
|
{
|
|
return ATTACK_BLOCKED;
|
|
}
|
|
}
|
|
|
|
return ATTACK_SUCCESS;
|
|
}
|
|
|
|
|
|
bool IsAreaAffectedBySpores(const Vector Location)
|
|
{
|
|
bool Result = false;
|
|
|
|
FOR_ALL_ENTITIES(kwsSporeProjectile, AvHSporeProjectile*)
|
|
|
|
if (vDist2DSq(theEntity->pev->origin, Location) <= BALANCE_VAR(kSporeCloudRadius))
|
|
{
|
|
Result = true;
|
|
break;
|
|
}
|
|
|
|
END_FOR_ALL_ENTITIES(kwsSporeProjectile)
|
|
|
|
return Result;
|
|
}
|
|
|
|
float UTIL_GetProjectileVelocityForWeapon(const AvHAIWeapon Weapon)
|
|
{
|
|
switch (Weapon)
|
|
{
|
|
case WEAPON_GORGE_SPIT:
|
|
return (float)kSpitVelocity;
|
|
case WEAPON_LERK_SPORES:
|
|
return (float)kShootCloudVelocity;
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
return (float)kAcidRocketVelocity;
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
return (float)kBileBombVelocity;
|
|
case WEAPON_LERK_SPIKE:
|
|
return (float)kSpikeVelocity;
|
|
case WEAPON_MARINE_GRENADE:
|
|
case WEAPON_MARINE_GL:
|
|
return (float)BALANCE_VAR(kGrenadeForce);
|
|
default:
|
|
return 0.0f; // Hitscan.
|
|
}
|
|
}
|
|
|
|
char* UTIL_WeaponTypeToClassname(const AvHAIWeapon WeaponType)
|
|
{
|
|
switch (WeaponType)
|
|
{
|
|
case WEAPON_MARINE_MG:
|
|
return kwsMachineGun;
|
|
case WEAPON_MARINE_PISTOL:
|
|
return kwsPistol;
|
|
case WEAPON_MARINE_KNIFE:
|
|
return kwsKnife;
|
|
case WEAPON_MARINE_SHOTGUN:
|
|
return kwsShotGun;
|
|
case WEAPON_MARINE_HMG:
|
|
return kwsHeavyMachineGun;
|
|
case WEAPON_MARINE_WELDER:
|
|
return kwsWelder;
|
|
case WEAPON_MARINE_MINES:
|
|
return kwsMine;
|
|
case WEAPON_MARINE_GRENADE:
|
|
return kwsGrenade;
|
|
case WEAPON_MARINE_GL:
|
|
return kwsGrenadeGun;
|
|
|
|
case WEAPON_SKULK_BITE:
|
|
return kwsBiteGun;
|
|
case WEAPON_SKULK_PARASITE:
|
|
return kwsParasiteGun;
|
|
case WEAPON_SKULK_LEAP:
|
|
return kwsLeap;
|
|
case WEAPON_SKULK_XENOCIDE:
|
|
return kwsDivineWind;
|
|
|
|
case WEAPON_GORGE_SPIT:
|
|
return kwsSpitGun;
|
|
case WEAPON_GORGE_HEALINGSPRAY:
|
|
return kwsHealingSpray;
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
return kwsBileBombGun;
|
|
case WEAPON_GORGE_WEB:
|
|
return kwsWebSpinner;
|
|
|
|
case WEAPON_LERK_BITE:
|
|
return kwsBite2Gun;
|
|
case WEAPON_LERK_SPORES:
|
|
return kwsSporeGun;
|
|
case WEAPON_LERK_UMBRA:
|
|
return kwsUmbraGun;
|
|
case WEAPON_LERK_PRIMALSCREAM:
|
|
return kwsPrimalScream;
|
|
|
|
case WEAPON_FADE_SWIPE:
|
|
return kwsSwipe;
|
|
case WEAPON_FADE_BLINK:
|
|
return kwsBlinkGun;
|
|
case WEAPON_FADE_METABOLIZE:
|
|
return kwsMetabolize;
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
return kwsAcidRocketGun;
|
|
|
|
case WEAPON_ONOS_GORE:
|
|
return kwsClaws;
|
|
case WEAPON_ONOS_DEVOUR:
|
|
return kwsDevour;
|
|
case WEAPON_ONOS_STOMP:
|
|
return kwsStomp;
|
|
case WEAPON_ONOS_CHARGE:
|
|
return kwsCharge;
|
|
default:
|
|
return "";
|
|
}
|
|
|
|
return "";
|
|
} |