/* Emacs style mode select   -*- C++ -*-
 *-----------------------------------------------------------------------------
 *
 *
 *  PrBoom a Doom port merged with LxDoom and LSDLDoom
 *  based on BOOM, a modified and improved DOOM engine
 *  Copyright (C) 1999 by
 *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
 *  Copyright (C) 1999-2000 by
 *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * DESCRIPTION:
 *      Handling interactions (i.e., collisions).
 *
 *-----------------------------------------------------------------------------*/

#include "doomstat.h"
#include "dstrings.h"
#include "m_random.h"
#include "am_map.h"
#include "r_main.h"
#include "s_sound.h"
#include "sounds.h"
#include "d_deh.h"  // Ty 03/22/98 - externalized strings
#include "p_tick.h"
#include "i_system.h"

#include "p_inter.h"
#include "p_enemy.h"

#ifdef __GNUG__
#pragma implementation "p_inter.h"
#endif
#include "p_inter.h"

#define BONUSADD        6

// Ty 03/07/98 - add deh externals
// Maximums and such were hardcoded values.  Need to externalize those for
// dehacked support (and future flexibility).  Most var names came from the key
// strings used in dehacked.

int initial_health = 100;
int initial_bullets = 50;
int maxhealth = 100; // was MAXHEALTH as a #define, used only in this module
int max_armor = 200;
int green_armor_class = 1;  // these are involved with armortype below
int blue_armor_class = 2;
int max_soul = 200;
int soul_health = 100;
int mega_health = 200;
int god_health = 100;   // these are used in cheats (see st_stuff.c)
int idfa_armor = 200;
int idfa_armor_class = 2;
// not actually used due to pairing of cheat_k and cheat_fa
int idkfa_armor = 200;
int idkfa_armor_class = 2;

int bfgcells = 40;      // used in p_pspr.c
// Ty 03/07/98 - end deh externals

// a weapon is found with two clip loads,
// a big item has five clip loads
int maxammo[NUMAMMO]  = {200, 50, 300, 50};
int clipammo[NUMAMMO] = { 10,  4,  20,  1};

//
// GET STUFF
//

//
// P_GiveAmmo
// Num is the number of clip loads,
// not the individual count (0= 1/2 clip).
// Returns false if the ammo can't be picked up at all
//

boolean P_GiveAmmo(player_t *player, ammotype_t ammo, int num)
{
   int oldammo;

   if (ammo == am_noammo)
      return false;

#ifdef RANGECHECK
   if (ammo < 0 || ammo > NUMAMMO)
      I_Error ("P_GiveAmmo: bad type %i", ammo);
#endif

   if ( player->ammo[ammo] == player->maxammo[ammo]  )
      return false;

   if (num)
      num *= clipammo[ammo];
   else
      num = clipammo[ammo]/2;

   // give double ammo in trainer mode, you'll need in nightmare
   if (gameskill == sk_baby || gameskill == sk_nightmare)
      num <<= 1;

   oldammo = player->ammo[ammo];
   player->ammo[ammo] += num;

   if (player->ammo[ammo] > player->maxammo[ammo])
      player->ammo[ammo] = player->maxammo[ammo];

   // If non zero ammo, don't change up weapons, player was lower on purpose.
   if (oldammo)
      return true;

   // We were down to zero, so select a new weapon.
   // Preferences are not user selectable.

   switch (ammo)
   {
   case am_clip:
      if (player->readyweapon == wp_fist) {
         if (player->weaponowned[wp_chaingun])
            player->pendingweapon = wp_chaingun;
         else
            player->pendingweapon = wp_pistol;
      }
      break;

   case am_shell:
      if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol)
         if (player->weaponowned[wp_shotgun])
            player->pendingweapon = wp_shotgun;
      break;

   case am_cell:
      if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol)
         if (player->weaponowned[wp_plasma])
            player->pendingweapon = wp_plasma;
      break;

   case am_misl:
      if (player->readyweapon == wp_fist)
         if (player->weaponowned[wp_missile])
            player->pendingweapon = wp_missile;
   default:
      break;
   }
   return true;
}

//
// P_GiveWeapon
// The weapon name may have a MF_DROPPED flag ored in.
//

boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropped)
{
   boolean gaveammo;
   boolean gaveweapon;

   if (netgame && deathmatch!=2 && !dropped)
   {
      // leave placed weapons forever on net games
      if (player->weaponowned[weapon])
         return false;

      player->bonuscount += BONUSADD;
      player->weaponowned[weapon] = true;

      P_GiveAmmo(player, weaponinfo[weapon].ammo, deathmatch ? 5 : 2);

      player->pendingweapon = weapon;
      /* cph 20028/10 - for old-school DM addicts, allow old behavior
       * where only consoleplayer's pickup sounds are heard */
      if (!comp[comp_sound] || player == &players[consoleplayer])
         S_StartSound (player->mo, sfx_wpnup|PICKUP_SOUND); // killough 4/25/98
      return false;
   }

   if (weaponinfo[weapon].ammo != am_noammo)
   {
      // give one clip with a dropped weapon,
      // two clips with a found weapon
      gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, dropped ? 1 : 2);
   }
   else
      gaveammo = false;

   if (player->weaponowned[weapon])
      gaveweapon = false;
   else
   {
      gaveweapon = true;
      player->weaponowned[weapon] = true;
      player->pendingweapon = weapon;
   }
   return gaveweapon || gaveammo;
}

//
// P_GiveBody
// Returns false if the body isn't needed at all
//

boolean P_GiveBody(player_t *player, int num)
{
   if (player->health >= maxhealth)
      return false; // Ty 03/09/98 externalized MAXHEALTH to maxhealth
   player->health += num;
   if (player->health > maxhealth)
      player->health = maxhealth;
   player->mo->health = player->health;
   return true;
}

//
// P_GiveArmor
// Returns false if the armor is worse
// than the current armor.
//

boolean P_GiveArmor(player_t *player, int armortype)
{
   int hits = armortype*100;
   if (player->armorpoints >= hits)
      return false;   // don't pick up
   player->armortype = armortype;
   player->armorpoints = hits;
   return true;
}

//
// P_GiveCard
//

void P_GiveCard(player_t *player, card_t card)
{
   if (player->cards[card])
      return;
   player->bonuscount = BONUSADD;
   player->cards[card] = 1;
}

//
// P_GivePower
//
// Rewritten by Lee Killough
//

boolean P_GivePower(player_t *player, int power)
{
   static const int tics[NUMPOWERS] = {
                                         INVULNTICS, 1 /* strength */, INVISTICS,
                                         IRONTICS, 1 /* allmap */, INFRATICS,
                                      };

   switch (power)
   {
   case pw_invisibility:
      player->mo->flags |= MF_SHADOW;
      break;
   case pw_allmap:
      if (player->powers[pw_allmap])
         return false;
      break;
   case pw_strength:
      P_GiveBody(player,100);
      break;
   }

   // Unless player has infinite duration cheat, set duration (killough)

   if (player->powers[power] >= 0)
      player->powers[power] = tics[power];
   return true;
}

//
// P_TouchSpecialThing
//

void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
{
   player_t *player;
   int      i;
   int      sound;
   fixed_t  delta = special->z - toucher->z;

   if (delta > toucher->height || delta < -8*FRACUNIT)
      return;        // out of reach

   sound = sfx_itemup;
   player = toucher->player;

   // Dead thing touching.
   // Can happen with a sliding player corpse.
   if (toucher->health <= 0)
      return;

   // Identify by sprite.
   switch (special->sprite)
   {
      // armor
   case SPR_ARM1:
      if (!P_GiveArmor (player, green_armor_class))
         return;
      player->message = s_GOTARMOR; // Ty 03/22/98 - externalized
      break;

   case SPR_ARM2:
      if (!P_GiveArmor (player, blue_armor_class))
         return;
      player->message = s_GOTMEGA; // Ty 03/22/98 - externalized
      break;

      // bonus items
   case SPR_BON1:
      player->health++;               // can go over 100%
      if (player->health > (maxhealth * 2))
         player->health = (maxhealth * 2);
      player->mo->health = player->health;
      player->message = s_GOTHTHBONUS; // Ty 03/22/98 - externalized
      break;

   case SPR_BON2:
      player->armorpoints++;          // can go over 100%
      if (player->armorpoints > max_armor)
         player->armorpoints = max_armor;
      if (!player->armortype)
         player->armortype = green_armor_class;
      player->message = s_GOTARMBONUS; // Ty 03/22/98 - externalized
      break;

   case SPR_SOUL:
      player->health += soul_health;
      if (player->health > max_soul)
         player->health = max_soul;
      player->mo->health = player->health;
      player->message = s_GOTSUPER; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

   case SPR_MEGA:
      if (gamemode != commercial)
         return;
      player->health = mega_health;
      player->mo->health = player->health;
      P_GiveArmor (player,blue_armor_class);
      player->message = s_GOTMSPHERE; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

      // cards
      // leave cards for everyone
   case SPR_BKEY:
      if (!player->cards[it_bluecard])
         player->message = s_GOTBLUECARD; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_bluecard);
      if (!netgame)
         break;
      return;

   case SPR_YKEY:
      if (!player->cards[it_yellowcard])
         player->message = s_GOTYELWCARD; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_yellowcard);
      if (!netgame)
         break;
      return;

   case SPR_RKEY:
      if (!player->cards[it_redcard])
         player->message = s_GOTREDCARD; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_redcard);
      if (!netgame)
         break;
      return;

   case SPR_BSKU:
      if (!player->cards[it_blueskull])
         player->message = s_GOTBLUESKUL; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_blueskull);
      if (!netgame)
         break;
      return;

   case SPR_YSKU:
      if (!player->cards[it_yellowskull])
         player->message = s_GOTYELWSKUL; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_yellowskull);
      if (!netgame)
         break;
      return;

   case SPR_RSKU:
      if (!player->cards[it_redskull])
         player->message = s_GOTREDSKULL; // Ty 03/22/98 - externalized
      P_GiveCard (player, it_redskull);
      if (!netgame)
         break;
      return;

      // medikits, heals
   case SPR_STIM:
      if (!P_GiveBody (player, 10))
         return;
      player->message = s_GOTSTIM; // Ty 03/22/98 - externalized
      break;

   case SPR_MEDI:
      if (!P_GiveBody (player, 25))
         return;

      if (player->health < 50) // cph - 25 + the 25 just added, thanks to Quasar for reporting this bug
         player->message = s_GOTMEDINEED; // Ty 03/22/98 - externalized
      else
         player->message = s_GOTMEDIKIT; // Ty 03/22/98 - externalized
      break;


      // power ups
   case SPR_PINV:
      if (!P_GivePower (player, pw_invulnerability))
         return;
      player->message = s_GOTINVUL; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

   case SPR_PSTR:
      if (!P_GivePower (player, pw_strength))
         return;
      player->message = s_GOTBERSERK; // Ty 03/22/98 - externalized
      if (player->readyweapon != wp_fist)
         player->pendingweapon = wp_fist;
      sound = sfx_getpow;
      break;

   case SPR_PINS:
      if (!P_GivePower (player, pw_invisibility))
         return;
      player->message = s_GOTINVIS; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

   case SPR_SUIT:
      if (!P_GivePower (player, pw_ironfeet))
         return;
      player->message = s_GOTSUIT; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

   case SPR_PMAP:
      if (!P_GivePower (player, pw_allmap))
         return;
      player->message = s_GOTMAP; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

   case SPR_PVIS:
      if (!P_GivePower (player, pw_infrared))
         return;
      player->message = s_GOTVISOR; // Ty 03/22/98 - externalized
      sound = sfx_getpow;
      break;

      // ammo
   case SPR_CLIP:
      if (special->flags & MF_DROPPED)
      {
         if (!P_GiveAmmo (player,am_clip,0))
            return;
      }
      else
      {
         if (!P_GiveAmmo (player,am_clip,1))
            return;
      }
      player->message = s_GOTCLIP; // Ty 03/22/98 - externalized
      break;

   case SPR_AMMO:
      if (!P_GiveAmmo (player, am_clip,5))
         return;
      player->message = s_GOTCLIPBOX; // Ty 03/22/98 - externalized
      break;

   case SPR_ROCK:
      if (!P_GiveAmmo (player, am_misl,1))
         return;
      player->message = s_GOTROCKET; // Ty 03/22/98 - externalized
      break;

   case SPR_BROK:
      if (!P_GiveAmmo (player, am_misl,5))
         return;
      player->message = s_GOTROCKBOX; // Ty 03/22/98 - externalized
      break;

   case SPR_CELL:
      if (!P_GiveAmmo (player, am_cell,1))
         return;
      player->message = s_GOTCELL; // Ty 03/22/98 - externalized
      break;

   case SPR_CELP:
      if (!P_GiveAmmo (player, am_cell,5))
         return;
      player->message = s_GOTCELLBOX; // Ty 03/22/98 - externalized
      break;

   case SPR_SHEL:
      if (!P_GiveAmmo (player, am_shell,1))
         return;
      player->message = s_GOTSHELLS; // Ty 03/22/98 - externalized
      break;

   case SPR_SBOX:
      if (!P_GiveAmmo (player, am_shell,5))
         return;
      player->message = s_GOTSHELLBOX; // Ty 03/22/98 - externalized
      break;

   case SPR_BPAK:
      if (!player->backpack)
      {
         for (i=0 ; i<NUMAMMO ; i++)
            player->maxammo[i] *= 2;
         player->backpack = true;
      }
      for (i=0 ; i<NUMAMMO ; i++)
         P_GiveAmmo (player, i, 1);
      player->message = s_GOTBACKPACK; // Ty 03/22/98 - externalized
      break;

      // weapons
   case SPR_BFUG:
      if (!P_GiveWeapon (player, wp_bfg, false) )
         return;
      player->message = s_GOTBFG9000; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_MGUN:
      if (!P_GiveWeapon (player, wp_chaingun, (special->flags&MF_DROPPED)!=0) )
         return;
      player->message = s_GOTCHAINGUN; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_CSAW:
      if (!P_GiveWeapon (player, wp_chainsaw, false) )
         return;
      player->message = s_GOTCHAINSAW; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_LAUN:
      if (!P_GiveWeapon (player, wp_missile, false) )
         return;
      player->message = s_GOTLAUNCHER; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_PLAS:
      if (!P_GiveWeapon (player, wp_plasma, false) )
         return;
      player->message = s_GOTPLASMA; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_SHOT:
      if (!P_GiveWeapon (player, wp_shotgun, (special->flags&MF_DROPPED)!=0 ) )
         return;
      player->message = s_GOTSHOTGUN; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   case SPR_SGN2:
      if (!P_GiveWeapon(player, wp_supershotgun, (special->flags&MF_DROPPED)!=0))
         return;
      player->message = s_GOTSHOTGUN2; // Ty 03/22/98 - externalized
      sound = sfx_wpnup;
      break;

   default:
      I_Error ("P_SpecialThing: Unknown gettable thing");
   }

   if (special->flags & MF_COUNTITEM)
      player->itemcount++;
   P_RemoveMobj (special);
   player->bonuscount += BONUSADD;

   /* cph 20028/10 - for old-school DM addicts, allow old behavior
    * where only consoleplayer's pickup sounds are heard */
   if (!comp[comp_sound] || player == &players[consoleplayer])
      S_StartSound (player->mo, sound | PICKUP_SOUND);   // killough 4/25/98
}

//
// KillMobj
//
// killough 11/98: make static
static void P_KillMobj(mobj_t *source, mobj_t *target)
{
   mobjtype_t item;
   mobj_t     *mo;

   target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY);

   if (target->type != MT_SKULL)
      target->flags &= ~MF_NOGRAVITY;

   target->flags |= MF_CORPSE|MF_DROPOFF;
   target->height >>= 2;

   if (!((target->flags ^ MF_COUNTKILL) & (MF_FRIEND | MF_COUNTKILL)))
      totallive--;

   if (source && source->player)
   {
      // count for intermission
      if (target->flags & MF_COUNTKILL)
         source->player->killcount++;
      if (target->player)
         source->player->frags[target->player-players]++;
   }
   else
      if (target->flags & MF_COUNTKILL) { /* Add to kills tally */
         if ((compatibility_level < lxdoom_1_compatibility) || !netgame) {
            if (!netgame)
               // count all monster deaths,
               // even those caused by other monsters
               players[0].killcount++;
         } else
            if (!deathmatch) {
               // try and find a player to give the kill to, otherwise give the
               // kill to a random player.  this fixes the missing monsters bug
               // in coop - rain
               // CPhipps - not a bug as such, but certainly an inconsistency.
               if (target->lastenemy && target->lastenemy->health > 0
                     && target->lastenemy->player) // Fighting a player
                  target->lastenemy->player->killcount++;
               else {
                  // cph - randomely choose a player in the game to be credited
                  //  and do it uniformly between the active players
                  unsigned int activeplayers = 0, player, i;

                  for (player = 0; player<MAXPLAYERS; player++)
                     if (playeringame[player])
                        activeplayers++;

                  if (activeplayers) {
                     player = P_Random(pr_friends) % activeplayers;

                     for (i=0; i<MAXPLAYERS; i++)
                        if (playeringame[i])
                           if (!player--)
                              players[i].killcount++;
                  }
               }
            }
      }

   if (target->player)
   {
      // count environment kills against you
      if (!source)
         target->player->frags[target->player-players]++;

      target->flags &= ~MF_SOLID;
      target->player->playerstate = PST_DEAD;
      P_DropWeapon (target->player);

      if (target->player == &players[consoleplayer] && (automapmode & am_active))
         AM_Stop();    // don't die in auto map; switch view prior to dying
   }

   if (target->health < -target->info->spawnhealth && target->info->xdeathstate)
      P_SetMobjState (target, target->info->xdeathstate);
   else
      P_SetMobjState (target, target->info->deathstate);

   target->tics -= P_Random(pr_killtics)&3;

   if (target->tics < 1)
      target->tics = 1;

   // Drop stuff.
   // This determines the kind of object spawned
   // during the death frame of a thing.

   switch (target->type)
   {
   case MT_WOLFSS:
   case MT_POSSESSED:
      item = MT_CLIP;
      break;

   case MT_SHOTGUY:
      item = MT_SHOTGUN;
      break;

   case MT_CHAINGUY:
      item = MT_CHAINGUN;
      break;

   default:
      return;
   }

   mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item);
   mo->flags |= MF_DROPPED;    // special versions of items
}

//
// P_DamageMobj
// Damages both enemies and players
// "inflictor" is the thing that caused the damage
//  creature or missile, can be NULL (slime, etc)
// "source" is the thing to target after taking damage
//  creature or NULL
// Source and inflictor are the same for melee attacks.
// Source can be NULL for slime, barrel explosions
// and other environmental stuff.
//

void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage)
{
   player_t *player;
   boolean justhit;          /* killough 11/98 */

   /* killough 8/31/98: allow bouncers to take damage */
   if (!(target->flags & (MF_SHOOTABLE | MF_BOUNCES)))
      return; // shouldn't happen...

   if (target->health <= 0)
      return;

   /* proff 11/22/98: Andy Baker's Stealth monsters */
   if (target->flags & MF_STEALTH)
      P_BecomeVisible(target);

   if (target->flags & MF_SKULLFLY)
      target->momx = target->momy = target->momz = 0;

   player = target->player;
   if (player && gameskill == sk_baby)
      damage >>= 1;   // take half damage in trainer mode

   // Some close combat weapons should not
   // inflict thrust and push the victim out of reach,
   // thus kick away unless using the chainsaw.

   if (inflictor && !(target->flags & MF_NOCLIP) &&
         (!source || !source->player ||
          source->player->readyweapon != wp_chainsaw))
   {
      unsigned ang = R_PointToAngle2 (inflictor->x, inflictor->y,
                                      target->x,    target->y);

      fixed_t thrust = damage*(FRACUNIT>>3)*100/target->info->mass;

      // make fall forwards sometimes
      if ( damage < 40 && damage > target->health
            && target->z - inflictor->z > 64*FRACUNIT
            && P_Random(pr_damagemobj) & 1)
      {
         ang += ANG180;
         thrust *= 4;
      }

      ang >>= ANGLETOFINESHIFT;
      target->momx += FixedMul (thrust, finecosine[ang]);
      target->momy += FixedMul (thrust, finesine[ang]);

      /* killough 11/98: thrust objects hanging off ledges */
      if (target->intflags & MIF_FALLING && target->gear >= MAXGEAR)
         target->gear = 0;
   }

   // player specific
   if (player)
   {
      // end of game hell hack
      if (target->subsector->sector->special == 11 && damage >= target->health)
         damage = target->health - 1;

      // Below certain threshold,
      // ignore damage in GOD mode, or with INVUL power.
      // killough 3/26/98: make god mode 100% god mode in non-compat mode

      if ((damage < 1000 || (!comp[comp_god] && (player->cheats&CF_GODMODE))) &&
            (player->cheats&CF_GODMODE || player->powers[pw_invulnerability]))
         return;

      if (player->armortype)
      {
         int saved = player->armortype == 1 ? damage/3 : damage/2;
         if (player->armorpoints <= saved)
         {
            // armor is used up
            saved = player->armorpoints;
            player->armortype = 0;
         }
         player->armorpoints -= saved;
         damage -= saved;
      }

      player->health -= damage;       // mirror mobj health here for Dave
      if (player->health < 0)
         player->health = 0;

      player->attacker = source;
      player->damagecount += damage;  // add damage after armor / invuln

      if (player->damagecount > 100)
         player->damagecount = 100;  // teleport stomp does 10k points...
   }

   // do the damage
   target->health -= damage;
   if (target->health <= 0)
   {
      P_KillMobj (source, target);
      return;
   }

   // killough 9/7/98: keep track of targets so that friends can help friends
   if (mbf_features)
   {
      /* If target is a player, set player's target to source,
       * so that a friend can tell who's hurting a player
       */
      if (player)
         P_SetTarget(&target->target, source);

      /* killough 9/8/98:
       * If target's health is less than 50%, move it to the front of its list.
       * This will slightly increase the chances that enemies will choose to
       * "finish it off", but its main purpose is to alert friends of danger.
       */
      if (target->health*2 < target->info->spawnhealth)
      {
         thinker_t *cap = &thinkerclasscap[target->flags & MF_FRIEND ?
                                           th_friends : th_enemies];
         (target->thinker.cprev->cnext = target->thinker.cnext)->cprev =
            target->thinker.cprev;
         (target->thinker.cnext = cap->cnext)->cprev = &target->thinker;
         (target->thinker.cprev = cap)->cnext = &target->thinker;
      }
   }

   if ((justhit = (P_Random (pr_painchance) < target->info->painchance &&
                   !(target->flags & MF_SKULLFLY)))) //killough 11/98: see below
      P_SetMobjState(target, target->info->painstate);

   target->reactiontime = 0;           // we're awake now...

   /* killough 9/9/98: cleaned up, made more consistent: */

   if (source && source != target && source->type != MT_VILE &&
         (!target->threshold || target->type == MT_VILE) &&
         ((source->flags ^ target->flags) & MF_FRIEND ||
          monster_infighting ||
          !mbf_features))
   {
      /* if not intent on another player, chase after this one
       *
       * killough 2/15/98: remember last enemy, to prevent
       * sleeping early; 2/21/98: Place priority on players
       * killough 9/9/98: cleaned up, made more consistent:
       */

      if (!target->lastenemy || target->lastenemy->health <= 0 ||
            (!mbf_features ?
             !target->lastenemy->player :
             !((target->flags ^ target->lastenemy->flags) & MF_FRIEND) &&
             target->target != source)) // remember last enemy - killough
         P_SetTarget(&target->lastenemy, target->target);

      P_SetTarget(&target->target, source);       // killough 11/98
      target->threshold = BASETHRESHOLD;
      if (target->state == &states[target->info->spawnstate]
            && target->info->seestate != S_NULL)
         P_SetMobjState (target, target->info->seestate);
   }

   /* killough 11/98: Don't attack a friend, unless hit by that friend. */
   if (justhit && (target->target == source || !target->target ||
                   !(target->flags & target->target->flags & MF_FRIEND)))
      target->flags |= MF_JUSTHIT;    // fight back!
}
