ef2gamesource/dlls/game/actor_enemymanager.cpp
2015-04-29 22:34:44 +02:00

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;
}