mirror of
https://github.com/ENSL/NS.git
synced 2024-11-22 20:51:35 +00:00
58358d0927
* Initial bot commit * Added server commands and cvars for adding AI players to the game. * Added auto modes for automating the adding and removal of bots * Bots connect to the server and join teams correctly * Added round restart and new map detection for AI system Push before new project added for detour * Initial bot integration * Integrated all basic bot code for navigation and task performing * Added support for multi_managers to better understand how buttons and triggers affect doors * Improved bot understanding of door triggers and weldables * Reworked nav profiles Nav profiles for bots are now dynamically updated to take into account changing capabilities, such as picking up a welder * Improved bot door usage * Added weldable obstacles back into navigation Bots now understand how to get around weldable barriers * Replaced fixed arrays with vectors * Resource node and hive lists are now vectors. * Further improved bot weld behaviour * Added dynamic reachability calculations When barriers and doors are open/closed, new reachability calculations are done for structures and items so bots understand when items/structures become reachable or unreachable as the match progresses. * Added team-based reachability calculations Reachabilities for structures and items are now based on the team, so bots understand when they can't reach a structure from their spawn point. * Implemented long-range off-mesh connections and dynamic off-mesh connections * Implemented fully dynamic off-mesh connections Phase gates now use connections rather than custom path finding. Much more performant. * Replaced arrays with vectors for simpler code * Started Bot Swimming * Bots understand trigger_changetarget Bots can now navigate doors operated with a trigger_changetarget so they understand the sequence in which triggers must be activated to make it work * Push before trying to fix long-range connections * Implement new off-mesh connection system * Redid population of door triggers * Fixed trigger types and links to doors * Added lift and moving platform support * Lift improvements * Bots avoid getting crushed under a lift when summoning it * Bots are better at judging which stop a platform needs to be at * Tweak lift and welder usage * Fixed bug with multiple off-mesh connections close together * Finish lift movement * Fixed dodgy path finding * Improved skulk ladder usage and lerk lift usage * Fix crash with path finding * Re-implement commander AI * Commander improvements * Improve commander sieging * Commander scanning tweak * Reimplemented regular marine AI * Start reimplementing alien AI * Implement gorge building behaviours * Start alien tactical decisioning * Continuing alien building and other non-combat logic * More alien role work * Adjusted base node definitions * Iterate Capper Logic * Alien assault AI * Alien Combat * Fix grenade throwing, better combat * Marine combat AI improvements * Commander improvements * Commander + nav improvements * Drop mines * Improved bot stuck detection * Commander supply improvements * Bot fill timing config * Added nsbots.cfg to configure internal bots * Changed bot config file to "nsbots.cfg" * Bug fixing with navigation * Fix skulk movement on ladders * Improved commander placement and tactical refresh * Fixed bug with ladder climbing * Doors block off-mesh connections * Finished doors blocking connections * Marine and alien tactical bug fixes * Add commander beacon back in * Start combat mode stuff * First pass at combat mode * Bots attack turrets * Fix ladder and wall climbing * Commander chat request * Improved skulk ladders * Added nav meshes for new bot code * Added bot configuration to listen server menu * Added bot config file * Added default bot config to listenserver.cfg * Added default bot settings to server.cfg * Include VS filter for bot files * Crash fixes * Bot improvements * Bot stability and mine placement improvements * Fixed crash on new map start with bots * Reverted Svencoop fix * Fixed crash, added more cvars * Performance improvement * Commander building improvements * Stop bot spasming when waiting to take command * Fixed doors not blocking connections * Added bot disabled guard to round start * Commander improvements, movement improvements * Tweaked level load sequence * Performance improvements * Bot load spread * Fixed commander update * Refactor bot frame handling * Bug fixes + Pierow's dynamic load spread * Minor bug fixes * Fix door detection, prep for test * Fixed commander siege spam * linux compile test * fix hardcoded inlcudes * O1 compile flag for detour - fix linux server crash * Revert detour compile flags to original for windows * linux build update * remove x64 build configs * update bot nav meshes and configs * fix bot physics at high server fps, update navmeshes. from @RGreenlees --------- Co-authored-by: RGreenlees <RGreenlees@users.noreply.github.com> Co-authored-by: RichardGreenlees <richard.greenlees@forecast.global>
1262 lines
No EOL
31 KiB
C++
1262 lines
No EOL
31 KiB
C++
|
|
#include "AvHAIPlayerUtil.h"
|
|
#include "AvHAIPlayer.h"
|
|
#include "AvHAIHelper.h"
|
|
|
|
#include "AvHPlayerUpgrade.h"
|
|
#include "AvHAIMath.h"
|
|
#include "AvHGamerules.h"
|
|
#include "../pm_shared/pm_shared.h"
|
|
#include "../pm_shared/pm_defs.h"
|
|
|
|
#include <cfloat>
|
|
|
|
bool IsPlayerSkulk(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_ALIEN_PLAYER1);
|
|
}
|
|
|
|
bool IsPlayerGorge(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_ALIEN_PLAYER2);
|
|
}
|
|
|
|
bool IsPlayerLerk(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_ALIEN_PLAYER3);
|
|
}
|
|
|
|
bool IsPlayerFade(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_ALIEN_PLAYER4);
|
|
}
|
|
|
|
bool IsPlayerOnos(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_ALIEN_PLAYER5);
|
|
}
|
|
|
|
bool IsPlayerMarine(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_MARINE_PLAYER);
|
|
}
|
|
|
|
bool IsPlayerMarine(const AvHPlayer* Player)
|
|
{
|
|
if (!Player) { return false; }
|
|
return (Player->GetUser3() == AVH_USER3_MARINE_PLAYER);
|
|
}
|
|
|
|
bool IsPlayerAlien(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 != AVH_USER3_MARINE_PLAYER && Player->v.iuser3 != AVH_USER3_COMMANDER_PLAYER);
|
|
}
|
|
|
|
bool IsPlayerCommander(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser3 == AVH_USER3_COMMANDER_PLAYER);
|
|
}
|
|
|
|
bool IsPlayerClimbingWall(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (IsPlayerSkulk(Player) && (Player->v.iuser4 & MASK_WALLSTICKING));
|
|
}
|
|
|
|
bool IsPlayerInReadyRoom(const edict_t* Player)
|
|
{
|
|
return Player->v.playerclass == PLAYMODE_READYROOM;
|
|
}
|
|
|
|
bool IsPlayerActiveInGame(const edict_t* Player)
|
|
{
|
|
return !IsPlayerInReadyRoom(Player) && Player->v.team != 0 && !IsPlayerSpectator(Player) && !IsPlayerDead(Player) && !IsPlayerBeingDigested(Player) && !IsPlayerCommander(Player);
|
|
}
|
|
|
|
bool IsPlayerHuman(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (!(Player->v.flags & FL_FAKECLIENT));
|
|
}
|
|
|
|
bool IsPlayerBot(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.flags & FL_FAKECLIENT);
|
|
}
|
|
|
|
bool IsPlayerDead(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return true; }
|
|
return (Player->v.deadflag != DEAD_NO || Player->v.health <= 0.0f);
|
|
}
|
|
|
|
bool IsPlayerStunned(const edict_t* Player)
|
|
{
|
|
return !FNullEnt(Player) && !IsPlayerDead(Player) && !IsPlayerDigesting(Player) && (Player->v.iuser4 & MASK_PLAYER_STUNNED);
|
|
}
|
|
|
|
bool IsPlayerSpectator(const edict_t* Player)
|
|
{
|
|
return !FNullEnt(Player) && (Player->v.playerclass == PLAYMODE_OBSERVER);
|
|
}
|
|
|
|
bool IsPlayerBeingDigested(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_DIGESTING && Player->v.effects & EF_NODRAW);
|
|
}
|
|
|
|
bool IsPlayerDigesting(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_DIGESTING && !(Player->v.effects & EF_NODRAW));
|
|
}
|
|
|
|
bool IsPlayerGestating(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_ALIEN_EMBRYO);
|
|
}
|
|
|
|
bool IsPlayerCharging(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_ALIEN_MOVEMENT);
|
|
}
|
|
|
|
bool IsPlayerBuffed(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_BUFFED);
|
|
}
|
|
|
|
bool IsPlayerOnLadder(const edict_t* Player)
|
|
{
|
|
if (IsPlayerSkulk(Player))
|
|
{
|
|
edict_t* NearestLadder = UTIL_GetNearestLadderAtPoint(Player->v.origin);
|
|
|
|
if (FNullEnt(NearestLadder)) { return false; }
|
|
|
|
Vector NearestPointOnLadder = UTIL_GetClosestPointOnEntityToLocation(Player->v.origin, NearestLadder);
|
|
Vector NearestPointOnPlayer = UTIL_GetClosestPointOnEntityToLocation(NearestPointOnLadder, Player);
|
|
|
|
return (vDist2DSq(NearestPointOnLadder, NearestPointOnPlayer) <= sqrf(4.0f));
|
|
}
|
|
|
|
return (Player->v.movetype == MOVETYPE_FLY);
|
|
}
|
|
|
|
bool IsPlayerParasited(const edict_t* Player)
|
|
{
|
|
return (Player->v.iuser4 & MASK_PARASITED);
|
|
}
|
|
|
|
bool IsPlayerMotionTracked(const edict_t* Player)
|
|
{
|
|
return (Player->v.iuser4 & MASK_VIS_DETECTED);
|
|
}
|
|
|
|
float GetPlayerEnergy(const edict_t* Player)
|
|
{
|
|
return (Player->v.fuser3 * 0.001f);
|
|
}
|
|
|
|
int GetPlayerMaxArmour(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return 0; }
|
|
|
|
return AvHPlayerUpgrade::GetMaxArmorLevel(Player->v.iuser4, (AvHUser3)Player->v.iuser3);
|
|
|
|
}
|
|
|
|
int GetPlayerResources(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return 0; }
|
|
|
|
return (int)ceil(Player->v.vuser4.z / kNumericNetworkConstant);
|
|
}
|
|
|
|
int GetPlayerCombatExperience(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return 0; }
|
|
|
|
return (int)ceil(Player->v.vuser4.z / kNumericNetworkConstant);
|
|
}
|
|
|
|
int GetPlayerCombatLevel(const AvHPlayer* Player)
|
|
{
|
|
if (!Player) { return 0; }
|
|
|
|
return Player->GetExperienceLevel();
|
|
}
|
|
|
|
float GetPlayerRadius(const AvHPlayer* Player)
|
|
{
|
|
if (!Player) { return 0.0f; }
|
|
|
|
int hullnum = GetPlayerHullIndex(ENT(Player->pev));
|
|
|
|
switch (hullnum)
|
|
{
|
|
case human_hull:
|
|
case head_hull:
|
|
return 16.0f;
|
|
break;
|
|
case large_hull:
|
|
return 32.0f;
|
|
break;
|
|
default:
|
|
return 16.0f;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
float GetPlayerRadius(const edict_t* Player)
|
|
{
|
|
if (!Player) { return 0.0f; }
|
|
|
|
int hullnum = GetPlayerHullIndex(Player);
|
|
|
|
switch (hullnum)
|
|
{
|
|
case human_hull:
|
|
case head_hull:
|
|
return 16.0f;
|
|
break;
|
|
case large_hull:
|
|
return 32.0f;
|
|
break;
|
|
default:
|
|
return 16.0f;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
bool CanPlayerCrouch(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player) || Player->free || !IsEdictPlayer(Player)) { return false; }
|
|
|
|
switch (Player->v.iuser3)
|
|
{
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int GetPlayerHullIndex(const edict_t* Player, const bool bIsCrouching)
|
|
{
|
|
if (!Player) { return 0; }
|
|
|
|
AvHUser3 PlayerClass = (AvHUser3)Player->v.iuser3;
|
|
|
|
switch (PlayerClass)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
case AVH_USER3_ALIEN_PLAYER4: // Fade
|
|
return (bIsCrouching) ? head_hull : human_hull;
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_EMBRYO: // Gestating
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_PLAYER1: // Skulk
|
|
case AVH_USER3_ALIEN_PLAYER2: // Gorge
|
|
case AVH_USER3_ALIEN_PLAYER3:// Lerk
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_PLAYER5: // Onos
|
|
return (bIsCrouching) ? human_hull : large_hull;
|
|
default:
|
|
return head_hull;
|
|
}
|
|
|
|
return head_hull;
|
|
}
|
|
|
|
int GetPlayerHullIndex(const edict_t* Player)
|
|
{
|
|
if (!Player) { return 0; }
|
|
|
|
AvHUser3 PlayerClass = (AvHUser3)Player->v.iuser3;
|
|
|
|
bool bIsCrouching = (Player->v.flags & FL_DUCKING);
|
|
|
|
switch (PlayerClass)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
case AVH_USER3_ALIEN_PLAYER4: // Fade
|
|
return (bIsCrouching) ? head_hull : human_hull;
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_EMBRYO: // Gestating
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_PLAYER1: // Skulk
|
|
case AVH_USER3_ALIEN_PLAYER2: // Gorge
|
|
case AVH_USER3_ALIEN_PLAYER3:// Lerk
|
|
return head_hull;
|
|
case AVH_USER3_ALIEN_PLAYER5: // Onos
|
|
return (bIsCrouching) ? human_hull : large_hull;
|
|
default:
|
|
return head_hull;
|
|
}
|
|
|
|
return head_hull;
|
|
}
|
|
|
|
float GetPlayerEnergyRegenPerSecond(edict_t* Player)
|
|
{
|
|
int AdrenalineLevel = 0;
|
|
|
|
if (Player->v.iuser4 & MASK_UPGRADE_5)
|
|
{
|
|
AdrenalineLevel = 1;
|
|
|
|
if (Player->v.iuser4 & MASK_UPGRADE_13)
|
|
{
|
|
AdrenalineLevel = 3;
|
|
}
|
|
else if (Player->v.iuser4 & MASK_UPGRADE_12)
|
|
{
|
|
AdrenalineLevel = 2;
|
|
}
|
|
}
|
|
|
|
return kAlienEnergyRate * (1.0f + (AdrenalineLevel * kAdrenalineEnergyPercentPerLevel));
|
|
}
|
|
|
|
float GetPlayerOverallHealthPercent(const edict_t* Player)
|
|
{
|
|
if (IsEdictStructure(Player)) { return (Player->v.health / Player->v.max_health); }
|
|
|
|
float MaxHealthAndArmour = Player->v.max_health + GetPlayerMaxArmour(Player);
|
|
float CurrentHealthAndArmour = Player->v.health + Player->v.armorvalue;
|
|
|
|
return (CurrentHealthAndArmour / MaxHealthAndArmour);
|
|
}
|
|
|
|
Vector GetPlayerEyePosition(const edict_t* Player)
|
|
{
|
|
if (FNullEnt(Player)) { return g_vecZero; }
|
|
|
|
return (Player->v.origin + Player->v.view_ofs);
|
|
}
|
|
|
|
float GetPlayerHeight(const edict_t* Player, const bool bIsCrouching)
|
|
{
|
|
if (FNullEnt(Player)) { return 0.0f; }
|
|
|
|
return GetPlayerOriginOffsetFromFloor(Player, bIsCrouching).z * 2.0f;
|
|
}
|
|
|
|
Vector GetPlayerOriginOffsetFromFloor(const edict_t* pEdict, const bool bIsCrouching)
|
|
{
|
|
if (FNullEnt(pEdict)) { return g_vecZero; }
|
|
|
|
int iuser3 = pEdict->v.iuser3;
|
|
|
|
switch (iuser3)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
return (bIsCrouching) ? Vector(0.0f, 0.0f, 18.0f) : Vector(0.0f, 0.0f, 36.0f);
|
|
break;
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return Vector(0.0f, 0.0f, 36.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_EMBRYO:
|
|
return Vector(0.0f, 0.0f, 18.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
return Vector(0.0f, 0.0f, 18.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
return Vector(0.0f, 0.0f, 18.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
return Vector(0.0f, 0.0f, 18.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
return (bIsCrouching) ? Vector(0.0f, 0.0f, 18.0f) : Vector(0.0f, 0.0f, 36.0f);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
return (bIsCrouching) ? Vector(0.0f, 0.0f, 36.0f) : Vector(0.0f, 0.0f, 54.0f);
|
|
break;
|
|
default:
|
|
return Vector(0.0f, 0.0f, 36.0f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector GetPlayerBottomOfCollisionHull(const edict_t* pEdict)
|
|
{
|
|
if (FNullEnt(pEdict)) { return g_vecZero; }
|
|
|
|
int iuser3 = pEdict->v.iuser3;
|
|
bool bIsCrouching = (pEdict->v.flags & FL_DUCKING);
|
|
Vector origin = pEdict->v.origin;
|
|
|
|
|
|
switch (iuser3)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
return (bIsCrouching) ? (origin - Vector(0.0f, 0.0f, 18.0f)) : (origin - Vector(0.0f, 0.0f, 36.0f));
|
|
break;
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return origin;
|
|
break;
|
|
case AVH_USER3_ALIEN_EMBRYO:
|
|
return (origin - Vector(0.0f, 0.0f, 18.0f));
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
return (origin - Vector(0.0f, 0.0f, 18.0f));
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
return (origin - Vector(0.0f, 0.0f, 18.0f));
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
return (origin - Vector(0.0f, 0.0f, 18.0f));
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
return (bIsCrouching) ? (origin - Vector(0.0f, 0.0f, 18.0f)) : (origin - Vector(0.0f, 0.0f, 36.0f));
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
return (bIsCrouching) ? (origin - Vector(0.0f, 0.0f, 36.0f)) : (origin - Vector(0.0f, 0.0f, 54.0f));
|
|
break;
|
|
default:
|
|
return origin;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector GetPlayerTopOfCollisionHull(const edict_t* pEdict, const bool bIsCrouching)
|
|
{
|
|
if (FNullEnt(pEdict)) { return g_vecZero; }
|
|
|
|
int iuser3 = pEdict->v.iuser3;
|
|
Vector origin = pEdict->v.origin;
|
|
|
|
|
|
switch (iuser3)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 19.0f)) : (origin + Vector(0.0f, 0.0f, 37.0f));
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return origin;
|
|
case AVH_USER3_ALIEN_EMBRYO:
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
return (origin + Vector(0.0f, 0.0f, 19.0f));
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 19.0f)) : (origin + Vector(0.0f, 0.0f, 37.0f));
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 37.0f)) : (origin + Vector(0.0f, 0.0f, 55.0f));
|
|
default:
|
|
return origin;
|
|
}
|
|
}
|
|
|
|
Vector GetPlayerTopOfCollisionHull(const edict_t* pEdict)
|
|
{
|
|
if (FNullEnt(pEdict)) { return g_vecZero; }
|
|
|
|
if (!IsEdictPlayer(pEdict))
|
|
{
|
|
Vector Centre = UTIL_GetCentreOfEntity(pEdict);
|
|
Centre.z = pEdict->v.absmax.z;
|
|
|
|
return Centre;
|
|
}
|
|
|
|
int iuser3 = pEdict->v.iuser3;
|
|
bool bIsCrouching = (pEdict->v.flags & FL_DUCKING);
|
|
Vector origin = pEdict->v.origin;
|
|
|
|
|
|
switch (iuser3)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 19.0f)) : (origin + Vector(0.0f, 0.0f, 37.0f));
|
|
case AVH_USER3_COMMANDER_PLAYER:
|
|
return origin;
|
|
case AVH_USER3_ALIEN_EMBRYO:
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
return (origin + Vector(0.0f, 0.0f, 19.0f));
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 19.0f)) : (origin + Vector(0.0f, 0.0f, 37.0f));
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
return (bIsCrouching) ? (origin + Vector(0.0f, 0.0f, 37.0f)) : (origin + Vector(0.0f, 0.0f, 55.0f));
|
|
default:
|
|
return origin;
|
|
}
|
|
}
|
|
|
|
Vector GetPlayerAttemptedMoveDirection(const edict_t* Player)
|
|
{
|
|
if (Player->v.button == 0) { return g_vecZero; }
|
|
|
|
Vector ForwardDir = UTIL_GetForwardVector2D(Player->v.angles);
|
|
Vector RightDir = UTIL_GetVectorNormal2D(UTIL_GetCrossProduct(ForwardDir, UP_VECTOR));
|
|
|
|
if (Player->v.button & IN_FORWARD)
|
|
{
|
|
if (Player->v.button & IN_RIGHT)
|
|
{
|
|
return UTIL_GetVectorNormal2D(ForwardDir + RightDir);
|
|
}
|
|
|
|
if (Player->v.button & IN_LEFT)
|
|
{
|
|
return UTIL_GetVectorNormal2D(ForwardDir - RightDir);
|
|
}
|
|
|
|
return ForwardDir;
|
|
}
|
|
|
|
if (Player->v.button & IN_BACK)
|
|
{
|
|
Vector BackwardDir = -ForwardDir;
|
|
Vector RightDir = UTIL_GetCrossProduct(BackwardDir, UP_VECTOR);
|
|
|
|
if (Player->v.button & IN_RIGHT)
|
|
{
|
|
return UTIL_GetVectorNormal2D(BackwardDir - RightDir);
|
|
}
|
|
|
|
if (Player->v.button & IN_LEFT)
|
|
{
|
|
return UTIL_GetVectorNormal2D(BackwardDir + RightDir);
|
|
}
|
|
|
|
return BackwardDir;
|
|
}
|
|
|
|
if (Player->v.button & IN_RIGHT)
|
|
{
|
|
return RightDir;
|
|
}
|
|
|
|
if (Player->v.button & IN_LEFT)
|
|
{
|
|
return -RightDir;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
int GetPlayerIndex(AvHPlayer* Player)
|
|
{
|
|
return Player->entindex();
|
|
|
|
}
|
|
|
|
bool IsEdictPlayer(const edict_t* edict)
|
|
{
|
|
if (FNullEnt(edict)) { return false; }
|
|
|
|
return ((edict->v.flags & FL_CLIENT) || (edict->v.flags & FL_FAKECLIENT));
|
|
}
|
|
|
|
bool IsPlayerTouchingEntity(const edict_t* Player, const edict_t* TargetEntity)
|
|
{
|
|
CBaseEntity* TouchingEdict = nullptr;
|
|
|
|
while ((TouchingEdict = UTIL_FindEntityInSphere(TouchingEdict, Player->v.origin, 5.0f)) != NULL)
|
|
{
|
|
if (TouchingEdict->edict() == TargetEntity) { return true; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsPlayerInUseRange(const edict_t* Player, const edict_t* Target)
|
|
{
|
|
if (FNullEnt(Player) || FNullEnt(Target)) { return false; }
|
|
|
|
//if (vDist3DSq(Player->v.origin, UTIL_GetCentreOfEntity(Target)) > sqrf(vSize3D(Target->v.size) + vSize3D(Player->v.size))) { return false; }
|
|
|
|
CBaseEntity* UseObject = nullptr;
|
|
|
|
while ((UseObject = UTIL_FindEntityInSphere(UseObject, Player->v.origin, 64.0f)) != NULL)
|
|
{
|
|
if (UseObject->edict() == Target) { return true; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PlayerHasHeavyArmour(const edict_t* Player)
|
|
{
|
|
if (!IsPlayerMarine(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_UPGRADE_13);
|
|
}
|
|
|
|
bool PlayerHasJetpack(edict_t* Player)
|
|
{
|
|
if (!IsPlayerMarine(Player)) { return false; }
|
|
return (Player->v.iuser4 & MASK_UPGRADE_7);
|
|
}
|
|
|
|
bool PlayerHasEquipment(edict_t* Player)
|
|
{
|
|
if (!IsPlayerMarine(Player)) { return false; }
|
|
return PlayerHasHeavyArmour(Player) || PlayerHasJetpack(Player);
|
|
}
|
|
|
|
bool PlayerHasSpecialWeapon(const AvHPlayer* Player)
|
|
{
|
|
if (!IsPlayerMarine(Player)) { return false; }
|
|
return !PlayerHasWeapon(Player, WEAPON_MARINE_MG);
|
|
}
|
|
|
|
bool UTIL_PlayerHasLOSToEntity(const edict_t* Player, const edict_t* Target, const float MaxRange, const bool bUseHullSweep)
|
|
{
|
|
if (FNullEnt(Player) || FNullEnt(Target)) { return false; }
|
|
Vector StartTrace = GetPlayerEyePosition(Player);
|
|
Vector EndTrace = UTIL_GetCentreOfEntity(Target);
|
|
|
|
float Dist = vDist3D(StartTrace, EndTrace);
|
|
|
|
TraceResult hit;
|
|
|
|
if (bUseHullSweep)
|
|
{
|
|
UTIL_TraceHull(StartTrace, EndTrace, dont_ignore_monsters, head_hull, Player->v.pContainingEntity, &hit);
|
|
}
|
|
else
|
|
{
|
|
UTIL_TraceLine(StartTrace, EndTrace, dont_ignore_monsters, dont_ignore_glass, Player->v.pContainingEntity, &hit);
|
|
}
|
|
|
|
|
|
|
|
if (hit.fStartSolid || (hit.flFraction < 1.0f && ((Dist * hit.flFraction) <= MaxRange)))
|
|
{
|
|
return (hit.pHit == Target);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool UTIL_PlayerHasLOSToLocation(const edict_t* Player, const Vector Target, const float MaxRange)
|
|
{
|
|
if (FNullEnt(Player)) { return false; }
|
|
Vector StartTrace = GetPlayerEyePosition(Player);
|
|
|
|
if (vDist3DSq(StartTrace, Target) > sqrf(MaxRange)) { return false; }
|
|
|
|
TraceResult hit;
|
|
|
|
UTIL_TraceLine(StartTrace, Target, ignore_monsters, ignore_glass, Player->v.pContainingEntity, &hit);
|
|
|
|
return (hit.flFraction >= 1.0f);
|
|
|
|
}
|
|
|
|
bool PlayerHasWeapon(const AvHPlayer* Player, const AvHAIWeapon DesiredCombatWeapon)
|
|
{
|
|
bool HasWeaponInInventory = (Player->pev->weapons & (1 << DesiredCombatWeapon));
|
|
|
|
// Marines don't have a fixed inventory, so we can just do a simple check for them. Same goes to confirm the alien has the weapon in their inventory
|
|
if (IsPlayerMarine(Player) || !HasWeaponInInventory)
|
|
{
|
|
if (DesiredCombatWeapon == WEAPON_MARINE_GRENADE && HasWeaponInInventory)
|
|
{
|
|
AvHBasePlayerWeapon* Weapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[5]);
|
|
|
|
return Weapon->m_iClip > 0;
|
|
}
|
|
|
|
return HasWeaponInInventory;
|
|
}
|
|
|
|
// Aliens always have all weapons in their inventory, but they are enabled/disabled based on hive count (or combat unlocks).
|
|
// Now we check to see if the weapon is enabled for them.
|
|
|
|
edict_t* pEdict = ENT(Player->pev);
|
|
|
|
// Which slot the weapon sits in
|
|
int DesiredWeaponIndex = -1;
|
|
|
|
switch (DesiredCombatWeapon)
|
|
{
|
|
case WEAPON_SKULK_BITE:
|
|
case WEAPON_GORGE_SPIT:
|
|
case WEAPON_LERK_BITE:
|
|
case WEAPON_FADE_SWIPE:
|
|
case WEAPON_ONOS_GORE:
|
|
DesiredWeaponIndex = 1;
|
|
break;
|
|
case WEAPON_SKULK_PARASITE:
|
|
case WEAPON_GORGE_HEALINGSPRAY:
|
|
case WEAPON_LERK_SPORES:
|
|
case WEAPON_FADE_BLINK:
|
|
case WEAPON_ONOS_DEVOUR:
|
|
DesiredWeaponIndex = 2;
|
|
break;
|
|
case WEAPON_SKULK_LEAP:
|
|
case WEAPON_GORGE_BILEBOMB:
|
|
case WEAPON_LERK_UMBRA:
|
|
case WEAPON_FADE_METABOLIZE:
|
|
case WEAPON_ONOS_STOMP:
|
|
DesiredWeaponIndex = 3;
|
|
break;
|
|
case WEAPON_SKULK_XENOCIDE:
|
|
case WEAPON_GORGE_WEB:
|
|
case WEAPON_LERK_PRIMALSCREAM:
|
|
case WEAPON_FADE_ACIDROCKET:
|
|
case WEAPON_ONOS_CHARGE:
|
|
DesiredWeaponIndex = 4;
|
|
break;
|
|
default:
|
|
DesiredWeaponIndex = -1;
|
|
break;
|
|
}
|
|
|
|
if (DesiredWeaponIndex < 0) { return false; }
|
|
|
|
AvHBasePlayerWeapon* Weapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_rgpPlayerItems[DesiredWeaponIndex]);
|
|
|
|
return (Weapon && Weapon->m_iEnabled);
|
|
}
|
|
|
|
bool PlayerHasAlienUpgradeOfType(const edict_t* Player, const HiveTechStatus TechType)
|
|
{
|
|
if (!IsPlayerAlien(Player)) { return false; }
|
|
|
|
switch (TechType)
|
|
{
|
|
case HIVE_TECH_DEFENCE:
|
|
return ((Player->v.iuser4 & MASK_UPGRADE_1) || (Player->v.iuser4 & MASK_UPGRADE_2) || (Player->v.iuser4 & MASK_UPGRADE_3));
|
|
case HIVE_TECH_MOVEMENT:
|
|
return ((Player->v.iuser4 & MASK_UPGRADE_4) || (Player->v.iuser4 & MASK_UPGRADE_5) || (Player->v.iuser4 & MASK_UPGRADE_6));
|
|
case HIVE_TECH_SENSORY:
|
|
return ((Player->v.iuser4 & MASK_UPGRADE_7) || (Player->v.iuser4 & MASK_UPGRADE_8) || (Player->v.iuser4 & MASK_UPGRADE_9));
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
float GetPlayerCloakAmount(const edict_t* Player)
|
|
{
|
|
if (!(Player->v.iuser4 & MASK_UPGRADE_7) && !(Player->v.iuser4 & MASK_SENSORY_NEARBY)) { return 0.0f; }
|
|
|
|
if (Player->v.iuser4 & MASK_VIS_SIGHTED)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
else
|
|
{
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
bool IsPlayerReloading(const AvHPlayer* Player)
|
|
{
|
|
AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast<AvHBasePlayerWeapon*>(Player->m_pActiveItem);
|
|
|
|
if (!theBasePlayerWeapon) { return false; }
|
|
|
|
return (theBasePlayerWeapon->m_fInReload > 0 || theBasePlayerWeapon->m_fInSpecialReload > 0);
|
|
}
|
|
|
|
bool IsPlayerStandingOnPlayer(const edict_t* Player)
|
|
{
|
|
return (IsEdictPlayer(Player->v.groundentity));
|
|
}
|
|
|
|
AvHUser3 GetPlayerActiveClass(const AvHPlayer* Player)
|
|
{
|
|
if (Player->pev->iuser3 == AVH_USER3_ALIEN_EMBRYO) // If player is gestating...
|
|
{
|
|
switch (Player->GetEvolution()) // If they're targeting a new life form (i.e. not getting upgrade), then return the new life form. Otherwise, return previous
|
|
{
|
|
case ALIEN_LIFEFORM_ONE:
|
|
return AVH_USER3_ALIEN_PLAYER1;
|
|
case ALIEN_LIFEFORM_TWO:
|
|
return AVH_USER3_ALIEN_PLAYER2;
|
|
case ALIEN_LIFEFORM_THREE:
|
|
return AVH_USER3_ALIEN_PLAYER3;
|
|
case ALIEN_LIFEFORM_FOUR:
|
|
return AVH_USER3_ALIEN_PLAYER4;
|
|
case ALIEN_LIFEFORM_FIVE:
|
|
return AVH_USER3_ALIEN_PLAYER5;
|
|
default: // Player is gestating an upgrade
|
|
return Player->GetPreviousUser3();
|
|
}
|
|
}
|
|
|
|
// Player isn't gestating, just return whatever they are now
|
|
return (AvHUser3)Player->pev->iuser3;
|
|
}
|
|
|
|
edict_t* UTIL_GetNearestLadderAtPoint(const Vector SearchLocation)
|
|
{
|
|
CBaseEntity* entity = NULL;
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
|
|
CBaseEntity* closestLadderRef = entity;
|
|
float lowestDist = FLT_MAX;
|
|
|
|
while (entity)
|
|
{
|
|
Vector LadderMin = entity->pev->absmin;
|
|
Vector LadderMax = entity->pev->absmax;
|
|
|
|
float dist = vDistanceFromLine3D(LadderMin, LadderMax, SearchLocation);
|
|
|
|
if (dist < lowestDist)
|
|
{
|
|
closestLadderRef = entity;
|
|
lowestDist = dist;
|
|
}
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
}
|
|
|
|
return (closestLadderRef) ? closestLadderRef->edict() : nullptr;
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderNormal(edict_t* pEdict)
|
|
{
|
|
return UTIL_GetNearestLadderNormal(pEdict->v.origin);
|
|
}
|
|
|
|
Vector UTIL_GetNearestSurfaceNormal(Vector SearchLocation)
|
|
{
|
|
|
|
Vector Trace1End, Trace2End, Trace3End, Trace4End, Trace5End, Trace6End, Trace7End, Trace8End;
|
|
Trace1End = Trace2End = Trace3End = Trace4End = Trace5End = Trace6End = Trace7End = Trace8End = SearchLocation;
|
|
|
|
Trace1End.x += 32.0f;
|
|
Trace1End.y += 32.0f;
|
|
|
|
Trace2End.x += 32.0f;
|
|
Trace2End.y -= 32.0f;
|
|
|
|
Trace3End.x -= 32.0f;
|
|
Trace3End.y -= 32.0f;
|
|
|
|
Trace4End.x -= 32.0f;
|
|
Trace4End.y += 32.0f;
|
|
|
|
Trace5End.x += 32.0f;
|
|
|
|
Trace6End.x -= 32.0f;
|
|
|
|
Trace7End.y -= 32.0f;
|
|
|
|
Trace8End.y += 32.0f;
|
|
|
|
Vector ClosestNormal = ZERO_VECTOR;
|
|
float MinDist = 0.0f;
|
|
|
|
trace_t TraceResult;
|
|
NS_TraceLine(SearchLocation, Trace1End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace2End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace3End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace4End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace5End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace6End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace7End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
NS_TraceLine(SearchLocation, Trace8End, 1, PM_NORMAL, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
int PointContents = UTIL_PointContents(TraceResult.endpos);
|
|
|
|
if (vIsZero(ClosestNormal) || TraceResult.fraction < MinDist)
|
|
{
|
|
ClosestNormal = TraceResult.plane.normal;
|
|
MinDist = TraceResult.fraction;
|
|
}
|
|
}
|
|
|
|
return ClosestNormal;
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderNormal(Vector SearchLocation)
|
|
{
|
|
TraceResult result;
|
|
CBaseEntity* entity = NULL;
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
|
|
CBaseEntity* closestLadderRef = entity;
|
|
float lowestDist = FLT_MAX;
|
|
|
|
while (entity)
|
|
{
|
|
Vector LadderMin = entity->pev->absmin;
|
|
Vector LadderMax = entity->pev->absmax;
|
|
|
|
float dist = vDistanceFromLine3D(LadderMin, LadderMax, SearchLocation);
|
|
|
|
if (dist < lowestDist)
|
|
{
|
|
closestLadderRef = entity;
|
|
lowestDist = dist;
|
|
}
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
}
|
|
|
|
if (closestLadderRef)
|
|
{
|
|
if (vPointOverlaps3D(SearchLocation, closestLadderRef->pev->absmin, closestLadderRef->pev->absmax))
|
|
{
|
|
return UTIL_GetNearestSurfaceNormal(SearchLocation);
|
|
}
|
|
else
|
|
{
|
|
Vector CentrePoint = closestLadderRef->pev->absmin + (closestLadderRef->pev->size * 0.5f);
|
|
CentrePoint.z = SearchLocation.z;
|
|
|
|
trace_t TraceResult;
|
|
NS_TraceLine(SearchLocation, CentrePoint, 1, PM_WORLD_ONLY, -1, true, TraceResult);
|
|
|
|
if (TraceResult.fraction < 1.0f)
|
|
{
|
|
return TraceResult.plane.normal;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ZERO_VECTOR;
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderBottomPoint(edict_t* pEdict)
|
|
{
|
|
TraceResult result;
|
|
CBaseEntity* entity = NULL;
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
|
|
CBaseEntity* closestLadderRef = entity;
|
|
float lowestDist = 999999.0f;
|
|
|
|
while (entity)
|
|
{
|
|
Vector LadderMin = entity->pev->absmin;
|
|
Vector LadderMax = entity->pev->absmax;
|
|
|
|
float dist = vDistanceFromLine3D(LadderMin, LadderMax, pEdict->v.origin);
|
|
|
|
if (dist < lowestDist)
|
|
{
|
|
closestLadderRef = entity;
|
|
lowestDist = dist;
|
|
}
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
}
|
|
|
|
if (closestLadderRef)
|
|
{
|
|
Vector Centre = (closestLadderRef->pev->absmin + (closestLadderRef->pev->size * 0.5f));
|
|
Centre.z = closestLadderRef->pev->absmin.z;
|
|
return Centre;
|
|
|
|
}
|
|
|
|
return pEdict->v.origin;
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderTopPoint(const Vector SearchLocation)
|
|
{
|
|
TraceResult result;
|
|
CBaseEntity* entity = NULL;
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
|
|
CBaseEntity* closestLadderRef = entity;
|
|
float lowestDist = 999999.0f;
|
|
|
|
while (entity)
|
|
{
|
|
Vector LadderMin = entity->pev->absmin;
|
|
Vector LadderMax = entity->pev->absmax;
|
|
|
|
float dist = vDistanceFromLine3D(LadderMin, LadderMax, SearchLocation);
|
|
|
|
if (dist < lowestDist)
|
|
{
|
|
closestLadderRef = entity;
|
|
lowestDist = dist;
|
|
}
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
}
|
|
|
|
if (closestLadderRef)
|
|
{
|
|
Vector Centre = (closestLadderRef->pev->absmin + ((closestLadderRef->pev->absmax - closestLadderRef->pev->absmin) * 0.5f));
|
|
Centre.z = closestLadderRef->pev->absmax.z;
|
|
return Centre;
|
|
|
|
}
|
|
|
|
return SearchLocation;
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderTopPoint(edict_t* pEdict)
|
|
{
|
|
return UTIL_GetNearestLadderTopPoint(pEdict->v.origin);
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderCentrePoint(edict_t* pEdict)
|
|
{
|
|
return UTIL_GetNearestLadderCentrePoint(pEdict->v.origin);
|
|
}
|
|
|
|
Vector UTIL_GetNearestLadderCentrePoint(const Vector SearchLocation)
|
|
{
|
|
TraceResult result;
|
|
CBaseEntity* entity = NULL;
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
|
|
CBaseEntity* closestLadderRef = entity;
|
|
float lowestDist = 999999.0f;
|
|
|
|
while (entity)
|
|
{
|
|
Vector LadderMin = entity->pev->absmin;
|
|
Vector LadderMax = entity->pev->absmax;
|
|
|
|
float dist = vDistanceFromLine3D(LadderMin, LadderMax, SearchLocation);
|
|
|
|
if (dist < lowestDist)
|
|
{
|
|
closestLadderRef = entity;
|
|
lowestDist = dist;
|
|
}
|
|
|
|
entity = UTIL_FindEntityByClassname(entity, "func_ladder");
|
|
}
|
|
|
|
if (closestLadderRef)
|
|
{
|
|
return (closestLadderRef->pev->absmin + ((closestLadderRef->pev->absmax - closestLadderRef->pev->absmin) * 0.5f));
|
|
|
|
}
|
|
|
|
return SearchLocation;
|
|
}
|
|
|
|
void AIPlayer_Say(edict_t* pEntity, int teamonly, const char* Msg)
|
|
{
|
|
AvHPlayer* client;
|
|
AvHPlayer* theTalkingPlayer = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(pEntity));
|
|
int j;
|
|
char* p;
|
|
char text[256];
|
|
bool theTalkerInReadyRoom = theTalkingPlayer->GetInReadyRoom();
|
|
|
|
// We can get a raw string now, without the "say " prepended
|
|
if (!Msg)
|
|
return;
|
|
|
|
//Not yet.
|
|
if (theTalkingPlayer->m_flNextChatTime > gpGlobals->time)
|
|
return;
|
|
|
|
p = (char*)Msg;
|
|
|
|
// remove quotes if present
|
|
if (*p == '"')
|
|
{
|
|
p++;
|
|
p[strlen(p) - 1] = 0;
|
|
}
|
|
|
|
// make sure the text has content
|
|
char* pc = p;
|
|
for (pc = p; pc != NULL && *pc != 0; pc++)
|
|
{
|
|
if (isprint(*pc) && !isspace(*pc))
|
|
{
|
|
pc = NULL; // we've found an alphanumeric character, so text is valid
|
|
break;
|
|
}
|
|
}
|
|
if (pc != NULL)
|
|
return; // no character found, so say nothing
|
|
|
|
// turn on color set 2 (color on, no sound)
|
|
if (teamonly)
|
|
sprintf(text, "%c(TEAM) %s: ", 2, STRING(pEntity->v.netname));
|
|
else
|
|
sprintf(text, "%c%s: ", 2, STRING(pEntity->v.netname));
|
|
|
|
j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator
|
|
if ((int)strlen(p) > j)
|
|
p[j] = 0;
|
|
|
|
strcat(text, p);
|
|
strcat(text, "\n");
|
|
|
|
theTalkingPlayer->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL;
|
|
// loop through all players
|
|
// Start with the first player.
|
|
// This may return the world in single player if the client types something between levels or during spawn
|
|
// so check it, or it will infinite loop
|
|
|
|
client = NULL;
|
|
while (((client = (AvHPlayer*)UTIL_FindEntityByClassname(client, "player")) != NULL) && (!FNullEnt(client->edict())))
|
|
{
|
|
if (!client->pev)
|
|
continue;
|
|
|
|
if (client->edict() == pEntity)
|
|
continue;
|
|
|
|
if (!(client->IsNetClient())) // Not a client ? (should never be true)
|
|
continue;
|
|
|
|
// Don't differentiate between team and non-team when not playing
|
|
bool theTalkingPlayerIsPlaying = ((theTalkingPlayer->GetPlayMode() == PLAYMODE_PLAYING) || (theTalkingPlayer->GetPlayMode() == PLAYMODE_AWAITINGREINFORCEMENT) || (theTalkingPlayer->GetPlayMode() == PLAYMODE_REINFORCING));
|
|
bool theClientIsPlaying = ((client->GetPlayMode() == PLAYMODE_PLAYING) || (client->GetPlayMode() == PLAYMODE_AWAITINGREINFORCEMENT) || (client->GetPlayMode() == PLAYMODE_REINFORCING));
|
|
bool theTalkerIsObserver = theTalkingPlayer->IsObserver();
|
|
bool theClientIsObserver = client->IsObserver();
|
|
bool theClientIsHLTV = (client->pev->flags & FL_PROXY);
|
|
|
|
bool theClientInReadyRoom = client->GetInReadyRoom();
|
|
|
|
if (theClientInReadyRoom != theTalkerInReadyRoom && !theClientIsHLTV)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!theClientIsObserver || theClientIsPlaying) // Non-playing Observers hear everything.
|
|
{
|
|
|
|
if (theTalkingPlayerIsPlaying && teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE)
|
|
continue;
|
|
|
|
// chat can never go between play area and non-play area
|
|
if (theTalkingPlayerIsPlaying != theClientIsPlaying && !theClientIsHLTV)
|
|
continue;
|
|
|
|
// chat of any kind doesn't go from ready room to play area in tournament mode
|
|
if (theTalkerInReadyRoom && GetGameRules()->GetIsTournamentMode() && theClientIsPlaying && !theClientIsHLTV)
|
|
continue;
|
|
|
|
}
|
|
|
|
UTIL_SayText(text, client, ENTINDEX(pEntity));
|
|
|
|
}
|
|
|
|
// print to the sending client
|
|
UTIL_SayText(text, CBaseEntity::Instance(ENT(pEntity)));
|
|
|
|
// echo to server console
|
|
g_engfuncs.pfnServerPrint(text);
|
|
|
|
// UTIL_LogPrintf( "%s %s \"%s\"\n", GetLogStringForPlayer( pEntity ).c_str(), temp, p );
|
|
} |