openmohaa/code/fgame/actorenemy.cpp
smallmodel 915269e017
Fix the actor stuck targeting an enemy with really low threat (behind walls, not visible)
This fixes the actor being stuck aiming even if the player is near them
2024-08-31 16:24:59 +02:00

559 lines
16 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// actorenemy.cpp:
#include "actorenemy.h"
#include "worldspawn.h"
#include "sentient.h"
#include "actor.h"
float ActorEnemy::UpdateLMRF(Actor *pSelf, bool *pbInFovAndRange, bool *pbVisible)
{
float fMaxRange, fRange, fRangeScale, fNormalizedRange;
float fForward;
float fFovScale;
float fMinSightTime;
float fLMRF;
float fFarPlane;
Vector vDelta;
float fObfuscation;
*pbInFovAndRange = false;
*pbVisible = false;
vDelta = pSelf->VirtualEyePosition() - GetEnemy()->origin;
vDelta[2] = 0;
fRange = vDelta.lengthXYSquared();
fMaxRange = pSelf->m_fSight;
if (world->farplane_distance > 0) {
fFarPlane = world->farplane_distance * 0.828f;
if (fMaxRange > fFarPlane) {
fMaxRange = fFarPlane;
}
}
if (fRange > Square(fMaxRange)) {
return 8.0;
}
fForward = sqrt(fRange);
fMinSightTime = -DotProduct2D(pSelf->m_vEyeDir, vDelta);
if (fMinSightTime < 0) {
return 8.0;
}
fMinSightTime += 128;
if (pSelf->m_fFovDot * fForward > fMinSightTime) {
return 8.0;
}
*pbInFovAndRange = true;
if (!pSelf->CanSee(m_pEnemy, 0, 0, false)) {
return 8.0;
}
// Added in 2.0.
// Check for obfuscation
fObfuscation = G_VisualObfuscation(pSelf->VirtualEyePosition(), m_pEnemy->EyePosition());
if (fObfuscation >= 1.f) {
return 8.0;
}
*pbVisible = true;
fFovScale = (fMinSightTime / (fForward + 128.0) * -1.3 - (pSelf->m_fFovDot * 0.2 - 1.5)) / (1.0 - pSelf->m_fFovDot);
fRangeScale = fForward / fMaxRange;
fNormalizedRange = (((fRangeScale * 16.0 + -16.0) * fRangeScale + -1.0) * fRangeScale + 7.0) * fRangeScale;
fNormalizedRange = fNormalizedRange / 3.0;
fLMRF = Square(m_pEnemy->stealthMovementScale * fNormalizedRange) * (fForward + 128.0) / fMinSightTime;
fLMRF *= pSelf->m_fNoticeTimeScale * g_ai_noticescale->value;
if (fLMRF < fFovScale) {
return fFovScale;
}
return fLMRF;
}
float ActorEnemy::UpdateVisibility(Actor *pSelf, bool *pbInFovAndRange, bool *pbVisible)
{
float fLMRF, fFrameTime;
fFrameTime = level.time - m_fLastLookTime;
m_fLastLookTime = level.time;
fLMRF = UpdateLMRF(pSelf, pbInFovAndRange, pbVisible);
if (fLMRF < 8.0) {
m_fVisibility += fFrameTime / fLMRF;
} else if (m_fVisibility < 1.0) {
m_fVisibility -= fFrameTime * 0.25;
if (m_fVisibility < 0.0) {
m_fVisibility = 0.0;
}
}
m_fTotalVisibility = m_fVisibility + GetEnemy()->m_fPlayerSightLevel;
if (m_fTotalVisibility > 1) {
m_fVisibility = 1;
m_fTotalVisibility = 1;
}
return m_fTotalVisibility;
}
int ActorEnemy::UpdateThreat(Actor *pSelf)
{
static float fRangeThreatSquared[] = {
Square(2048),
Square(1400),
Square(1100),
Square(916),
Square(784),
Square(683),
Square(600),
Square(529),
Square(466),
Square(410),
Square(357),
Square(305),
Square(254),
Square(200),
Square(137)};
static int iWeaponThreat[7][5] = {
{5, 3, 1, 1, 1},
{8, 6, 4, 2, 1},
{10, 9, 7, 8, 4},
{12, 10, 8, 4, 3},
{12, 12, 9, 7, 4},
{12, 10, 5, 2, 1},
{10, 12, 10, 7, 5}
};
int iWeapon;
int iZone;
Vector vDelta;
int i;
m_iThreat = 0;
m_fCurrentRangeSquared = 1e38f;
if (m_pEnemy->m_bIsDisguised || m_fTotalVisibility < 0.999f || level.inttime < m_iNextEnemyTime) {
return m_iThreat;
}
m_iThreat = 10000;
if (m_bVisible == true) {
m_iThreat = 10500;
}
vDelta = m_pEnemy->origin - pSelf->origin;
m_fCurrentRangeSquared = vDelta.lengthSquared();
if (m_fCurrentRangeSquared < Square(256)) {
iZone = 0;
} else if (m_fCurrentRangeSquared < Square(768)) {
iZone = 1;
} else if (m_fCurrentRangeSquared < Square(1280)) {
iZone = 2;
} else if (m_fCurrentRangeSquared < Square(2048)) {
iZone = 3;
} else {
iZone = 4;
}
Weapon *pEnemyWeapon = m_pEnemy->GetActiveWeapon(WEAPON_MAIN);
iWeapon = 0;
if (pEnemyWeapon) {
int iWeapClass = pEnemyWeapon->GetWeaponClass();
if (iWeapClass & WEAPON_CLASS_PISTOL) {
iWeapon = 1;
} else if (iWeapClass & WEAPON_CLASS_RIFLE) {
iWeapon = 2;
} else if (iWeapClass & WEAPON_CLASS_SMG) {
iWeapon = 3;
} else if (iWeapClass & WEAPON_CLASS_MG) {
iWeapon = 4;
} else if (iWeapClass & WEAPON_CLASS_GRENADE) {
iWeapon = 5;
} else {
iWeapon = 6;
}
}
for (i = 0; i < ARRAY_LEN(fRangeThreatSquared) && fRangeThreatSquared[i] > m_fCurrentRangeSquared; i++) {}
m_iThreat += iWeaponThreat[iWeapon][iZone] + m_pEnemy->m_iThreatBias + i;
float fMinSafeDistSquared = 1.21 * pSelf->m_fMinDistanceSquared + 16384;
if (m_fCurrentRangeSquared < fMinSafeDistSquared) {
m_iThreat += 500 - (sqrt(m_fCurrentRangeSquared / fMinSafeDistSquared) * 500);
}
vec2_t vLine;
VectorSub2D(m_vLastKnownPos, pSelf->origin, vLine);
float fDot = DotProduct2D(vLine, m_vLastKnownPos);
Sentient *pSquadMate;
for (pSquadMate = m_pEnemy->m_pNextSquadMate; pSquadMate != m_pEnemy; pSquadMate = pSquadMate->m_pNextSquadMate) {
if (fDot > DotProduct2D(vLine, pSquadMate->origin)) {
m_iThreat -= 4;
}
}
if (m_pEnemy->IsSubclassOfActor()) {
Actor *pActorEnemy = static_cast<Actor *>(m_pEnemy.Pointer());
if (pActorEnemy->m_ThinkState == THINKSTATE_PAIN) {
m_iThreat -= 2;
}
}
fDot = vDelta * pSelf->orientation[0];
if (fDot > 0) {
if (m_fCurrentRangeSquared * 0.5 >= Square(fDot) && m_fCurrentRangeSquared * 0.5 > Square(fDot)) {
m_iThreat++;
}
} else {
if (m_fCurrentRangeSquared * 0.5 > Square(fDot)) {
m_iThreat++;
}
}
int iEnemyDiscount = m_pEnemy->m_iAttackerCount;
if (m_pEnemy == pSelf->m_Enemy) {
iEnemyDiscount -= 2;
if (level.inttime < pSelf->m_iEnemyChangeTime + 1000) {
m_iThreat += 5;
}
}
if (iEnemyDiscount > 4) {
iEnemyDiscount = 4;
}
m_iThreat -= iEnemyDiscount;
if (m_pEnemy == pSelf->m_pLastAttacker) {
m_iThreat += 5;
}
if (m_pEnemy == pSelf->m_FavoriteEnemy) {
m_iThreat += 250;
}
return m_iThreat;
}
ActorEnemySet::ActorEnemySet()
{
m_iCheckCount = 0;
m_pCurrentEnemy = NULL;
m_iCurrentThreat = 0;
m_fCurrentVisibility = 0.0;
}
ActorEnemy *ActorEnemySet::AddPotentialEnemy(Sentient *pEnemy)
{
ActorEnemy NewEnemy;
if (pEnemy->IsDead() || pEnemy->m_iThreatBias == THREATBIAS_IGNOREME) {
return NULL;
}
for (int i = 0; i < m_Enemies.NumObjects(); i++) {
ActorEnemy *pActorEnemy = &m_Enemies[i];
if (pActorEnemy->m_pEnemy == pEnemy) {
pActorEnemy->m_iAddTime = level.inttime;
return pActorEnemy;
}
}
NewEnemy.m_iAddTime = level.inttime;
NewEnemy.m_iNextEnemyTime = 0;
NewEnemy.m_fLastLookTime = level.time;
NewEnemy.m_fVisibility = 0.0;
NewEnemy.m_fTotalVisibility = 0.0;
NewEnemy.m_iThreat = 0;
NewEnemy.m_pEnemy = pEnemy;
NewEnemy.m_fCurrentRangeSquared = 1e38f;
NewEnemy.m_vLastKnownPos = vec_zero;
NewEnemy.m_iLastSightChangeTime = 0;
NewEnemy.m_bVisible = false;
int index = m_Enemies.AddObject(NewEnemy);
return m_Enemies.AddressOfObjectAt(index);
}
void ActorEnemySet::FlagBadEnemy(Sentient *pEnemy)
{
ActorEnemy *pActorEnemy = NULL;
for (int i = 1; i <= m_Enemies.NumObjects(); i++) {
if (m_Enemies.ObjectAt(i).m_pEnemy == pEnemy) {
pActorEnemy = m_Enemies.AddressOfObjectAt(i);
break;
}
}
if (!pActorEnemy) {
return;
}
// Added in 2.0.
// Set the reaction time
pActorEnemy->m_iNextEnemyTime = level.inttime + 5000;
pActorEnemy->m_fLastLookTime = level.time;
pActorEnemy->m_fVisibility = 0.0;
pActorEnemy->m_fTotalVisibility = 0.0;
pActorEnemy->m_iThreat = 0;
pActorEnemy->m_iLastSightChangeTime = level.inttime;
pActorEnemy->m_bVisible = false;
if (pEnemy == m_pCurrentEnemy) {
m_pCurrentEnemy = NULL;
m_fCurrentVisibility = 0.0;
m_iCurrentThreat = 0;
}
}
void ActorEnemySet::CheckEnemies(Actor *pSelf)
{
float fVisibility;
int iThreat;
int i;
int nChecked;
bool bInFovAndRange;
bool bVisible;
float fRangeSquared;
ActorEnemy *pActorEnemy;
for (i = 1; i <= m_Enemies.NumObjects();) {
pActorEnemy = &m_Enemies[i - 1];
if (!pActorEnemy->GetEnemy() || pActorEnemy->GetEnemy()->m_Team == pSelf->m_Team
|| pActorEnemy->GetEnemy()->IsDead() || level.inttime > pActorEnemy->m_iAddTime + 10000
|| pActorEnemy->GetEnemy()->m_iThreatBias == THREATBIAS_IGNOREME || pActorEnemy->GetEnemy()->IsDisabled()) {
m_Enemies.RemoveObjectAt(i);
} else {
i++;
}
}
if (!m_Enemies.NumObjects()) {
m_iCurrentThreat = 0;
m_pCurrentEnemy = NULL;
return;
}
bInFovAndRange = false;
for (nChecked = 0; nChecked < m_Enemies.NumObjects() && !bInFovAndRange; nChecked++) {
m_iCheckCount++;
if (m_iCheckCount > m_Enemies.NumObjects()) {
m_iCheckCount = 1;
}
pActorEnemy = &m_Enemies[m_iCheckCount - 1];
fVisibility = pActorEnemy->UpdateVisibility(pSelf, &bInFovAndRange, &bVisible);
if (fVisibility > 0) {
if (fVisibility >= m_fCurrentVisibility) {
m_fCurrentVisibility = fVisibility;
m_pCurrentEnemy = pActorEnemy->GetEnemy();
} else if (pActorEnemy->m_pEnemy == m_pCurrentEnemy) {
m_fCurrentVisibility = fVisibility;
}
if (g_showawareness->integer) {
Com_Printf(
"ent #%3i: enemy #%i: awareness = %5.1f%%, threat = %i\n",
pSelf->entnum,
pActorEnemy->GetEnemy()->entnum,
(fVisibility * 100.0),
0
);
}
} else if (pActorEnemy->m_pEnemy == m_pCurrentEnemy) {
m_iCurrentThreat = 0;
m_pCurrentEnemy = NULL;
m_fCurrentVisibility = 0;
}
if (bVisible) {
if (!pActorEnemy->m_bVisible) {
pActorEnemy->m_bVisible = true;
pActorEnemy->m_iLastSightChangeTime = level.inttime;
}
pActorEnemy->m_vLastKnownPos = pActorEnemy->m_pEnemy->origin;
} else if (pActorEnemy->m_bVisible) {
pActorEnemy->m_bVisible = false;
pActorEnemy->m_iLastSightChangeTime = level.inttime;
}
}
if (m_pCurrentEnemy && m_pCurrentEnemy->IsDead()) {
m_pCurrentEnemy = NULL;
m_iCurrentThreat = 0;
m_fCurrentVisibility = 0;
}
m_iCurrentThreat = 0;
fRangeSquared = 1e37f;
if (m_fCurrentVisibility >= 0.999f) {
for (i = 1; i <= m_Enemies.NumObjects(); i++) {
pActorEnemy = &m_Enemies.ObjectAt(i);
pActorEnemy->UpdateThreat(pSelf);
iThreat = pActorEnemy->GetThreat();
if (m_iCurrentThreat >= iThreat) {
if (m_iCheckCount != iThreat) {
continue;
}
if (pActorEnemy->GetRangeSquared() < fRangeSquared) {
continue;
}
}
m_iCurrentThreat = pActorEnemy->GetThreat();
m_pCurrentEnemy = pActorEnemy->GetEnemy();
fRangeSquared = pActorEnemy->m_fCurrentRangeSquared;
}
}
if ((!m_pCurrentEnemy || !m_pCurrentEnemy->m_bIsDisguised) && m_iCurrentThreat <= 0) {
m_pCurrentEnemy = NULL;
m_iCurrentThreat = 0;
m_fCurrentVisibility = 0;
}
}
bool ActorEnemySet::HasAlternateEnemy(void) const
{
for (int i = 1; i <= m_Enemies.NumObjects(); i++) {
const ActorEnemy& actorEnemy = m_Enemies.ObjectAt(i);
if (actorEnemy.GetVisibility() >= 0.999f && actorEnemy.GetEnemy() != m_pCurrentEnemy) {
return true;
}
}
return false;
}
void ActorEnemySet::RemoveAll(void)
{
m_Enemies.ClearObjectList();
m_iCheckCount = 0;
m_pCurrentEnemy = NULL;
m_fCurrentVisibility = 0.0;
m_iCurrentThreat = 0;
}
void ActorEnemySet::ConfirmEnemy(Actor *pSelf, Sentient *pEnemy)
{
ActorEnemy *pActorEnemy;
pSelf->m_bEnemyIsDisguised = false;
pActorEnemy = ActorEnemySet::AddPotentialEnemy(pEnemy);
if (!pActorEnemy) {
return;
}
// Added in 2.0.
// Ignore if below reaction time
if (level.inttime < pActorEnemy->m_iNextEnemyTime) {
return;
}
pActorEnemy->m_fVisibility = 1.0;
pActorEnemy->m_fTotalVisibility = 1.0;
pActorEnemy->m_vLastKnownPos = pEnemy->origin;
if (m_fCurrentVisibility < 1.0) {
m_iCurrentThreat = pActorEnemy->UpdateThreat(pSelf);
m_fCurrentVisibility = 1.0;
m_pCurrentEnemy = pEnemy;
}
}
void ActorEnemySet::ConfirmEnemyIfCanSeeSharerOrEnemy(Actor *pSelf, Actor *pSharer, Sentient *pEnemy)
{
ActorEnemy *pActorEnemy;
pSelf->m_bEnemyIsDisguised = false;
pActorEnemy = ActorEnemySet::AddPotentialEnemy(pEnemy);
if (!pActorEnemy) {
return;
}
// Added in 2.0.
// Ignore if below reaction time
if (level.inttime < pActorEnemy->m_iNextEnemyTime) {
return;
}
if (pActorEnemy->m_fTotalVisibility >= 1.f) {
pActorEnemy->m_vLastKnownPos = pEnemy->origin;
return;
}
if (!pActorEnemy->m_bVisible
&& !pSelf->CanSee(pSharer, pSelf->m_bSilent ? 90 : 0, world->farplane_distance * 0.828f, false)) {
return;
}
pActorEnemy->m_fVisibility = 1.0;
pActorEnemy->m_fTotalVisibility = 1.0;
pActorEnemy->m_vLastKnownPos = pEnemy->origin;
if (m_fCurrentVisibility < 1.0) {
m_iCurrentThreat = pActorEnemy->UpdateThreat(pSelf);
m_fCurrentVisibility = 1.0;
m_pCurrentEnemy = pEnemy;
}
}
bool ActorEnemySet::CaresAboutPerfectInfo(Sentient *pEnemy)
{
ActorEnemy *pActorEnemy = ActorEnemySet::AddPotentialEnemy(pEnemy);
if (!pActorEnemy) {
return false;
}
// Added in 2.0.
// Ignore if below reaction time
if (level.inttime < pActorEnemy->m_iNextEnemyTime) {
return false;
}
if (pActorEnemy->m_fTotalVisibility < 1.0f) {
return true;
}
if ((pEnemy->origin - pActorEnemy->m_vLastKnownPos).lengthSquared() <= Square(512)) {
return false;
}
return true;
}