/* 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:
 *      Moving object handling. Spawn functions.
 *
 *-----------------------------------------------------------------------------*/

#include "doomdef.h"
#include "doomstat.h"
#include "m_random.h"
#include "r_main.h"
#include "p_maputl.h"
#include "p_map.h"
#include "p_tick.h"
#include "sounds.h"
#include "st_stuff.h"
#include "hu_stuff.h"
#include "s_sound.h"
#include "info.h"
#include "g_game.h"
#include "p_inter.h"
#include "rockmacros.h"
//
// P_SetMobjState
// Returns true if the mobj is still present.
//

statenum_t i IBSS_ATTR;                       // initial state
statenum_t seenstate_tab[NUMSTATES] IBSS_ATTR; // fast transition table

boolean P_SetMobjState(mobj_t* mobj,statenum_t state)
{
   state_t*  st;
   // killough 4/9/98: remember states seen, to detect cycles:
   statenum_t *seenstate = seenstate_tab;      // pointer to table
   static int recursion;                       // detects recursion
   i = state;
   boolean ret = true;                         // return value

   if (recursion++)                            // if recursion detected,
      memset(seenstate,0,sizeof(seenstate_tab)); // clear state table

   do
   {
      if (state == S_NULL)
      {
         mobj->state = (state_t *) S_NULL;
         P_RemoveMobj (mobj);
         ret = false;
         break;                 // killough 4/9/98
      }

      st = &states[state];
      mobj->state = st;
      mobj->tics = st->tics;
      mobj->sprite = st->sprite;
      mobj->frame = st->frame;

      // Modified handling.
      // Call action functions when the state is set

      if (st->action)
         st->action(mobj);

      seenstate[state] = 1 + st->nextstate;   // killough 4/9/98

      state = st->nextstate;
   } while (!mobj->tics && !seenstate[state]);   // killough 4/9/98

   if (ret && !mobj->tics)  // killough 4/9/98: detect state cycles
      doom_printf("Warning: State Cycle Detected");

   if (!--recursion)
      for (;(state=seenstate[i]);i=state-1)
         seenstate[i] = 0;  // killough 4/9/98: erase memory of states

   return ret;
}


//
// P_ExplodeMissile
//

void P_ExplodeMissile (mobj_t* mo)
{
   mo->momx = mo->momy = mo->momz = 0;

   P_SetMobjState (mo, mobjinfo[mo->type].deathstate);

   mo->tics -= P_Random(pr_explode)&3;

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

   mo->flags &= ~MF_MISSILE;

   if (mo->info->deathsound)
      S_StartSound (mo, mo->info->deathsound);
}


//
// P_XYMovement
//
// Attempts to move something if it has momentum.
//

void P_XYMovement (mobj_t* mo)
{
   player_t *player;
   fixed_t xmove, ymove;
#if 0
   fixed_t   ptryx;
   fixed_t   ptryy;
   fixed_t   xmove;
   fixed_t   ymove;
   fixed_t   oldx,oldy; // phares 9/10/98: reducing bobbing/momentum on ice
   // when up against walls
#endif
   if (!(mo->momx | mo->momy)) // Any momentum?
   {
      if (mo->flags & MF_SKULLFLY)
      {

         // the skull slammed into something

         mo->flags &= ~MF_SKULLFLY;
         mo->momz = 0;

         P_SetMobjState (mo, mo->info->spawnstate);
      }
      return;
   }

   player = mo->player;

   if (mo->momx > MAXMOVE)
      mo->momx = MAXMOVE;
   else if (mo->momx < -MAXMOVE)
      mo->momx = -MAXMOVE;

   if (mo->momy > MAXMOVE)
      mo->momy = MAXMOVE;
   else if (mo->momy < -MAXMOVE)
      mo->momy = -MAXMOVE;

   xmove = mo->momx;
   ymove = mo->momy;

#if 0
   oldx = mo->x; // phares 9/10/98: new code to reduce bobbing/momentum
   oldy = mo->y; // when on ice & up against wall. These will be compared
   // to your x,y values later to see if you were able to move
#endif

   do
   {
      fixed_t ptryx, ptryy;
      // killough 8/9/98: fix bug in original Doom source:
      // Large negative displacements were never considered.
      // This explains the tendency for Mancubus fireballs
      // to pass through walls.
      // CPhipps - compatibility optioned

      if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2 ||
            (!comp[comp_moveblock]
             && (xmove < -MAXMOVE/2 || ymove < -MAXMOVE/2)))
      {
         ptryx = mo->x + xmove/2;
         ptryy = mo->y + ymove/2;
         xmove >>= 1;
         ymove >>= 1;
      }
      else
      {
         ptryx = mo->x + xmove;
         ptryy = mo->y + ymove;
         xmove = ymove = 0;
      }

      // killough 3/15/98: Allow objects to drop off

      if (!P_TryMove (mo, ptryx, ptryy, true))
      {
         // blocked move

         // killough 8/11/98: bouncing off walls
         // killough 10/98:
         // Add ability for objects other than players to bounce on ice

         if (!(mo->flags & MF_MISSILE) &&
               mbf_features &&
               (mo->flags & MF_BOUNCES ||
                (!player && blockline &&
                 variable_friction && mo->z <= mo->floorz &&
                 P_GetFriction(mo, NULL) > ORIG_FRICTION)))
         {
            if (blockline)
            {
               fixed_t r = ((blockline->dx >> FRACBITS) * mo->momx +
                            (blockline->dy >> FRACBITS) * mo->momy) /
                           ((blockline->dx >> FRACBITS)*(blockline->dx >> FRACBITS)+
                            (blockline->dy >> FRACBITS)*(blockline->dy >> FRACBITS));
               fixed_t x = FixedMul(r, blockline->dx);
               fixed_t y = FixedMul(r, blockline->dy);

               // reflect momentum away from wall

               mo->momx = x*2 - mo->momx;
               mo->momy = y*2 - mo->momy;

               // if under gravity, slow down in
               // direction perpendicular to wall.

               if (!(mo->flags & MF_NOGRAVITY))
               {
                  mo->momx = (mo->momx + x)/2;
                  mo->momy = (mo->momy + y)/2;
               }
            }
            else
               mo->momx = mo->momy = 0;
         }
         else
            if (player)   // try to slide along it
               P_SlideMove (mo);
            else
               if (mo->flags & MF_MISSILE)
               {
                  // explode a missile

                  if (ceilingline &&
                        ceilingline->backsector &&
                        ceilingline->backsector->ceilingpic == skyflatnum)
                     if (demo_compatibility ||  // killough
                           mo->z > ceilingline->backsector->ceilingheight)
                     {
                        // Hack to prevent missiles exploding
                        // against the sky.
                        // Does not handle sky floors.

                        P_RemoveMobj (mo);
                        return;
                     }
                  P_ExplodeMissile (mo);
               }
               else // whatever else it is, it is now standing still in (x,y)
                  mo->momx = mo->momy = 0;
      }
   } while (xmove || ymove);

   // slow down

#if 0  /* killough 10/98: this is unused code (except maybe in .deh files?) */
   if (player && player->cheats & CF_NOMOMENTUM)
   {
      // debug option for no sliding at all
      mo->momx = mo->momy = 0;
      player->momx = player->momy = 0;         /* killough 10/98 */
      return;
   }
#endif

   /* no friction for missiles or skulls ever, no friction when airborne */
   if (mo->flags & (MF_MISSILE | MF_SKULLFLY) || mo->z > mo->floorz)
      return;

   /* killough 8/11/98: add bouncers
    * killough 9/15/98: add objects falling off ledges
    * killough 11/98: only include bouncers hanging off ledges
    */
   if (((mo->flags & MF_BOUNCES && mo->z > mo->dropoffz) ||
         mo->flags & MF_CORPSE || mo->intflags & MIF_FALLING) &&
         (mo->momx > FRACUNIT/4 || mo->momx < -FRACUNIT/4 ||
          mo->momy > FRACUNIT/4 || mo->momy < -FRACUNIT/4) &&
         mo->floorz != mo->subsector->sector->floorheight)
      return;  // do not stop sliding if halfway off a step with some momentum

   // killough 11/98:
   // Stop voodoo dolls that have come to rest, despite any
   // moving corresponding player, except in old demos:

   if (mo->momx > -STOPSPEED && mo->momx < STOPSPEED &&
         mo->momy > -STOPSPEED && mo->momy < STOPSPEED &&
         (!player || !(player->cmd.forwardmove | player->cmd.sidemove) ||
          (player->mo != mo && compatibility_level >= lxdoom_1_compatibility)))
   {
      // if in a walking frame, stop moving

      // killough 10/98:
      // Don't affect main player when voodoo dolls stop, except in old demos:

      if (player && (unsigned)(player->mo->state - states - S_PLAY_RUN1) < 4
            && (player->mo == mo || compatibility_level >= lxdoom_1_compatibility))
         P_SetMobjState(player->mo, S_PLAY);

      mo->momx = mo->momy = 0;

      /* killough 10/98: kill any bobbing momentum too (except in voodoo dolls)
       * cph - DEMOSYNC - needs compatibility check?
       */
      if (player && player->mo == mo)
         player->momx = player->momy = 0;
   }
   else
   {
      /* phares 3/17/98
       *
       * Friction will have been adjusted by friction thinkers for
       * icy or muddy floors. Otherwise it was never touched and
       * remained set at ORIG_FRICTION
       *
       * killough 8/28/98: removed inefficient thinker algorithm,
       * instead using touching_sectorlist in P_GetFriction() to
       * determine friction (and thus only when it is needed).
       *
       * killough 10/98: changed to work with new bobbing method.
       * Reducing player momentum is no longer needed to reduce
       * bobbing, so ice works much better now.
       *
       * cph - DEMOSYNC - need old code for Boom demos?
       */

      fixed_t friction = P_GetFriction(mo, NULL);

      mo->momx = FixedMul(mo->momx, friction);
      mo->momy = FixedMul(mo->momy, friction);

      /* killough 10/98: Always decrease player bobbing by ORIG_FRICTION.
       * This prevents problems with bobbing on ice, where it was not being
       * reduced fast enough, leading to all sorts of kludges being developed.
       */

      if (player && player->mo == mo)     /* Not voodoo dolls */
      {
         player->momx = FixedMul(player->momx, ORIG_FRICTION);
         player->momy = FixedMul(player->momy, ORIG_FRICTION);
      }

   }
}


//
// P_ZMovement
//
// Attempt vertical movement.

static void P_ZMovement (mobj_t* mo)
{
   /* killough 7/11/98:
    * BFG fireballs bounced on floors and ceilings in Pre-Beta Doom
    * killough 8/9/98: added support for non-missile objects bouncing
    * (e.g. grenade, mine, pipebomb)
    */

   if (mo->flags & MF_BOUNCES && mo->momz) {
      mo->z += mo->momz;
      if (mo->z <= mo->floorz) {                /* bounce off floors */
         mo->z = mo->floorz;
         if (mo->momz < 0) {
            mo->momz = -mo->momz;
            if (!(mo->flags & MF_NOGRAVITY)) { /* bounce back with decay */
               mo->momz = mo->flags & MF_FLOAT ?   // floaters fall slowly
                          mo->flags & MF_DROPOFF ?          // DROPOFF indicates rate
                          FixedMul(mo->momz, (fixed_t)(FRACUNIT*.85)) :
                          FixedMul(mo->momz, (fixed_t)(FRACUNIT*.70)) :
                                FixedMul(mo->momz, (fixed_t)(FRACUNIT*.45)) ;

               /* Bring it to rest below a certain speed */
               if (abs(mo->momz) <= mo->info->mass*(GRAVITY*4/256))
                  mo->momz = 0;
            }

            /* killough 11/98: touchy objects explode on impact */
            if (mo->flags & MF_TOUCHY && mo->intflags & MIF_ARMED
                  && mo->health > 0)
               P_DamageMobj(mo, NULL, NULL, mo->health);
            else if (mo->flags & MF_FLOAT && sentient(mo))
               goto floater;
            return;
         }
} else if (mo->z >= mo->ceilingz - mo->height) {
         /* bounce off ceilings */
         mo->z = mo->ceilingz - mo->height;
         if (mo->momz > 0) {
            if (mo->subsector->sector->ceilingpic != skyflatnum)
               mo->momz = -mo->momz;    /* always bounce off non-sky ceiling */
            else if (mo->flags & MF_MISSILE)
               P_RemoveMobj(mo);        /* missiles don't bounce off skies */
            else if (mo->flags & MF_NOGRAVITY)
               mo->momz = -mo->momz; // bounce unless under gravity

            if (mo->flags & MF_FLOAT && sentient(mo))
               goto floater;

            return;
         }
      } else {
         if (!(mo->flags & MF_NOGRAVITY))      /* free-fall under gravity */
            mo->momz -= mo->info->mass*(GRAVITY/256);

         if (mo->flags & MF_FLOAT && sentient(mo)) goto floater;
         return;
      }

      /* came to a stop */
      mo->momz = 0;

      if (mo->flags & MF_MISSILE) {
         if (ceilingline &&
               ceilingline->backsector &&
               ceilingline->backsector->ceilingpic == skyflatnum &&
               mo->z > ceilingline->backsector->ceilingheight)
            P_RemoveMobj(mo);  /* don't explode on skies */
         else
            P_ExplodeMissile(mo);
      }

      if (mo->flags & MF_FLOAT && sentient(mo)) goto floater;
      return;
   }

   /* killough 8/9/98: end bouncing object code */

   // check for smooth step up

   if (mo->player &&
         mo->player->mo == mo &&  // killough 5/12/98: exclude voodoo dolls
         mo->z < mo->floorz)
   {
      mo->player->viewheight -= mo->floorz-mo->z;
      mo->player->deltaviewheight = (VIEWHEIGHT - mo->player->viewheight)>>3;
   }

   // adjust altitude

   mo->z += mo->momz;

floater:
   if ((mo->flags & MF_FLOAT) && mo->target)

      // float down towards target if too close

      if (!((mo->flags ^ MF_FLOAT) & (MF_FLOAT | MF_SKULLFLY | MF_INFLOAT)) &&
            mo->target)     /* killough 11/98: simplify */
      {
         fixed_t delta;
         if (P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y) <
               abs(delta = mo->target->z + (mo->height>>1) - mo->z)*3)
            mo->z += delta < 0 ? -FLOATSPEED : FLOATSPEED;
      }

   // clip movement

   if (mo->z <= mo->floorz)
   {
      // hit the floor

      /* Note (id):
       *  somebody left this after the setting momz to 0,
       *  kinda useless there.
       * cph - This was the a bug in the linuxdoom-1.10 source which
       *  caused it not to sync Doom 2 v1.9 demos. Someone
       *  added the above comment and moved up the following code. So
       *  demos would desync in close lost soul fights.
       * Note that this only applies to original Doom 1 or Doom2 demos -
       *  Final Doom and Ultimate Doom.  So we test demo_compatibility *and*
       *  gamemission. (Note we assume that Doom1 is always Ult Doom, which
       *  seems to hold for most published demos.)
       */
      int correct_lost_soul_bounce = !demo_compatibility || (gamemission != doom2);

      if (correct_lost_soul_bounce && mo->flags & MF_SKULLFLY)
         mo->momz = -mo->momz; // the skull slammed into something

      if (mo->momz < 0)
      {
         /* killough 11/98: touchy objects explode on impact */
         if (mo->flags & MF_TOUCHY && mo->intflags & MIF_ARMED && mo->health > 0)
            P_DamageMobj(mo, NULL, NULL, mo->health);
         else
            if (mo->player && /* killough 5/12/98: exclude voodoo dolls */
                  mo->player->mo == mo && mo->momz < -GRAVITY*8)
            {
               // Squat down.
               // Decrease viewheight for a moment
               // after hitting the ground (hard),
               // and utter appropriate sound.

               mo->player->deltaviewheight = mo->momz>>3;
               if (mo->health) /* cph - prevent "oof" when dead */
                  S_StartSound (mo, sfx_oof);
            }
         mo->momz = 0;
      }
      mo->z = mo->floorz;

      /* cph 2001/05/26 -
       * See lost soul bouncing comment above. We need this here for bug
       * compatibility with original Doom2 v1.9 - if a soul is charging and
       * hit by a raising floor this incorrectly reverses its Y momentum.
       */
      if (!correct_lost_soul_bounce && mo->flags & MF_SKULLFLY)
         mo->momz = -mo->momz; // the skull slammed into something

      if ( (mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP) )
      {
         P_ExplodeMissile (mo);
         return;
      }
   }
   else // still above the floor                                     // phares
      if (!(mo->flags & MF_NOGRAVITY))
      {
         if (!mo->momz)
            mo->momz = -GRAVITY;
         mo->momz -= GRAVITY;
      }

   if (mo->z + mo->height > mo->ceilingz)
   {

      // hit the ceiling

      if (mo->momz > 0)
         mo->momz = 0;

      mo->z = mo->ceilingz - mo->height;

      if (mo->flags & MF_SKULLFLY)
         mo->momz = -mo->momz; // the skull slammed into something

      if ( (mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP) )
      {
         P_ExplodeMissile (mo);
         return;
      }
   }
}

//
// P_NightmareRespawn
//

void P_NightmareRespawn(mobj_t* mobj)
{
   fixed_t      x;
   fixed_t      y;
   fixed_t      z;
   subsector_t* ss;
   mobj_t*      mo;
   mapthing_t*  mthing;

   x = mobj->spawnpoint.x << FRACBITS;
   y = mobj->spawnpoint.y << FRACBITS;

   /* haleyjd: stupid nightmare respawning bug fix
    *
    * 08/09/00: compatibility added, time to ramble :)
    * This fixes the notorious nightmare respawning bug that causes monsters
    * that didn't spawn at level startup to respawn at the point (0,0)
    * regardless of that point's nature. SMMU and Eternity need this for
    * script-spawned things like Halif Swordsmythe, as well.
    *
    * cph - copied from eternity, except comp_respawnfix becomes comp_respawn
    *   and the logic is reversed (i.e. like the rest of comp_ it *disables*
    *   the fix)
    */
   if(!comp[comp_respawn] && !x && !y)
   {
      // spawnpoint was zeroed out, so use point of death instead
      x = mobj->x;
      y = mobj->y;
   }

   // something is occupying its position?

   if (!P_CheckPosition (mobj, x, y) )
      return; // no respwan

   // spawn a teleport fog at old spot
   // because of removal of the body?

   mo = P_SpawnMobj (mobj->x,
                     mobj->y,
                     mobj->subsector->sector->floorheight,
                     MT_TFOG);

   // initiate teleport sound

   S_StartSound (mo, sfx_telept);

   // spawn a teleport fog at the new spot

   ss = R_PointInSubsector (x,y);

   mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_TFOG);

   S_StartSound (mo, sfx_telept);

   // spawn the new monster

   mthing = &mobj->spawnpoint;
   if (mobj->info->flags & MF_SPAWNCEILING)
      z = ONCEILINGZ;
   else
      z = ONFLOORZ;

   // inherit attributes from deceased one

   mo = P_SpawnMobj (x,y,z, mobj->type);
   mo->spawnpoint = mobj->spawnpoint;
   mo->angle = ANG45 * (mthing->angle/45);

   if (mthing->options & MTF_AMBUSH)
      mo->flags |= MF_AMBUSH;

   /* killough 11/98: transfer friendliness from deceased */
   mo->flags = (mo->flags & ~MF_FRIEND) | (mobj->flags & MF_FRIEND);

   mo->reactiontime = 18;

   // remove the old monster,

   P_RemoveMobj (mobj);
}


//
// P_MobjThinker
//

void P_MobjThinker (mobj_t* mobj)
{
   // killough 11/98:
   // removed old code which looked at target references
   // (we use pointer reference counting now)

   // momentum movement
   if (mobj->momx | mobj->momy || mobj->flags & MF_SKULLFLY)
   {
      P_XYMovement(mobj);
      if (mobj->thinker.function != P_MobjThinker) // cph - Must've been removed
         return;       // killough - mobj was removed
   }

   if (mobj->z != mobj->floorz || mobj->momz)
   {
      P_ZMovement(mobj);
      if (mobj->thinker.function != P_MobjThinker) // cph - Must've been removed
         return;       // killough - mobj was removed
   }
   else
      if (!(mobj->momx | mobj->momy) && !sentient(mobj))
      {                                  // non-sentient objects at rest
         mobj->intflags |= MIF_ARMED;     // arm a mine which has come to rest

         // killough 9/12/98: objects fall off ledges if they are hanging off
         // slightly push off of ledge if hanging more than halfway off

         if (mobj->z > mobj->dropoffz &&      // Only objects contacting dropoff
               !(mobj->flags & MF_NOGRAVITY) && // Only objects which fall
               !comp[comp_falloff]) // Not in old demos
            P_ApplyTorque(mobj);               // Apply torque
         else
            mobj->intflags &= ~MIF_FALLING, mobj->gear = 0;  // Reset torque
      }

   // cycle through states,
   // calling action functions at transitions

   if (mobj->tics != -1)
   {
      mobj->tics--;

      // you can cycle through multiple states in a tic

      if (!mobj->tics)
         if (!P_SetMobjState (mobj, mobj->state->nextstate) )
            return;     // freed itself
   }
   else
   {

      // check for nightmare respawn

      if (! (mobj->flags & MF_COUNTKILL) )
         return;

      if (!respawnmonsters)
         return;

      mobj->movecount++;

      if (mobj->movecount < 12*35)
         return;

      if (leveltime & 31)
         return;

      if (P_Random (pr_respawn) > 4)
         return;

      P_NightmareRespawn (mobj);
   }

}


//
// P_SpawnMobj
//
mobj_t* P_SpawnMobj(fixed_t x,fixed_t y,fixed_t z,mobjtype_t type)
{
   mobj_t*     mobj;
   state_t*    st;
   mobjinfo_t* info;

   mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL);
   memset (mobj, 0, sizeof (*mobj));
   info = &mobjinfo[type];
   mobj->type = type;
   mobj->info = info;
   mobj->x = x;
   mobj->y = y;
   mobj->radius = info->radius;
   mobj->height = info->height;                                      // phares
   mobj->flags  = info->flags;

   /* killough 8/23/98: no friends, bouncers, or touchy things in old demos */
   if (!mbf_features)
      mobj->flags &= ~(MF_BOUNCES | MF_FRIEND | MF_TOUCHY);
   else
      if (type == MT_PLAYER)         // Except in old demos, players
         mobj->flags |= MF_FRIEND;    // are always friends.

   mobj->health = info->spawnhealth;

   if (gameskill != sk_nightmare)
      mobj->reactiontime = info->reactiontime;

   mobj->lastlook = P_Random (pr_lastlook) % MAXPLAYERS;

   // do not set the state with P_SetMobjState,
   // because action routines can not be called yet

   st = &states[info->spawnstate];

   mobj->state  = st;
   mobj->tics   = st->tics;
   mobj->sprite = st->sprite;
   mobj->frame  = st->frame;
   mobj->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98

   // set subsector and/or block links

   P_SetThingPosition (mobj);

   mobj->dropoffz =           /* killough 11/98: for tracking dropoffs */
      mobj->floorz   = mobj->subsector->sector->floorheight;
   mobj->ceilingz = mobj->subsector->sector->ceilingheight;

   mobj->z = z == ONFLOORZ ? mobj->floorz : z == ONCEILINGZ ?
             mobj->ceilingz - mobj->height : z;

   mobj->thinker.function = P_MobjThinker;
   mobj->above_thing = 0;                                            // phares
   mobj->below_thing = 0;                                            // phares

   mobj->target = mobj->tracer = mobj->lastenemy = NULL;
   P_AddThinker (&mobj->thinker);
   if (!((mobj->flags ^ MF_COUNTKILL) & (MF_FRIEND | MF_COUNTKILL)))
      totallive++;
   return mobj;
}


mapthing_t itemrespawnque[ITEMQUESIZE];
int        itemrespawntime[ITEMQUESIZE];
int        iquehead;
int        iquetail;


//
// P_RemoveMobj
//

void P_RemoveMobj (mobj_t* mobj)
{
   if ((mobj->flags & MF_SPECIAL)
         && !(mobj->flags & MF_DROPPED)
         && (mobj->type != MT_INV)
         && (mobj->type != MT_INS))
   {
      itemrespawnque[iquehead] = mobj->spawnpoint;
      itemrespawntime[iquehead] = leveltime;
      iquehead = (iquehead+1)&(ITEMQUESIZE-1);

      // lose one off the end?

      if (iquehead == iquetail)
         iquetail = (iquetail+1)&(ITEMQUESIZE-1);
   }

   // unlink from sector and block lists

   P_UnsetThingPosition (mobj);

   // Delete all nodes on the current sector_list               phares 3/16/98

   if (sector_list)
   {
      P_DelSeclist(sector_list);
      sector_list = NULL;
   }

   // stop any playing sound

   S_StopSound (mobj);

   // killough 11/98:
   //
   // Remove any references to other mobjs.
   //
   // Older demos might depend on the fields being left alone, however,
   // if multiple thinkers reference each other indirectly before the
   // end of the current tic.
   // CPhipps - only leave dead references in old demos; I hope lxdoom_1 level
   // demos are rare and don't rely on this. I hope.

   if ((compatibility_level >= lxdoom_1_compatibility) ||
         (!demorecording && !demoplayback)) {
      P_SetTarget(&mobj->target,    NULL);
      P_SetTarget(&mobj->tracer,    NULL);
      P_SetTarget(&mobj->lastenemy, NULL);
   }
   // free block

   //  P_RemoveThinker ((thinker_t*)mobj);
   P_RemoveThinker (&mobj->thinker);
}


/*
 * P_FindDoomedNum
 *
 * Finds a mobj type with a matching doomednum
 *
 * killough 8/24/98: rewrote to use hashing
 */

int P_FindDoomedNum(unsigned type)
{
   static struct { int first, next; } *hash;
   register int i;

   if (!hash)
   {
      hash = Z_Malloc(sizeof (*hash) * NUMMOBJTYPES, PU_CACHE, (void*)(void*) &hash);
      for (i=0; i<NUMMOBJTYPES; i++)
         hash[i].first = NUMMOBJTYPES;
      for (i=0; i<NUMMOBJTYPES; i++)
         if (mobjinfo[i].doomednum != -1)
         {
            unsigned h = (unsigned) mobjinfo[i].doomednum % NUMMOBJTYPES;
            hash[i].next = hash[h].first;
            hash[h].first = i;
         }
   }

   i = hash[type % NUMMOBJTYPES].first;
   while ((i < NUMMOBJTYPES) && ((unsigned)mobjinfo[i].doomednum != type))
      i = hash[i].next;
   return i;
}

//
// P_RespawnSpecials
//

void P_RespawnSpecials (void)
{
   fixed_t       x;
   fixed_t       y;
   fixed_t       z;
   subsector_t*  ss;
   mobj_t*       mo;
   mapthing_t*   mthing;
   int           i;

   // only respawn items in deathmatch

   if (deathmatch != 2)
      return;

   // nothing left to respawn?

   if (iquehead == iquetail)
      return;

   // wait at least 30 seconds

   if (leveltime - itemrespawntime[iquetail] < 30*35)
      return;

   mthing = &itemrespawnque[iquetail];

   x = mthing->x << FRACBITS;
   y = mthing->y << FRACBITS;

   // spawn a teleport fog at the new spot

   ss = R_PointInSubsector (x,y);
   mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_IFOG);
   S_StartSound (mo, sfx_itmbk);

   // find which type to spawn

   /* killough 8/23/98: use table for faster lookup */
   i = P_FindDoomedNum(mthing->type);

   // spawn it

   if (mobjinfo[i].flags & MF_SPAWNCEILING)
      z = ONCEILINGZ;
   else
      z = ONFLOORZ;

   mo = P_SpawnMobj (x,y,z, i);
   mo->spawnpoint = *mthing;
   mo->angle = ANG45 * (mthing->angle/45);

   // pull it from the queue

   iquetail = (iquetail+1)&(ITEMQUESIZE-1);
}

//
// P_SpawnPlayer
// Called when a player is spawned on the level.
// Most of the player structure stays unchanged
//  between levels.
//

extern byte playernumtotrans[MAXPLAYERS];

void P_SpawnPlayer (mapthing_t* mthing)
{
   player_t* p;
   fixed_t   x;
   fixed_t   y;
   fixed_t   z;
   mobj_t*   mobj;
   int       i;

   // not playing?

   if (!playeringame[mthing->type-1])
      return;

   p = &players[mthing->type-1];

   if (p->playerstate == PST_REBORN)
      G_PlayerReborn (mthing->type-1);

   x    = mthing->x << FRACBITS;
   y    = mthing->y << FRACBITS;
   z    = ONFLOORZ;
   mobj = P_SpawnMobj (x,y,z, MT_PLAYER);

   // set color translations for player sprites

   if (mthing->type > 0)
      mobj->flags |= playernumtotrans[mthing->type-1]<<MF_TRANSSHIFT;

   mobj->angle      = ANG45 * (mthing->angle/45);
   mobj->player     = p;
   mobj->health     = p->health;

   p->mo            = mobj;
   p->playerstate   = PST_LIVE;
   p->refire        = 0;
   p->message       = NULL;
   p->damagecount   = 0;
   p->bonuscount    = 0;
   p->extralight    = 0;
   p->fixedcolormap = 0;
   p->viewheight    = VIEWHEIGHT;

   p->momx = p->momy = 0;   // killough 10/98: initialize bobbing to 0.

   // setup gun psprite

   P_SetupPsprites (p);

   // give all cards in death match mode

   if (deathmatch)
      for (i = 0 ; i < NUMCARDS ; i++)
         p->cards[i] = true;

   if (mthing->type-1 == consoleplayer)
   {
      ST_Start(); // wake up the status bar
      HU_Start(); // wake up the heads up text
   }
}


//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//

void P_SpawnMapThing (mapthing_t* mthing)
{
   int     i;
   //int     bit;
   mobj_t* mobj;
   fixed_t x;
   fixed_t y;
   fixed_t z;

   // killough 2/26/98: Ignore type-0 things as NOPs
   // phares 5/14/98: Ignore Player 5-8 starts (for now)

   switch(mthing->type)
   {
   case 0:
   case DEN_PLAYER5:
   case DEN_PLAYER6:
   case DEN_PLAYER7:
   case DEN_PLAYER8:
      return;
   }

   // killough 11/98: clear flags unused by Doom
   //
   // We clear the flags unused in Doom if we see flag mask 256 set, since
   // it is reserved to be 0 under the new scheme. A 1 in this reserved bit
   // indicates it's a Doom wad made by a Doom editor which puts 1's in
   // bits that weren't used in Doom (such as HellMaker wads). So we should
   // then simply ignore all upper bits.

   if (demo_compatibility ||
         (compatibility_level >= lxdoom_1_compatibility  &&
          mthing->options & MTF_RESERVED)) {
      if (!demo_compatibility) // cph - Add warning about bad thing flags
         printf("P_SpawnMapThing: correcting bad flags (%u) (thing type %d)\n",
                 mthing->options, mthing->type);
      mthing->options &= MTF_EASY|MTF_NORMAL|MTF_HARD|MTF_AMBUSH|MTF_NOTSINGLE;
   }

   // count deathmatch start positions

   // doom2.exe has at most 10 deathmatch starts
   if (mthing->type == 11 && (!compatibility || deathmatch_p-deathmatchstarts < 10))
   {
      // 1/11/98 killough -- new code removes limit on deathmatch starts:

      size_t offset = deathmatch_p - deathmatchstarts;

      if (offset >= num_deathmatchstarts)
      {
         num_deathmatchstarts = num_deathmatchstarts ?
                                num_deathmatchstarts*2 : 16;
         deathmatchstarts = realloc(deathmatchstarts,
                                    num_deathmatchstarts *
                                    sizeof(*deathmatchstarts));
         deathmatch_p = deathmatchstarts + offset;
      }
      memcpy(deathmatch_p++, mthing, sizeof(*mthing));
      return;
   }

   // check for players specially

   if (mthing->type <= 4 && mthing->type > 0)  // killough 2/26/98 -- fix crashes
   {
#ifdef DOGS
      // killough 7/19/98: Marine's best friend :)
      if (!netgame && mthing->type > 1 && mthing->type <= dogs+1 &&
            !players[mthing->type-1].secretcount)
      {  // use secretcount to avoid multiple dogs in case of multiple starts
         players[mthing->type-1].secretcount = 1;

         // killough 10/98: force it to be a friend
         mthing->options |= MTF_FRIEND;
         i = MT_DOGS;
         goto spawnit;
      }
#endif


      // save spots for respawning in network games

      playerstarts[mthing->type-1] = *mthing;
      if (!deathmatch)
         P_SpawnPlayer (mthing);
      return;
   }

   // check for apropriate skill level

   /* jff "not single" thing flag */
   if (!netgame && mthing->options & MTF_NOTSINGLE)
      return;

   //jff 3/30/98 implement "not deathmatch" thing flag

   if (netgame && deathmatch && mthing->options & MTF_NOTDM)
      return;

   //jff 3/30/98 implement "not cooperative" thing flag

   if (netgame && !deathmatch && mthing->options & MTF_NOTCOOP)
      return;

   // killough 11/98: simplify
   if (gameskill == sk_baby || gameskill == sk_easy ?
         !(mthing->options & MTF_EASY) :
         gameskill == sk_hard || gameskill == sk_nightmare ?
         !(mthing->options & MTF_HARD) : !(mthing->options & MTF_NORMAL))
      return;

   // find which type to spawn

   // killough 8/23/98: use table for faster lookup
   i = P_FindDoomedNum(mthing->type);

   // phares 5/16/98:
   // Do not abort because of an unknown thing. Ignore it, but post a
   // warning message for the player.

   if (i == NUMMOBJTYPES)
   {
      doom_printf("Unknown Thing type %i at (%i, %i)",mthing->type,mthing->x,mthing->y);
      return;
   }

   // don't spawn keycards and players in deathmatch

   if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH)
      return;

   // don't spawn any monsters if -nomonsters

   if (nomonsters && (i == MT_SKULL || (mobjinfo[i].flags & MF_COUNTKILL)))
      return;

   // spawn it
#ifdef DOGS
spawnit:
#endif

   x = mthing->x << FRACBITS;
   y = mthing->y << FRACBITS;

   if (mobjinfo[i].flags & MF_SPAWNCEILING)
      z = ONCEILINGZ;
   else
      z = ONFLOORZ;

   mobj = P_SpawnMobj (x,y,z, i);
   mobj->spawnpoint = *mthing;

   if (mobj->tics > 0)
      mobj->tics = 1 + (P_Random (pr_spawnthing) % mobj->tics);

   if (!(mobj->flags & MF_FRIEND) &&
         mthing->options & MTF_FRIEND &&
         mbf_features)
   {
      mobj->flags |= MF_FRIEND;            // killough 10/98:
      P_UpdateThinker(&mobj->thinker);     // transfer friendliness flag
   }

   /* killough 7/20/98: exclude friends */
   if (!((mobj->flags ^ MF_COUNTKILL) & (MF_FRIEND | MF_COUNTKILL)))
      totalkills++;

   if (mobj->flags & MF_COUNTITEM)
      totalitems++;

   mobj->angle = ANG45 * (mthing->angle/45);
   if (mthing->options & MTF_AMBUSH)
      mobj->flags |= MF_AMBUSH;
}


//
// GAME SPAWN FUNCTIONS
//

//
// P_SpawnPuff
//

extern fixed_t attackrange;

void P_SpawnPuff(fixed_t x,fixed_t y,fixed_t z)
{
   mobj_t* th;
   // killough 5/5/98: remove dependence on order of evaluation:
   int t = P_Random(pr_spawnpuff);
   z += (t - P_Random(pr_spawnpuff))<<10;

   th = P_SpawnMobj (x,y,z, MT_PUFF);
   th->momz = FRACUNIT;
   th->tics -= P_Random(pr_spawnpuff)&3;

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

   // don't make punches spark on the wall

   if (attackrange == MELEERANGE)
      P_SetMobjState (th, S_PUFF3);
}


//
// P_SpawnBlood
//
void P_SpawnBlood(fixed_t x,fixed_t y,fixed_t z,int damage)
{
   mobj_t* th;
   // killough 5/5/98: remove dependence on order of evaluation:
   int t = P_Random(pr_spawnblood);
   z += (t - P_Random(pr_spawnblood))<<10;
   th = P_SpawnMobj(x,y,z, MT_BLOOD);
   th->momz = FRACUNIT*2;
   th->tics -= P_Random(pr_spawnblood)&3;

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

   if (damage <= 12 && damage >= 9)
      P_SetMobjState (th,S_BLOOD2);
   else if (damage < 9)
      P_SetMobjState (th,S_BLOOD3);
}


//
// P_CheckMissileSpawn
// Moves the missile forward a bit
//  and possibly explodes it right there.
//

void P_CheckMissileSpawn (mobj_t* th)
{
   th->tics -= P_Random(pr_missile)&3;
   if (th->tics < 1)
      th->tics = 1;

   // move a little forward so an angle can
   // be computed if it immediately explodes

   th->x += (th->momx>>1);
   th->y += (th->momy>>1);
   th->z += (th->momz>>1);

   // killough 8/12/98: for non-missile objects (e.g. grenades)
   if (!(th->flags & MF_MISSILE) && mbf_features)
      return;

   // killough 3/15/98: no dropoff (really = don't care for missiles)

   if (!P_TryMove (th, th->x, th->y, false))
      P_ExplodeMissile (th);
}


//
// P_SpawnMissile
//

mobj_t* P_SpawnMissile(mobj_t* source,mobj_t* dest,mobjtype_t type)
{
   mobj_t* th;
   angle_t an;
   int     dist;

   th = P_SpawnMobj (source->x,source->y,source->z + 4*8*FRACUNIT,type);

   if (th->info->seesound)
      S_StartSound (th, th->info->seesound);

   P_SetTarget(&th->target, source);    // where it came from
   an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y);

   // fuzzy player

   if (dest->flags & MF_SHADOW)
   {  // killough 5/5/98: remove dependence on order of evaluation:
      int t = P_Random(pr_shadow);
      an += (t - P_Random(pr_shadow))<<20;
   }

   th->angle = an;
   an >>= ANGLETOFINESHIFT;
   th->momx = FixedMul (th->info->speed, finecosine[an]);
   th->momy = FixedMul (th->info->speed, finesine[an]);

   dist = P_AproxDistance (dest->x - source->x, dest->y - source->y);
   dist = dist / th->info->speed;

   if (dist < 1)
      dist = 1;

   th->momz = (dest->z - source->z) / dist;
   P_CheckMissileSpawn (th);

   return th;
}


//
// P_SpawnPlayerMissile
// Tries to aim at a nearby monster
//

void P_SpawnPlayerMissile(mobj_t* source,mobjtype_t type)
{
   mobj_t *th;
   fixed_t x, y, z, slope = 0;

   // see which target is to be aimed at

   angle_t an = source->angle;

   // killough 7/19/98: autoaiming was not in original beta
   {
      // killough 8/2/98: prefer autoaiming at enemies
      uint_64_t mask = mbf_features ? MF_FRIEND : 0;

      do
      {
         slope = P_AimLineAttack(source, an, 16*64*FRACUNIT, mask);
         if (!linetarget)
            slope = P_AimLineAttack(source, an += 1<<26, 16*64*FRACUNIT, mask);
         if (!linetarget)
            slope = P_AimLineAttack(source, an -= 2<<26, 16*64*FRACUNIT, mask);
         if (!linetarget)
            an = source->angle, slope = 0;
      }
      while (mask && (mask=0, !linetarget));  // killough 8/2/98
   }

   x = source->x;
   y = source->y;
   z = source->z + 4*8*FRACUNIT;

   th = P_SpawnMobj (x,y,z, type);

   if (th->info->seesound)
      S_StartSound (th, th->info->seesound);

   P_SetTarget(&th->target, source);
   th->angle = an;
   th->momx = FixedMul(th->info->speed,finecosine[an>>ANGLETOFINESHIFT]);
   th->momy = FixedMul(th->info->speed,finesine[an>>ANGLETOFINESHIFT]);
   th->momz = FixedMul(th->info->speed,slope);

   P_CheckMissileSpawn(th);
}
