mirror of
https://github.com/UberGames/EF2GameSource.git
synced 2024-11-10 06:31:42 +00:00
1012 lines
23 KiB
C++
1012 lines
23 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /EF2/Code/DLLs/game/actor_enemymanager.cpp $
|
|
// $Revision:: 41 $
|
|
// $Author:: Singlis $
|
|
// $Date:: 9/26/03 2:35p $
|
|
//
|
|
// Copyright (C) 2001 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
//
|
|
|
|
#include "_pch_cpp.h"
|
|
|
|
//======================================
|
|
// EnemyManager Implementation
|
|
//=====================================
|
|
|
|
//
|
|
// Name: EnemyManager()
|
|
// Parameters: None
|
|
// Description: Constructor
|
|
//
|
|
EnemyManager::EnemyManager()
|
|
{
|
|
// Should always use other constructor
|
|
gi.Error(ERR_FATAL, "EnemyManager::EnemyManager -- Default Constructor Called");
|
|
}
|
|
|
|
//
|
|
// Name: EnemyManager()
|
|
// Parameters: Actor *actor
|
|
// Description: Constructor
|
|
//
|
|
EnemyManager::EnemyManager(Actor *actor)
|
|
{
|
|
//Initialize our Actor
|
|
if (actor)
|
|
act = actor;
|
|
else
|
|
gi.Error(ERR_DROP, "EnemyManager::EnemyManager -- actor is NULL");
|
|
|
|
_lockedOnCurrentEnemy = false;
|
|
|
|
}
|
|
|
|
//
|
|
// Name: ~EnemyManager()
|
|
// Parameters: None
|
|
// Description: Destructor
|
|
//
|
|
EnemyManager::~EnemyManager()
|
|
{
|
|
}
|
|
|
|
|
|
//
|
|
// Name: GetCurrentEnemy()
|
|
// Parameters: None
|
|
// Description: Returns the _currentEnemy
|
|
//
|
|
EntityPtr EnemyManager::GetCurrentEnemy()
|
|
{
|
|
if (act->forcedEnemy)
|
|
{
|
|
if (act->forcedEnemy->health <= 0)
|
|
{
|
|
act->forcedEnemy = nullptr;
|
|
_lockedOnCurrentEnemy = false;
|
|
return _currentEnemy;
|
|
}
|
|
|
|
return act->forcedEnemy;
|
|
}
|
|
|
|
if (!_currentEnemy)
|
|
FindHighestHateEnemy();
|
|
|
|
return _currentEnemy;
|
|
}
|
|
|
|
//
|
|
// Name: SetCurrentEnemy()
|
|
// Parameters: Entity *enemy
|
|
// Description: sets _currentEnemy
|
|
//
|
|
void EnemyManager::SetCurrentEnemy(Entity *enemy)
|
|
{
|
|
if (enemy)
|
|
_currentEnemy = enemy;
|
|
else
|
|
{
|
|
_lockedOnCurrentEnemy = false;
|
|
}
|
|
}
|
|
|
|
|
|
EntityPtr EnemyManager::GetAlternateTarget()
|
|
{
|
|
return _alternateTarget;
|
|
}
|
|
|
|
void EnemyManager::SetAlternateTarget(Entity *target)
|
|
{
|
|
_alternateTarget = target;
|
|
}
|
|
//
|
|
// Name: FindHightestHateEnemy()
|
|
// Parameters: None
|
|
// Description: Sets the _currentEnemy to the highest enemy on the hate list
|
|
//
|
|
void EnemyManager::FindHighestHateEnemy()
|
|
{
|
|
float hateValue = 0;
|
|
HateListEntry_t listIndex;
|
|
float hateFactor;
|
|
|
|
|
|
|
|
if (IsLockedOnCurrentEnemy())
|
|
return;
|
|
|
|
|
|
for (auto i = _hateList.NumObjects(); i > 0; i--)
|
|
{
|
|
hateFactor = 100;
|
|
|
|
listIndex = _hateList.ObjectAt(i);
|
|
listIndex.hate = listIndex.hate * (1 / listIndex.lastDistance);
|
|
|
|
if (!listIndex.enemy)
|
|
{
|
|
_hateList.RemoveObjectAt(i);
|
|
continue;
|
|
}
|
|
|
|
//If the enemy is a player, make sure to modify our hate
|
|
if (listIndex.enemy->isSubclassOf(Sentient) && !act->GetActorFlag(ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS))
|
|
{
|
|
hateFactor *= dynamic_cast<Sentient*>(static_cast<Entity*>(listIndex.enemy))->GetHateModifier();
|
|
}
|
|
|
|
|
|
listIndex.hate *= hateFactor;
|
|
|
|
if (listIndex.hate >= hateValue && listIndex.hate > 0.0)
|
|
{
|
|
_currentEnemy = listIndex.enemy;
|
|
hateValue = listIndex.hate;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Name: FindClosestEnemy()
|
|
// Parameters: None
|
|
// Description: Sets the _currentEnemy to the enemy closest to the actor
|
|
//
|
|
void EnemyManager::FindClosestEnemy()
|
|
{
|
|
auto distance = 99999999.9f;
|
|
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = _hateList.ObjectAt(i);
|
|
|
|
if (!act->combatSubsystem->CanAttackTarget(listIndex.enemy))
|
|
continue;
|
|
|
|
if (listIndex.lastDistance < distance)
|
|
{
|
|
_currentEnemy = listIndex.enemy;
|
|
distance = listIndex.lastDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EnemyManager::FindNextEnemy()
|
|
{
|
|
auto hateValue = 0.0f;
|
|
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = _hateList.ObjectAt(i);
|
|
if (listIndex.hate <= _currentEnemyHate && listIndex.hate > hateValue)
|
|
{
|
|
hateValue = listIndex.hate;
|
|
_lastEnemy = _currentEnemy;
|
|
_currentEnemy = listIndex.enemy;
|
|
_currentEnemyHate = hateValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Name: ClearCurrentEnemy()
|
|
// Parameters: None
|
|
// Description: sets the _currentEnemy to NULL
|
|
//
|
|
void EnemyManager::ClearCurrentEnemy()
|
|
{
|
|
_lockedOnCurrentEnemy = false;
|
|
_currentEnemy = nullptr;
|
|
}
|
|
|
|
//
|
|
// Name: ClearHateList()
|
|
// Parameters: None
|
|
// Description: Wipes out the hate list
|
|
//
|
|
void EnemyManager::ClearHateList()
|
|
{
|
|
for (auto i = _hateList.NumObjects(); i > 0; i--)
|
|
{
|
|
_hateList.RemoveObjectAt(i);
|
|
}
|
|
|
|
_hateList.FreeObjectList();
|
|
}
|
|
|
|
//
|
|
// Name: Likes()
|
|
// Parameters: Entity *ent
|
|
// Description: Checks if Actor likes the entity
|
|
//
|
|
qboolean EnemyManager::Likes(Entity *ent)
|
|
{
|
|
if (!ent)
|
|
return false;
|
|
|
|
if (ent->isClient())
|
|
{
|
|
return act->actortype == IS_FRIEND;
|
|
}
|
|
|
|
if (act->actortype == IS_MONSTER)
|
|
{
|
|
// Monsters don't like anyone
|
|
return false;
|
|
}
|
|
|
|
if (ent->isSubclassOf(Actor))
|
|
{
|
|
auto actor = dynamic_cast<Actor *>(ent);
|
|
|
|
if (actor->enemytype == act->enemytype)
|
|
return true;
|
|
|
|
if (actor->actortype != act->actortype)
|
|
return false;
|
|
|
|
if (act->actortype == IS_FRIEND)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: Hates()
|
|
// Parameters: Entity *ent
|
|
// Description: Checks if Actor hates the entity
|
|
//
|
|
qboolean EnemyManager::Hates(Entity *ent)
|
|
{
|
|
if (!ent)
|
|
return false;
|
|
|
|
if (ent->isClient())
|
|
{
|
|
return act->actortype != IS_CIVILIAN && (act->actortype != IS_FRIEND && act->actortype != IS_TEAMMATE);
|
|
}
|
|
if (ent->isSubclassOf(Actor) && act->actortype != IS_INANIMATE)
|
|
{
|
|
auto actor = dynamic_cast<Actor *>(ent);
|
|
if (actor->actortype <= IS_ENEMY && act->actortype <= IS_ENEMY)
|
|
{
|
|
return false;
|
|
}
|
|
if (actor->actortype == IS_FRIEND && act->actortype <= IS_ENEMY)
|
|
{
|
|
return true;
|
|
}
|
|
if (actor->actortype == IS_TEAMMATE && act->actortype <= IS_ENEMY)
|
|
{
|
|
return true;
|
|
}
|
|
if (actor->actortype <= IS_ENEMY && act->actortype == IS_FRIEND)
|
|
{
|
|
return true;
|
|
}
|
|
if (actor->actortype <= IS_ENEMY && act->actortype == IS_TEAMMATE)
|
|
{
|
|
return true;
|
|
}
|
|
if (actor->actortype == IS_TEAMMATE && act->actortype == IS_TEAMMATE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: IsValidEnemy
|
|
// Parameters: Entity *enemy
|
|
// Description: Checks if the passed in entity is a valid target or not
|
|
//
|
|
qboolean EnemyManager::IsValidEnemy(Entity *enemy)
|
|
{
|
|
|
|
if (!enemy || enemy == world || enemy == act || enemy->flags & FlagNotarget || enemy->takedamage == DamageNo || enemy->deadflag)
|
|
return false;
|
|
|
|
if (!Hates(enemy))
|
|
return false;
|
|
|
|
if (!CanAttack(enemy))
|
|
return false;
|
|
|
|
if ((enemy->edict->s.renderfx & RF_DONTDRAW))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Name: CanAttack
|
|
// Parameters: Entity *ent
|
|
// Description: Checks if the actor is allowed to attack the passed in entity
|
|
//
|
|
qboolean EnemyManager::CanAttack(Entity *ent)
|
|
{
|
|
|
|
if (!ent)
|
|
return false;
|
|
|
|
if (act->targetType == ATTACK_ANY)
|
|
return true;
|
|
|
|
|
|
if (ent->isSubclassOf(Player) && act->targetType == ATTACK_PLAYER_ONLY)
|
|
return true;
|
|
|
|
if (ent->isSubclassOf(Actor) && act->targetType == ATTACK_ACTORS_ONLY)
|
|
return true;
|
|
|
|
if (ent->isSubclassOf(LevelInteractionTrigger) && act->targetType == ATTACK_LEVEL_INTERACTION)
|
|
return true;
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//
|
|
// Name: CanAttackAnyEnemy
|
|
// Parameters: None
|
|
// Description: Checks if we can shoot any of our enemies in our list
|
|
qboolean EnemyManager::CanAttackAnyEnemy()
|
|
{
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = &_hateList.ObjectAt(i);
|
|
|
|
auto target = listIndex->enemy;
|
|
if (target)
|
|
{
|
|
if (act->combatSubsystem->CanAttackTarget(target))
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: AdjustHate()
|
|
// Parameters: Entity *enemy -- The entity to adjust
|
|
// float adjustment -- how much to adjust
|
|
// Description: Adjusts the hate value of the enemy
|
|
//
|
|
void EnemyManager::AdjustHate(Entity *enemy, float adjustment)
|
|
{
|
|
if (!enemy)
|
|
return;
|
|
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = &_hateList.ObjectAt(i);
|
|
|
|
if (listIndex->enemy == enemy)
|
|
{
|
|
listIndex->hate += adjustment;
|
|
if (listIndex->hate < 0.0f)
|
|
listIndex->hate = 0.0f;
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Name: AdjustDamageCaused()
|
|
// Parameters: Entity *enemy -- The entity to adjust
|
|
// float adjustment -- how much to adjust
|
|
// Description: Adjusts the damage caused
|
|
//
|
|
void EnemyManager::AdjustDamageCaused(Entity *enemy, float adjustment)
|
|
{
|
|
if (!enemy)
|
|
return;
|
|
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto& listIndex = _hateList.ObjectAt(i);
|
|
|
|
if (listIndex.enemy == enemy)
|
|
{
|
|
listIndex.damageCaused += adjustment;
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Name: TryToAddToHateList
|
|
// Parameters: Entity *enemy
|
|
// Description: Checks if the passed in entity should be added to the hate list
|
|
// and calls _AddToHateList if it does
|
|
//
|
|
void EnemyManager::TryToAddToHateList(Entity *enemy)
|
|
{
|
|
if (!enemy)
|
|
return;
|
|
|
|
if (IsValidEnemy(enemy) && !IsInHateList(enemy))
|
|
{
|
|
if (!act->combatSubsystem->CanAttackTarget(enemy) && !CanGetToEntity(enemy))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_AddToHateList(enemy);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Name: _AddToHateList()
|
|
// Parameters: Entity *enemy -- The enemy to add
|
|
// Description: Adds the enemy to the hate list
|
|
//
|
|
void EnemyManager::_AddToHateList(Entity *enemy)
|
|
{
|
|
HateListEntry_t listIndex;
|
|
|
|
if (!enemy)
|
|
return;
|
|
|
|
listIndex.enemy = enemy;
|
|
listIndex.damageCaused = 0;
|
|
listIndex.hate = DEFAULT_INITIAL_HATE;
|
|
listIndex.lastSightTime = level.time;
|
|
listIndex.canSee = false;
|
|
//listIndex.lastDistance = -1;
|
|
listIndex.nextSightTime = level.time + MIN_SIGHT_DELAY;
|
|
UpdateDistance(&listIndex);
|
|
_hateList.AddObject(listIndex);
|
|
|
|
}
|
|
|
|
//
|
|
// Name: IsInHateList()
|
|
// Parameters: Entity *enemy
|
|
// Description: Checks if enemy is in the hate list
|
|
//
|
|
qboolean EnemyManager::IsInHateList(Entity *enemy)
|
|
{
|
|
return _findEntityInHateList(enemy) > 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: IsLastInHateList
|
|
// Class: EnemyManager
|
|
//
|
|
// Description: Determine if the Entity passed is in the hate list,
|
|
// and if it is at the end of the list.
|
|
//
|
|
// Parameters: Entity* enemy - the entity to look for
|
|
//
|
|
// Returns: qboolean
|
|
//----------------------------------------------------------------
|
|
qboolean EnemyManager::IsLastInHateList(Entity* enemy)
|
|
{
|
|
return _findEntityInHateList(enemy) == _hateList.NumObjects();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: _findEntityInHateList
|
|
// Class: EnemyManager
|
|
//
|
|
// Description: Search the hate list for the passed entity.
|
|
//
|
|
// Parameters: Entity* searchEnt - the entity to look for
|
|
//
|
|
// Returns: The index of the entity in the list
|
|
// 0 means it isn't in the list.
|
|
//----------------------------------------------------------------
|
|
int EnemyManager::_findEntityInHateList(Entity *searchEnt)
|
|
{
|
|
if (!searchEnt)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// manually iterate through the hatelist
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = _hateList.ObjectAt(i);
|
|
|
|
if (listIndex.enemy == searchEnt)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// was not found, return 0
|
|
return 0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: TrivialUpdate
|
|
// Class: EnemyManager
|
|
//
|
|
// Description: Minimal Hatelist update required for Actors
|
|
//
|
|
// Parameters:
|
|
// None
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void EnemyManager::TrivialUpdate()
|
|
{
|
|
for (auto i = _hateList.NumObjects(); i > 0; i--)
|
|
{
|
|
_hateList.ObjectAt(i).lastDistance = 0.0f;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Name: Update()
|
|
// Parameters: None
|
|
// Description: Updates HateListEntry_ts
|
|
//
|
|
void EnemyManager::Update()
|
|
{
|
|
for (auto i = _hateList.NumObjects(); i > 0; i--)
|
|
{
|
|
auto listIndex = &_hateList.ObjectAt(i);
|
|
|
|
//Check if enemy is alive
|
|
if (!act->IsEntityAlive(listIndex->enemy))
|
|
{
|
|
if (listIndex->enemy == _currentEnemy)
|
|
ClearCurrentEnemy();
|
|
|
|
_hateList.RemoveObjectAt(i);
|
|
continue;
|
|
}
|
|
|
|
UpdateDistance(listIndex);
|
|
UpdateCanSee(listIndex);
|
|
|
|
// Bump the player's action level, TODO: this needs to change to a general you have been targeted call sometime
|
|
|
|
if (listIndex->enemy && listIndex->enemy->isSubclassOf(Player))
|
|
{
|
|
if (act->GetActorFlag(ACTOR_FLAG_UPDATE_ACTION_LEVEL))
|
|
{
|
|
dynamic_cast<Player *>(static_cast<Entity *>(listIndex->enemy))->IncreaseActionLevel(100);
|
|
}
|
|
}
|
|
|
|
if (act->GetActorFlag(ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS))
|
|
UpdateAttackers(listIndex);
|
|
|
|
/*if ( !act->combatSubsystem->CanAttackTarget( listIndex->enemy ) )
|
|
{
|
|
listIndex->hate = 0.0f;
|
|
if ( listIndex->enemy == _currentEnemy )
|
|
ClearCurrentEnemy();
|
|
|
|
_hateList.RemoveObjectAt(i);
|
|
}
|
|
*/
|
|
|
|
|
|
//If we haven't seen
|
|
if (
|
|
act->timeBetweenSleepChecks > 0.0f &&
|
|
(listIndex->lastSightTime + act->timeBetweenSleepChecks <= level.time && !gi.inPVS(listIndex->enemy->centroid, act->centroid))
|
|
)
|
|
{
|
|
if (listIndex->enemy == _currentEnemy && !_lockedOnCurrentEnemy)
|
|
ClearCurrentEnemy();
|
|
_hateList.RemoveObjectAt(i);
|
|
}
|
|
|
|
}
|
|
// Try and go back to sleep
|
|
TrySleep();
|
|
|
|
|
|
//If we don't have an enemy, tell our senses to keep looking
|
|
act->sensoryPerception->SenseEnemies();
|
|
|
|
|
|
if (GetCurrentEnemy() || act->GetActorFlag(ACTOR_FLAG_INVESTIGATING) || act->mode == ActorModeScript || act->mode == ActorModeTalk)
|
|
act->last_time_active = level.time;
|
|
|
|
// Take care of enemies
|
|
act->strategos->Evaluate();
|
|
|
|
}
|
|
|
|
//
|
|
// Name: UpdateDistance
|
|
// Parameters: HateListEntry_t -- The List Entry to update
|
|
// Description: Updates the distance of the enemy
|
|
//
|
|
void EnemyManager::UpdateDistance(HateListEntry_t *listIndex)
|
|
{
|
|
Vector distance;
|
|
|
|
//float distanceLength;
|
|
|
|
if (!listIndex || !listIndex->enemy)
|
|
return;
|
|
|
|
Vector enemyDistance;
|
|
enemyDistance = listIndex->enemy->origin;
|
|
|
|
distance = enemyDistance - act->origin;
|
|
//distanceLength = distance.length();
|
|
|
|
listIndex->lastDistance = distance.length();
|
|
|
|
}
|
|
|
|
void EnemyManager::UpdateAttackers(HateListEntry_t *listIndex)
|
|
{
|
|
if (_currentEnemy == listIndex->enemy)
|
|
return;
|
|
|
|
auto group = dynamic_cast<ActorGroup*>(groupcoordinator->GetGroup(act->GetGroupID()));
|
|
if (!group)
|
|
return;
|
|
|
|
auto numberOfAttackers = group->CountMembersAttackingEnemy(listIndex->enemy);
|
|
|
|
listIndex->hate = listIndex->hate / (numberOfAttackers + 1);
|
|
|
|
}
|
|
|
|
//
|
|
// Name: UpdateCanSee
|
|
// Parameters: HateListEntry_t -- The List Entry to update
|
|
// Description: Updates the cansee of the enemy
|
|
//
|
|
void EnemyManager::UpdateCanSee(HateListEntry_t *listIndex)
|
|
{
|
|
if (!listIndex || listIndex->nextSightTime > level.time)
|
|
return;
|
|
|
|
qboolean canSee;
|
|
canSee = act->sensoryPerception->CanSeeEntity(act, listIndex->enemy, true, true);
|
|
|
|
// Notify Strategos of status change
|
|
if (canSee != listIndex->canSee)
|
|
act->strategos->NotifySightStatusChanged(listIndex->enemy, canSee);
|
|
|
|
// Update the SightCheckTimes
|
|
listIndex->canSee = canSee;
|
|
|
|
// We use a faster delay for our current enemy than everyone else
|
|
float next_sight_delay;
|
|
if (listIndex->enemy == _currentEnemy)
|
|
next_sight_delay = act->max_inactive_time / 5.0f + G_CRandom(0.5f);
|
|
else
|
|
next_sight_delay = MIN_SIGHT_DELAY + (act->max_inactive_time / 2.0f + G_CRandom(0.5f));
|
|
|
|
if (next_sight_delay < 1.0f)
|
|
next_sight_delay = 1.0f;
|
|
|
|
listIndex->nextSightTime = level.time + next_sight_delay;
|
|
|
|
if (canSee)
|
|
listIndex->lastSightTime = level.time;
|
|
|
|
if (listIndex->lastSightTime + act->max_inactive_time < level.time)
|
|
ClearCurrentEnemy();
|
|
}
|
|
|
|
|
|
//
|
|
// Name: LockOnCurrentEnemy
|
|
// Parameters: qboolean lock
|
|
// Description: Sets the _lockedOnCurrentEnemy flag
|
|
//
|
|
void EnemyManager::LockOnCurrentEnemy(qboolean lock)
|
|
{
|
|
_lockedOnCurrentEnemy = lock;
|
|
}
|
|
|
|
//
|
|
// Name: IsLockedOnCurrentEnemy
|
|
// Parameters: None
|
|
// Description: Returns the _lockedOnCurrentEnemy flag
|
|
//
|
|
qboolean EnemyManager::IsLockedOnCurrentEnemy()
|
|
{
|
|
return _lockedOnCurrentEnemy;
|
|
}
|
|
|
|
|
|
Vector EnemyManager::GetAwayFromEnemies()
|
|
{
|
|
|
|
|
|
Vector bestAwayVector;
|
|
|
|
for (auto i = 1; i <= _hateList.NumObjects(); i++)
|
|
{
|
|
auto listIndex = _hateList.ObjectAt(i);
|
|
auto enemy = listIndex.enemy;
|
|
auto enemyToSelf = act->origin - enemy->origin;
|
|
auto mag = enemyToSelf.lengthSquared();
|
|
auto temp = enemyToSelf * (1 / mag);
|
|
|
|
bestAwayVector = bestAwayVector + temp;
|
|
}
|
|
|
|
bestAwayVector.normalize();
|
|
|
|
return bestAwayVector;
|
|
}
|
|
|
|
qboolean EnemyManager::InEnemyLineOfFire()
|
|
{
|
|
if (!_currentEnemy)
|
|
return false;
|
|
|
|
auto angleDiff = 0.0f;
|
|
|
|
auto enemyToSelf = act->origin - _currentEnemy->origin;
|
|
enemyToSelf = enemyToSelf.toAngles();
|
|
|
|
if (_currentEnemy->isSubclassOf(Actor))
|
|
{
|
|
auto enemyActor = dynamic_cast<Actor*>(static_cast<Entity*>(_currentEnemy));
|
|
|
|
if (!enemyActor)
|
|
return false;
|
|
|
|
angleDiff = abs(int(AngleNormalize180(enemyActor->angles[YAW] - enemyToSelf[YAW])));
|
|
}
|
|
|
|
if (_currentEnemy->isSubclassOf(Player))
|
|
{
|
|
auto enemyPlayer = dynamic_cast<Player*>(static_cast<Entity*>(_currentEnemy));
|
|
|
|
if (!enemyPlayer)
|
|
return false;
|
|
|
|
auto pAngles = enemyPlayer->GetVAngles();
|
|
|
|
angleDiff = abs(int(AngleNormalize180(pAngles[YAW] - enemyToSelf[YAW])));
|
|
|
|
}
|
|
|
|
if (angleDiff < 10.0f)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
float EnemyManager::GetDistanceFromEnemy()
|
|
{
|
|
if (!_currentEnemy)
|
|
return -1;
|
|
|
|
auto selfToEnemy = _currentEnemy->origin - act->origin;
|
|
|
|
return selfToEnemy.length();
|
|
}
|
|
|
|
//
|
|
// Name: DoArchive
|
|
// Parameters: Archiver &arc -- The archiver object
|
|
// Actor *actor -- The actor
|
|
// Description: Sets the act pointer and calls Archive
|
|
//
|
|
void EnemyManager::DoArchive(Archiver &arc, Actor *actor)
|
|
{
|
|
Archive(arc);
|
|
if (actor)
|
|
act = actor;
|
|
else
|
|
gi.Error(ERR_FATAL, "EnemyManager::DoArchive -- actor is NULL");
|
|
}
|
|
|
|
//
|
|
// Name: Archive()
|
|
// Parameters: Archiver &arc -- The archiver object
|
|
// Description: Archives the class
|
|
//
|
|
void EnemyManager::Archive(Archiver &arc)
|
|
{
|
|
HateListEntry_t hateEntry;
|
|
int32_t numEntries;
|
|
|
|
if (arc.Saving())
|
|
{
|
|
numEntries = _hateList.NumObjects();
|
|
arc.ArchiveInteger(&numEntries);
|
|
|
|
for (auto i = 1; i <= numEntries; i++)
|
|
{
|
|
hateEntry = _hateList.ObjectAt(i);
|
|
|
|
arc.ArchiveSafePointer(&hateEntry.enemy);
|
|
arc.ArchiveFloat(&hateEntry.lastSightTime);
|
|
arc.ArchiveFloat(&hateEntry.nextSightTime);
|
|
arc.ArchiveBoolean(&hateEntry.canSee);
|
|
arc.ArchiveFloat(&hateEntry.damageCaused);
|
|
arc.ArchiveFloat(&hateEntry.hate);
|
|
arc.ArchiveFloat(&hateEntry.lastDistance);
|
|
}
|
|
} else
|
|
{
|
|
arc.ArchiveInteger(&numEntries);
|
|
|
|
_hateList.Resize(numEntries);
|
|
|
|
for (auto i = 1; i <= numEntries; i++)
|
|
{
|
|
_hateList.AddObject(hateEntry);
|
|
|
|
auto realHateEntry = &_hateList.ObjectAt(i);
|
|
|
|
arc.ArchiveSafePointer(&realHateEntry->enemy);
|
|
arc.ArchiveFloat(&realHateEntry->lastSightTime);
|
|
arc.ArchiveFloat(&realHateEntry->nextSightTime);
|
|
arc.ArchiveBoolean(&realHateEntry->canSee);
|
|
arc.ArchiveFloat(&realHateEntry->damageCaused);
|
|
arc.ArchiveFloat(&realHateEntry->hate);
|
|
arc.ArchiveFloat(&realHateEntry->lastDistance);
|
|
}
|
|
}
|
|
|
|
arc.ArchiveSafePointer(&_currentEnemy);
|
|
arc.ArchiveSafePointer(&_lastEnemy);
|
|
|
|
arc.ArchiveSafePointer(&_alternateTarget);
|
|
|
|
arc.ArchiveBoolean(&_lockedOnCurrentEnemy);
|
|
arc.ArchiveFloat(&_currentEnemyHate);
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: TrySleep
|
|
// Class: EnemyManager
|
|
//
|
|
// Description: Puts the actor to sleep if it can
|
|
//
|
|
// Parameters:
|
|
// None
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void EnemyManager::TrySleep(void)
|
|
{
|
|
const Entity *currentEnemy = GetCurrentEnemy();
|
|
// This is here because, if the ai gets turned off in the level, we don't want to go to sleep for if/when
|
|
// the ai gets turned back on in the level
|
|
if (!level.ai_on)
|
|
act->last_time_active = level.time;
|
|
|
|
// See if we should go back to sleep -- If max_inactive_time < 1 , then the actor will never go to sleep
|
|
// If we DO have a max_inactive_time > 0 AND our last_time_active == 0 ( meaning it's our first time in here ) we
|
|
// should go ahead and try to fall asleep even though we have hit our max_inactive_time
|
|
if (!currentEnemy && act->max_inactive_time > 0.0f && act->last_time_active + act->max_inactive_time < level.time)
|
|
{
|
|
//Well, we haven't had an enemy for a while, so let's see if the player is nearby
|
|
for (auto i = 0; i < game.maxclients; i++)
|
|
{
|
|
auto player = GetPlayer(i);
|
|
|
|
if (player)
|
|
{
|
|
//We should NOT fall asleep if:
|
|
// our mode is ActorModeIdle _AND_ player flags are not equal to FlagNotarget
|
|
// OR
|
|
// Player is within our vision distance
|
|
// OR
|
|
// Player is within the PVS
|
|
|
|
//
|
|
// 7/15/02 -- SK
|
|
// Added via && the "gi.inPVS( player->centroid, act->centroid )" to the ActorModeIdle
|
|
// checks because, without it, all AI_OFF'd actors would still be considered "active"
|
|
// and this was ruining the framerate... I do not believe this is going to have any
|
|
// detrimental side effects.
|
|
//
|
|
if (act->mode == ActorModeIdle && !(player->flags & FlagNotarget) && gi.inPVS(player->centroid, act->centroid) || act->sensoryPerception->WithinVisionDistance(player) || gi.inPVS(player->centroid, act->centroid))
|
|
{
|
|
act->last_time_active = level.time;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (act->mode == ActorModeAi)
|
|
{
|
|
act->EndMode();
|
|
|
|
act->Sleep();
|
|
|
|
if (act->idle_thread.length() > 1)
|
|
{
|
|
ExecuteThread(act->idle_thread, false);
|
|
}
|
|
} else
|
|
{
|
|
act->Sleep();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EnemyManager::IsAnyEnemyInRange(float range)
|
|
{
|
|
for (auto i = _hateList.NumObjects(); i > 0; i--)
|
|
{
|
|
auto listIndex = &_hateList.ObjectAt(i);
|
|
|
|
//Check if enemy is alive
|
|
if (!act->IsEntityAlive(listIndex->enemy))
|
|
{
|
|
if (listIndex->enemy == _currentEnemy)
|
|
ClearCurrentEnemy();
|
|
|
|
_hateList.RemoveObjectAt(i);
|
|
continue;
|
|
}
|
|
|
|
if (act->WithinDistance(listIndex->enemy, range))
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
float EnemyManager::getEnemyCount()
|
|
{
|
|
return _hateList.NumObjects();
|
|
}
|
|
|
|
bool EnemyManager::CanGetToEntity(Entity *ent)
|
|
{
|
|
FindMovementPath find;
|
|
auto success = false;
|
|
|
|
// Set up our pathing heuristics
|
|
find.heuristic.self = act;
|
|
find.heuristic.setSize(act->size);
|
|
find.heuristic.entnum = act->entnum;
|
|
|
|
auto path = find.FindPath(act->origin, ent->origin);
|
|
|
|
if (path)
|
|
{
|
|
success = true;
|
|
}
|
|
|
|
delete path;
|
|
return success;
|
|
}
|
|
|
|
bool EnemyManager::HasEnemy()
|
|
{
|
|
if (_currentEnemy)
|
|
return true;
|
|
|
|
return false;
|
|
}
|