//----------------------------------------------------------------------------- // // $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(static_cast(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(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(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(static_cast(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(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(static_cast(_currentEnemy)); if (!enemyActor) return false; angleDiff = abs(int(AngleNormalize180(enemyActor->angles[YAW] - enemyToSelf[YAW]))); } if (_currentEnemy->isSubclassOf(Player)) { auto enemyPlayer = dynamic_cast(static_cast(_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; }