Added LookForEnemiesEx() for returning all enemies in an area. (#2753)

* Compartmentalized the LookForEnemiesInBlock checks

* Added LookForEnemiesEx().

This function allows for ZScript code to get an array with all enemies of the caller found in range. Using similar sight logic as functions like LookForEnemies().

* Added noPlayers parameter to LookForEnemiesEx().

This parameter allows the function to also find players around it.

* Added VM abort to LookForEnemiesEx().

Prevent crashes by passing a null array by reference.
This commit is contained in:
inkoalawetrust 2024-10-14 10:23:12 +03:00 committed by Rachael Alexanderson
commit 9df656a9cf
No known key found for this signature in database
GPG key ID: 26A8ACCE97115EE0
2 changed files with 160 additions and 104 deletions

View file

@ -1309,6 +1309,161 @@ int P_IsVisible(AActor *lookee, AActor *other, INTBOOL allaround, FLookExParams
return P_CheckSight(lookee, other, SF_SEEPASTSHOOTABLELINES);
}
bool isTargetablePlayer(AActor *actor, player_t *player, INTBOOL allaround, void* lookparams)
{
FLookExParams* params = (FLookExParams*)lookparams;
if (!(player->mo->flags & MF_SHOOTABLE))
return false; // not shootable (observer or dead)
if (actor->IsFriend(player->mo))
return false; // same +MF_FRIENDLY, ignore
if (player->cheats & CF_NOTARGET)
return false; // no target
if (player->health <= 0)
return false; // dead
if (!P_IsVisible(actor, player->mo, allaround, params))
return false; // out of sight
// [RC] Well, let's let special monsters with this flag active be able to see
// the player then, eh?
if (!(actor->flags6 & MF6_SEEINVISIBLE))
{
if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) ||
player->mo->flags3 & MF3_GHOST)
{
if (player->mo->Distance2D(actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5 * 5)
{ // Player is sneaking - can't detect
return false;
}
if (pr_lookforplayers() < 225)
{ // Player isn't sneaking, but still didn't detect
return false;
}
}
}
return true;
}
bool ValidEnemyInBlock(AActor* lookee, AActor* other, void* lookparams)
{
FLookExParams* params = (FLookExParams*)lookparams;
if (!(other->flags & MF_SHOOTABLE))
return false; // not shootable (observer or dead)
if (other == lookee)
return false; // is self
if (other->health <= 0)
return false; // dead
if (other->flags2 & MF2_DORMANT)
return false; // don't target dormant things
if (!(other->flags3 & MF3_ISMONSTER))
return false; // don't target it if it isn't a monster (could be a barrel)
if (other->flags7 & MF7_NEVERTARGET)
return false;
bool keepChecking = false;
if (lookee->flags & MF_FRIENDLY)
{
if (other->flags & MF_FRIENDLY)
{
if (!lookee->IsFriend(other))
{
// This is somebody else's friend, so go after it
keepChecking = true;
}
else if (other->target != NULL && !(other->target->flags & MF_FRIENDLY))
{
other = other->target;
if (!(other->flags & MF_SHOOTABLE) ||
other->health <= 0 ||
(other->flags2 & MF2_DORMANT))
{
return false;
}
}
}
else
{
keepChecking = true;
}
}
else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && other->flags & MF_FRIENDLY)
{
keepChecking = true;
}
// [MBF] If the monster is already engaged in a one-on-one attack
// with a healthy friend, don't attack around 60% the time.
// [GrafZahl] This prevents friendlies from attacking all the same
// target.
if (keepChecking)
{
AActor* targ = other->target;
if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend(targ) &&
targ->health * 2 >= targ->SpawnHealth())
{
return false;
}
}
// [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here?
if (!keepChecking || !P_IsVisible(lookee, other, true, params))
return false; // out of sight
return true;
}
//============================================================================
//
// LookForEnemiesEx
//
// [inkoalawetrust] Return a script array of all valid enemies of the caller
// in range. For ZScript.
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, LookForEnemiesEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_OUTPOINTER(targets,TArray<AActor*>);
PARAM_FLOAT(range);
PARAM_BOOL(noPlayers);
PARAM_BOOL(allaround);
PARAM_POINTER(params, FLookExParams);
if (targets == nullptr)
ThrowAbortException(X_WRITE_NIL,"No targets array passed");
if (range == -1)
range = self->friendlyseeblocks * FBlockmap::MAPBLOCKUNITS;
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
FMultiBlockThingsIterator it(check, self, range, false);
FMultiBlockThingsIterator::CheckResult cres;
while (it.Next(&cres))
{
if (cres.thing->player == nullptr && ValidEnemyInBlock(cres.thing, self, params) ||
!noPlayers && cres.thing->player && isTargetablePlayer(self, cres.thing->player, allaround, params))
targets->Push(cres.thing);
}
ACTION_RETURN_INT(targets->Size());
}
//---------------------------------------------------------------------------
//
// FUNC P_LookForMonsters
@ -1552,80 +1707,10 @@ AActor *LookForEnemiesInBlock (AActor *lookee, int index, void *extparam)
for (block = lookee->Level->blockmap.blocklinks[index]; block != NULL; block = block->NextActor)
{
link = block->Me;
if (!(link->flags & MF_SHOOTABLE))
continue; // not shootable (observer or dead)
if (link == lookee)
if (!ValidEnemyInBlock(lookee, block->Me, params))
continue;
if (link->health <= 0)
continue; // dead
if (link->flags2 & MF2_DORMANT)
continue; // don't target dormant things
if (!(link->flags3 & MF3_ISMONSTER))
continue; // don't target it if it isn't a monster (could be a barrel)
if (link->flags7 & MF7_NEVERTARGET)
continue;
other = NULL;
if (lookee->flags & MF_FRIENDLY)
{
if (link->flags & MF_FRIENDLY)
{
if (!lookee->IsFriend(link))
{
// This is somebody else's friend, so go after it
other = link;
}
else if (link->target != NULL && !(link->target->flags & MF_FRIENDLY))
{
other = link->target;
if (!(other->flags & MF_SHOOTABLE) ||
other->health <= 0 ||
(other->flags2 & MF2_DORMANT))
{
other = NULL;;
}
}
}
else
{
other = link;
}
}
else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && link->flags & MF_FRIENDLY)
{
other = link;
}
// [MBF] If the monster is already engaged in a one-on-one attack
// with a healthy friend, don't attack around 60% the time.
// [GrafZahl] This prevents friendlies from attacking all the same
// target.
if (other)
{
AActor *targ = other->target;
if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend (targ) &&
targ->health*2 >= targ->SpawnHealth())
{
continue;
}
}
// [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here?
if (other == NULL || !P_IsVisible (lookee, other, true, params))
continue; // out of sight
return other;
return block->Me;
}
return NULL;
}
@ -1825,38 +1910,8 @@ int P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params)
player = actor->Level->Players[pnum];
if (!(player->mo->flags & MF_SHOOTABLE))
continue; // not shootable (observer or dead)
if (actor->IsFriend(player->mo))
continue; // same +MF_FRIENDLY, ignore
if (player->cheats & CF_NOTARGET)
continue; // no target
if (player->health <= 0)
continue; // dead
if (!P_IsVisible (actor, player->mo, allaround, params))
continue; // out of sight
// [RC] Well, let's let special monsters with this flag active be able to see
// the player then, eh?
if(!(actor->flags6 & MF6_SEEINVISIBLE))
{
if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) ||
player->mo->flags3 & MF3_GHOST)
{
if (player->mo->Distance2D (actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5*5)
{ // Player is sneaking - can't detect
continue;
}
if (pr_lookforplayers() < 225)
{ // Player isn't sneaking, but still didn't detect
continue;
}
}
}
if (!isTargetablePlayer(actor, player, allaround, params))
continue;
// [RH] Need to be sure the reactiontime is 0 if the monster is
// leaving its goal to go after a player.