in doom_py/src/vizdoom/src/p_enemy.cpp [2155:2506]
void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missilestate, bool playactive, bool nightmarefast, bool dontmove)
{
int delta;
if (actor->flags5 & MF5_INCONVERSATION)
return;
if (actor->flags & MF_INCHASE)
{
return;
}
actor->flags |= MF_INCHASE;
// [RH] Andy Baker's stealth monsters
if (actor->flags & MF_STEALTH)
{
actor->visdir = -1;
}
if (actor->reactiontime)
{
actor->reactiontime--;
}
// [RH] Don't chase invisible targets
if (actor->target != NULL &&
actor->target->renderflags & RF_INVISIBLE &&
actor->target != actor->goal)
{
actor->target = NULL;
}
// modify target threshold
if (actor->threshold)
{
if (actor->target == NULL || actor->target->health <= 0)
{
actor->threshold = 0;
}
else
{
actor->threshold--;
}
}
if (nightmarefast && G_SkillProperty(SKILLP_FastMonsters))
{ // Monsters move faster in nightmare mode
if (actor->tics > 3)
{
actor->tics -= actor->tics / 2;
if (actor->tics < 3)
{
actor->tics = 3;
}
}
}
// turn towards movement direction if not there yet
if (actor->strafecount)
{
A_FaceTarget(actor);
}
else if (actor->movedir < 8)
{
actor->angle &= (angle_t)(7<<29);
delta = actor->angle - (actor->movedir << 29);
if (delta > 0)
{
actor->angle -= ANG90/2;
}
else if (delta < 0)
{
actor->angle += ANG90/2;
}
}
// [RH] If the target is dead or a friend (and not a goal), stop chasing it.
if (actor->target && actor->target != actor->goal && (actor->target->health <= 0 || actor->IsFriend(actor->target)))
actor->target = NULL;
// [RH] Friendly monsters will consider chasing whoever hurts a player if they
// don't already have a target.
if (actor->flags & MF_FRIENDLY && actor->target == NULL)
{
player_t *player;
if (actor->FriendPlayer != 0)
{
player = &players[actor->FriendPlayer - 1];
}
else
{
int i;
if (!multiplayer)
{
i = 0;
}
else for (i = pr_newchasedir() & (MAXPLAYERS-1); !playeringame[i]; i = (i+1) & (MAXPLAYERS-1))
{
}
player = &players[i];
}
if (player->attacker && player->attacker->health > 0 && player->attacker->flags & MF_SHOOTABLE && pr_newchasedir() < 80)
{
if (!(player->attacker->flags & MF_FRIENDLY) ||
(deathmatch && actor->FriendPlayer != 0 && player->attacker->FriendPlayer != 0 &&
actor->FriendPlayer != player->attacker->FriendPlayer))
{
actor->target = player->attacker;
}
}
}
if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
{ // look for a new target
if (actor->target != NULL && (actor->target->flags2 & MF2_NONSHOOTABLE))
{
// Target is only temporarily unshootable, so remember it.
actor->lastenemy = actor->target;
// Switch targets faster, since we're only changing because we can't
// hurt our old one temporarily.
actor->threshold = 0;
}
if (P_LookForPlayers (actor, true, NULL) && actor->target != actor->goal)
{ // got a new target
actor->flags &= ~MF_INCHASE;
return;
}
if (actor->target == NULL)
{
if (actor->flags & MF_FRIENDLY)
{
//CALL_ACTION(A_Look, actor);
if (actor->target == NULL)
{
if (!dontmove) CALL_ACTION(A_Wander, actor);
actor->flags &= ~MF_INCHASE;
return;
}
}
else
{
actor->SetIdle();
actor->flags &= ~MF_INCHASE;
return;
}
}
}
// do not attack twice in a row
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (!actor->isFast() && !dontmove)
{
P_NewChaseDir (actor);
}
actor->flags &= ~MF_INCHASE;
return;
}
// [RH] Don't attack if just moving toward goal
if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != NULL))
{
AActor * savedtarget = actor->target;
actor->target = actor->goal;
bool result = actor->CheckMeleeRange();
actor->target = savedtarget;
if (result)
{
// reached the goal
NActorIterator iterator (NAME_PatrolPoint, actor->goal->args[0]);
NActorIterator specit (NAME_PatrolSpecial, actor->goal->tid);
AActor *spec;
// Execute the specials of any PatrolSpecials with the same TID
// as the goal.
while ( (spec = specit.Next()) )
{
P_ExecuteSpecial(spec->special, NULL, actor, false, spec->args[0],
spec->args[1], spec->args[2], spec->args[3], spec->args[4]);
}
angle_t lastgoalang = actor->goal->angle;
int delay;
AActor * newgoal = iterator.Next ();
if (newgoal != NULL && actor->goal == actor->target)
{
delay = newgoal->args[1];
actor->reactiontime = delay * TICRATE + level.maptime;
}
else
{
delay = 0;
actor->reactiontime = actor->GetDefault()->reactiontime;
actor->angle = lastgoalang; // Look in direction of last goal
}
if (actor->target == actor->goal) actor->target = NULL;
actor->flags |= MF_JUSTATTACKED;
if (newgoal != NULL && delay != 0)
{
actor->flags4 |= MF4_INCOMBAT;
actor->SetIdle();
}
actor->flags &= ~MF_INCHASE;
actor->goal = newgoal;
return;
}
if (actor->goal == actor->target) goto nomissile;
}
// Strafe (Hexen's class bosses)
// This was the sole reason for the separate A_FastChase function but
// it can be just as easily handled by a simple flag so the monsters
// can take advantage of all the other enhancements of A_Chase.
if (fastchase && !dontmove)
{
if (actor->FastChaseStrafeCount > 0)
{
actor->FastChaseStrafeCount--;
}
else
{
actor->FastChaseStrafeCount = 0;
actor->velx = 0;
actor->vely = 0;
fixed_t dist = actor->AproxDistance (actor->target);
if (dist < CLASS_BOSS_STRAFE_RANGE)
{
if (pr_chase() < 100)
{
angle_t ang = actor->AngleTo(actor->target);
if (pr_chase() < 128) ang += ANGLE_90;
else ang -= ANGLE_90;
actor->velx = 13 * finecosine[ang>>ANGLETOFINESHIFT];
actor->vely = 13 * finesine[ang>>ANGLETOFINESHIFT];
actor->FastChaseStrafeCount = 3; // strafe time
}
}
}
}
// [RH] Scared monsters attack less frequently
if (((actor->target->player == NULL ||
!(actor->target->player->cheats & CF_FRIGHTENING)) &&
!(actor->flags4 & MF4_FRIGHTENED)) ||
pr_scaredycat() < 43)
{
// check for melee attack
if (meleestate && actor->CheckMeleeRange ())
{
if (actor->AttackSound)
S_Sound (actor, CHAN_WEAPON, actor->AttackSound, 1, ATTN_NORM);
actor->SetState (meleestate);
actor->flags &= ~MF_INCHASE;
return;
}
// check for missile attack
if (missilestate)
{
if (!actor->isFast() && actor->movecount)
{
goto nomissile;
}
if (!P_CheckMissileRange (actor))
goto nomissile;
actor->SetState (missilestate);
actor->flags |= MF_JUSTATTACKED;
actor->flags4 |= MF4_INCOMBAT;
actor->flags &= ~MF_INCHASE;
return;
}
}
nomissile:
// possibly choose another target
if ((multiplayer || actor->TIDtoHate)
&& !actor->threshold
&& !P_CheckSight (actor, actor->target, 0) )
{
bool lookForBetter = false;
bool gotNew;
if (actor->flags3 & MF3_NOSIGHTCHECK)
{
actor->flags3 &= ~MF3_NOSIGHTCHECK;
lookForBetter = true;
}
AActor * oldtarget = actor->target;
gotNew = P_LookForPlayers (actor, true, NULL);
if (lookForBetter)
{
actor->flags3 |= MF3_NOSIGHTCHECK;
}
if (gotNew && actor->target != oldtarget)
{
actor->flags &= ~MF_INCHASE;
return; // got a new target
}
}
//
// chase towards player
//
if (actor->strafecount)
actor->strafecount--;
// class bosses don't do this when strafing
if ((!fastchase || !actor->FastChaseStrafeCount) && !dontmove)
{
// CANTLEAVEFLOORPIC handling was completely missing in the non-serpent functions.
fixed_t oldX = actor->X();
fixed_t oldY = actor->Y();
FTextureID oldFloor = actor->floorpic;
// chase towards player
if (--actor->movecount < 0 || !P_Move (actor))
{
P_NewChaseDir (actor);
}
// if the move was illegal, reset it
// (copied from A_SerpentChase - it applies to everything with CANTLEAVEFLOORPIC!)
if (actor->flags2&MF2_CANTLEAVEFLOORPIC && actor->floorpic != oldFloor )
{
if (P_TryMove (actor, oldX, oldY, false))
{
if (nomonsterinterpolation)
{
actor->PrevX = oldX;
actor->PrevY = oldY;
}
}
P_NewChaseDir (actor);
}
}
else if (dontmove && actor->movecount > 0) actor->movecount--;
// make active sound
if (playactive && pr_chase() < 3)
{
actor->PlayActiveSound ();
}
actor->flags &= ~MF_INCHASE;
}