// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: p_mobj.cpp 3798 2013-04-24 03:09:33Z dr_sean $
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2013 by The Odamex Team.
//
// 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 "m_alloc.h"
#include "i_system.h"
#include "z_zone.h"
#include "m_random.h"
#include "doomdef.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "c_effect.h"
#include "s_sound.h"
#include "doomstat.h"
#include "v_video.h"
#include "c_cvars.h"
#include "vectors.h"
#include "g_game.h"
#include "p_mobj.h"
#include "p_ctf.h"
#include "gi.h"

#define WATER_SINK_FACTOR		3
#define WATER_SINK_SMALL_FACTOR	4
#define WATER_SINK_SPEED		(FRACUNIT/2)
#define WATER_JUMP_SPEED		(FRACUNIT*7/2)

extern bool predicting;
extern fixed_t attackrange;
extern bool HasBehavior;
extern AActor *shootthing;

void P_SpawnPlayer (player_t &player, mapthing2_t *mthing);
void P_ShowSpawns(mapthing2_t* mthing);
void P_ExplodeMissile(AActor* mo);
void SV_SpawnMobj(AActor *mobj);
void SV_SendDestroyActor(AActor *);
void SV_ExplodeMissile(AActor *);

EXTERN_CVAR(sv_freelook)
EXTERN_CVAR(sv_itemsrespawn)
EXTERN_CVAR(sv_itemrespawntime)
EXTERN_CVAR(co_zdoomphys)
EXTERN_CVAR(co_realactorheight)
EXTERN_CVAR(sv_teamspawns)
EXTERN_CVAR(sv_nomonsters)
EXTERN_CVAR(sv_monstershealth)
EXTERN_CVAR(co_fixweaponimpacts)
EXTERN_CVAR(co_allowdropoff)
EXTERN_CVAR(co_fineautoaim)
EXTERN_CVAR(sv_allowshowspawns)

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

NetIDHandler ServerNetID;

// denis - fast netid lookup
typedef std::map<size_t, AActor::AActorPtr> netid_map_t;
netid_map_t actor_by_netid;

IMPLEMENT_SERIAL(AActor, DThinker)

AActor::~AActor ()
{
    // Please avoid calling the destructor directly (or through delete)!
    // Use Destroy() instead.

    // Zero all pointers generated by this->ptr()
    self.update_all(NULL);
}

void MapThing::Serialize (FArchive &arc)
{
	if (arc.IsStoring ())
	{
		arc << thingid << x << y << z << angle << type << flags << special
			<< args[0] << args[1] << args[2] << args[3] << args[4];
	}
	else
	{
		arc >> thingid >> x >> y >> z >> angle >> type >> flags >> special
			>> args[0] >> args[1] >> args[2] >> args[3] >> args[4];
	}
}

AActor::AActor () :
    x(0), y(0), z(0), snext(NULL), sprev(NULL), angle(0), sprite(SPR_UNKN), frame(0),
    pitch(0), effects(0), subsector(NULL),
    floorz(0), ceilingz(0), dropoffz(0), floorsector(NULL), radius(0), height(0),
    momx(0), momy(0), momz(0), validcount(0), type(MT_UNKNOWNTHING), info(NULL), tics(0), state(NULL),
    damage(0), flags(0), flags2(0), special1(0), special2(0), health(0), movedir(0), movecount(0),
    visdir(0), reactiontime(0), threshold(0), player(NULL), lastlook(0), special(0), inext(NULL),
    iprev(NULL), translation(NULL), translucency(0), waterlevel(0), gear(0), onground(false),
    touching_sectorlist(NULL), deadtic(0), oldframe(0), rndindex(0), netid(0),
    tid(0), bmapnode(this)
{
	memset(args, 0, sizeof(args));
	self.init(this);
}

AActor::AActor (const AActor &other) :
    x(other.x), y(other.y), z(other.z), snext(other.snext), sprev(other.sprev),
    angle(other.angle), sprite(other.sprite), frame(other.frame),
    pitch(other.pitch), effects(other.effects),
    subsector(other.subsector),
    floorz(other.floorz), ceilingz(other.ceilingz), dropoffz(other.dropoffz),
    floorsector(other.floorsector),	radius(other.radius), height(other.height), momx(other.momx),
	momy(other.momy), momz(other.momz), validcount(other.validcount),
	type(other.type), info(other.info), tics(other.tics), state(other.state),
	damage(other.damage), flags(other.flags), flags2(other.flags2), special1(other.special1),
	special2(other.special2), health(other.health), movedir(other.movedir),
	movecount(other.movecount), visdir(other.visdir), reactiontime(other.reactiontime),
    threshold(other.threshold), player(other.player), lastlook(other.lastlook),
    special(other.special),inext(other.inext), iprev(other.iprev), translation(other.translation),
    translucency(other.translucency), waterlevel(other.waterlevel), gear(other.gear),
    onground(other.onground), touching_sectorlist(other.touching_sectorlist),
    deadtic(other.deadtic), oldframe(other.oldframe),
    rndindex(other.rndindex), netid(other.netid), tid(other.tid), bmapnode(other.bmapnode)
{
	memcpy(args, other.args, sizeof(args));
	self.init(this);
}

AActor &AActor::operator= (const AActor &other)
{
	x = other.x;
    y = other.y;
    z = other.z;
    snext = other.snext;
    sprev = other.sprev;
    angle = other.angle;
    sprite = other.sprite;
    frame = other.frame;
    pitch = other.pitch;
    effects = other.effects;
    subsector = other.subsector;
    floorz = other.floorz;
    ceilingz = other.ceilingz;
	dropoffz = other.dropoffz;
	floorsector = other.floorsector;
    radius = other.radius;
    height = other.height;
    momx = other.momx;
    momy = other.momy;
    momz = other.momz;
    validcount = other.validcount;
    type = other.type;
    info = other.info;
    tics = other.tics;
    state = other.state;
    damage = other.damage;
    flags = other.flags;
    flags2 = other.flags2;
    special1 = other.special1;
    special2 = other.special2;
    health = other.health;
    movedir = other.movedir;
    movecount = other.movecount;
    visdir = other.visdir;
    reactiontime = other.reactiontime;
    threshold = other.threshold;
    player = other.player;
    lastlook = other.lastlook;
    inext = other.inext;
    iprev = other.iprev;
    translation = other.translation;
    translucency = other.translucency;
    waterlevel = other.waterlevel;
	gear = other.gear;
    onground = other.onground;
    touching_sectorlist = other.touching_sectorlist;
    deadtic = other.deadtic;
    oldframe = other.oldframe;
    rndindex = other.rndindex;
    netid = other.netid;
    tid = other.tid;
    special = other.special;
    memcpy(args, other.args, sizeof(args));
	bmapnode = other.bmapnode;

	return *this;
}

//
//
// P_SpawnMobj
//
//

AActor::AActor (fixed_t ix, fixed_t iy, fixed_t iz, mobjtype_t itype) :
    x(0), y(0), z(0), snext(NULL), sprev(NULL), angle(0), sprite(SPR_UNKN), frame(0),
    pitch(0), effects(0), subsector(NULL),
    floorz(0), ceilingz(0), dropoffz(0), floorsector(NULL), radius(0), height(0), momx(0), momy(0), momz(0),
    validcount(0), type(MT_UNKNOWNTHING), info(NULL), tics(0), state(NULL), damage(0), flags(0), flags2(0),
    special1(0), special2(0), health(0), movedir(0), movecount(0), visdir(0),
    reactiontime(0), threshold(0), player(NULL), lastlook(0), special(0), inext(NULL),
    iprev(NULL), translation(NULL), translucency(0), waterlevel(0), gear(0), onground(false),
    touching_sectorlist(NULL), deadtic(0), oldframe(0), rndindex(0), netid(0),
    tid(0), bmapnode(this)
{
	state_t *st;

	// Fly!!! fix it in P_RespawnSpecial
	if ((unsigned int)itype >= NUMMOBJTYPES)
	{
		I_Error ("Tried to spawn actor type %d\n", itype);
	}

	self.init(this);
	info = &mobjinfo[itype];
	type = itype;
	x = ix;
	y = iy;
	radius = info->radius;
	height = P_ThingInfoHeight(info);
	flags = info->flags;
	flags2 = info->flags2;
	health = info->spawnhealth;
	translucency = info->translucency;
	rndindex = M_Random();

    if (multiplayer && serverside)
        netid = ServerNetID.ObtainNetID();

	if (sv_skill != sk_nightmare)
		reactiontime = info->reactiontime;

    if (clientside)
        lastlook = P_Random() % MAXPLAYERS_VANILLA;
    else
        lastlook = P_Random() % MAXPLAYERS;

    // do not set the state with P_SetMobjState,
    // because action routines can not be called yet
	st = &states[info->spawnstate];
	state = st;
	tics = st->tics;
	sprite = st->sprite;
	frame = st->frame;
	touching_sectorlist = NULL;	// NULL head of sector list // phares 3/13/98

	// set subsector and/or block links
	LinkToWorld ();

	if(!subsector)
		return;

	floorz = P_FloorHeight(this);
	ceilingz = P_CeilingHeight(this);
	dropoffz = floorz;
	floorsector = subsector->sector;

	if (iz == ONFLOORZ)
	{
		z = floorz;
	}
	else if (iz == ONCEILINGZ)
	{
		z = ceilingz - height;
	}
	else if (flags2 & MF2_FLOATBOB)
	{
		z = floorz + iz;		// artifact z passed in as height
	}
	else
	{
		z = iz;
	}

	memset(args, 0, sizeof(args));
}

//
// P_AnimationTick
//
void P_AnimationTick(AActor *mo)
{
	if (mo && mo->tics != -1)
	{
		mo->tics--;

		// you can cycle through multiple states in a tic
		if (!mo->tics)
			if (!P_SetMobjState (mo, mo->state->nextstate) )
				return;         // freed itself
	}
}

//
// P_ClearAllNetIds
//
void P_ClearAllNetIds()
{
	actor_by_netid.clear();
}

//
// P_FindThingById
// denis - fast netid lookup
//
AActor* P_FindThingById(size_t id)
{
	netid_map_t::iterator i = actor_by_netid.find(id);

	if(i == actor_by_netid.end())
		return AActor::AActorPtr();
	else
		return i->second;
}

//
// P_SetThingId
//
void P_SetThingId(AActor *mo, size_t newnetid)
{
	mo->netid = newnetid;
	actor_by_netid[newnetid] = mo->ptr();
}


//
// P_ClearId
//
void P_ClearId(size_t id)
{
    AActor *mo = P_FindThingById(id);

	if(!mo)
		return;

	if(mo->player)
	{
		if(mo->player->mo == mo)
			mo->player->mo = AActor::AActorPtr();

		mo->player = NULL;
	}

	mo->Destroy();
}

//
// P_RemoveMobj
//
void AActor::Destroy ()
{
	SV_SendDestroyActor(this);

    // Add special to item respawn queue if it is destined to be respawned
	if ((flags & MF_SPECIAL) && !(flags & MF_DROPPED))
	{
		if (type != MT_INV && type != MT_INS &&
            (type < MT_BSOK || type > MT_RDWN))
		{
			itemrespawnque[iquehead] = spawnpoint;
			itemrespawntime[iquehead] = level.time;
			iquehead = (iquehead+1)&(ITEMQUESIZE-1);

			// lose one off the end?
			if (iquehead == iquetail)
				iquetail = (iquetail+1)&(ITEMQUESIZE-1);
		}
	}

	// [RH] Unlink from tid chain
	RemoveFromHash ();

	// unlink from sector and block lists
	UnlinkFromWorld ();

	// 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
    if (clientside)
		S_RelinkSound (this, NULL);

	Super::Destroy ();
}

//
// P_CalculateMinMom
//
// Determines the value for mo->momz that will cause the player to grunt when
// landing.
//
// [SL] Factored out of P_MoveActor for reuse and changed to fixed-point math
// for consistency purposes.
//
fixed_t P_CalculateMinMom(AActor *mo)
{
	fixed_t levelgravity, sectorgravity;

	if (co_zdoomphys)
	{
		levelgravity = FixedDiv(FLOAT2FIXED(level.gravity), 100 << FRACBITS);
		sectorgravity = FLOAT2FIXED(mo->subsector->sector->gravity);
	}
	else
	{
		levelgravity = GRAVITY * 8;
		sectorgravity = FLOAT2FIXED(mo->subsector->sector->gravity);
	}

	return -FixedMul(levelgravity, sectorgravity);
}

//
// P_MoveActor
//
// Tries to move an actor based on its momentum while performing
// clipping against other actors and walls.
//
// [SL] 2012-03-10 - Factored out from AActor::RunThink() to allow other
// functions to utilize the movement clipping code.
//
void P_MoveActor(AActor *mo)
{
	if (!mo || !mo->subsector)
		return;

	AActor *onmo = NULL;
    fixed_t minmom;

	// [RH] If standing on a steep slope, fall down it
	if (!(mo->flags & (MF_NOCLIP|MF_NOGRAVITY)) &&
		!(mo->player && mo->player->spectator) && mo->momz <= 0 &&
		mo->floorz == mo->z && mo->floorsector &&
		mo->floorsector->floorplane.c < STEEPSLOPE &&
		P_FloorHeight(mo->x, mo->y, mo->floorsector) <= mo->floorz)
	{
		const msecnode_t *node;
		bool dopush = true;

		if (mo->floorsector->floorplane.c > STEEPSLOPE*2/3)
		{
			for (node = mo->touching_sectorlist; node; node = node->m_tnext)
			{
				const sector_t *sec = node->m_sector;
				if (sec->floorplane.c >= STEEPSLOPE)
				{
					if (P_FloorHeight(mo->x, mo->y, sec) >= mo->z - 24*FRACUNIT)
					{
						dopush = false;
						break;
					}
				}
			}
		}
		if (dopush)
		{
			mo->momx += mo->floorsector->floorplane.a;
			mo->momy += mo->floorsector->floorplane.b;
		}
	}

	// Handle X and Y momemtums
    BlockingMobj = NULL;

	P_XYMovement(mo);

	if (mo->ObjectFlags & OF_MassDestruction)
		return;		// actor was destroyed

	if (mo->flags2 & MF2_FLOATBOB)
	{ // Floating item bobbing motion (special1 is height)
		mo->z = mo->floorz + mo->special1;
	}
	if ((mo->z != mo->floorz) || mo->momz || BlockingMobj)
	{
	    // Handle Z momentum and gravity
		if (co_realactorheight && (mo->flags2 & MF2_PASSMOBJ))
		{
		    if (!(onmo = P_CheckOnmobj(mo)))
			{
				P_ZMovement(mo);
				if (mo->player && mo->flags2 & MF2_ONMOBJ)
				{
					mo->flags2 &= ~MF2_ONMOBJ;
				}
			}
			else
			{
			    if (mo->player)
				{
					minmom = P_CalculateMinMom(mo);

					if (mo->momz < minmom && !(mo->flags2&MF2_FLY))
						PlayerLandedOnThing(mo, onmo);
				}
				if (onmo->z + onmo->height - mo->z <= 24 * FRACUNIT)
				{
					if (mo->player)
					{
						mo->player->viewheight -= onmo->z + onmo->height - mo->z;
						mo->player->deltaviewheight =
							(VIEWHEIGHT - mo->player->viewheight)>>3;
					}
					mo->z = onmo->z + onmo->height;
				}

				mo->flags2 |= MF2_ONMOBJ;
				mo->momz = 0;
			}
		}
	    else
	    {
            P_ZMovement(mo);
	    }

        if (mo->ObjectFlags & OF_MassDestruction)
            return;		// actor was destroyed
	}

	if (mo->subsector)
	{
		//byte lastwaterlevel = waterlevel;
		mo->waterlevel = 0;
		if (mo->subsector->sector->waterzone)
			mo->waterlevel = 3;

		sector_t *hsec = mo->subsector->sector->heightsec;
		if (hsec && hsec->waterzone && !mo->subsector->sector->waterzone)
		{
			if (mo->z < hsec->floorheight)
			{
				fixed_t floorheight = P_FloorHeight(mo->x, mo->y, hsec);
				if (mo->z < floorheight)
				{
					mo->waterlevel = 1;
					if (mo->z + mo->height/2 < floorheight)
					{
						mo->waterlevel = 2;
						if (mo->z + mo->height <= floorheight)
							mo->waterlevel = 3;
					}
				}
				else if (mo->z + mo->height > P_CeilingHeight(mo->x, mo->y, hsec))
				{
					mo->waterlevel = 3;
				}
			}
			else if (mo->z + mo->height > hsec->ceilingheight)
			{
				mo->waterlevel = 3;
			}
		}
	}

	// killough 9/12/98: objects fall off ledges if they are hanging off
	// slightly push off of ledge if hanging more than halfway off
	// [RH] Be more restrictive to avoid pushing monsters/players down steps
	if (!(mo->flags & MF_NOGRAVITY) && !(mo->flags2 & MF2_FLOATBOB) && (mo->z > mo->dropoffz) &&
		 (mo->health <= 0 || (mo->flags & MF_COUNTKILL && mo->z - mo->dropoffz > 24*FRACUNIT)) &&
		  co_allowdropoff)
	{
		P_ApplyTorque(mo);   // Apply torque
	}
	else
	{
		mo->flags &= ~MF_FALLING;
		mo->gear = 0;           // Reset torque
	}
}


//
// P_TestActorMovement
//
// [SL] 2012-03-10 - Performs collision testing to see if an actor can move
// to (tryx, tryy, tryz).  (destx, desty, destz) is the location the actor
// would be moved to with wall and actor collsion taken into account.
//
void P_TestActorMovement(AActor *mo, fixed_t tryx, fixed_t tryy, fixed_t tryz,
						fixed_t &destx, fixed_t &desty, fixed_t &destz)
{
	// backup the actor's position/state
	ActorSnapshot backup(gametic, mo);

	mo->momx = tryx - mo->x;
	mo->momy = tryy - mo->y;
	mo->momz = tryz - mo->z;

	// Perform collision testing
	P_MoveActor(mo);

	// the position the actor would move to
	destx = mo->x;
	desty = mo->y;
	destz = mo->z;

	// restore the actor's position/state
	backup.toActor(mo);
}

//
// P_MobjThinker
//
void AActor::RunThink ()
{
	if(!subsector)
		return;

    // server removal of corpses only
    if (!clientside && serverside)
    {
        if (type == MT_PLAYER && health <= 0)
            deadtic++;
    }

	// GhostlyDeath -- Was a spectator but now it's nothing!
	if ((this->flags & MF_SPECTATOR ) && !player)
	{
		this->Destroy();
		return;
	}

	// remove dead players but don't tell clients about it
	if (type == MT_PLAYER && !player && deadtic >= REMOVECORPSESTIC)
	{
		this->Destroy();
		return;
	}

	// [RH] Fade a stealth monster in and out of visibility
	if (visdir > 0)
	{
		translucency += 2*FRACUNIT/TICRATE;
		if (translucency > FRACUNIT)
		{
			translucency = FRACUNIT;
			visdir = 0;
		}
	}
	else if (visdir < 0)
	{
		translucency -= 3*FRACUNIT/TICRATE/2;
		if (translucency < 0)
		{
			translucency = 0;
			visdir = 0;
		}
	}

	P_MoveActor(this);

	if(predicting)
		return;

	if (flags2 & MF2_DORMANT)
		return;

    // cycle through states,
    // calling action functions at transitions
	if (tics != -1)
	{
		// run P_AnimationTick on everything except players who aren't voodoo dolls
		if (!(player && this == player->mo))
			P_AnimationTick(this);
	}
	else
	{
		// check for nightmare respawn
		if (!(flags & MF_COUNTKILL) || !respawnmonsters)
			return;

		movecount++;

		if (movecount < 12*TICRATE)
			return;

		if (level.time & 31)
			return;

		if (P_Random (this) > 4)
			return;

		P_NightmareRespawn (this);
	}
}


void AActor::Serialize (FArchive &arc)
{
	Super::Serialize (arc);
	if (arc.IsStoring ())
	{
		int playerid = player ? player->id : 0;
		arc << netid
			<< x
			<< y
			<< z
			<< pitch
			<< angle

			// [SL] Removed AActor::roll
			// delete this next time saved-game compatibilty changes
			<< 0

			<< sprite
			<< frame
			<< effects
			<< floorz
			<< ceilingz
			<< dropoffz
			<< radius
			<< height
			<< momx
			<< momy
			<< momz
			<< type
			<< tics
			<< state
			<< flags
			<< flags2
			<< special1
			<< special2
			<< health
			<< movedir
			<< visdir
			<< movecount
			/*<< target ? target->netid : 0*/
			/*<< lastenemy ? lastenemy->netid : 0*/
			<< reactiontime
			<< threshold
			<< playerid
			<< lastlook
			<< tracer
			<< tid
            << special
			<< args[0]
			<< args[1]
			<< args[2]
			<< args[3]
			<< args[4]
			/*<< goal ? goal->netid : 0*/
			<< (unsigned)0
			<< translucency
			<< waterlevel
			<< gear;

		if (translation)
			arc << (DWORD)(translation - translationtables);
		else
			arc << (DWORD)0xffffffff;
		spawnpoint.Serialize (arc);
	}
	else
	{
		unsigned dummy;
		unsigned playerid;
		int newnetid;
		AActor* tmptracer;

		arc >> newnetid
			>> x
			>> y
			>> z
			>> pitch
			>> angle

			// [SL] Removed AActor::roll
			// delete this next time saved-game compatibilty changes
			>> dummy

			>> sprite
			>> frame
			>> effects
			>> floorz
			>> ceilingz
			>> dropoffz
			>> radius
			>> height
			>> momx
			>> momy
			>> momz
			>> type
			>> tics
			>> state
			>> flags
			>> flags2
			>> special1
			>> special2
			>> health
			>> movedir
			>> visdir
			>> movecount
			/*>> target->netid*/
			/*>> lastenemy->netid*/
			>> reactiontime
			>> threshold
			>> playerid
			>> lastlook
			>> tmptracer
			>> tid
			>> special
			>> args[0]
			>> args[1]
			>> args[2]
			>> args[3]
			>> args[4]
			/*>> goal->netid*/
			>> dummy
			>> translucency
			>> waterlevel
			>> gear;

		tracer.init(tmptracer);

		P_SetThingId(this, newnetid);

		DWORD trans;
		arc >> trans;
		if (trans == (DWORD)0xffffffff)
			translation = NULL;
		else
			translation = translationtables + trans;
		spawnpoint.Serialize (arc);
		if(type >= NUMMOBJTYPES)
			I_Error("Unknown object type in saved game");
		if(sprite >= NUMSPRITES)
			I_Error("Unknown sprite in saved game");
		info = &mobjinfo[type];
		touching_sectorlist = NULL;

		LinkToWorld ();
		floorsector = subsector->sector;

		AddToHash ();
		if(playerid && validplayer(idplayer(playerid)))
		{
			player = &idplayer(playerid);
			player->mo = ptr();
			player->camera = player->mo;
		}
	}
}

//
// P_ThingInfoHeight [From EE]
//
// haleyjd 07/06/05:
//
// Function to retrieve proper thing height information for a thing.
//
int P_ThingInfoHeight(mobjinfo_t *mi)
{
   return
      (co_realactorheight && mi->cdheight ?
       mi->cdheight : mi->height);
}

extern void SV_UpdateMobjState(AActor *mo);

// P_SetMobjState
//
// Returns true if the mobj is still present.
bool P_SetMobjState(AActor *mobj, statenum_t state, bool cl_update)
{
	state_t* st;

	// denis - prevent harmful state cycles
	static unsigned int callstack;
	if(callstack++ > 16)
	{
		callstack = 0;
		I_Error("P_SetMobjState: callstack depth exceeded bounds");
	}

	do
	{
		if (state == S_NULL)
		{
			mobj->state = (state_t *) S_NULL;
			mobj->Destroy();

			callstack--;
			return false;
		}

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

		// [AM] Broadcast the state of the mobj to every player, after changing
		//      it but before running the action associated with it.
		if (serverside && cl_update)
			SV_UpdateMobjState(mobj);

		// Modified handling.
		// Call action functions when the state is set
		if (st->action)
			st->action(mobj);

		state = st->nextstate;
	} while (!mobj->tics);

	callstack--;
	return true;
}

//
// P_XYMovement
//
void P_XYMovement(AActor *mo)
{
	fixed_t ptryx, ptryy;

	if (!mo || !mo->subsector)
		return;

	if (mo->flags2 & MF2_WINDTHRUST)
	{
		static const int windTab[3] = {2048*5, 2048*10, 2048*25};
		int special = mo->subsector->sector->special;
		switch (special)
		{
			case 40: case 41: case 42: // Wind_East
				P_ThrustMobj (mo, 0, windTab[special-40]);
				break;
			case 43: case 44: case 45: // Wind_North
				P_ThrustMobj (mo, ANG90, windTab[special-43]);
				break;
			case 46: case 47: case 48: // Wind_South
				P_ThrustMobj (mo, ANG270, windTab[special-46]);
				break;
			case 49: case 50: case 51: // Wind_West
				P_ThrustMobj (mo, ANG180, windTab[special-49]);
				break;
		}
	}

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

	fixed_t maxmove = (mo->waterlevel < 2) || (mo->flags & MF_MISSILE) ? MAXMOVE : MAXMOVE/4;

	fixed_t xmove = mo->momx = clamp (mo->momx, -maxmove, maxmove);
	fixed_t ymove = mo->momy = clamp (mo->momy, -maxmove, maxmove);

	player_t *player = mo->player;
	if (!player || !player->mo)
		player = NULL;

	maxmove /= 2;

	// [RH] Adjust player movement on sloped floors
	bool walkplane = P_CheckSlopeWalk (mo, xmove, ymove);

	do
	{
		// This is where the "wallrunning" effect happens. Vanilla only
		// allows wallrunning North and East, while ZDoom physics allow
		// North and South.

		if (xmove > maxmove || ymove > maxmove ||
			(co_zdoomphys && (xmove < -maxmove || ymove < -maxmove)))
		{
			if (co_zdoomphys)
			{
				// [SL] Note: xmove >> 1 behaves differently than xmove / 2
				// for negative numbers
				ptryx = mo->x + (xmove >> 1);
				ptryy = mo->y + (ymove >> 1);
			}
			else
			{
				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, walkplane))
		{
			// blocked move
            if (mo->flags2 & MF2_SLIDE)
			{
				// try to slide along it
				if (BlockingMobj == NULL)
				{
					// slide against wall
					if (BlockingLine != NULL &&
						mo->player && mo->waterlevel && mo->waterlevel < 3 &&
						(mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove) &&
						BlockingLine->sidenum[1] != R_NOSIDE)
					{
						mo->momz = WATER_JUMP_SPEED;
					}

					P_SlideMove (mo);
				}
				else
				{
					// slide against mobj

					// try sliding in the x direction
					fixed_t tx = 0, ty = ptryy - mo->y;
					walkplane = P_CheckSlopeWalk(mo, tx, ty);
					if (P_TryMove (mo, mo->x + tx, mo->y + ty, true, walkplane))
						mo->momx = 0;
					else
					{
						// try sliding in the y direction
						tx = ptryx - mo->x, ty = 0;
						walkplane = P_CheckSlopeWalk(mo, tx, ty);
						if (P_TryMove (mo, mo->x + tx, mo->y + ty, true, walkplane))
							mo->momy = 0;
						else
							mo->momx = mo->momy = 0;
					}
				}
			}
			else if (mo->flags & MF_MISSILE)
			{
				// [SL] 2012-01-25 - Don't explode missiles on horizon line
				if (BlockingLine && BlockingLine->special == Line_Horizon &&
					co_fixweaponimpacts)
				{
					mo->Destroy();
					return;
				}

				// explode a missile
				if (ceilingline &&
					ceilingline->backsector &&
					ceilingline->backsector->ceilingpic == skyflatnum)
				{
					// Hack to prevent missiles exploding
					// against the sky.
					// Does not handle sky floors.

					// [SL] 2011-09-16 - Add fix for impact of missiles against
					// lower or upper walls whose line is facing away from a
					// bordering sector with a F_SKY ceiling texture.  In vanilla
					// Doom, the missile disappears when hitting such a wall
					// instead of exploding.

					if (!co_fixweaponimpacts ||
						mo->z > P_CeilingHeight(mo->x, mo->y, ceilingline->backsector))
					{
						mo->Destroy ();
						return;
					}
				}
				// [SL] 2011-06-02 - Only server should control explosions
				if (serverside)
					 P_ExplodeMissile (mo);

			}
			else
			{
				mo->momx = mo->momy = 0;
			}
		}
	} while (xmove || ymove);

	// slow down
	if (player && player->mo == mo && player->cheats & CF_NOMOMENTUM)
	{
		// debug option for no sliding at all
		mo->momx = mo->momy = 0;
		return;
	}

	if (mo->flags & (MF_MISSILE | MF_SKULLFLY))
	{
		return; 	// no friction for missiles ever
	}

	// [ML] From ZDoom 1.23...
	if (mo->z > mo->floorz && !(mo->flags2 & MF2_ONMOBJ) &&
		!(mo->flags2 & MF2_FLY)	&& !mo->waterlevel)
	{ // [RH] Friction when falling is available for larger aircontrols
		if (co_zdoomphys && player != NULL && level.airfriction != FRACUNIT)
		{
			mo->momx = FixedMul (mo->momx, level.airfriction);
			mo->momy = FixedMul (mo->momy, level.airfriction);

			if (player->mo == mo)		//  Not voodoo dolls
			{
				mo->momx = FixedMul (mo->momx, level.airfriction);
				mo->momy = FixedMul (mo->momy, level.airfriction);
			}
		}
		return;
	}

	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 > P_FloorHeight(mo))
				return;
		}
	}

	// killough 11/98:
	// Stop voodoo dolls that have come to rest, despite any
	// moving corresponding player:
	if (mo->momx > -STOPSPEED && mo->momx < STOPSPEED
		&& mo->momy > -STOPSPEED && mo->momy < STOPSPEED
		&& (!player || (player->mo != mo)
		|| !(player->cmd.ucmd.forwardmove | player->cmd.ucmd.sidemove)))
	{
		// if in a walking frame, stop moving
		// killough 10/98:
		// Don't affect main player when voodoo dolls stop:
		if (player && (unsigned)((player->mo->state - states) - S_PLAY_RUN1) < 4
			&& (player->mo == mo))
		{
			P_SetMobjState (player->mo, S_PLAY);
		}

		mo->momx = mo->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.

		fixed_t friction = P_GetFriction (mo, NULL);

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

//
// P_ZMovement
// joek - from choco with love
//
void P_ZMovement(AActor *mo)
{
	fixed_t	dist;
	fixed_t	delta;
	fixed_t oldz = mo->z;

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

	// apply gravity (if in zdoomland)
	if (co_zdoomphys && (mo->z > mo->floorz && !(mo->flags & MF_NOGRAVITY)))
	{
		fixed_t startmomz = mo->momz;

		if (!mo->waterlevel || mo->flags & MF_CORPSE ||
			(mo->player && !(mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove)))
		{
			mo->momz -= (fixed_t)(level.gravity * mo->subsector->sector->gravity *
						(mo->flags2 & MF2_LOGRAV ? 10.24 : 81.92));
		}
		if (mo->waterlevel > 1)
		{
			fixed_t sinkspeed = mo->flags & MF_CORPSE ? -WATER_SINK_SPEED/3 : -WATER_SINK_SPEED;

			if (mo->momz < sinkspeed)
			{
				mo->momz = (startmomz < sinkspeed) ? startmomz : sinkspeed;
			}
			else
			{
				mo->momz = startmomz + ((mo->momz - startmomz) >>
						   (mo->waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR));
			}
		}
	}

	// adjust height
	mo->z += mo->momz;

	// GhostlyDeath <Jun, 4 2008> -- Floating monsters shouldn't adjust to spectator height

	if (mo->flags & MF_FLOAT && mo->target &&
	  !(mo->target->player && mo->target->player->spectator))
	{
		// float down towards target if too close
		if ( !(mo->flags & MF_SKULLFLY) && !(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;
		}
	}

	if (mo->player && (mo->flags2 & MF2_FLY) && (mo->z > mo->floorz))
	{
		mo->z += finesine[(FINEANGLES/80*level.time)&FINEMASK]/8;
		mo->momz = FixedMul (mo->momz, FRICTION_FLY);
	}

	if (mo->waterlevel && !(mo->flags & MF_NOGRAVITY))
	{
		mo->momz = FixedMul (mo->momz, mo->subsector->sector->friction);
	}

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

		// [AM] If there is a actor special for hitting the floor,
		//      activate it.
		if (mo->subsector->sector->SecActTarget != NULL &&
		    P_FloorHeight(mo->x, mo->y, mo->subsector->sector) == mo->floorz) {
			A_TriggerAction(mo->subsector->sector->SecActTarget,
			                mo, SECSPAC_HitFloor);
		}

		// 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 - not
		//  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.)
		//
		//  fraggle - cph got the logic here slightly wrong.  There are three
		//  versions of Doom 1.9:
		//
		//  * The version used in registered doom 1.9 + doom2 - no bounce
		//  * The version used in ultimate doom - has bounce
		//  * The version used in final doom - has bounce
		//
		// So we need to check that this is either retail or commercial
		// (but not doom2)

		int correct_lost_soul_bounce = co_zdoomphys || gamemode == retail ||
                                     ((gamemode == commercial
                                     && (gamemission == pack_tnt || gamemission == pack_plut)));

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

		mo->z = mo->floorz;

		if (mo->momz < 0)
		{
			if (mo->player)
			{
				bool momsquat = false;

				fixed_t minmom = P_CalculateMinMom(mo);
				if (mo->momz < minmom)
					momsquat = true;

				mo->player->jumpTics = 7;	// delay any jumping for a short while
				if (momsquat && !(mo->player->spectator) && !(mo->flags2 & MF2_FLY))
				{
					// Squat down.
					// Decrease viewheight for a moment
					// after hitting the ground (hard),
					// and utter appropriate sound.
					PlayerLandedOnThing(mo, NULL);
				}
			}

			mo->momz = 0;
		}

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

		if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
		{
			if ((HasBehavior || co_fixweaponimpacts) &&
				mo->subsector->sector->floorpic == skyflatnum)
			{
				mo->Destroy();
				return;
			}

			// [SL] 2011-06-02 - Only server should control explosions
			if (serverside)
				P_ExplodeMissile (mo);
			return;
		}
	}
	else
	{
		// actor is above the floor

		// apply gravity (if standard or boom)
		if (!co_zdoomphys)
		{
			fixed_t startmomz = mo->momz;

			if (!mo->waterlevel || (mo->player &&
			   !(mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove)))
			{
				if (mo->flags2 & MF2_LOGRAV)
				{
					if (mo->momz == 0)
						mo->momz = (fixed_t)(GRAVITY * mo->subsector->sector->gravity * -0.2);
					else
						mo->momz -= (fixed_t)(GRAVITY * mo->subsector->sector->gravity * 0.1);
				}
				else if (! (mo->flags & MF_NOGRAVITY) )
				{
					if (mo->momz == 0)
						mo->momz = (fixed_t)(GRAVITY * mo->subsector->sector->gravity * -2);
					else
						mo->momz -= (fixed_t)(GRAVITY * mo->subsector->sector->gravity);
				}

				if (mo->waterlevel > 1)
				{
					fixed_t sinkspeed = mo->flags & MF_CORPSE ? -WATER_SINK_SPEED/3 : -WATER_SINK_SPEED;

					if (mo->momz < sinkspeed)
						mo->momz = (startmomz < sinkspeed) ? startmomz : sinkspeed;
					else
						mo->momz = startmomz + ((mo->momz - startmomz) >>
							(mo->waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR));
				}
			}
		}
	}

	if (mo->z + mo->height > mo->ceilingz)
	{
		// hit the ceiling

		// [AM] If there is a actor special for hitting the floor,
		//      activate it.
		if (mo->subsector->sector->SecActTarget != NULL &&
		    P_CeilingHeight(mo->x, mo->y, mo->subsector->sector) == mo->ceilingz) {
			A_TriggerAction(mo->subsector->sector->SecActTarget,
			                mo, SECSPAC_HitCeiling);
		}

		if (mo->flags2 & MF2_FLOORBOUNCE)
		{
			// reverse momentum here for ceiling bounce
			mo->momz = FixedMul (mo->momz, (fixed_t)(-0.75*FRACUNIT));
			if (mo->info->seesound)
				S_Sound(mo, CHAN_BODY, mo->info->seesound, 1, ATTN_IDLE);

			return;
		}

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

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

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

		if (mo->flags & MF_MISSILE && !(mo->flags & MF_NOCLIP))
		{
			if ((HasBehavior || co_fixweaponimpacts) &&
				 mo->subsector->sector->ceilingpic == skyflatnum)
			{
				mo->Destroy ();
				return;
			}

			// [SL] 2011-06-02 - Only server should control explosions
			if (serverside)
				P_ExplodeMissile (mo);
			return;
		}
	}

	// [AM] Handle actor specials that deal with fake floors and ceilings.
	if (mo->subsector->sector->heightsec != NULL && mo->subsector->sector->SecActTarget != NULL) {
		sector_t *hs = mo->subsector->sector->heightsec;
		fixed_t waterz = P_FloorHeight(mo->x, mo->y, hs);
		fixed_t newz;
		fixed_t viewheight;

		if (mo->player != NULL) {
			viewheight = mo->player->viewheight;
		} else {
			viewheight = mo->height / 2;
		}

		newz = mo->z + viewheight;
		oldz += viewheight;

		if (oldz <= waterz && newz > waterz) {
			// View went above fake floor
			A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesSurface);
		} else if (oldz > waterz && newz <= waterz) {
			// View went below fake floor
			A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesDive);
		}

		if (!(hs->MoreFlags & SECF_FAKEFLOORONLY)) {
			waterz = P_CeilingHeight(mo->x, mo->y, hs);
			if (oldz <= waterz && newz > waterz) {
				// View went above fake ceiling
				A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesAboveC);
			} else if (oldz > waterz && newz <= waterz) {
				// View went below fake ceiling
				A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesBelowC);
			}
		}
	}
}

//
// PlayerLandedOnThing
//
void PlayerLandedOnThing(AActor *mo, AActor *onmobj)
{
	if ((clientside && predicting) || (mo->player && mo->player->spectator))
		return;

	mo->player->deltaviewheight = mo->momz>>3;

	// The server sends the sound to us for other players
	if (mo->player->id != consoleplayer_id && !serverside)
		return;

	if (co_zdoomphys)
	{
		// [SL] 2011-06-16 - ZDoom Oomphiness
		if (mo->health > 0)
		{
			if (mo->momz < (fixed_t)(level.gravity * mo->subsector->sector->gravity * -983.04f))
				UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);

			UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);
		}
	}
	else
	{
		// [SL] 2011-06-16 - Vanilla Doom Oomphiness
		UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);
	}
//	mo->player->centering = true;
}

//
// P_NightmareRespawn
//
void P_NightmareRespawn (AActor *mobj)
{
    fixed_t         x;
    fixed_t         y;
    fixed_t         z;
    subsector_t*    ss;
    mapthing2_t*    mthing;
    AActor          *mo;

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

    // something is occupying it's position?
    if (!P_CheckPosition (mobj, x, y))
		return; // no respawn

    // spawn a teleport fog at old spot
    // because of removal of the body?
	mo = new AActor(
        mobj->x,
        mobj->y,
        P_FloorHeight(mobj),
        MT_TFOG
    );
	// initiate teleport sound
    if (clientside)
        S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);

    // spawn a teleport fog at the new spot
    ss = R_PointInSubsector (x,y);

	// spawn a teleport fog at the new spot
    mo = new AActor (x, y,  P_FloorHeight(x, y, ss->sector), MT_TFOG);
    if (clientside)
        S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);

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

    // spawn it
    if (mobj->info->flags & MF_SPAWNCEILING)
		z = ONCEILINGZ;
	else if (mobj->info->flags2 & MF2_FLOATBOB)
		z = mthing->z << FRACBITS;
    else
		z = ONFLOORZ;

	// spawn it
	// inherit attributes from deceased one
	if(serverside)
	{
		mo = new AActor (x, y, z, mobj->type);
		mo->spawnpoint = mobj->spawnpoint;
		mo->angle = ANG45 * (mthing->angle/45);

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

        if (serverside)
            SV_SpawnMobj(mo);

		mo->reactiontime = 18;
	}

	// remove the old monster,
	mobj->Destroy ();
}

AActor *AActor::TIDHash[128];

//
// [RH] Some new functions to work with Thing IDs. ------->
//

//
// P_ClearTidHashes
//
// Clears the tid hashtable.
//

void AActor::ClearTIDHashes ()
{
	int i;

	for (i = 0; i < 128; i++)
		TIDHash[i] = NULL;
}

//
// P_AddMobjToHash
//
// Inserts an mobj into the correct chain based on its tid.
// If its tid is 0, this function does nothing.
//
void AActor::AddToHash ()
{
	if (tid == 0)
	{
		inext = iprev = NULL;
		return;
	}
	else
	{
		int hash = TIDHASH (tid);

		inext = TIDHash[hash];
		iprev = NULL;
		TIDHash[hash] = this;
	}
}

//
// P_RemoveMobjFromHash
//
// Removes an mobj from its hash chain.
//
void AActor::RemoveFromHash ()
{
	if (tid == 0)
		return;
	else
	{
		if (iprev == NULL)
		{
			// First mobj in the chain (probably)
			int hash = TIDHASH(tid);

			if (TIDHash[hash] == this)
				TIDHash[hash] = inext;
			if (inext)
			{
				inext->iprev = NULL;
				inext = NULL;
			}
		}
		else
		{
			// Not the first mobj in the chain
			iprev->inext = inext;
			if (inext)
			{
				inext->iprev = iprev;
				inext = NULL;
			}
			iprev = NULL;
		}
	}
}

//
// P_FindMobjByTid
//
// Returns the next mobj with the tid after the one given,
// or the first with that tid if no mobj is passed. Returns
// NULL if there are no more.
//
AActor *AActor::FindByTID (int tid) const
{
	return FindByTID (this, tid);
}

AActor *AActor::FindByTID (const AActor *actor, int tid)
{
	// Mobjs without tid are never stored.
	if (tid == 0)
		return NULL;

	if (!actor)
		actor = TIDHash[TIDHASH(tid)];
	else
		actor = actor->inext;

	while (actor && actor->tid != tid)
		actor = actor->inext;

	return const_cast<AActor *>(actor);
}

//
// P_FindGoal
//
// Like FindByTID except it also matches on type.
//
AActor *AActor::FindGoal (int tid, int kind) const
{
	return FindGoal (this, tid, kind);
}

AActor *AActor::FindGoal (const AActor *actor, int tid, int kind)
{
	do
	{
		actor = FindByTID (actor, tid);
	} while (actor && actor->type != kind);

	return const_cast<AActor *>(actor);
}

// <------- [RH] End new functions

//
// GAME SPAWN FUNCTIONS
//

//
// P_SpawnPuff
//
void P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z)
{
	// [SL] Allow only servers and clients that are predicting their own shots
	if (!serverside && (shootthing != consoleplayer().mo ||
					   !consoleplayer().userinfo.predict_weapons))
		return;

    AActor *puff;

	z += (P_RandomDiff () << 10);

    puff = new AActor(x, y, z, MT_PUFF);
    puff->momz = FRACUNIT;
    puff->tics -= P_Random(puff) & 3;

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

	// don't make punches spark on the wall
	if (attackrange == MELEERANGE)
        P_SetMobjState(puff, S_PUFF3);

    if (serverside)
	{
		// [SL] 2012-10-02 - Allow a client to predict their own bullet puffs
		// so don't send the puffs to the client already predicting
		if (shootthing && shootthing->player && shootthing->player->userinfo.predict_weapons)
			puff->players_aware.set(shootthing->player->id);

		SV_SpawnMobj(puff);
	}
}

//
// P_SpawnBlood
//
void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage)
{
	// denis - not clientside
	if(!serverside)
		return;

	AActor *th;

	z += P_RandomDiff () << 10;
	th = new AActor (x, y, z, MT_BLOOD);
	th->momz = FRACUNIT*2;
	th->tics -= P_Random (th) & 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);
    if (serverside)
        SV_SpawnMobj(th);
}


//
//  P_HitFloor
//

bool P_HitFloor (AActor *thing)
{
    return true;
}


bool SV_AwarenessUpdate(player_t &pl, AActor* mo);
//
// P_CheckMissileSpawn
// Moves the missile forward a bit
//	and possibly explodes it right there.
//
bool P_CheckMissileSpawn (AActor* th)
{
	if (!th)
		return false;

	th->tics -= P_Random (th) & 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 3/15/98: no dropoff (really = don't care for missiles)

	// [SL] 2011-06-02 - If a missile explodes immediatley upon firing,
	// make sure we spawn the missile first, send it to all clients immediately
	// instead of queueing it, then explode it.
	for (size_t i = 0; i < players.size(); i++)
	{
		SV_AwarenessUpdate(players[i], th);
	}

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

	return true;
}

//
// P_SpawnMissile
//
AActor* P_SpawnMissile (AActor *source, AActor *dest, mobjtype_t type)
{
    AActor *th;
    angle_t	an;
    int		dist;
    fixed_t     dest_x, dest_y, dest_z, dest_flags;

	// denis: missile spawn code from chocolate doom
	//
    // fraggle: This prevents against *crashes* when dest == NULL.
    // For example, when loading a game saved when a mancubus was
    // in the middle of firing, mancubus->target == NULL.  SpawnMissile
    // then gets called with dest == NULL.
    //
    // However, this is not the *correct* behavior.  At the moment,
    // the missile is aimed at 0,0,0.  In reality, monsters seem to aim
    // somewhere else.

    if (dest)
    {
        dest_x = dest->x;
        dest_y = dest->y;
        dest_z = dest->z;
        dest_flags = dest->flags;
    }
    else
    {
        dest_x = 0;
        dest_y = 0;
        dest_z = 0;
        dest_flags = 0;
    }

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

    if (th->info->seesound)
		S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM);

    th->target = source->ptr();	// where it came from
    an = P_PointToAngle (source->x, source->y, dest_x, dest_y);

    // fuzzy player
    if (dest_flags & MF_SHADOW)
		an += P_RandomDiff()<<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 (AActor *source, mobjtype_t type)
{
	if(!serverside)
		return;

	angle_t an;
	fixed_t slope;
	fixed_t pitchslope = finetangent[FINEANGLES/4-(source->pitch>>ANGLETOFINESHIFT)];

	// see which target is to be aimed at
	an = source->angle;
	if (source->player && source->player->userinfo.aimdist == 0 && sv_freelook)
	{
		slope = pitchslope;
	}
	else
	{
		// [AM] Refactored autoaim into a single function.
		if (co_fineautoaim)
			slope = P_AutoAimLineAttack(source, an, 1 << 26, 10, 16 * 64 * FRACUNIT);
		else
			slope = P_AutoAimLineAttack(source, an, 1 << 26, 1, 16 * 64 * FRACUNIT);

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

			if(sv_freelook)
				slope = pitchslope;
			else
				slope = 0;
		}

		if (linetarget && source->player)
		{
			if (sv_freelook && abs(slope - pitchslope) > source->player->userinfo.aimdist)
			{
				an = source->angle;
				slope = pitchslope;
			}
		}
	}

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

	if (th->info->seesound)
		S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM);

	th->target = source->ptr();
	th->angle = an;

	if (co_zdoomphys)
	{
		v3float_t velocity;
		float speed = FIXED2FLOAT (th->info->speed);

		velocity.x = FIXED2FLOAT (finecosine[an>>ANGLETOFINESHIFT]);
		velocity.y = FIXED2FLOAT (finesine[an>>ANGLETOFINESHIFT]);
		velocity.z = FIXED2FLOAT (slope);

		M_NormalizeVec3f(&velocity, &velocity);

		th->momx = FLOAT2FIXED (velocity.x * speed);
		th->momy = FLOAT2FIXED (velocity.y * speed);
		th->momz = FLOAT2FIXED (velocity.z * speed);
	}
	else
	{
		fixed_t speed = th->info->speed;

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

	P_CheckMissileSpawn (th);
}


//
// P_RespawnSpecials
//
void P_RespawnSpecials (void)
{
	fixed_t 			x;
	fixed_t 			y;
	fixed_t 			z;

	AActor* 			mo;
	mapthing2_t* 		mthing;

	int 				i;

    // clients do no control respawning of items
	if(!serverside)
		return;

    // allow respawning if we specified it
	if (!sv_itemsrespawn)
		return;

	// nothing left to respawn?
	if (iquehead == iquetail)
		return;

	// wait a certain number of seconds before respawning this special
	if (level.time - itemrespawntime[iquetail] < sv_itemrespawntime*TICRATE)
		return;

	mthing = &itemrespawnque[iquetail];

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

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

	// [Fly] crashes sometimes without it
	if (i >= NUMMOBJTYPES)
	{
		// pull it from the que
		iquetail = (iquetail+1)&(ITEMQUESIZE-1);
		return;
	}

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

	// spawn a teleport fog at the new spot
	mo = new AActor (x, y, z, MT_IFOG);
	SV_SpawnMobj(mo);
    if (clientside)
        S_Sound (mo, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE);

	// spawn it
	mo = new AActor (x, y, z, (mobjtype_t)i);
	mo->spawnpoint = *mthing;
	mo->angle = ANG45 * (mthing->angle/45);

	if (z == ONFLOORZ)
		mo->z += mthing->z << FRACBITS;
	else if (z == ONCEILINGZ)
		mo->z -= mthing->z << FRACBITS;

	if (mo->flags2 & MF2_FLOATBOB)
	{ // Seed random starting index for bobbing motion
		mo->health = M_Random();
		mo->special1 = mthing->z << FRACBITS;
	}

	mo->special = 0;

	// pull it from the que
	iquetail = (iquetail+1)&(ITEMQUESIZE-1);

	SV_SpawnMobj(mo);
}


//
// P_ExplodeMissile
//
void P_ExplodeMissile (AActor* mo)
{
	SV_ExplodeMissile(mo);

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

	P_SetMobjState (mo, mobjinfo[mo->type].deathstate);
	if (mobjinfo[mo->type].deathstate != S_NULL)
	{
		// [RH] If the object is already translucent, don't change it.
		// Otherwise, make it 66% translucent.
		//if (mo->translucency == FRACUNIT)
		//	mo->translucency = TRANSLUC66;

		mo->translucency = FRACUNIT;

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

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

		mo->flags &= ~MF_MISSILE;

		if (mo->info->deathsound)
			S_Sound (mo, CHAN_VOICE, mo->info->deathsound, 1, ATTN_NORM);

		mo->effects = 0;		// [RH]
	}
}

//
// P_ThrustMobj
//
void P_ThrustMobj (AActor *mo, angle_t angle, fixed_t move)
{
	angle >>= ANGLETOFINESHIFT;
	mo->momx += FixedMul (move, finecosine[angle]);
	mo->momy += FixedMul (move, finesine[angle]);
}

//
// P_GetMapThingPlayerNumber
//
// Returns the player number for a coop player start mapthing
//
size_t P_GetMapThingPlayerNumber(mapthing2_t *mthing)
{
	if (!mthing)
		return 0;

	return mthing->type <= 4 ?
			mthing->type - 1 :
			(mthing->type - 4001 + 4) % MAXPLAYERSTARTS;
}

//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
// [RH] position is used to weed out unwanted start spots
//
void P_SpawnMapThing (mapthing2_t *mthing, int position)
{
	int i = -1;
	int bit;
	AActor *mobj;
	fixed_t x, y, z;

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

	if (sv_allowshowspawns)
		P_ShowSpawns(mthing);

	// only servers control spawning of items
    // EXCEPT the client must spawn Type 14 (teleport exit).
	// otherwise teleporters won't work well.
	if (!serverside && (mthing->type != 14))
		return;

	// count deathmatch start positions
	if (mthing->type == 11 || (!sv_teamspawns && mthing->type >= 5080 && mthing->type <= 5082))
	{
		// [Nes] Maximum vanilla demo starts are fixed at 10.
		if (deathmatch_p >= &deathmatchstarts[10] && (demoplayback || demorecording) && democlassic)
			return;

		if (deathmatch_p == &deathmatchstarts[MaxDeathmatchStarts])
		{
			// [RH] Get more deathmatchstarts
			int offset = MaxDeathmatchStarts;
			MaxDeathmatchStarts *= 2;
			deathmatchstarts = (mapthing2_t *)Realloc (deathmatchstarts, MaxDeathmatchStarts * sizeof(mapthing2_t));
			deathmatch_p = &deathmatchstarts[offset];
		}
		memcpy (deathmatch_p, mthing, sizeof(*mthing));
		deathmatch_p++;
		return;
	}

	// [Toke - CTF - starts] CTF starts - count Blue team start positions
	if (mthing->type == 5080 && sv_teamspawns)
	{
		if (blueteam_p == &blueteamstarts[MaxBlueTeamStarts])
		{
			int offset = MaxBlueTeamStarts;
			MaxBlueTeamStarts *= 2;
			blueteamstarts = (mapthing2_t *)Realloc (blueteamstarts, MaxBlueTeamStarts * sizeof(mapthing2_t));
			blueteam_p = &blueteamstarts[offset];
		}
		memcpy (blueteam_p, mthing, sizeof(*mthing));
		blueteam_p++;
		return;
	}

	// [Toke - CTF - starts] CTF starts - count Red team start positions
	if (mthing->type == 5081 && sv_teamspawns)
	{
		if (redteam_p == &redteamstarts[MaxRedTeamStarts])
		{
			int offset = MaxRedTeamStarts;
			MaxRedTeamStarts *= 2;
			redteamstarts = (mapthing2_t *)Realloc (redteamstarts, MaxRedTeamStarts * sizeof(mapthing2_t));
			redteam_p = &redteamstarts[offset];
		}
		memcpy (redteam_p, mthing, sizeof(*mthing));
		redteam_p++;
		return;
	}

	// [RH] Record polyobject-related things
	if (HexenHack)
	{
		switch (mthing->type)
		{
		case PO_HEX_ANCHOR_TYPE:
			mthing->type = PO_ANCHOR_TYPE;
			break;
		case PO_HEX_SPAWN_TYPE:
			mthing->type = PO_SPAWN_TYPE;
			break;
		case PO_HEX_SPAWNCRUSH_TYPE:
			mthing->type = PO_SPAWNCRUSH_TYPE;
			break;
		}
	}

	if (mthing->type == PO_ANCHOR_TYPE ||
		mthing->type == PO_SPAWN_TYPE ||
		mthing->type == PO_SPAWNCRUSH_TYPE)
	{
		polyspawns_t *polyspawn = new polyspawns_t;
		polyspawn->next = polyspawns;
		polyspawn->x = mthing->x << FRACBITS;
		polyspawn->y = mthing->y << FRACBITS;
		polyspawn->angle = mthing->angle;
		polyspawn->type = mthing->type;
		polyspawns = polyspawn;
		if (mthing->type != PO_ANCHOR_TYPE)
			po_NumPolyobjs++;
		return;
	}

	// check for players specially
	if ((mthing->type <= 4 && mthing->type > 0)
		|| (mthing->type >= 4001 && mthing->type <= 4001 + MAXPLAYERSTARTS - 4))
	{
		// [RH] Only spawn spots that match position.
		if (mthing->args[0] != position)
			return;

		size_t playernum = P_GetMapThingPlayerNumber(mthing);

		// search for spots that already are for this player number
		for (size_t i = 0; i < playerstarts.size(); i++)
		{
			size_t otherplayernum = P_GetMapThingPlayerNumber(&playerstarts[i]);

			if (otherplayernum == playernum)
			{
				// consider playerstarts[i] to be a voodoo doll start
				voodoostarts.push_back(playerstarts[i]);
				playerstarts.erase(playerstarts.begin() + i);
				break;
			}
		}

		// save spots for respawning in network games
		playerstarts.push_back(*mthing);
		player_t &p = idplayer(playernum+1);

		if (clientside && sv_gametype == GM_COOP && (validplayer(p) && p.ingame()))
		{
			P_SpawnPlayer (p, mthing);
			return;
		}

		return;
	}

	// Filter mapthings based on the gamemode
	if (!multiplayer)
	{
		if (!(mthing->flags & MTF_SINGLE))
			return;
	}
	else if (sv_gametype == GM_DM || sv_gametype == GM_TEAMDM)
	{
		if (!(mthing->flags & MTF_DEATHMATCH))
			return;
	}
	else if (sv_gametype == GM_COOP)
	{
		if (!(mthing->flags & MTF_COOPERATIVE))
			return;
	}

	// check for apropriate skill level
	if (sv_skill == sk_baby)
		bit = 1;
	else if (sv_skill == sk_nightmare)
		bit = 4;
	else
		bit = 1 << (sv_skill.asInt() - 2);

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

	// [RH] sound sequence overrides
	if (mthing->type >= 1400 && mthing->type < 1410)
	{
		R_PointInSubsector (mthing->x<<FRACBITS,
			mthing->y<<FRACBITS)->sector->seqType = mthing->type - 1400;
		return;
	}
	else if (mthing->type == 1411)
	{
		int type;

		if (mthing->args[0] == 255)
			type = -1;
		else
			type = mthing->args[0];

		if (type > 63)
		{
			Printf (PRINT_HIGH, "Sound sequence %d out of range\n", type);
		}
		else
		{
			R_PointInSubsector (mthing->x << FRACBITS,
				mthing->y << FRACBITS)->sector->seqType = type;
		}
		return;
	}

	// [RH] Determine if it is an old ambient thing, and if so,
	//		map it to MT_AMBIENT with the proper parameter.
	if (mthing->type >= 14001 && mthing->type <= 14064)
	{
		mthing->args[0] = mthing->type - 14000;
		mthing->type = 14065;
		i = MT_AMBIENT;
	}

	// [ML] Determine if it is a musicchanger thing, and if so,
	//		map it to MT_MUSICCHANGE with the proper parameter.
	if (mthing->type >= 14101 && mthing->type <= 14164)
	{
		mthing->args[0] = mthing->type - 14100;
		mthing->type = 14165;
		i = MT_MUSICCHANGE;
	}

	// [RH] Check if it's a particle fountain
	if (mthing->type >= 9027 && mthing->type <= 9033)
	{
		mthing->args[0] = mthing->type - 9026;
		i = MT_FOUNTAIN;
	}

	if (i == -1)	// we have to search for the type
	{
		// find which type to spawn
		for (i = 0; i < NUMMOBJTYPES; i++)
			if (mthing->type == mobjinfo[i].doomednum)
				break;
	}

	if (i >= NUMMOBJTYPES || i < 0)
	{
		// [RH] Don't die if the map tries to spawn an unknown thing
		Printf (PRINT_HIGH, "Unknown type %i at (%i, %i)\n",
			mthing->type,
			mthing->x, mthing->y);
		i = MT_UNKNOWNTHING;
	}
	// [RH] If the thing's corresponding sprite has no frames, also map
	//		it to the unknown thing.
	else if (sprites[states[mobjinfo[i].spawnstate].sprite].numframes == 0)
	{
		Printf (PRINT_HIGH, "Type %i at (%i, %i) has no frames\n",
				mthing->type, mthing->x, mthing->y);
		i = MT_UNKNOWNTHING;
	}

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

	// don't spawn deathmatch weapons in offline single player mode
	if (!multiplayer)
	{
		switch (i)
		{
			case MT_CHAINGUN:
			case MT_SHOTGUN:
			case MT_SUPERSHOTGUN:
			case MT_MISC25: 		// BFG
			case MT_MISC26: 		// chainsaw
			case MT_MISC27: 		// rocket launcher
			case MT_MISC28: 		// plasma gun
				if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
					return;
				break;
			default:
				break;
		}
	}

	// [csDoom] don't spawn any monsters
	if (sv_nomonsters || !serverside)
	{
		if (i == MT_SKULL || (mobjinfo[i].flags & MF_COUNTKILL) )
		{
			return;
		}
	}

    // [SL] 2011-05-31 - Moved so that clients get right level.total_items, etc
	if (i == MT_SECRETTRIGGER)
	{
		level.total_secrets++;
	}
	if (mobjinfo[i].flags & MF_COUNTKILL)
		level.total_monsters++;
	if (mobjinfo[i].flags & MF_COUNTITEM)
		level.total_items++;

	// spawn it
	x = mthing->x << FRACBITS;
	y = mthing->y << FRACBITS;

	if (i == MT_WATERZONE)
	{
		sector_t *sec = R_PointInSubsector (x, y)->sector;
		sec->waterzone = 1;
		return;
	}

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

	mobj = new AActor (x, y, z, (mobjtype_t)i);

	if (z == ONFLOORZ)
		mobj->z += mthing->z << FRACBITS;
	else if (z == ONCEILINGZ)
		mobj->z -= mthing->z << FRACBITS;
	mobj->spawnpoint = *mthing;

	if (mobj->flags2 & MF2_FLOATBOB)
	{ // Seed random starting index for bobbing motion
		mobj->health = M_Random();
		mobj->special1 = mthing->z << FRACBITS;
	}

	// [RH] Set the thing's special
	mobj->special = mthing->special;
	memcpy (mobj->args, mthing->args, sizeof(mobj->args));

	// [RH] If it's an ambient sound, activate it
	if (i == MT_AMBIENT)
		S_ActivateAmbient (mobj, mobj->args[0]);

	// [RH] If a fountain and not dormant, start it
	if (i == MT_FOUNTAIN && !(mthing->flags & MTF_DORMANT))
		mobj->effects = mobj->args[0] << FX_FOUNTAINSHIFT;

	// [SL] ZDoom Custom Bridge Things
	if (i == MT_ZDOOMBRIDGE)
	{
		mobj->radius = mobj->args[0] << FRACBITS;
		mobj->height = mobj->args[1] << FRACBITS;
	}

	// [AM] Adjust monster health based on server setting
	if ((i == MT_SKULL || (mobjinfo[i].flags & MF_COUNTKILL)) && sv_monstershealth != 1.0f)
		mobj->health *= sv_monstershealth;

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

	if (i != MT_SPARK)
		mobj->angle = ANG45 * (mthing->angle/45);

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

	// [RH] Add ThingID to mobj and link it in with the others
	mobj->tid = mthing->thingid;
	mobj->AddToHash ();

	SV_SpawnMobj(mobj);

	if ((mthing->type >= 9992 && mthing->type <= 9999) ||
		(mthing->type >= 9982 && mthing->type <= 9983)) {
		// Add ourselves to this sector's list of actions.
		if (mobj->subsector->sector->SecActTarget != NULL) {
			mobj->tracer = mobj->subsector->sector->SecActTarget->ptr();
		}
		mobj->subsector->sector->SecActTarget = mobj->ptr();
	}

	if (sv_gametype == GM_CTF) {
		// [Toke - CTF] Setup flag sockets
		if (mthing->type == ID_BLUE_FLAG)
		{
			flagdata *data = &CTFdata[it_blueflag];
			if (data->flaglocated)
				return;

			CTF_RememberFlagPos (mthing);
			CTF_SpawnFlag(it_blueflag);
		}

		if (mthing->type == ID_RED_FLAG)
		{
			flagdata *data = &CTFdata[it_redflag];
			if (data->flaglocated)
				return;

			CTF_RememberFlagPos (mthing);
			CTF_SpawnFlag(it_redflag);
		}
	}

	// [RH] Go dormant as needed
	if (mthing->flags & MTF_DORMANT)
		P_DeactivateMobj (mobj);
}


//
// P_VisibleToPlayers
//
// Returns true if mo is currently in any player's field of view
//

bool P_VisibleToPlayers(AActor *mo)
{
	if (!mo)
		return false;

	for (size_t i = 0; i < players.size(); i++)
	{
		// players aren't considered visible to themselves
		if (mo->player && mo->player->id == players[i].id)
			continue;

		if (!players[i].mo || players[i].spectator)
			continue;

		if (P_CheckSightEdges(players[i].mo, mo, 5.0))
			return true;
	}

	return false;
}

VERSION_CONTROL (p_mobj_cpp, "$Id: p_mobj.cpp 3798 2013-04-24 03:09:33Z dr_sean $")
