//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
//
// 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.
//
// DESCRIPTION:
//	Moving object handling. Spawn functions.
//

#include <stdio.h>

#include "i_system.h"
#include "z_zone.h"
#include "m_random.h"
#include "doomdef.h"
#include "p_local.h"
#include "sounds.h"
#include "st_stuff.h"
#include "hu_stuff.h"
#include "s_sound.h"
#include "doomstat.h"
#include "d_main.h"     // villsa [STRIFE]


void G_PlayerReborn (int player);
void P_SpawnMapThing (mapthing_t*	mthing);


//
// P_SetMobjState
// Returns true if the mobj is still present.
//
// [STRIFE] Verified unmodified
//
int test;

boolean
P_SetMobjState
( mobj_t*	mobj,
  statenum_t	state )
{
    state_t*	st;

    do
    {
	if (state == S_NULL)
	{
	    mobj->state = (state_t *) S_NULL;
	    P_RemoveMobj (mobj);
	    return false;
	}

	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.acp1)		
	    st->action.acp1(mobj);	
	
	state = st->nextstate;
    } while (!mobj->tics);
				
    return true;
}


//
// P_ExplodeMissile
//
// [STRIFE] Removed randomization of deathstate tics
//
void P_ExplodeMissile (mobj_t* mo)
{
    mo->momx = mo->momy = mo->momz = 0;

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

    // villsa [STRIFE] removed tics randomization

    mo->flags &= ~MF_MISSILE;

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


//
// P_XYMovement
//
// [STRIFE] Modifications for:
// * No SKULLFLY logic (replaced by BOUNCE flag)
// * Missiles can activate G1/GR line types
// * Player walking logic
// * Air friction for players
//
#define STOPSPEED       0x1000
#define FRICTION        0xe800
#define AIRFRICTION     0xfff0  // [STRIFE]

void P_XYMovement (mobj_t* mo) 
{
    fixed_t     ptryx;
    fixed_t     ptryy;
    player_t*   player;
    fixed_t     xmove;
    fixed_t     ymove;

    // villsa [STRIFE] unused
    /*
    if (!mo->momx && !mo->momy)
    {
        if (mo->flags & MF_SKULLFLY)
        {
            // the skull slammed into something
            mo->flags &= ~MF_SKULLFLY;
            mo->momx = mo->momy = 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;

    do
    {
        if (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;
        }

        if (!P_TryMove (mo, ptryx, ptryy))
        {
            // blocked move
            if (mo->player)
            {   // try to slide along it
                P_SlideMove (mo);
            }
            // villsa [STRIFE] check for bouncy missiles
            else if(mo->flags & MF_BOUNCE)
            {
                mo->momx >>= 3;
                mo->momy >>= 3;

                if (P_TryMove(mo, mo->x - xmove, ymove + mo->y))
                    mo->momy = -mo->momy;
                else
                    mo->momx = -mo->momx;

                xmove = 0;
                ymove = 0;
            }
            else if (mo->flags & MF_MISSILE)
            {
                // haley 20110203: [STRIFE]
                // This modification allows missiles to activate shoot specials.
                // *** BUG: In vanilla Strife the second condition is simply
                // if(numspechit). However, numspechit can be negative, and
                // when it is, this accesses spechit[-2]. This always causes the
                // DOS exe to read from NULL, and the 'special' value there (in
                // DOS 6.22 at least) is 0x70, which does nothing.
                if(blockingline && blockingline->special)
                    P_ShootSpecialLine(mo, blockingline);
                if(numspechit > 0)
                    P_ShootSpecialLine(mo, spechit[numspechit-1]);

                // explode a missile
                if (ceilingline &&
                    ceilingline->backsector &&
                    ceilingline->backsector->ceilingpic == skyflatnum)
                {
                    // Hack to prevent missiles exploding
                    // against the sky.
                    // Does not handle sky floors.
                    P_RemoveMobj (mo);
                    return;
                }
                P_ExplodeMissile (mo);
            }
            else
                mo->momx = mo->momy = 0;
        }
    } while (xmove || ymove);
    
    // slow down
    if (player && player->cheats & CF_NOMOMENTUM)
    {
        // debug option for no sliding at all
        mo->momx = mo->momy = 0;
        return;
    }

    // villsa [STRIFE] replace skullfly flag with MF_BOUNCE
    if (mo->flags & (MF_MISSILE | MF_BOUNCE) )
        return;     // no friction for missiles ever

    // haleyjd 20110224: [STRIFE] players experience friction even in the air,
    // although less than when on the ground. With this fix, the 1.2-and-up 
    // IWAD demo is now in sync!
    if (mo->z > mo->floorz)
    {
        if(player)
        {
            mo->momx = FixedMul (mo->momx, AIRFRICTION);
            mo->momy = FixedMul (mo->momy, AIRFRICTION);
        }
        return;     // no friction when airborne
    }

    if (mo->flags & MF_CORPSE)
    {
        // do not stop sliding
        //  if halfway off a step with some momentum
        if (mo->momx > FRACUNIT/4
            || mo->momx < -FRACUNIT/4
            || mo->momy > FRACUNIT/4
            || mo->momy < -FRACUNIT/4)
        {
            if (mo->floorz != mo->subsector->sector->floorheight)
                return;
        }
    }

    if (mo->momx > -STOPSPEED
        && mo->momx < STOPSPEED
        && mo->momy > -STOPSPEED
        && mo->momy < STOPSPEED
        && (!player
            || (player->cmd.forwardmove == 0
                && player->cmd.sidemove == 0 ) ) )
    {
        // if in a walking frame, stop moving
        // villsa [STRIFE]: different player state (haleyjd - verified 20110202)
        if ( player&&(unsigned)((player->mo->state - states) - S_PLAY_01) < 4)
            P_SetMobjState (player->mo, S_PLAY_00);

        mo->momx = 0;
        mo->momy = 0;
    }
    else
    {
        mo->momx = FixedMul (mo->momx, FRICTION);
        mo->momy = FixedMul (mo->momy, FRICTION);
    }
}

//
// P_ZMovement
//
// [STRIFE] Modifications for:
// * 3D Object Clipping
// * Different momz handling
// * No SKULLFLY logic (replaced with BOUNCE)
// * Missiles don't hit sky flats
//
void P_ZMovement (mobj_t* mo)
{
    fixed_t	dist;
    fixed_t	delta;

    // check for smooth step up
    if (mo->player && mo->z < mo->floorz)
    {
        mo->player->viewheight -= mo->floorz-mo->z;

        mo->player->deltaviewheight
            = (VIEWHEIGHT - mo->player->viewheight)>>3;
    }
    
    // adjust height
    // villsa [STRIFE] check for things standing on top of other things
    if(!P_CheckPositionZ(mo, mo->z + mo->momz))
    {
        if(mo->momz >= 0)
            mo->ceilingz = mo->height + mo->z;
        else
            mo->floorz = mo->z;
    }

    //mo->z += mo->momz; // villsa [STRIFE] unused

    if ( mo->flags & MF_FLOAT
        && mo->target)
    {
        // float down towards target if too close
        if ( /*!(mo->flags & MF_SKULLFLY)   // villsa [STRIFE] unused
             &&*/ !(mo->flags & MF_INFLOAT) )
        {
            dist = P_AproxDistance (mo->x - mo->target->x,
                                    mo->y - mo->target->y);

            delta =(mo->target->z + (mo->height>>1)) - mo->z;

            if (delta<0 && dist < -(delta*3) )
                mo->z -= FLOATSPEED;
            else if (delta>0 && dist < (delta*3) )
                mo->z += FLOATSPEED;
        }
    }
    
    // clip movement
    if (mo->z <= mo->floorz)
    {
        // hit the floor

        if (mo->flags & MF_BOUNCE)
        {
            // the skull slammed into something
            // villsa [STRIFE] affect reactiontime
            // momz is also shifted by 1
            mo->momz = -mo->momz >> 1;
            mo->reactiontime >>= 1;

            // villsa [STRIFE] get terrain type
            if(P_GetTerrainType(mo) != FLOOR_SOLID)
                mo->flags &= ~MF_BOUNCE;
        }

        if (mo->momz < 0)
        {
            if (mo->player
                && 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;

                // villsa [STRIFE] fall damage
                // haleyjd 09/18/10: Repaired calculation
                if(mo->momz < -20*FRACUNIT)
                    P_DamageMobj(mo, NULL, mo, mo->momz / -25000);

                // haleyjd 20110224: *Any* fall centers your view, not just
                // damaging falls (moved outside the above if).
                // [crispy] center view when not using mouse look
                if (!critical->mouselook)
                    mo->player->centerview = 1;
                // [crispy] dead men don't say "oof"
                if (mo->health > 0 || !crispy->soundfix)
                {
                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.
        //

        // villsa [STRIFE] unused
        /*
        if (!correct_lost_soul_bounce && mo->flags & MF_SKULLFLY)
            mo->momz = -mo->momz;
        */

        // villsa [STRIFE] also check for MF_BOUNCE
        if ( (mo->flags & MF_MISSILE)
            && !(mo->flags & (MF_NOCLIP|MF_BOUNCE)) )
        {
            P_ExplodeMissile (mo);
        }
    }
    else // haleyjd 20110224: else here, not else if - Strife change or what?
    {
        if (! (mo->flags & MF_NOGRAVITY) )
        {
            if (mo->momz == 0)
                mo->momz = -GRAVITY*2;
            else
                mo->momz -= GRAVITY;
        }

        if (mo->z + mo->height > mo->ceilingz)
        {
            // villsa [STRIFE] replace skullfly flag with MF_BOUNCE
            if (mo->flags & MF_BOUNCE)
            {
                // villsa [STRIFE] affect reactiontime
                // momz is also shifted by 1
                mo->momz = -mo->momz >> 1;
                mo->reactiontime >>= 1;
            }

            // hit the ceiling
            if (mo->momz > 0)
                mo->momz = 0;

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

            // villsa [STRIFE] also check for MF_BOUNCE
            if ( (mo->flags & MF_MISSILE)
                && !(mo->flags & (MF_NOCLIP|MF_BOUNCE)) )
            {
                // villsa [STRIFE] check against skies
                if(mo->subsector->sector->ceilingpic == skyflatnum)
                    P_RemoveMobj(mo);
                else
                    P_ExplodeMissile (mo);
            }
        }
    }
} 



//
// P_NightmareRespawn
//
// [STRIFE] Modifications for:
// * Destination fog z coordinate
// * Restoration of all Strife mapthing flags
//
void
P_NightmareRespawn (mobj_t* mobj)
{
    fixed_t      x;
    fixed_t      y;
    fixed_t      z; 
    mobj_t*      mo;
    mapthing_t*  mthing;

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

    // somthing is occupying it's 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); 

    // haleyjd [STRIFE]: Uses ONFLOORZ instead of ss->sector->floorheight
    mo = P_SpawnMobj (x, y, ONFLOORZ , MT_TFOG); 

    S_StartSound (mo, sfx_telept);

    // spawn the new monster
    mthing = &mobj->spawnpoint;

    // spawn it
    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;
    if (mthing->options & MTF_STAND)       // [STRIFE] Standing mode, for NPCs
        mobj->flags |= MF_STAND;
    if (mthing->options & MTF_FRIEND)      // [STRIFE] Allies
        mobj->flags |= MF_ALLY;
    if (mthing->options & MTF_TRANSLUCENT) // [STRIFE] Translucent object
        mobj->flags |= MF_SHADOW;
    if (mthing->options & MTF_MVIS)        // [STRIFE] Alt. Translucency
        mobj->flags |= MF_MVIS;

    mo->reactiontime = 18;

    // remove the old monster,
    P_RemoveMobj (mobj);
}


//
// P_MobjThinker
//
// [STRIFE] Modified for:
// * Terrain effects
// * Stonecold cheat
// * Altered skill 5 respawn behavior
//
void P_MobjThinker (mobj_t* mobj)
{
    // [crispy] suppress interpolation of player missiles for the first tic
    if (mobj->interp < 0)
    {
        mobj->interp++;
    }
    else
    // [AM] Handle interpolation unless we're an active player.
    if (!(mobj->player != NULL && mobj == mobj->player->mo))
    {
        // Assume we can interpolate at the beginning
        // of the tic.
        mobj->interp = true;

        // Store starting position for mobj interpolation.
        mobj->oldx = mobj->x;
        mobj->oldy = mobj->y;
        mobj->oldz = mobj->z;
        mobj->oldangle = mobj->angle;
    }

    // momentum movement
    if (mobj->momx
        || mobj->momy
        /*|| (mobj->flags&MF_SKULLFLY)*/ )  // villsa [STRIFE] unused
    {
        P_XYMovement (mobj);

        // FIXME: decent NOP/NULL/Nil function pointer please.
        if (mobj->thinker.function.acv == (actionf_v) (-1))
            return;     // mobj was removed

        // villsa [STRIFE] terrain clipping
        if(P_GetTerrainType(mobj) == FLOOR_SOLID)
            mobj->flags &= ~MF_FEETCLIPPED;
        else
            mobj->flags |= MF_FEETCLIPPED;

    }
    if ( (mobj->z != mobj->floorz && !(mobj->flags & MF_NOGRAVITY)) // villsa [STRIFE]
        || mobj->momz )
    {
        P_ZMovement (mobj);

        // FIXME: decent NOP/NULL/Nil function pointer please.
        if (mobj->thinker.function.acv == (actionf_v) (-1))
            return;     // mobj was removed

        // villsa [STRIFE] terrain clipping and sounds
        if(P_GetTerrainType(mobj) == FLOOR_SOLID)
            mobj->flags &= ~MF_FEETCLIPPED;
        else
        {
            S_StartSound(mobj, sfx_wsplsh);
            mobj->flags |= MF_FEETCLIPPED;
        }

    }

    
    // cycle through states,
    // calling action functions at transitions
    if (mobj->tics != -1)
    {
        mobj->tics--;

        // villsa [STRIFE] stonecold cheat
        if(stonecold)
        {
            if(mobj->flags & MF_COUNTKILL)
                P_DamageMobj(mobj, mobj, mobj, 10);
        }

        // 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++;

        // haleyjd [STRIFE]: respawn time increased from 12 to 16
        if (mobj->movecount < 16*TICRATE)
            return;

        if ( leveltime&31 )
            return;

        if (P_Random () > 4)
            return;

        // haleyjd [STRIFE]: NOTDMATCH things don't respawn
        if(mobj->flags & MF_NOTDMATCH)
            return;

        P_NightmareRespawn (mobj);
    }
}


//
// P_SpawnMobj
//
// [STRIFE] Modifications to reactiontime and for terrain types.
//
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;
    mobj->flags = info->flags;
    mobj->health = info->spawnhealth;

    // haleyjd 09/25/10: [STRIFE] Doesn't do this; messes up flamethrower
    // and a lot of other stuff using reactiontime as a counter.
    //if (gameskill != sk_nightmare)
    mobj->reactiontime = info->reactiontime;
    
    mobj->lastlook = P_Random () % 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;

    // set subsector and/or block links
    P_SetThingPosition (mobj);

    mobj->floorz = mobj->subsector->sector->floorheight;
    mobj->ceilingz = mobj->subsector->sector->ceilingheight;

    if (z == ONFLOORZ)
    {
        mobj->z = mobj->floorz;

        // villsa [STRIFE]
        if(P_GetTerrainType(mobj) != FLOOR_SOLID)
            mobj->flags |= MF_FEETCLIPPED;

    }
    else if (z == ONCEILINGZ)
        mobj->z = mobj->ceilingz - mobj->info->height;
    else 
        mobj->z = z;

    // [AM] Do not interpolate on spawn.
    mobj->interp = false;

    // [AM] Just in case interpolation is attempted...
    mobj->oldx = mobj->x;
    mobj->oldy = mobj->y;
    mobj->oldz = mobj->z;
    mobj->oldangle = mobj->angle;

    mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;

    P_AddThinker (&mobj->thinker);

    return mobj;
}


//
// P_RemoveMobj
//
// [STRIFE] Modifications for item respawn timing
//
mapthing_t  itemrespawnque[ITEMQUESIZE];
int         itemrespawntime[ITEMQUESIZE];
int         iquehead;
int         iquetail;

void P_RemoveMobj (mobj_t* mobj)
{
    // villsa [STRIFE] removed invuln/invis. sphere exceptions
    if ((mobj->flags & MF_SPECIAL)
        && !(mobj->flags & MF_DROPPED))
    {
        itemrespawnque[iquehead] = mobj->spawnpoint;
        itemrespawntime[iquehead] = leveltime + 30*TICRATE; // [STRIFE]

        // [STRIFE] haleyjd 20130915
        // -random parameter affects the behavior of respawning items here.
        if(randomparm && iquehead != iquetail)
        {
            short type    = itemrespawnque[iquehead].type;
            short options = itemrespawnque[iquehead].options;

            // swap the type and options of iquehead and iquetail
            itemrespawnque[iquehead].type    = itemrespawnque[iquetail].type;
            itemrespawnque[iquehead].options = itemrespawnque[iquetail].options;
            itemrespawnque[iquetail].type    = type;
            itemrespawnque[iquetail].options = options;
        }

        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);
    
    // [crispy] removed map objects may finish their sounds
    if (crispy->soundfull)
    {
        S_UnlinkSound(mobj);
    }
    else
    {
    // stop any playing sound
    S_StopSound (mobj);
    }
    
    // free block
    P_RemoveThinker ((thinker_t*)mobj);
}




//
// P_RespawnSpecials
//
// [STRIFE] modification to item respawn time handling
//
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;

    // haleyjd [STRIFE]: 30 second wait is not accounted for here, see above.
    if (leveltime < itemrespawntime[iquetail])
        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
    for (i=0 ; i< NUMMOBJTYPES ; i++)
    {
        if (mthing->type == mobjinfo[i].doomednum)
            break;
    }

    if (i >= NUMMOBJTYPES)
    {
        I_Error("P_RespawnSpecials: Failed to find mobj type with doomednum "
                "%d when respawning thing. This would cause a buffer overrun "
                "in vanilla Strife.", 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 que
    iquetail = (iquetail+1)&(ITEMQUESIZE-1);
}



// [crispy] weapon sound sources
degenmobj_t muzzles[MAXPLAYERS];

mobj_t *Crispy_PlayerSO (int p)
{
    return crispy->soundfull ? (mobj_t *) &muzzles[p] : players[p].mo;
}

//
// P_SpawnPlayer
// Called when a player is spawned on the level.
// Most of the player structure stays unchanged
//  between levels.
//
// [STRIFE] Modifications for:
// * stonecold cheat, -workparm
// * default inventory/questflags
//
void P_SpawnPlayer(mapthing_t* mthing)
{
    player_t*   p;
    fixed_t     x;
    fixed_t     y;
    fixed_t     z;
    mobj_t*     mobj;

    if(mthing->type == 0)
        return;

    // 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 > 1)
        mobj->flags |= (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;

    // [crispy] weapon sound source
    p->so = Crispy_PlayerSO(mthing->type - 1);

    pspr_interp = false; // [crispy] interpolate weapon bobbing

    // setup gun psprite
    P_SetupPsprites(p);

    // villsa [STRIFE]
    stonecold = false;

    // villsa [STRIFE] what a nasty hack...
    if(gamemap == 10)
        p->weaponowned[wp_sigil] = true;

    // villsa [STRIFE] instead of just giving cards in deathmatch mode, also
    // set accuracy to 50 and give all quest flags
    if(deathmatch)
    {
        int i;

        p->accuracy = 50;
        p->questflags = QF_ALLQUESTS; // 0x7fffffff

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

    // villsa [STRIFE] set godmode?
    if(workparm)
        p->cheats |= CF_GODMODE;

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


//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
// [STRIFE] Modifications for:
// * No Lost Souls, item count
// * New mapthing_t flag bits
// * 8-player support
//
void P_SpawnMapThing (mapthing_t* mthing)
{
    int         i;
    int         bit;
    mobj_t*     mobj;
    fixed_t     x;
    fixed_t     y;
    fixed_t     z;

    // count deathmatch start positions
    if (mthing->type == 11)
    {
        if (deathmatch_p < &deathmatchstarts[10])
        {
            memcpy (deathmatch_p, mthing, sizeof(*mthing));
            deathmatch_p++;
        }
        return;
    }

    if (mthing->type <= 0)
    {
        // Thing type 0 is actually "player -1 start".  
        // For some reason, Vanilla Doom accepts/ignores this.

        return;
    }

    // check for players specially
    // haleyjd 20120209: [STRIFE] 8 player starts
    if (mthing->type <= 8)
    {
        // save spots for respawning in network games
        playerstarts[mthing->type-1] = *mthing;
        if (!deathmatch)
            P_SpawnPlayer (mthing);

        return;
    }

    // check for appropriate skill level
    if (!netgame && (mthing->options & 16) )
        return;

    if (gameskill == sk_baby)
        bit = 1;
    else if (gameskill == sk_nightmare)
        bit = 4;
    else
        // avoid undefined behavior (left shift by negative value and rhs too big)
        // by accurately emulating what doom.exe did: reduce mod 32.
        // For more details check:
        // https://github.com/chocolate-doom/chocolate-doom/issues/1677
        bit = (int) (1U << ((gameskill - 1) & 0x1F));

    if (!(mthing->options & bit) )
        return;

    // find which type to spawn
    for (i=0 ; i< NUMMOBJTYPES ; i++)
        if (mthing->type == mobjinfo[i].doomednum)
            break;

    if (i==NUMMOBJTYPES)
        I_Error ("P_SpawnMapThing: Unknown type %i at (%i, %i)",
                 mthing->type,
                 mthing->x, mthing->y);

    // don't spawn keycards and players in deathmatch
    if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH)
        return;

    // don't spawn any monsters if -nomonsters
    // villsa [STRIFE] Removed MT_SKULL
    if (nomonsters && (mobjinfo[i].flags & MF_COUNTKILL))
        return;
    
    // spawn it
    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 () % mobj->tics);
    if (mobj->flags & MF_COUNTKILL)
        totalkills++;

    // villsa [STRIFE] unused
    /*
    if (mobj->flags & MF_COUNTITEM)
        totalitems++;
    */

    mobj->angle = ANG45 * (mthing->angle/45);
    if (mthing->options & MTF_AMBUSH)
        mobj->flags |= MF_AMBUSH;
    if (mthing->options & MTF_STAND)       // [STRIFE] Standing mode, for NPCs
        mobj->flags |= MF_STAND;
    if (mthing->options & MTF_FRIEND)      // [STRIFE] Allies
        mobj->flags |= MF_ALLY;
    if (mthing->options & MTF_TRANSLUCENT) // [STRIFE] Translucent object
        mobj->flags |= MF_SHADOW;
    if (mthing->options & MTF_MVIS)        // [STRIFE] Alt. Translucency
        mobj->flags |= MF_MVIS;
}



//
// GAME SPAWN FUNCTIONS
//


//
// P_SpawnPuff
//
// [STRIFE] Modifications for:
// * No spawn tics randomization
// * Player melee behavior
//

void
P_SpawnPuff
( fixed_t	x,
  fixed_t	y,
  fixed_t	z )
{
    mobj_t*	th;
    int t;
    
    t = P_Random();
    z += ((t - P_Random()) << 10);

    // [STRIFE] removed momz and tics randomization

    th = P_SpawnMobj(x, y, z, MT_STRIFEPUFF); // [STRIFE]: new type

    // don't make punches spark on the wall
    // [STRIFE] Use a separate melee attack range for the player
    if(attackrange == PLAYERMELEERANGE)
        P_SetMobjState(th, S_POW2_00); // 141

    // villsa [STRIFE] unused
    /*
    if (th->tics < 1)
        th->tics = 1;
    */
}

//
// P_SpawnSparkPuff
//
// villsa [STRIFE] new function
//
mobj_t* P_SpawnSparkPuff(fixed_t x, fixed_t y, fixed_t z)
{
    int t = P_Random();
    return P_SpawnMobj(x, y, ((t - P_Random()) << 10) + z, MT_SPARKPUFF);
}

//
// P_SpawnBlood
// 
// [STRIFE] Modifications for:
// * No spawn tics randomization
// * Different damage ranges for state setting
//
void
P_SpawnBlood
( fixed_t       x,
  fixed_t       y,
  fixed_t       z,
  int           damage )
{
    mobj_t*     th;
    int temp;
    
    temp = P_Random();
    z += (temp - P_Random()) << 10;
    th = P_SpawnMobj(x, y, z, MT_BLOOD_DEATH);
    th->momz = FRACUNIT*2;
    
    // villsa [STRIFE]: removed tics randomization

    // villsa [STRIFE] different checks for damage range
    if(damage >= 10 && damage <= 13)
        P_SetMobjState(th, S_BLOD_00);
    else if(damage >= 7 && damage < 10)
        P_SetMobjState(th, S_BLOD_01);
    else if(damage < 7)
        P_SetMobjState(th, S_BLOD_02);
}



//
// P_CheckMissileSpawn
// Moves the missile forward a bit
//  and possibly explodes it right there.
//
// [STRIFE] Modifications for:
// * No spawn tics randomization
//
void P_CheckMissileSpawn (mobj_t* th)
{
    // villsa [STRIFE] removed tics randomization
    
    // 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);

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

// Certain functions assume that a mobj_t pointer is non-NULL,
// causing a crash in some situations where it is NULL.  Vanilla
// Doom did not crash because of the lack of proper memory 
// protection. This function substitutes NULL pointers for
// pointers to a dummy mobj, to avoid a crash.

mobj_t *P_SubstNullMobj(mobj_t *mobj)
{
    if (mobj == NULL)
    {
        static mobj_t dummy_mobj;

        dummy_mobj.x = 0;
        dummy_mobj.y = 0;
        dummy_mobj.z = 0;
        dummy_mobj.flags = 0;

        mobj = &dummy_mobj;
    }

    return mobj;
}

//
// P_SpawnMissile
//
// [STRIFE] Added MVIS inaccuracy
//
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);

    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)
    {
        int t = P_Random(); // haleyjd 20110223: remove order-of-evaluation dependencies
        an += (t - P_Random()) << 21;
    }
    // villsa [STRIFE] check for heavily transparent things
    else if(dest->flags & MF_MVIS)
    {
        int t = P_Random();
        an += (t - P_Random()) << 22;
    }

    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_SpawnFacingMissile
//
// villsa [STRIFE] new function
// Spawn a missile based on source's angle
//
mobj_t* P_SpawnFacingMissile(mobj_t* source, mobj_t* target, mobjtype_t type)
{
    mobj_t* th;
    angle_t an;
    fixed_t dist;

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

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

    th->target = source;    // where it came from
    th->angle = source->angle; // haleyjd 09/06/10: fix0red
    an = th->angle;

    // fuzzy player
    if (target->flags & MF_SHADOW)
    {
        int t = P_Random();
        an += (t - P_Random()) << 21;
    }
    // villsa [STRIFE] check for heavily transparent things
    else if(target->flags & MF_MVIS)
    {
        int t = P_Random();
        an += (t - P_Random()) << 22;
    }

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

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

    if(dist < 1)
        dist = 1;

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

    return th;
}

//
// P_SpawnPlayerMissile
//
// Tries to aim at a nearby monster
// villsa [STRIFE] now returns a mobj
// * Also modified to allow up/down look, and to account for foot-clipping
//   by liquid terrain.
//
mobj_t* P_SpawnPlayerMissile(mobj_t* source, mobjtype_t type)
{
    mobj_t*	th;
    angle_t	an;
    
    fixed_t	x;
    fixed_t	y;
    fixed_t	z;
    fixed_t	slope;
    
    // see which target is to be aimed at
    an = source->angle;
    slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);
    
    if (!linetarget)
    {
        an += 1<<26;
        slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);

        if (!linetarget)
        {
            an -= 2<<26;
            slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);
        }

        if (!linetarget)
        {
            an = source->angle;

            // haleyjd 09/21/10: [STRIFE] Removed, for look up/down support.
            //slope = 0; 
        }
    }

    // villsa [STRIFE]
    if(linetarget)
        source->target = linetarget;

    x = source->x;
    y = source->y;
    
    // villsa [STRIFE]
    if(!(source->flags & MF_FEETCLIPPED))
        z = source->z + 32*FRACUNIT;
    else
        z = source->z + 22*FRACUNIT;

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

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

    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);

    // [crispy] suppress interpolation of player missiles for the first tic
    th->interp = -1;

    P_CheckMissileSpawn (th);

    return th;
}

//
// P_SpawnMortar
//
// villsa [STRIFE] new function
// Spawn a high-arcing ballistic projectile
//
mobj_t* P_SpawnMortar(mobj_t *source, mobjtype_t type)
{
    mobj_t* th;
    angle_t an;
    fixed_t slope;

    an = source->angle;

    th = P_SpawnMobj(source->x, source->y, source->z, type);
    th->target = source;
    th->angle = an;
    an >>= ANGLETOFINESHIFT;

    // haleyjd 20110203: corrected order of function calls
    th->momx = FixedMul(th->info->speed, finecosine[an]);
    th->momy = FixedMul(th->info->speed, finesine[an]);

    P_CheckMissileSpawn(th);
    
    slope = P_AimLineAttack(source, source->angle, 1024*FRACUNIT);
    th->momz = FixedMul(th->info->speed, slope);

    return th;
}
