//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1994-1996 Raven Software
// Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2017 Christoph Oelckers
//
// 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 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
//		Moving object handling. Spawn functions.
//
//-----------------------------------------------------------------------------

/* For code that originates from ZDoom the following applies:
**
**---------------------------------------------------------------------------
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

// HEADER FILES ------------------------------------------------------------
#include <float.h>


#include "m_random.h"
#include "doomdef.h"
#include "p_local.h"
#include "p_maputl.h"
#include "p_lnspec.h"
#include "p_effect.h"
#include "p_terrain.h"
#include "hu_stuff.h"
#include "v_video.h"
#include "c_dispatch.h"
#include "b_bot.h"	//Added by MC:
#include "a_sharedglobal.h"
#include "gi.h"
#include "sbar.h"
#include "p_acs.h"
#include "cmdlib.h"
#include "decallib.h"
#include "a_keys.h"
#include "p_conversation.h"
#include "g_game.h"
#include "teaminfo.h"
#include "r_sky.h"
#include "d_event.h"
#include "p_enemy.h"
#include "gstrings.h"
#include "po_man.h"
#include "p_spec.h"
#include "p_checkposition.h"
#include "serializer_doom.h"
#include "serialize_obj.h"
#include "r_utility.h"
#include "thingdef.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "a_morph.h"
#include "events.h"
#include "actorinlines.h"
#include "a_dynlight.h"
#include "fragglescript/t_fs.h"
#include "shadowinlines.h"
#include "model.h"
#include "d_net.h"

// MACROS ------------------------------------------------------------------

#define WATER_SINK_FACTOR		0.125
#define WATER_SINK_SMALL_FACTOR	0.25
#define WATER_SINK_SPEED		0.5
#define WATER_JUMP_SPEED		3.5

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void PlayerLandedOnThing (AActor *mo, AActor *onmobj);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

EXTERN_CVAR (Int,  cl_rockettrails)

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static FRandom pr_explodemissile ("ExplodeMissile");
static FRandom pr_reflect ("Reflect");
static FRandom pr_nightmarerespawn ("NightmareRespawn");
static FRandom pr_botspawnmobj ("BotSpawnActor");
static FRandom pr_spawnmapthing ("SpawnMapThing");
static FRandom pr_spawnpuff ("SpawnPuff");
static FRandom pr_spawnblood ("SpawnBlood");
static FRandom pr_splatter ("BloodSplatter");
static FRandom pr_takedamage ("TakeDamage");
static FRandom pr_splat ("FAxeSplatter");
static FRandom pr_ripperblood ("RipperBlood");
static FRandom pr_chunk ("Chunk");
static FRandom pr_checkmissilespawn ("CheckMissileSpawn");
static FRandom pr_missiledamage ("MissileDamage");
static FRandom pr_multiclasschoice ("MultiClassChoice");
static FRandom pr_rockettrail("RocketTrail");
static FRandom pr_uniquetid("UniqueTID");

// PUBLIC DATA DEFINITIONS -------------------------------------------------

FRandom pr_spawnmobj ("SpawnActor");
FRandom pr_bounce("Bounce");
FRandom pr_spawnmissile("SpawnMissile");

CUSTOM_CVAR (Float, sv_gravity, 800.f, CVAR_SERVERINFO|CVAR_NOSAVE|CVAR_NOINITCALL)
{
	for (auto Level : AllLevels())
	{
		Level->gravity = self;
	}
}

CVAR (Bool, cl_missiledecals, true, CVAR_ARCHIVE)
CVAR (Bool, addrocketexplosion, false, CVAR_ARCHIVE)
CVAR (Int, cl_pufftype, 0, CVAR_ARCHIVE);
CVAR (Int, cl_bloodtype, 0, CVAR_ARCHIVE);

// CODE --------------------------------------------------------------------

IMPLEMENT_CLASS(DActorModelData, false, false);
IMPLEMENT_CLASS(AActor, false, true)

IMPLEMENT_POINTERS_START(AActor)
	IMPLEMENT_POINTER(target)
	IMPLEMENT_POINTER(lastenemy)
	IMPLEMENT_POINTER(tracer)
	IMPLEMENT_POINTER(damagesource)
	IMPLEMENT_POINTER(goal)
	IMPLEMENT_POINTER(LastLookActor)
	IMPLEMENT_POINTER(Inventory)
	IMPLEMENT_POINTER(LastHeard)
	IMPLEMENT_POINTER(master)
	IMPLEMENT_POINTER(Poisoner)
	IMPLEMENT_POINTER(alternative)
	IMPLEMENT_POINTER(ViewPos)
	IMPLEMENT_POINTER(modelData)
IMPLEMENT_POINTERS_END

//==========================================================================
//
// Make sure Actors can never have their networking disabled.
//
//==========================================================================

void AActor::EnableNetworking(const bool enable)
{
	if (!enable)
	{
		ThrowAbortException(X_OTHER, "Cannot disable networking on Actors. Consider a Thinker instead.");
		return;
	}

	Super::EnableNetworking(true);
}

//==========================================================================
//
// AActor :: Serialize
//
//==========================================================================

#define A(a,b) ((a), (b), def->b)

void AActor::Serialize(FSerializer &arc)
{
	AActor *def = GetDefault();

	Super::Serialize(arc);

	arc
		.Sprite("sprite", sprite, &def->sprite)
		A("pos", __Pos)
		A("angles", Angles)
		A("frame", frame)
		A("scale", Scale)
		A("nolocalrender", NoLocalRender) // Note: This will probably be removed later since a better solution is needed
		A("renderstyle", RenderStyle)
		A("renderflags", renderflags)
		A("renderflags2", renderflags2)
		A("picnum", picnum)
		A("floorpic", floorpic)
		A("ceilingpic", ceilingpic)
		A("tidtohate", TIDtoHate)
		A("lastlookpn", LastLookPlayerNumber)
		("lastlookactor", LastLookActor)
		A("effects", effects)
		A("fountaincolor", fountaincolor)
		A("alpha", Alpha)
		A("fillcolor", fillcolor)
		A("sector", Sector)
		A("floorz", floorz)
		A("ceilingz", ceilingz)
		A("dropoffz", dropoffz)
		A("floorsector", floorsector)
		A("ceilingsector", ceilingsector)
		A("radius", radius)
		A("renderradius", renderradius)
		A("height", Height)
		A("ppassheight", projectilepassheight)
		A("vel", Vel)
		A("tics", tics)
		A("state", state)
		A("damage", DamageVal)
		A("projectilekickback", projectileKickback)
		A("flags", flags)
		A("flags2", flags2)
		A("flags3", flags3)
		A("flags4", flags4)
		A("flags5", flags5)
		A("flags6", flags6)
		A("flags7", flags7)
		A("flags8", flags8)
		A("flags9", flags9)
		A("weaponspecial", weaponspecial)
		A("special1", special1)
		A("special2", special2)
		A("specialf1", specialf1)
		A("specialf2", specialf2)
		A("health", health)
		A("movedir", movedir)
		A("visdir", visdir)
		A("movecount", movecount)
		A("strafecount", strafecount)
		("target", target)
		("lastenemy", lastenemy)
		("lastheard", LastHeard)
		A("reactiontime", reactiontime)
		A("threshold", threshold)
		A("player", player)
		A("spawnpoint", SpawnPoint)
		A("spawnangle", SpawnAngle)
		A("starthealth", StartHealth)
		A("skillrespawncount", skillrespawncount)
		("tracer", tracer)
		A("floorclip", Floorclip)
		A("tid", tid)
		A("special", special)
		A("accuracy", accuracy)
		A("stamina", stamina)
		("goal", goal)
		A("waterlevel", waterlevel)
		A("boomwaterlevel", boomwaterlevel)
		A("minmissilechance", MinMissileChance)
		A("spawnflags", SpawnFlags)
		("inventory", Inventory)
		A("inventoryid", InventoryID)
		A("floatbobphase", FloatBobPhase)
		A("floatbobstrength", FloatBobStrength)
		A("floatbobfactor", FloatBobFactor)
		A("translation", Translation)
		A("bloodcolor", BloodColor)
		A("bloodtranslation", BloodTranslation)
		A("seesound", SeeSound)
		A("attacksound", AttackSound)
		A("paimsound", PainSound)
		A("deathsound", DeathSound)
		A("activesound", ActiveSound)
		A("usesound", UseSound)
		A("bouncesound", BounceSound)
		A("wallbouncesound", WallBounceSound)
		A("crushpainsound", CrushPainSound)
		A("speed", Speed)
		A("floatspeed", FloatSpeed)
		A("mass", Mass)
		A("painchance", PainChance)
		A("spawnstate", SpawnState)
		A("seestate", SeeState)
		A("meleestate", MeleeState)
		A("missilestate", MissileState)
		A("maxdropoffheight", MaxDropOffHeight)
		A("maxslopesteepness", MaxSlopeSteepness)
		A("maxstepheight", MaxStepHeight)
		A("bounceflags", BounceFlags)
		A("bouncefactor", bouncefactor)
		A("wallbouncefactor", wallbouncefactor)
		A("bouncecount", bouncecount)
		A("maxtargetrange", maxtargetrange)
		A("missilechancemult", missilechancemult)
		A("meleethreshold", meleethreshold)
		A("meleerange", meleerange)
		A("damagetype", DamageType)
		A("damagetypereceived", DamageTypeReceived)
		A("paintype", PainType)
		A("deathtype", DeathType)
		A("gravity", Gravity)
		A("fastchasestrafecount", FastChaseStrafeCount)
		("master", master)
		A("smokecounter", smokecounter)
		("blockingmobj", BlockingMobj)
		A("blockingline", BlockingLine)
		A("movementblockingline", MovementBlockingLine)
		A("blocking3dfloor", Blocking3DFloor)
		A("blockingceiling", BlockingCeiling)
		A("blockingfloor", BlockingFloor)
		A("visibletoteam", VisibleToTeam)
		A("pushfactor", pushfactor)
		A("species", Species)
		A("score", Score)
		A("designatedteam", DesignatedTeam)
		A("lastpush", lastpush)
		A("activationtype", activationtype)
		A("lastbump", lastbump)
		A("painthreshold", PainThreshold)
		A("damagefactor", DamageFactor)
		A("damagemultiply", DamageMultiply)
		A("waveindexxy", WeaveIndexXY)
		A("weaveindexz", WeaveIndexZ)
		A("freezetics", freezetics)
		A("pdmgreceived", PoisonDamageReceived)
		A("pdurreceived", PoisonDurationReceived)
		A("ppreceived", PoisonPeriodReceived)
		("poisoner", Poisoner)
		A("posiondamage", PoisonDamage)
		A("poisonduration", PoisonDuration)
		A("poisonperiod", PoisonPeriod)
		A("poisondamagetype", PoisonDamageType)
		A("poisondmgtypereceived", PoisonDamageTypeReceived)
		A("conversationroot", ConversationRoot)
		A("conversation", Conversation)
		A("friendplayer", FriendPlayer)
		A("telefogsourcetype", TeleFogSourceType)
		A("telefogdesttype", TeleFogDestType)
		A("ripperlevel", RipperLevel)
		A("riplevelmin", RipLevelMin)
		A("riplevelmax", RipLevelMax)
		A("devthreshold", DefThreshold)
		A("spriteangle", SpriteAngle)
		A("spriterotation", SpriteRotation)
		("alternative", alternative)
		A("thrubits", ThruBits)
		A("cameraheight", CameraHeight)
		A("camerafov", CameraFOV)
		A("tag", Tag)
		A("visiblestartangle", VisibleStartAngle)
		A("visibleendangle", VisibleEndAngle)
		A("visiblestartpitch", VisibleStartPitch)
		A("visibleendpitch", VisibleEndPitch)
		A("woundhealth", WoundHealth)
		A("rdfactor", RadiusDamageFactor)
		A("selfdamagefactor", SelfDamageFactor)
		A("stealthalpha", StealthAlpha)
		A("renderhidden", RenderHidden)
		A("renderrequired", RenderRequired)
		A("friendlyseeblocks", friendlyseeblocks)
		A("viewangles", ViewAngles)
		A("spawntime", SpawnTime)
		A("spawnorder", SpawnOrder)
		A("friction", Friction)
		A("SpriteOffset", SpriteOffset)
		("viewpos", ViewPos)
		A("lightlevel", LightLevel)
		A("userlights", UserLights)
		A("WorldOffset", WorldOffset)
		("modelData", modelData)
		A("LandingSpeed", LandingSpeed)

		("unmorphtime", UnmorphTime)
		("morphflags", MorphFlags)
		("premorphproperties", PremorphProperties)
		("morphexitflash", MorphExitFlash)
		("damagesource", damagesource);


		SerializeTerrain(arc, "floorterrain", floorterrain, &def->floorterrain);
		SerializeArgs(arc, "args", args, def->args, special);

}

#undef A

//==========================================================================
//
// This must be done after the world is set up.
//
//==========================================================================

void AActor::PostSerialize()
{
	touching_sectorlist = nullptr;
	touching_rendersectors = nullptr;
	LinkToWorld(nullptr, false, Sector);

	AddToHash();
	if (player)
	{
		if (Level->PlayerInGame(player) &&
			player->cls != NULL &&
			!(flags4 & MF4_NOSKIN) &&
			state->sprite == GetDefaultByType(player->cls)->SpawnState->sprite)
		{ // Give player back the skin
			sprite = Skins[player->userinfo.GetSkin()].sprite;
		}
		if (Speed == 0)
		{
			Speed = GetDefault()->Speed;
		}
	}
	ClearInterpolation();
	ClearFOVInterpolation();
	UpdateWaterLevel(false);
}


//==========================================================================
//
// AActor::InStateSequence
//
// Checks whether the current state is in a contiguous sequence that
// starts with basestate
//
//==========================================================================

static int InStateSequence(FState * newstate, FState * basestate)
{
	if (basestate == NULL) return false;

	FState * thisstate = basestate;
	do
	{
		if (newstate == thisstate) return true;
		basestate = thisstate;
		thisstate = thisstate->GetNextState();
	}
	while (thisstate == basestate+1);
	return false;
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, InStateSequence, InStateSequence)
{
	PARAM_PROLOGUE;
	PARAM_POINTER(newstate, FState);
	PARAM_POINTER(basestate, FState);
	ACTION_RETURN_BOOL(InStateSequence(newstate, basestate));
}

DEFINE_ACTION_FUNCTION_NATIVE(FState, InStateSequence, InStateSequence)
{
	PARAM_SELF_STRUCT_PROLOGUE(FState);
	PARAM_POINTER(basestate, FState);
	ACTION_RETURN_BOOL(InStateSequence(self, basestate));
}


bool AActor::IsMapActor()
{
	// [SP] Don't remove owned inventory objects.
	return (!IsKindOf(NAME_Inventory) || GC::ReadBarrier(PointerVar<AActor>(NAME_Owner)) == nullptr);
}

//==========================================================================
//
// AActor::GetTics
//
// Get the actual duration of the next state
// We are using a state flag now to indicate a state that should be
// accelerated in Fast mode or slowed in Slow mode.
//
//==========================================================================

inline int GetTics(AActor* actor, FState * newstate)
{
	int tics = newstate->GetTics();
	if (actor->isFast() && newstate->GetFast())
	{
		return tics - (tics>>1);
	}
	else if (actor->isSlow() && newstate->GetSlow())
	{
		return tics<<1;
	}
	return tics;
}

//==========================================================================
//
// AActor::SetState
//
// Returns true if the mobj is still present.
//
//==========================================================================

bool AActor::SetState (FState *newstate, bool nofunction)
{
	if (debugfile && player && (player->cheats & CF_PREDICTING))
		fprintf (debugfile, "for pl %d: SetState while predicting!\n", Level->PlayerNum(player));
	
	auto oldstate = state;
	do
	{
		if (newstate == NULL)
		{
			state = NULL;
			Destroy ();
			return false;
		}
		int prevsprite, newsprite;

		if (state != NULL)
		{
			prevsprite = state->sprite;
		}
		else
		{
			prevsprite = -1;
		}
		if (!(newstate->UseFlags & SUF_ACTOR))
		{
			Printf(TEXTCOLOR_RED "State %s in %s not flagged for use as an actor sprite\n", FState::StaticGetStateName(newstate).GetChars(), GetClass()->TypeName.GetChars());
			state = nullptr;
			Destroy();
			return false;
		}
		state = newstate;
		tics = GetTics(this, newstate);
		renderflags = (renderflags & ~RF_FULLBRIGHT) | ActorRenderFlags::FromInt (newstate->GetFullbright());
		newsprite = newstate->sprite;
		if (newsprite != SPR_FIXED)
		{ // okay to change sprite and/or frame
			if (!newstate->GetSameFrame())
			{ // okay to change frame
				frame = newstate->GetFrame();
			}
			if (newsprite != SPR_NOCHANGE)
			{ // okay to change sprite
				if (!(flags4 & MF4_NOSKIN) && newsprite == SpawnState->sprite)
				{ // [RH] If the new sprite is the same as the original sprite, and
				// this actor is attached to a player, use the player's skin's
				// sprite. If a player is not attached, do not change the sprite
				// unless it is different from the previous state's sprite; a
				// player may have been attached, died, and respawned elsewhere,
				// and we do not want to lose the skin on the body. If it wasn't
				// for Dehacked, I would move sprite changing out of the states
				// altogether, since actors rarely change their sprites after
				// spawning.
					if (player != NULL && Skins.Size() > 0)
					{
						sprite = Skins[player->userinfo.GetSkin()].sprite;
					}
					else if (newsprite != prevsprite)
					{
						sprite = newsprite;
					}
				}
				else
				{
					sprite = newsprite;
				}
			}
		}

		if (!nofunction)
		{
			FState *returned_state;
			FStateParamInfo stp = { newstate, STATE_Actor, PSP_WEAPON };
			if (newstate->CallAction(this, this, &stp, &returned_state))
			{
				// Check whether the called action function resulted in destroying the actor
				if (ObjectFlags & OF_EuthanizeMe)
				{
					return false;
				}
				if (returned_state != NULL)
				{ // The action was an A_Jump-style function that wants to change the next state.
					newstate = returned_state;
					tics = 0;		 // make sure we loop and set the new state properly
					continue;
				}
			}
		}
		newstate = newstate->GetNextState();
	} while (tics == 0);

	if (GetInfo()->LightAssociations.Size() || (state && state->Light > 0) || (oldstate && oldstate->Light > 0))
	{
		flags8 |= MF8_RECREATELIGHTS;
		Level->flags3 |= LEVEL3_LIGHTCREATED;
	}
	return true;
}

DEFINE_ACTION_FUNCTION(AActor, SetState)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_POINTER(state, FState);
	PARAM_BOOL(nofunction);
	ACTION_RETURN_BOOL(self->SetState(state, nofunction));
};


//============================================================================
//
// DestroyAllInventory
// Destroys all the inventory the actor is holding.
//
//============================================================================

static void DestroyAllInventory(AActor* actor)
{
	AActor *inv = PARAM_NULLCHECK(actor, self)->Inventory;
	if (inv != nullptr)
	{
		TArray<AActor *> toDelete;

		// Delete the list in a two stage approach.
		// This is necessary because an item may destroy another item (e.g. sister weapons)
		// which would break the list and leave parts of it undestroyed, maybe doing bad things later.
		while (inv != nullptr)
		{
			toDelete.Push(inv);
			auto item = inv->Inventory;
			inv->Inventory = nullptr;
			inv->PointerVar<AActor>(NAME_Owner) = nullptr;
			inv = item;
		}
		for (auto p : toDelete)
		{
			// the item may already have been deleted by another one, so check this here to avoid problems.
			if (!(p->ObjectFlags & OF_EuthanizeMe))
			{
				p->Destroy();
			}
		}
	}
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, DestroyAllInventory, DestroyAllInventory)
{
	PARAM_SELF_PROLOGUE(AActor);
	DestroyAllInventory(self);
	return 0;
}
//============================================================================
//
// AActor :: UseInventory
//
// Attempts to use an item. If the use succeeds, one copy of the item is
// removed from the inventory. If all copies are removed, then the item is
// destroyed.
//
//============================================================================

bool AActor::UseInventory (AActor *item)
{
	IFVIRTUAL(AActor, UseInventory)
	{
		VMValue params[] = { this, item };
		int retval = 0;
		VMReturn ret(&retval);
		VMCall(func, params, 2, &ret, 1);
		return !!retval;
	}
	return false;
}

//===========================================================================
//
// AActor :: DropInventory
//
// Removes a single copy of an item and throws it out in front of the actor.
//
//===========================================================================

AActor *AActor::DropInventory (AActor *item, int amt)
{
	IFVM(Actor, DropInventory)
	{
		VMValue params[] = { this, item, amt };
		AActor *retval = 0;
		VMReturn ret((void**)&retval);
		VMCall(func, params, 3, &ret, 1);
		return retval;
	}
	return nullptr;
}

//============================================================================
//
// AActor :: FindInventory
//
//============================================================================

AActor *AActor::FindInventory (PClassActor *type, bool subclass)
{
	AActor *item;

	if (type == NULL)
	{
		return NULL;
	}
	for (item = Inventory; item != NULL; item = item->Inventory)
	{
		if (!subclass)
		{
			if (item->GetClass() == type)
			{
				break;
			}
		}
		else
		{
			if (item->IsKindOf(type))
			{
				break;
			}
		}
	}
	return item;
}

AActor *AActor::FindInventory (FName type, bool subclass)
{
	return FindInventory(PClass::FindActor(type), subclass);
}

DEFINE_ACTION_FUNCTION(AActor, FindInventory)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_CLASS(type, AActor);
	PARAM_BOOL(subclass);
	ACTION_RETURN_OBJECT(self->FindInventory(type, subclass));
}

//============================================================================
//
// AActor :: GiveInventoryType
//
//============================================================================

AActor *AActor::GiveInventoryType (PClassActor *type)
{
	if (type != nullptr)
	{
		auto item = Spawn (Level, type);
		if (!CallTryPickup (item, this))
		{
			item->Destroy ();
			return nullptr;
		}
		return item;
	}
	return nullptr;
}

DEFINE_ACTION_FUNCTION(AActor, GiveInventoryType)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_CLASS(type, AActor);
	ACTION_RETURN_OBJECT(self->GiveInventoryType(type));
}

//============================================================================
//
// AActor :: CopyFriendliness
//
// Makes this actor hate (or like) the same things another actor does.
//
//============================================================================

void AActor::CopyFriendliness (AActor *other, bool changeTarget, bool resetHealth)
{
	if (health > 0) Level->total_monsters -= CountsAsKill();
	TIDtoHate = other->TIDtoHate;
	LastLookActor = other->LastLookActor;
	LastLookPlayerNumber = other->LastLookPlayerNumber;
	flags  = (flags & ~MF_FRIENDLY) | (other->flags & MF_FRIENDLY);
	flags3 = (flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (other->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS));
	flags4 = (flags4 & ~(MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED)) | (other->flags4 & (MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED));
	FriendPlayer = other->FriendPlayer;
	DesignatedTeam = other->DesignatedTeam;
	if (changeTarget && other->target != NULL && !(other->target->flags3 & MF3_NOTARGET) && !(other->target->flags7 & MF7_NEVERTARGET))
	{
		// LastHeard must be set as well so that A_Look can react to the new target if called
		LastHeard = target = other->target;
	}	
	if (resetHealth) health = SpawnHealth();	
	if (health > 0) Level->total_monsters += CountsAsKill();
}

DEFINE_ACTION_FUNCTION(AActor, CopyFriendliness)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(other, AActor);
	PARAM_BOOL(changetarget);
	PARAM_BOOL(resethealth);
	self->CopyFriendliness(other, changetarget, resethealth);
	return 0;
}
//---------------------------------------------------------------------------
//
// FUNC P_GetRealMaxHealth
//
// Taken out of P_GiveBody so that the bot code can also use it to decide
// whether to pick up an item or not.
//
//---------------------------------------------------------------------------

int P_GetRealMaxHealth(AActor *actor, int max)
{
	// Max is 0 by default, preserving default behavior for P_GiveBody()
	// calls while supporting health pickups.
	auto player = actor->player;
	if (max <= 0)
	{
		max = actor->GetMaxHealth(true);
		// [MH] First step in predictable generic morph effects
		if (actor->alternative != nullptr)
		{
			if (player->MorphStyle & MORPH_FULLHEALTH)
			{
				if (!(player->MorphStyle & MORPH_ADDSTAMINA))
				{
					max -= actor->stamina + actor->IntVar(NAME_BonusHealth);
				}
			}
			else // old health behaviour
			{
				max = MAXMORPHHEALTH;
				if (player->MorphStyle & MORPH_ADDSTAMINA)
				{
					max += actor->stamina + actor->IntVar(NAME_BonusHealth);
				}
			}
		}
	}
	else
	{
		// Bonus health should be added on top of the item's limit.
		if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA))
		{
			max += actor->IntVar(NAME_BonusHealth);
		}
	}
	return max;
}

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

bool P_GiveBody(AActor *actor, int num, int max)
{
	if (actor->health <= 0 || (actor->player != NULL && actor->player->playerstate == PST_DEAD))
	{ // Do not heal dead things.
		return false;
	}

	player_t *player = actor->player;

	num = clamp(num, -65536, 65536);	// prevent overflows for bad values
	if (player != NULL)
	{
		max = P_GetRealMaxHealth(player->mo, max);	// do not pass voodoo dolls in here.
		// [RH] For Strife: A negative value sets you up with a percentage of your full health.
		if (num < 0)
		{
			num = max * -num / 100;
			if (player->health < num)
			{
				player->health = num;
				actor->health = num;
				return true;
			}
		}
		else if (num > 0)
		{
			if (player->health < max)
			{
				num = int(num * G_SkillProperty(SKILLP_HealthFactor));
				if (num < 1) num = 1;
				player->health += num;
				if (player->health > max)
				{
					player->health = max;
				}
				actor->health = player->health;
				return true;
			}
		}
	}
	else
	{
		// Parameter value for max is ignored on monsters, preserving original
		// behaviour of health as well as on existing calls to P_GiveBody().
		max = actor->SpawnHealth();
		if (num < 0)
		{
			num = max * -num / 100;
			if (actor->health < num)
			{
				actor->health = num;
				return true;
			}
		}
		else if (actor->health < max)
		{
			actor->health += num;
			if (actor->health > max)
			{
				actor->health = max;
			}
			return true;
		}
	}
	return false;
}

DEFINE_ACTION_FUNCTION(AActor, GiveBody)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_INT(num);
	PARAM_INT(max);
	ACTION_RETURN_BOOL(P_GiveBody(self, num, max));
}

//============================================================================
//
// AActor :: CheckLocalView
//
// Returns true if this actor is local for the player. Here, local means the
// player is either looking out this actor's eyes, or this actor is the player
// and the player is looking out the eyes of something non-"sentient."
//
//============================================================================

bool AActor::CheckLocalView() const
{
	auto p = Level->GetConsolePlayer();
	if (p == nullptr)
	{
		return false;
	}
	if (p->camera == this)
	{
		return true;
	}
	if (p->mo != this || p->camera == nullptr)
	{
		return false;
	}
	if (p->camera->player == NULL &&
		!(p->camera->flags3 & MF3_ISMONSTER))
	{
		return true;
	}
	return false;
}

DEFINE_ACTION_FUNCTION(AActor, CheckLocalView)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_INT(cp);
	ACTION_RETURN_BOOL(self->CheckLocalView());
}

void AActor::DisableLocalRendering(const unsigned int pNum, const bool disable)
{
	if (pNum == consoleplayer)
		NoLocalRender = disable;
}

static void DisableLocalRendering(AActor* const self, const unsigned int pNum, const int disable)
{
	self->DisableLocalRendering(pNum, disable);
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, DisableLocalRendering, DisableLocalRendering)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_UINT(pNum);
	PARAM_INT(disable);

	DisableLocalRendering(self, pNum, disable);
	return 0;
}

bool AActor::ShouldRenderLocally() const
{
	return !NoLocalRender;
}

static int ShouldRenderLocally(const AActor* const self)
{
	return self->ShouldRenderLocally();
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, ShouldRenderLocally, ShouldRenderLocally)
{
	PARAM_SELF_PROLOGUE(AActor);

	ACTION_RETURN_INT(ShouldRenderLocally(self));
}

//============================================================================
//
// AActor :: IsInsideVisibleAngles
//
// Returns true if this actor is within viewing angle/pitch visibility. 
//
//============================================================================

bool AActor::IsInsideVisibleAngles() const
{
	// Don't bother masking if not wanted.
	if (!(renderflags & RF_MASKROTATION))
		return true;
	
	auto p = Level->GetConsolePlayer();

	if (p == nullptr || p->camera == nullptr)
		return true;
	
	DAngle anglestart = DAngle::fromDeg(VisibleStartAngle.Degrees());
	DAngle angleend = DAngle::fromDeg(VisibleEndAngle.Degrees());
	DAngle pitchstart = DAngle::fromDeg(VisibleStartPitch.Degrees());
	DAngle pitchend = DAngle::fromDeg(VisibleEndPitch.Degrees());
	
	if (anglestart > angleend)
	{
		DAngle temp = anglestart;
		anglestart = angleend;
		angleend = temp;
	}

	if (pitchstart > pitchend)
	{
		DAngle temp = pitchstart;
		pitchstart = pitchend;
		pitchend = temp;
	}
	

	AActor *mo = p->camera;

	if (mo != nullptr)
	{
		DVector2 offset = Level->Displacements.getOffset(r_viewpoint.sector->PortalGroup, Sector->PortalGroup);
		DVector3 diffang = r_viewpoint.Pos + offset - Pos();
		DAngle to = diffang.Angle();

		if (!(renderflags & RF_ABSMASKANGLE)) 
			to = deltaangle(Angles.Yaw, to);

		if ((to >= anglestart && to <= angleend))
		{
			to = diffang.Pitch();
			if (!(renderflags & RF_ABSMASKPITCH))
				to = deltaangle(Angles.Pitch, to);

			return !!(to >= pitchstart && to <= pitchend);
		}
		else return false;
	}
	return true;
}

//============================================================================
//
// AActor :: IsVisibleToPlayer
//
// Returns true if this actor should be seen by the console player.
//
// Not that even for secondary maps this must check the real player!
//
//============================================================================

bool AActor::IsVisibleToPlayer() const
{
	auto p = Level->GetConsolePlayer();
	// [BB] Safety check. This should never be NULL. Nevertheless, we return true to leave the default ZDoom behavior unaltered.
	if (p == nullptr || p->camera == nullptr )
		return true;

	if (!ShouldRenderLocally())
		return false;
 
	if (VisibleToTeam != 0 && teamplay &&
		(signed)(VisibleToTeam-1) != p->userinfo.GetTeam() )
		return false;

	auto &vis = GetInfo()->VisibleToPlayerClass;
	if (vis.Size() == 0) return true;	// early out for the most common case.

	const player_t* pPlayer = p->camera->player;

	if (pPlayer)
	{
		for(auto cls : vis)
		{
			if (cls && pPlayer->mo->GetClass()->IsDescendantOf(cls))
			{
				return true;
			}
		}
		return false;
	}

	// [BB] Passed all checks.
	return true;
}

//============================================================================
//
// AActor :: ConversationAnimation
//
// Plays a conversation-related animation:
//	 0 = greeting
//   1 = "yes"
//   2 = "no"
//
//============================================================================

void AActor::ConversationAnimation (int animnum)
{
	FState * state = NULL;
	switch (animnum)
	{
	case 0:
		state = FindState(NAME_Greetings);
		break;
	case 1:
		state = FindState(NAME_Yes);
		break;
	case 2:
		state = FindState(NAME_No);
		break;
	}
	if (state != NULL) SetState(state);
}

//============================================================================
//
// AActor :: Touch
//
// Something just touched this actor. Normally used only for inventory items,
// but some Strife monsters also use it.
//
//============================================================================

void AActor::CallTouch(AActor *toucher)
{
	IFVIRTUAL(AActor, Touch)
	{
		VMValue params[2] = { (DObject*)this, toucher };
		VMCall(func, params, 2, nullptr, 0);
	}
}

//============================================================================
//
// AActor :: Grind
//
// Handles the an actor being crushed by a door, crusher or polyobject.
// Originally part of P_DoCrunch(), it has been made into its own actor
// function so that it could be called from a polyobject without hassle.
// Bool items is true if it should destroy() dropped items, false otherwise.
//============================================================================

static int Grind(AActor* actor, int items)
{
	PARAM_NULLCHECK(actor, actor);
	// crunch bodies to giblets
	if ((actor->flags & MF_CORPSE) && !(actor->flags3 & MF3_DONTGIB) && (actor->health <= 0))
	{
		FState * state = actor->FindState(NAME_Crush);

		// In Heretic and Chex Quest we don't change the actor's sprite, just its size.
		if (state == NULL && gameinfo.dontcrunchcorpses)
		{
			actor->flags &= ~MF_SOLID;
			actor->flags3 |= MF3_DONTGIB;
			actor->Height = 0;
			actor->radius = 0;
			actor->Vel.Zero();
			return false;
		}

		bool isgeneric = false;
		// ZDoom behavior differs from standard as crushed corpses cannot be raised.
		// The reason for the change was originally because of a problem with players,
		// see rh_log entry for February 21, 1999. Don't know if it is still relevant.
		if (state == NULL 									// Only use the default crushed state if:
			&& !(actor->flags & MF_NOBLOOD)						// 1. the monster bleeeds,
			&& actor->player == NULL)								// 3. and the thing isn't a player.
		{
			isgeneric = true;
			state = actor->FindState(NAME_GenericCrush);
			if (state != NULL && (sprites[state->sprite].numframes <= 0))
				state = NULL; // If one of these tests fails, do not use that state.
		}
		if (state != NULL && !(actor->flags & MF_ICECORPSE))
		{
			if (actor->flags4 & MF4_BOSSDEATH)
			{
				A_BossDeath(actor);
			}
			actor->flags &= ~MF_SOLID;
			actor->flags3 |= MF3_DONTGIB;
			actor->Height = 0;
			actor->radius = 0;
			actor->Vel.Zero();
			actor->SetState (state);
			if (isgeneric)	// Not a custom crush state, so colorize it appropriately.
			{
				S_Sound (actor, CHAN_BODY, 0, "misc/fallingsplat", 1, ATTN_IDLE);
				actor->Translation = actor->BloodTranslation;
			}
			actor->Level->localEventManager->WorldThingGround(actor, state);
			return false;
		}
		if (!(actor->flags & MF_NOBLOOD))
		{
			if (actor->flags4 & MF4_BOSSDEATH)
			{
				A_BossDeath(actor);
			}

			PClassActor *i = PClass::FindActor("RealGibs");

			if (i != NULL)
			{
				i = i->GetReplacement(actor->Level);

				const AActor *defaults = GetDefaultByType (i);
				if (defaults->SpawnState == NULL ||
					sprites[defaults->SpawnState->sprite].numframes == 0)
				{ 
					i = NULL;
				}
			}
			if (i == NULL)
			{
				// if there's no gib sprite don't crunch it.
				actor->flags &= ~MF_SOLID;
				actor->flags3 |= MF3_DONTGIB;
				actor->Height = 0;
				actor->radius = 0;
				actor->Vel.Zero();
				return false;
			}

			AActor *gib = Spawn (actor->Level, i, actor->Pos(), ALLOW_REPLACE);
			if (gib != NULL)
			{
				gib->RenderStyle = actor->RenderStyle;
				gib->Alpha = actor->Alpha;
				gib->Height = 0;
				gib->radius = 0;
				gib->Translation = actor->BloodTranslation;
			}
			S_Sound (actor, CHAN_BODY, 0, "misc/fallingsplat", 1, ATTN_IDLE);
			actor->Level->localEventManager->WorldThingGround(actor, nullptr);
		}
		if (actor->flags & MF_ICECORPSE)
		{
			actor->tics = 1;
			actor->Vel.Zero();
		}
		else if (actor->player)
		{
			actor->flags |= MF_NOCLIP;
			actor->flags3 |= MF3_DONTGIB;
			actor->renderflags |= RF_INVISIBLE;
		}
		else
		{
			actor->Destroy ();
		}
		return false;		// keep checking
	}

	// killough 11/98: kill touchy things immediately
	if (actor->flags6 & MF6_TOUCHY && (actor->flags6 & MF6_ARMED || actor->IsSentient()))
    {
		actor->flags6 &= ~MF6_ARMED; // Disarm
		P_DamageMobj (actor, NULL, NULL, actor->health, NAME_Crush, DMG_FORCED);  // kill object
		return true;   // keep checking
    }

	if (!(actor->flags & MF_SOLID) || (actor->flags & MF_NOCLIP))
	{
		return false;
	}

	if (!(actor->flags & MF_SHOOTABLE))
	{
		return false;		// assume it is bloody gibs or something
	}
	return true;
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, Grind, Grind)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_BOOL(items);
	ACTION_RETURN_BOOL(Grind(self, items));
}

bool AActor::CallGrind(bool items)
{
	IFVIRTUAL(AActor, Grind)
	{
		VMValue params[] = { (DObject*)this, items };
		int retv;
		VMReturn ret(&retv);
		VMCall(func, params, 2, &ret, 1);
		return !!retv;
	}
	return false;
}

//============================================================================
//
// AActor :: Massacre
//
// Called by the massacre cheat to kill monsters. Returns true if the monster
// was killed and false if it was already dead.
//============================================================================

bool AActor::Massacre ()
{
	int prevhealth;

	if (health > 0)
	{
		auto f = flags;
		auto f2 = flags2;

		flags |= MF_SHOOTABLE;
		flags2 &= ~(MF2_DORMANT|MF2_INVULNERABLE);
		do
		{
			prevhealth = health;
			P_DamageMobj (this, NULL, NULL, TELEFRAG_DAMAGE, NAME_Massacre);
		}
		while (health != prevhealth && health > 0);	//abort if the actor wasn't hurt.
		if (health > 0)
		{
			// restore flags if this did not kill the monster.
			flags = f;
			flags2 = f2;
		}
		return health <= 0;
	}
	return false;
}

//----------------------------------------------------------------------------
//
// Serialize DActorModelData
//
//----------------------------------------------------------------------------

void SerializeModelID(FSerializer &arc, const char *key, int &id)
{	// TODO: make it a proper serializable type (FModelID) instead of an int
	if(arc.isWriting())
	{
		if(id >= 0)
		{
			arc(key, Models[id]->mFilePath);
		}
	}
	else
	{
		if(arc.HasKey(key))
		{
			std::pair<FString, FString> modelFile;
			arc(key, modelFile);

			id = FindModel(modelFile.first.GetChars(), modelFile.second.GetChars(), true);
		}
		else
		{
			id = -1;
		}
	}
}

FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &mo, ModelOverride *def)
{
	arc.BeginObject(key);
	SerializeModelID(arc, "model", mo.modelID);
	arc("surfaceSkinIDs", mo.surfaceSkinIDs);
	arc.EndObject();
	return arc;
}

FSerializer &Serialize(FSerializer &arc, const char *key, AnimModelOverride &amo, AnimModelOverride *def)
{
	int ok = arc.BeginObject(key);
	if(arc.isReading() && !ok)
	{
		amo.id = -1;
	}
	else if(ok)
	{
		SerializeModelID(arc, "model", amo.id);
		arc.EndObject();
	}
	return arc;
}

FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelAnim &ao, struct ModelAnim *def)
{
	arc.BeginObject(key);
	arc("firstFrame", ao.firstFrame);
	arc("lastFrame", ao.lastFrame);
	arc("loopFrame", ao.loopFrame);
	arc("startFrame", ao.startFrame);
	arc("flags", ao.flags);
	arc("framerate", ao.framerate);
	arc("startTic", ao.startTic);
	arc("switchOffset", ao.switchOffset);
	arc.EndObject();
	return arc;
}

FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def)
{
	arc.BeginObject(key);
	if(arc.isReading())
	{
		if(arc.HasKey("firstFrame"))
		{ // legacy save, clear interpolation
			ao = nullptr;
		}
		else
		{
			FString type = "nullptr";
			arc("type", type);
			if(type.Compare("nullptr") == 0)
			{
				ao = nullptr;
			}
			else if(type.Compare("interp") == 0)
			{
				ModelAnimFrameInterp tmp;
				arc("inter", tmp.inter);
				arc("frame1", tmp.frame1);
				arc("frame2", tmp.frame2);
				ao = tmp;
			}
			else if(type.Compare("precalcIQM") == 0)
			{
				//TODO, unreachable
				ao = nullptr;
			}
		}
	}
	else // if(arc.isWriting())
	{
		if(std::holds_alternative<std::nullptr_t>(ao))
		{
			FString tmp = "nullptr";
			arc("type", tmp);
		}
		else if(std::holds_alternative<ModelAnimFrameInterp>(ao))
		{
			FString type = "interp";
			arc("type", type);
			arc("inter", std::get<ModelAnimFrameInterp>(ao).inter);
			arc("frame1", std::get<ModelAnimFrameInterp>(ao).frame1);
			arc("frame2", std::get<ModelAnimFrameInterp>(ao).frame2);
		}
		else if(std::holds_alternative<ModelAnimFramePrecalculatedIQM>(ao))
		{
			//TODO
			FString type = "nullptr";
			arc("type", type);
		}
	}
	arc.EndObject();
	return arc;
}

void DActorModelData::Serialize(FSerializer& arc)
{
	Super::Serialize(arc);
	arc("modelDef", modelDef)
		("models", models)
		("skinIDs", skinIDs)
		("animationIDs", animationIDs)
		("modelFrameGenerators", modelFrameGenerators)
		("flags", flags)
		("overrideFlagsSet", overrideFlagsSet)
		("overrideFlagsClear", overrideFlagsClear)
		("curAnim", curAnim)
		("prevAnim", prevAnim);
}

void DActorModelData::OnDestroy()
{
	models.Reset();
	modelFrameGenerators.Reset();
	skinIDs.Reset();
	//surfaceSkinIDs.Reset();
	animationIDs.Reset();
}

void DViewPosition::Serialize(FSerializer& arc)
{
	Super::Serialize(arc);
	arc("offset", Offset)
		("flags", Flags);
}

//----------------------------------------------------------------------------
//
// PROC P_ExplodeMissile
//
//----------------------------------------------------------------------------

void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target, bool onsky, FName damageType)
{
	// [ZZ] line damage callback
	if (line)
	{
		P_ProjectileHitLinedef(mo, line);
	}

	if (mo->flags3 & MF3_EXPLOCOUNT)
	{
		if (++mo->threshold < mo->DefThreshold)
		{
			return;
		}
	}
	mo->Vel.Zero();
	mo->effects = 0;		// [RH]
	mo->flags &= ~MF_SHOOTABLE;
	
	FState *nextstate = nullptr;
	
	if (target != nullptr)
	{
		if (mo->flags7 & MF7_HITTARGET)	mo->target = target;
		if (mo->flags7 & MF7_HITMASTER)	mo->master = target;
		if (mo->flags7 & MF7_HITTRACER)	mo->tracer = target;
		if ((target->flags & (MF_SHOOTABLE | MF_CORPSE)) || (target->flags6 & MF6_KILLED))
		{
			if (target->flags & MF_NOBLOOD) nextstate = mo->FindState(NAME_Crash);
			if (nextstate == NULL) nextstate = mo->FindState(NAME_Death, NAME_Extreme);
		}
	}
	if (nextstate == NULL) nextstate = mo->FindState(NAME_Death, damageType);
	
	if (onsky || (line != NULL && line->special == Line_Horizon))
	{
		if (!(mo->flags3 & MF3_SKYEXPLODE))
		{
			// [RH] Don't explode missiles on horizon lines.
			mo->Destroy();
			return;
		}
		nextstate = mo->FindState(NAME_Death, NAME_Sky);
	}

	if (line != NULL && cl_missiledecals)
	{
		DVector3 pos = mo->PosRelative(line);
		int side = P_PointOnLineSidePrecise (pos, line);
		if (line->sidedef[side] == NULL)
			side ^= 1;
		if (line->sidedef[side] != NULL)
		{
			FDecalBase *base = mo->DecalGenerator;
			if (base != NULL)
			{
				// Find the nearest point on the line, and stick a decal there
				DVector3 linepos;
				double den, frac;

				den = line->Delta().LengthSquared();
				if (den != 0)
				{
					frac = clamp<double>((mo->Pos().XY() - line->v1->fPos()) | line->Delta(), 0, den) / den;

					linepos = DVector3(line->v1->fPos() + line->Delta() * frac, pos.Z);

					F3DFloor * ffloor=NULL;
					if (line->sidedef[side^1] != NULL)
					{
						sector_t * backsector = line->sidedef[side^1]->sector;
						extsector_t::xfloor &xf = backsector->e->XFloor;
						// find a 3D-floor to stick to
						for(unsigned int i=0;i<xf.ffloors.Size();i++)
						{
							F3DFloor * rover=xf.ffloors[i];

							if ((rover->flags&(FF_EXISTS|FF_SOLID|FF_RENDERSIDES))==(FF_EXISTS|FF_SOLID|FF_RENDERSIDES))
							{
								if (pos.Z <= rover->top.plane->ZatPoint(linepos) && pos.Z >= rover->bottom.plane->ZatPoint(linepos))
								{
									ffloor = rover;
									break;
								}
							}
						}
					}

					DImpactDecal::StaticCreate(mo->Level, base->GetDecal(), linepos, line->sidedef[side], ffloor);
				}
			}
		}
	}

	// play the sound before changing the state, so that AActor::OnDestroy can call S_RelinkSounds on it and the death state can override it.
	if (mo->DeathSound.isvalid())
	{
		S_Sound (mo, CHAN_VOICE, 0, mo->DeathSound, 1,
			(mo->flags3 & MF3_FULLVOLDEATH) ? ATTN_NONE : ATTN_NORM);
	}

	mo->SetState (nextstate);
	if (!(mo->ObjectFlags & OF_EuthanizeMe))
	{
		// The rest only applies if the missile actor still exists.
		// [RH] Change render style of exploding rockets
		if (mo->flags5 & MF5_DEHEXPLOSION)
		{
			if (deh.ExplosionStyle == 255)
			{
				if (addrocketexplosion)
				{
					mo->RenderStyle = STYLE_Add;
					mo->Alpha = 1.;
				}
				else
				{
					mo->RenderStyle = STYLE_Translucent;
					mo->Alpha = 0.6666;
				}
			}
			else
			{
				mo->RenderStyle = ERenderStyle(deh.ExplosionStyle);
				mo->Alpha = deh.ExplosionAlpha;
			}
		}

		if (mo->flags4 & MF4_RANDOMIZE)
		{
			mo->tics -= (pr_explodemissile() & 3) * TICRATE / 35;
			if (mo->tics < 1)
				mo->tics = 1;
		}

		mo->flags &= ~MF_MISSILE;

	}
}

DEFINE_ACTION_FUNCTION(AActor, ExplodeMissile)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_POINTER(line, line_t);
	PARAM_OBJECT(target, AActor);
	P_ExplodeMissile(self, line, target);
	return 0;
}


void AActor::PlayBounceSound(bool onfloor, double volume)
{
	if (!onfloor && (BounceFlags & BOUNCE_NoWallSound))
	{
		return;
	}

	if (!(BounceFlags & BOUNCE_Quiet))
	{
		volume = clamp(volume, 0.0, 1.0);
		if (BounceFlags & BOUNCE_UseSeeSound)
		{
			S_Sound (this, CHAN_VOICE, 0, SeeSound, (float)volume, ATTN_IDLE);
		}
		else if (onfloor || !WallBounceSound.isvalid())
		{
			S_Sound (this, CHAN_VOICE, 0, BounceSound, (float)volume, ATTN_IDLE);
		}
		else
		{
			S_Sound (this, CHAN_VOICE, 0, WallBounceSound, (float)volume, ATTN_IDLE);
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC P_FloorBounceMissile
//
// Returns true if the missile was destroyed
//----------------------------------------------------------------------------

bool AActor::FloorBounceMissile (secplane_t &plane, bool is3DFloor)
{
	if (flags & MF_MISSILE)
	{
		switch (SpecialBounceHit(nullptr, nullptr, &plane, is3DFloor))
		{
			// This one is backwards for some reason...
			case 1:		return false;
			case 0:		return true;
			default:	break;
		}
	}

	// [ZZ] if bouncing missile hits a damageable sector(plane), it dies
	if (P_ProjectileHitPlane(this, -1) && bouncecount > 0)
	{
		Vel.Zero();
		Speed = 0;
		bouncecount = 0;
		if (flags & MF_MISSILE)
			P_ExplodeMissile(this, nullptr, nullptr);
		else CallDie(nullptr, nullptr);
		return true;
	}

	if (Z() <= floorz && P_HitFloor (this))
	{
		// Landed in some sort of liquid
		if (BounceFlags & BOUNCE_ExplodeOnWater)
		{
			if (flags & MF_MISSILE)
				P_ExplodeMissile(this, NULL, NULL);
			else
				CallDie(NULL, NULL);
			return true;
		}
		if (!(BounceFlags & BOUNCE_CanBounceWater))
		{
			Destroy ();
			return true;
		}
	}

	DVector3 norm = plane.Normal();
	if (is3DFloor)
		norm = -norm;

	bool onsky;

	if (norm.Z < 0)
	{ // on ceiling
		if (!(BounceFlags & BOUNCE_Ceilings))
			return true;

		onsky = ceilingpic == skyflatnum;
	}
	else
	{ // on floor
		if (!(BounceFlags & BOUNCE_Floors))
			return true;

		onsky = floorpic == skyflatnum;
	}

	if (onsky && (BounceFlags & BOUNCE_NotOnSky))
	{
		Destroy();
		return true;
	}

	// The amount of bounces is limited
	if (bouncecount>0 && --bouncecount==0)
	{
		if (flags & MF_MISSILE)
			P_ExplodeMissile(this, NULL, NULL);
		else
			CallDie(NULL, NULL);
		return true;
	}

	double dot = (Vel | norm) * 2;

	if (BounceFlags & (BOUNCE_HereticType | BOUNCE_MBF))
	{
		Vel -= norm * dot;
		if (!(BounceFlags & BOUNCE_KeepAngle))
			AngleFromVel();
		if (!(BounceFlags & BOUNCE_MBF)) // Heretic projectiles die, MBF projectiles don't.
		{
			flags |= MF_INBOUNCE;
			SetState (FindState(NAME_Death));
			flags &= ~MF_INBOUNCE;
			return false;
		}
		else Vel.Z *= GetMBFBounceFactor(this);
	}
	else // Don't run through this for MBF-style bounces
	{
		// The reflected velocity keeps only about 70% of its original speed
		Vel = (Vel - norm * dot) * bouncefactor;
		if (!(BounceFlags & BOUNCE_KeepAngle))
			AngleFromVel();
	}

	if (BounceFlags & BOUNCE_ModifyPitch)
		Angles.Pitch = -VecToAngle(Vel.XY().Length(), Vel.Z);

	PlayBounceSound(true, 1.0);

	// Set bounce state
	if (BounceFlags & BOUNCE_UseBounceState)
	{
		FName names[2] = { NAME_Bounce, norm.Z < 0 ? NAME_Ceiling : NAME_Floor };
		FState *bouncestate = FindState(2, names);
		if (bouncestate != nullptr)
		{
			SetState(bouncestate);
		}
	}

	if (BounceFlags & BOUNCE_MBF) // Bring it to rest below a certain speed
	{
		if (fabs(Vel.Z) < Mass * GetGravity() / 64)
			Vel.Z = 0;
	}
	else if (BounceFlags & (BOUNCE_AutoOff|BOUNCE_AutoOffFloorOnly))
	{
		if (norm.Z > 0 || (BounceFlags & BOUNCE_AutoOff))
		{
			// AutoOff only works when bouncing off a floor, not a ceiling (or in compatibility mode.)
			if (!(flags & MF_NOGRAVITY) && (norm.Z < 0 || ((Vel | norm) < 3)))
				BounceFlags &= ~BOUNCE_TypeMask;
		}
	}
	return false;
}

//----------------------------------------------------------------------------
//
// FUNC P_FaceMobj
//
// Returns 1 if 'source' needs to turn clockwise, or 0 if 'source' needs
// to turn counter clockwise.  'delta' is set to the amount 'source'
// needs to turn.
//
//----------------------------------------------------------------------------

static int P_FaceMobj (AActor *source, AActor *target, DAngle *delta)
{
	DAngle diff;

	diff = deltaangle(source->Angles.Yaw, source->AngleTo(target));
	if (diff > nullAngle)
	{
		*delta = diff;
		return 1;
	}
	else
	{
		*delta = -diff;
		return 0;
	}
}

//----------------------------------------------------------------------------
//
// CanSeek
//
// Checks if a seeker missile can home in on its target
//
//----------------------------------------------------------------------------

bool AActor::CanSeek(AActor *target) const
{
	if (target->flags5 & MF5_CANTSEEK) return false;
	if ((flags2 & MF2_DONTSEEKINVISIBLE) && 
		((target->flags & MF_SHADOW) || 
		 (target->renderflags & RF_INVISIBLE) || 
		 (target->flags8 & MF8_MINVISIBLE) ||
		 !target->RenderStyle.IsVisible(target->Alpha)
		)
	   ) return false;
	return true;
}

DEFINE_ACTION_FUNCTION(AActor, CanSeek)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(target, AActor);
	ACTION_RETURN_BOOL(self->CanSeek(target));
}

//----------------------------------------------------------------------------
//
// FUNC P_SeekerMissile
//
// The missile's tracer field must be the target.  Returns true if
// target was tracked, false if not.
//
//----------------------------------------------------------------------------

bool P_SeekerMissile (AActor *actor, DAngle thresh, DAngle turnMax, bool precise, bool usecurspeed)
{
	int dir;
	DAngle delta;
	AActor *target;
	double speed;

	speed = !usecurspeed ? actor->Speed : actor->VelToSpeed();
	target = actor->tracer;
	if (target == NULL || !actor->CanSeek(target))
	{
		return false;
	}
	if (!(target->flags & MF_SHOOTABLE))
	{ // Target died
		actor->tracer = nullptr;
		return false;
	}
	if (speed == 0)
	{ // Technically, we're not seeking since our speed is 0, but the target *is* seekable.
		return true;
	}
	dir = P_FaceMobj (actor, target, &delta);
	if (delta > thresh)
	{
		delta /= 2;
		if (delta > turnMax)
		{
			delta = turnMax;
		}
	}
	if (dir)
	{ // Turn clockwise
		actor->Angles.Yaw += delta;
	}
	else
	{ // Turn counter clockwise
		actor->Angles.Yaw -= delta;
	}
	
	if (!precise)
	{
		actor->VelFromAngle(speed);

		if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER)))
		{
			if (actor->Top() < target->Z() ||
				target->Top() < actor->Z())
			{ // Need to seek vertically
				actor->Vel.Z = (target->Center() - actor->Center()) / actor->DistanceBySpeed(target, speed);
			}
		}
	}
	else
	{
		DAngle pitch = nullAngle;
		if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER)))
		{ // Need to seek vertically
			double dist = max(1., actor->Distance2D(target));
			// Aim at a player's eyes and at the middle of the actor for everything else.
			double aimheight = target->Height/2;
			if (target->player)
			{
				aimheight = target->player->DefaultViewHeight();
			}
			pitch = DVector2(dist, target->Z() + aimheight - actor->Center()).Angle();
		}
		actor->Vel3DFromAngle(-pitch, speed);
	}

	return true;
}


//
// P_XYMovement
//
// Returns the actor's old floorz.
//
#define STOPSPEED			(0x1000/65536.)
#define CARRYSTOPSPEED		((0x1000*32/3)/65536.)

static double P_XYMovement (AActor *mo, DVector2 scroll) 
{
	static int pushtime = 0;
	bool bForceSlide = !scroll.isZero();
	DVector2 ptry;
	player_t *player;
	DVector2 move;
	const secplane_t * walkplane;
	static const double windTab[3] = { 5 / 32., 10 / 32., 25 / 32. };
	int steps, step, totalsteps;
	DVector2 start;
	double Oldfloorz = mo->floorz;
	double oldz = mo->Z();

	double maxmove = (mo->waterlevel < 1) || (mo->flags & MF_MISSILE) || 
					  (mo->player && mo->player->crouchoffset<-10) ? MAXMOVE : MAXMOVE/4;

	if (mo->flags2 & MF2_WINDTHRUST && mo->waterlevel < 2 && !(mo->flags & MF_NOCLIP))
	{
		int special = mo->Sector->special;
		switch (special)
		{
			case 40: case 41: case 42: // Wind_East
				mo->Thrust(DAngle::fromDeg(0.), windTab[special-40]);
				break;
			case 43: case 44: case 45: // Wind_North
				mo->Thrust(DAngle::fromDeg(90.), windTab[special-43]);
				break;
			case 46: case 47: case 48: // Wind_South
				mo->Thrust(DAngle::fromDeg(270.), windTab[special-46]);
				break;
			case 49: case 50: case 51: // Wind_West
				mo->Thrust(DAngle::fromDeg(180.), windTab[special-49]);
				break;
		}
	}

	// [RH] No need to clamp these now. However, wall running needs it so
	// that large thrusts can't propel an actor through a wall, because wall
	// running depends on the player's original movement continuing even after
	// it gets blocked.
	//
	// [anon] When friction is turned off, turn off the crouching and water
	//  speed caps as well, since it is a sort of friction, and the modders
	//  most likely want to deal with that themselves.
	if ((mo->player != NULL && (mo->Level->i_compatflags & COMPATF_WALLRUN)) || ((mo->waterlevel >= 1 ||
		(mo->player != NULL && mo->player->crouchfactor < 0.75)) && !(mo->flags8 & MF8_NOFRICTION)))
	{
		// preserve the direction instead of clamping x and y independently.
		double cx = mo->Vel.X == 0 ? 1. : clamp(mo->Vel.X, -maxmove, maxmove) / mo->Vel.X;
		double cy = mo->Vel.Y == 0 ? 1. : clamp(mo->Vel.Y, -maxmove, maxmove) / mo->Vel.Y;
		double fac = min(cx, cy);

		mo->Vel.X *= fac;
		mo->Vel.Y *= fac;
	}
	const double VELOCITY_THRESHOLD = 5000;	// don't let it move faster than this. Fixed point overflowed at 32768 but that's too much to make this safe.
	if (mo->Vel.LengthSquared() >= VELOCITY_THRESHOLD*VELOCITY_THRESHOLD)
	{
		mo->Vel.MakeResize(VELOCITY_THRESHOLD);
	}
	move = mo->Vel.XY();
	// [RH] Carrying sectors didn't work with low speeds in BOOM. This is
	// because BOOM relied on the speed being fast enough to accumulate
	// despite friction. If the speed is too low, then its movement will get
	// cancelled, and it won't accumulate to the desired speed.
	mo->flags4 &= ~MF4_SCROLLMOVE;
	if (fabs(scroll.X) > CARRYSTOPSPEED)
	{
		scroll.X *= CARRYFACTOR;
		mo->Vel.X += scroll.X;
		mo->flags4 |= MF4_SCROLLMOVE;
	}
	if (fabs(scroll.Y) > CARRYSTOPSPEED)
	{
		scroll.Y *= CARRYFACTOR;
		mo->Vel.Y += scroll.Y;
		mo->flags4 |= MF4_SCROLLMOVE;
	}
	move += scroll;

	if (move.isZero())
	{
		if ((mo->flags & MF_SKULLFLY) && !(mo->flags9 & MF9_NOAUTOOFFSKULLFLY))
		{
			// the skull slammed into something
			mo->flags &= ~MF_SKULLFLY;
			mo->Vel.Zero();
			if (!(mo->flags2 & MF2_DORMANT))
			{
				if (mo->SeeState != NULL) mo->SetState (mo->SeeState);
				else mo->SetIdle();
			}
			else
			{
				mo->SetIdle();
				mo->tics = -1;
			}
		}
		return Oldfloorz;
	}

	player = mo->player;

	// [RH] Adjust player movement on sloped floors
	DVector2 startmove = move;
	walkplane = P_CheckSlopeWalk (mo, move);

	// [RH] Take smaller steps when moving faster than the object's size permits.
	// Moving as fast as the object's "diameter" is bad because it could skip
	// some lines because the actor could land such that it is just touching the
	// line. For Doom to detect that the line is there, it needs to actually cut
	// through the actor.

	{
		double maxmove = mo->radius - 1;

		if (maxmove <= 0)
		{ // gibs can have radius 0, so don't divide by zero below!
			maxmove = MAXMOVE;
		}

		const double xspeed = fabs (move.X);
		const double yspeed = fabs (move.Y);

		steps = 1;

		if (xspeed > yspeed)
		{
			if (xspeed > maxmove)
			{
				steps = int(1 + xspeed / maxmove);
			}
		}
		else
		{
			if (yspeed > maxmove)
			{
				steps = int(1 + yspeed / maxmove);
			}
		}
	}

	// P_SlideMove needs to know the step size before P_CheckSlopeWalk
	// because it also calls P_CheckSlopeWalk on its clipped steps.
	DVector2 onestep = startmove / steps;

	start = mo->Pos().XY();
	step = 1;
	totalsteps = steps;

	// [RH] Instead of doing ripping damage each step, do it each tic.
	// This makes it compatible with Heretic and Hexen, which only did
	// one step for their missiles with ripping damage (excluding those
	// that don't use P_XYMovement). It's also more intuitive since it
	// makes the damage done dependant on the amount of time the projectile
	// spends inside a target rather than on the projectile's size. The
	// last actor ripped through is recorded so that if the projectile
	// passes through more than one actor this tic, each one takes damage
	// and not just the first one.
	pushtime++;

	FCheckPosition tm(!!(mo->flags2 & MF2_RIP));

	DAngle oldangle = mo->Angles.Yaw;
	do
	{
		if (mo->Level->i_compatflags & COMPATF_WALLRUN) pushtime++;
		tm.PushTime = pushtime;

		ptry = start + move * step / steps;

		DVector2 startvel = mo->Vel.XY();

		// killough 3/15/98: Allow objects to drop off
		// [RH] If walking on a slope, stay on the slope
		if (!P_TryMove (mo, ptry, true, walkplane, tm))
		{
			// blocked move
			AActor *BlockingMobj = mo->BlockingMobj;
			line_t *BlockingLine = mo->MovementBlockingLine = mo->BlockingLine;

			// [ZZ] 
			if (!BlockingLine && !BlockingMobj) // hit floor or ceiling while XY movement - sector actions
			{
				int hitpart = -1;
				sector_t* hitsector = nullptr;
				secplane_t* hitplane = nullptr;
				if (tm.ceilingsector && mo->Z() + mo->Height > tm.ceilingsector->ceilingplane.ZatPoint(tm.pos.XY()))
					mo->BlockingCeiling = tm.ceilingsector;
				if (tm.floorsector && mo->Z() < tm.floorsector->floorplane.ZatPoint(tm.pos.XY()))
					mo->BlockingFloor = tm.floorsector;
				// the following two only set the appropriate field - to avoid issues caused by running actions right in the middle of XY movement
				P_CheckFor3DFloorHit(mo, mo->floorz, false);
				P_CheckFor3DCeilingHit(mo, mo->ceilingz, false);
			}

			if (!(mo->flags & MF_MISSILE) && (mo->BounceFlags & BOUNCE_MBF) 
				&& (BlockingMobj != NULL ? P_BounceActor(mo, BlockingMobj, false) : P_BounceWall(mo)))
			{
				// Do nothing, relevant actions already done in the condition.
				// This allows to avoid setting velocities to 0 in the final else of this series.
			}
			else if ((mo->flags2 & (MF2_SLIDE|MF2_BLASTED) || bForceSlide) && !(mo->flags&MF_MISSILE))
			{	// 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) &&
						mo->BlockingLine->sidedef[1] != NULL)
					{
						double spd = mo->FloatVar(NAME_WaterClimbSpeed);
						if (fabs(spd) >= EQUAL_EPSILON)
							mo->Vel.Z = spd;
					}
					// If the blocked move executed any push specials that changed the
					// actor's velocity, do not attempt to slide.
					if (mo->Vel.XY() == startvel)
					{
						if (player && (mo->Level->i_compatflags & COMPATF_WALLRUN))
						{
						// [RH] Here is the key to wall running: The move is clipped using its full speed.
						// If the move is done a second time (because it was too fast for one move), it
						// is still clipped against the wall at its full speed, so you effectively
						// execute two moves in one tic.
							P_SlideMove (mo, mo->Vel.XY(), 1);
						}
						else
						{
							P_SlideMove (mo, onestep, totalsteps);
						}
						if (mo->Vel.XY().isZero())
						{
							steps = 0;
						}
						else
						{
							if (!player || !(mo->Level->i_compatflags & COMPATF_WALLRUN))
							{
								move = mo->Vel.XY();
								onestep = move / steps;
								P_CheckSlopeWalk (mo, move);
							}
							start = mo->Pos().XY() - move * step / steps;
						}
					}
					else
					{
						steps = 0;
					}
				}
				else
				{ // slide against another actor
					DVector2 t;
					t.X = 0, t.Y = onestep.Y;
					walkplane = P_CheckSlopeWalk (mo, t);
					if (P_TryMove (mo, mo->Pos().XY() + t, true, walkplane, tm))
					{
						mo->Vel.X = 0;
					}
					else
					{
						t.X = onestep.X, t.Y = 0;
						walkplane = P_CheckSlopeWalk (mo, t);
						if (P_TryMove (mo, mo->Pos().XY() + t, true, walkplane, tm))
						{
							mo->Vel.Y = 0;
						}
						else
						{
							mo->Vel.X = mo->Vel.Y = 0;
						}
					}
					if (player && player->mo == mo)
					{
						if (mo->Vel.X == 0)
							player->Vel.X = 0;
						if (mo->Vel.Y == 0)
							player->Vel.Y = 0;
					}
					steps = 0;
				}
			}
			else if (mo->flags & MF_MISSILE)
			{
				steps = 0;
				if (BlockingMobj)
				{
					if (mo->BounceFlags & BOUNCE_Actors)
					{
						// Bounce test and code moved to P_BounceActor
						if (!P_BounceActor(mo, BlockingMobj, false))
						{	// Struck a player/creature
							P_ExplodeMissile (mo, NULL, BlockingMobj);
						}
						return Oldfloorz;
					}
				}
				else
				{
					// Struck a wall
					if (P_BounceWall (mo))
					{
						mo->PlayBounceSound(false, 1.0);
						return Oldfloorz;
					}
				}
				if (BlockingMobj && P_ReflectOffActor(mo, BlockingMobj))
				{
					return Oldfloorz;
				}

				// explode a missile
				bool onsky = false;
				if (tm.ceilingline && tm.ceilingline->hitSkyWall(mo))
				{
					if (!(mo->flags3 & MF3_SKYEXPLODE))
					{
						// Hack to prevent missiles exploding against the sky.
						// Does not handle sky floors.
						mo->Destroy();
						return Oldfloorz;
					}
					else onsky = true;
				}
				// [RH] Don't explode on horizon lines.
				if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon)
				{
					if (!(mo->flags3 & MF3_SKYEXPLODE))
					{
						mo->Destroy();
						return Oldfloorz;
					}
					else onsky = true;
				}
				if (mo->BlockingCeiling) // hit floor or ceiling while XY movement
				{
					P_ProjectileHitPlane(mo, SECPART_Ceiling);
				}
				if (mo->BlockingFloor)
				{
					P_ProjectileHitPlane(mo, SECPART_Floor);
				}
				P_ExplodeMissile (mo, mo->BlockingLine, BlockingMobj, onsky);
				return Oldfloorz;
			}
			else
			{
				mo->Vel.X = mo->Vel.Y = 0;
				steps = 0;
			}
		}
		else
		{
			if (mo->Pos().XY() != ptry)
			{
				// If the new position does not match the desired position, the player
				// must have gone through a teleporter or portal.
				
				if (mo->Vel.X == 0 && mo->Vel.Y == 0)
				{
					// Stop moving right now if it was a regular teleporter.
					step = steps;
				}
				else
				{
					// It was a portal, line-to-line or fogless teleporter, so the move should continue.
					// For that we need to adjust the start point, and the movement vector.
					DAngle anglediff = deltaangle(oldangle, mo->Angles.Yaw);

					if (anglediff != nullAngle)
					{
						move = move.Rotated(anglediff);
						oldangle = mo->Angles.Yaw;
					}
					start = mo->Pos().XY() - move * step / steps;
				}
			}
		}
	} while (++step <= steps);

	// Friction

	if (player && player->mo == mo && player->cheats & CF_NOVELOCITY)
	{ // debug option for no sliding at all
		mo->Vel.X = mo->Vel.Y = 0;
		player->Vel.X = player->Vel.Y = 0;
		return Oldfloorz;
	}

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

	if (mo->Z() > mo->floorz && !(mo->flags2 & MF2_ONMOBJ) &&
		!mo->IsNoClip2() &&
		(!(mo->flags2 & MF2_FLY) || !(mo->flags & MF_NOGRAVITY)) &&
		!mo->waterlevel)
	{ // [RH] Friction when falling is available for larger aircontrols
		auto airfriction = mo->Level->airfriction;
		if (player != NULL && airfriction != 1.)
		{
			mo->Vel.X *= airfriction;
			mo->Vel.Y *= airfriction;

			if (player->mo == mo)		//  Not voodoo dolls
			{
				player->Vel.X *= airfriction;
				player->Vel.Y *= airfriction;
			}
		}
		return Oldfloorz;
	}

	// killough 8/11/98: add bouncers
	// killough 9/15/98: add objects falling off ledges
	// killough 11/98: only include bouncers hanging off ledges
	if ((mo->flags & MF_CORPSE) || (mo->BounceFlags & BOUNCE_MBF && mo->Z() > mo->dropoffz) || (mo->flags6 & MF6_FALLING))
	{ // Don't stop sliding if halfway off a step with some velocity
		if (fabs(mo->Vel.X) > 0.25 || fabs(mo->Vel.Y) > 0.25)
		{
			if (mo->floorz > mo->Sector->floorplane.ZatPoint(mo))
			{
				if (mo->dropoffz != mo->floorz) // 3DMidtex or other special cases that must be excluded
				{
					unsigned i;
					for(i=0;i<mo->Sector->e->XFloor.ffloors.Size();i++)
					{
						// Sliding around on 3D floors looks extremely bad so
						// if the floor comes from one in the current sector stop sliding the corpse!
						F3DFloor * rover=mo->Sector->e->XFloor.ffloors[i];
						if (!(rover->flags&FF_EXISTS)) continue;
						if (rover->flags&FF_SOLID && rover->top.plane->ZatPoint(mo) == mo->floorz) break;
					}
					if (i==mo->Sector->e->XFloor.ffloors.Size()) 
						return Oldfloorz;
				}
			}
		}
	}

	// killough 11/98:
	// Stop voodoo dolls that have come to rest, despite any
	// moving corresponding player:
	if (fabs(mo->Vel.X) < STOPSPEED && fabs(mo->Vel.Y) < 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 && player->mo == mo && !(player->cheats & CF_PREDICTING))
		{
			PlayIdle (player->mo);
		}

		mo->Vel.X = mo->Vel.Y = 0;
		mo->flags4 &= ~MF4_SCROLLMOVE;

		// killough 10/98: kill any bobbing velocity too (except in voodoo dolls)
		if (player && player->mo == mo)
			player->Vel.X = player->Vel.Y = 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 velocity is no longer needed to reduce
		// bobbing, so ice works much better now.

		double friction = P_GetFriction (mo, NULL);

		mo->Vel.X *= friction;
		mo->Vel.Y *= friction;

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

		if (player && player->mo == mo)		//  Not voodoo dolls
		{
			player->Vel.X *= ORIG_FRICTION;
			player->Vel.Y *= ORIG_FRICTION;
		}

		// Don't let the velocity become less than the smallest representable fixed point value.
		if (fabs(mo->Vel.X) < MinVel) mo->Vel.X = 0;
		if (fabs(mo->Vel.Y) < MinVel) mo->Vel.Y = 0;
		if (player && player->mo == mo)		//  Not voodoo dolls
		{
			if (fabs(player->Vel.X) < MinVel) player->Vel.X = 0;
			if (fabs(player->Vel.Y) < MinVel) player->Vel.Y = 0;
		}
	}
	return Oldfloorz;
}


static void P_MonsterFallingDamage (AActor *mo)
{
	int damage;
	double vel;

	if (!(mo->Level->flags2 & LEVEL2_MONSTERFALLINGDAMAGE) && !(mo->flags8 & MF8_FALLDAMAGE))
		return;
	if (mo->floorsector->Flags & SECF_NOFALLINGDAMAGE)
		return;

	vel = fabs(mo->Vel.Z);
	if (vel > 35)
	{ // automatic death
		damage = TELEFRAG_DAMAGE;
	}
	else
	{
		damage = int((vel - 23)*6);
	}
	if (!(mo->Level->flags3 & LEVEL3_PROPERMONSTERFALLINGDAMAGE)) damage = TELEFRAG_DAMAGE;
	P_DamageMobj (mo, NULL, NULL, damage, NAME_Falling);
}

//
// P_ZMovement
//

static void P_ZMovement (AActor *mo, double oldfloorz)
{
	double dist;
	double delta;
	double oldz = mo->Z();
	double grav = mo->GetGravity();

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

	mo->AddZ(mo->Vel.Z);

	mo->CallFallAndSink(grav, oldfloorz);

	// Hexen compatibility handling for floatbobbing. Ugh...
	// Hexen yanked all items to the floor, except those being spawned at map start in the air.
	// Those were kept at their original height.
	// Do this only if the item was actually spawned by the map above ground to avoid problems.
	if (mo->specialf1 > 0 && (mo->flags2 & MF2_FLOATBOB) && (mo->Level->ib_compatflags & BCOMPATF_FLOATBOB))
	{
		mo->SetZ(mo->floorz + mo->specialf1);
	}


//
// adjust height
//
	if ((mo->flags & MF_FLOAT) && !(mo->flags2 & MF2_DORMANT) && mo->target)
	{	// float down towards target if too close
		if (!(mo->flags & (MF_SKULLFLY | MF_INFLOAT)))
		{
			dist = mo->Distance2D (mo->target);
			delta = (mo->target->Center()) - mo->Z();
			if (delta < 0 && dist < -(delta*3))
				mo->AddZ(-mo->FloatSpeed);
			else if (delta > 0 && dist < (delta*3))
				mo->AddZ(mo->FloatSpeed);
		}
	}
	if (mo->player && (mo->flags & MF_NOGRAVITY) && (mo->Z() > mo->floorz))
	{
		FBaseCVar* const fViewBobCvar = G_GetUserCVar(int(mo->player - players),"FViewBob");
		bool const fViewBob = fViewBobCvar->GetGenericRep(fViewBobCvar->GetRealType()).Bool;

		if (!mo->IsNoClip2() && fViewBob)
		{
			mo->AddZ(DAngle::fromDeg(360 / 80.f * mo->Level->maptime).Sin() / 8 * mo->FloatVar(NAME_FlyBob));
		}

		if (!(mo->flags8 & MF8_NOFRICTION))
		{
			mo->Vel.Z *= FRICTION_FLY;
		}
	}
	if (mo->waterlevel && !(mo->flags & MF_NOGRAVITY) && !(mo->flags8 & MF8_NOFRICTION))
	{
		double friction = -1;

		// Check 3D floors -- might be the source of the waterlevel
		for (auto rover : mo->Sector->e->XFloor.ffloors)
		{
			if (!(rover->flags & FF_EXISTS)) continue;
			if (!(rover->flags & FF_SWIMMABLE)) continue;

			if (mo->Z() >= rover->top.plane->ZatPoint(mo) ||
				mo->Center() < rover->bottom.plane->ZatPoint(mo))
				continue;

			friction = rover->model->GetFriction(rover->top.isceiling);
			break;
		}
		if (friction < 0)
			friction = mo->Sector->GetFriction();	// get real friction, even if from a terrain definition

		mo->Vel.Z *= friction;
	}

//
// clip movement
//
	if (mo->Z() <= mo->floorz)
	{	// Hit the floor
		if ((!mo->player || !(mo->player->cheats & CF_PREDICTING)) &&
			mo->Sector->SecActTarget != NULL &&
			mo->Sector->floorplane.ZatPoint(mo) == mo->floorz)
		{ // [RH] Let the sector do something to the actor
			mo->Sector->TriggerSectorActions(mo, SECSPAC_HitFloor);
		}
		P_CheckFor3DFloorHit(mo, mo->floorz, true);
		// [RH] Need to recheck this because the sector action might have
		// teleported the actor so it is no longer below the floor.
		if (mo->Z() <= mo->floorz)
		{
			mo->BlockingFloor = mo->Sector;
			if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
			{
				mo->SetZ(mo->floorz);
				if (mo->BounceFlags & BOUNCE_Floors)
				{
					F3DFloor* ff = nullptr;
					NextLowestFloorAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), 0, mo->MaxStepHeight, nullptr, &ff);
					mo->FloorBounceMissile (ff != nullptr ? *ff->top.plane : mo->floorsector->floorplane, ff != nullptr);
					/* if (!CanJump(mo)) */ return;
				}
				else if (mo->flags3 & MF3_NOEXPLODEFLOOR)
				{
					P_HitFloor (mo);
					mo->Vel.Z = 0;
					return;
				}
				else if ((mo->flags3 & MF3_FLOORHUGGER) && !(mo->flags5 & MF5_NODROPOFF))
				{ // Floor huggers can go up steps
					return;
				}
				else
				{
					bool onsky = false;
					if (mo->floorpic == skyflatnum)
					{
						if (!(mo->flags3 & MF3_SKYEXPLODE))
						{
							// [RH] Just remove the missile without exploding it
							//		if this is a sky floor.
							mo->Destroy();
							return;
						}
						else onsky = true;
					}
					P_HitFloor (mo);
					// hit floor: direct damage callback
					P_ProjectileHitPlane(mo, SECPART_Floor);
					P_ExplodeMissile (mo, NULL, NULL, onsky);
					return;
				}
			}
			else if (mo->BounceFlags & BOUNCE_MBF && mo->Vel.Z) // check for MBF-like bounce on non-missiles
			{
				F3DFloor* ff = nullptr;
				NextLowestFloorAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), 0, mo->MaxStepHeight, nullptr, &ff);
				mo->FloorBounceMissile(ff != nullptr ? *ff->top.plane : mo->floorsector->floorplane, ff != nullptr);
			}
			if (mo->flags3 & MF3_ISMONSTER)		// Blasted mobj falling
			{
				if (mo->Vel.Z < -23)
				{
					P_MonsterFallingDamage (mo);
				}
			}
			mo->SetZ(mo->floorz);
			if (mo->Vel.Z < 0)
			{
				// Spawn splashes, etc.
				P_HitFloor (mo);
				if (mo->DamageType == NAME_Ice && mo->Vel.Z < mo->LandingSpeed)
				{
					mo->tics = 1;
					mo->Vel.Zero();
					return;
				}
				// Let the actor do something special for hitting the floor
				if (mo->flags7 & MF7_SMASHABLE)
				{
					P_DamageMobj(mo, nullptr, nullptr, mo->health, NAME_Smash);
				}
				if (mo->player)
				{
					if (mo->player->jumpTics < 0 || mo->Vel.Z < mo->LandingSpeed)
					{ // delay any jumping for a short while
						mo->player->jumpTics = 7;
					}
					if (mo->Vel.Z < mo->LandingSpeed && !(mo->flags & MF_NOGRAVITY))
					{
						// Squat down.
						// Decrease viewheight for a moment after hitting the ground (hard),
						// and utter appropriate sound.
						PlayerLandedOnThing (mo, NULL);
					}
				}
				mo->Vel.Z = 0;
			}
			if (mo->flags & MF_SKULLFLY)
			{ // The skull slammed into something
				mo->Vel.Z = -mo->Vel.Z;
			}
			mo->Crash();
		}
	}

	if (mo->flags2 & MF2_FLOORCLIP)
	{
		mo->AdjustFloorClip ();
	}

	if (mo->Top() > mo->ceilingz)
	{ // hit the ceiling
		if ((!mo->player || !(mo->player->cheats & CF_PREDICTING)) &&
			mo->Sector->SecActTarget != NULL &&
			mo->Sector->ceilingplane.ZatPoint(mo) == mo->ceilingz)
		{ // [RH] Let the sector do something to the actor
			mo->Sector->TriggerSectorActions (mo, SECSPAC_HitCeiling);
		}
		P_CheckFor3DCeilingHit(mo, mo->ceilingz, true);
		// [RH] Need to recheck this because the sector action might have
		// teleported the actor so it is no longer above the ceiling.
		if (mo->Top() > mo->ceilingz)
		{
			mo->BlockingCeiling = mo->Sector;
			mo->SetZ(mo->ceilingz - mo->Height);
			if (mo->BounceFlags & BOUNCE_Ceilings)
			{	// ceiling bounce
				F3DFloor* ff = nullptr;
				NextHighestCeilingAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), mo->Top(), 0, nullptr, &ff);
				mo->FloorBounceMissile(ff != nullptr ? *ff->bottom.plane : mo->ceilingsector->ceilingplane, ff != nullptr);
				/* if (!CanJump(mo)) */ return;
			}
			if (mo->flags & MF_SKULLFLY)
			{	// the skull slammed into something
				mo->Vel.Z = -mo->Vel.Z;
			}
			if (mo->Vel.Z > 0)
				mo->Vel.Z = 0;
			if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
			{
				if (mo->flags3 & MF3_CEILINGHUGGER)
				{
					return;
				}
				bool onsky = false;
				if (mo->ceilingpic == skyflatnum)
				{
					if (!(mo->flags3 & MF3_SKYEXPLODE))
					{
						mo->Destroy();
						return;
					}
					else onsky = true;
				}
				// hit ceiling: direct damage callback
				P_ProjectileHitPlane(mo, SECPART_Ceiling);
				P_ExplodeMissile (mo, NULL, NULL, onsky);
				return;
			}
		}
	}
	P_CheckFakeFloorTriggers (mo, oldz);
}

void P_CheckFakeFloorTriggers (AActor *mo, double oldz, bool oldz_has_viewheight)
{
	if (mo->player && (mo->player->cheats & CF_PREDICTING))
	{
		return;
	}
	sector_t *sec = mo->Sector;
	assert (sec != NULL);
	if (sec == NULL)
	{
		return;
	}
	if (sec->heightsec != NULL && sec->SecActTarget != NULL)
	{
		sector_t *hs = sec->heightsec;
		double waterz = hs->floorplane.ZatPoint(mo);
		double newz;
		double viewheight;

		if (mo->player != NULL)
		{
			viewheight = mo->player->viewheight;
		}
		else
		{
			viewheight = mo->Height;
		}

		if (oldz > waterz && mo->Z() <= waterz)
		{ // Feet hit fake floor
			sec->TriggerSectorActions (mo, SECSPAC_HitFakeFloor);
		}

		newz = mo->Z() + viewheight;
		if (!oldz_has_viewheight)
		{
			oldz += viewheight;
		}

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

		if (!(hs->MoreFlags & SECMF_FAKEFLOORONLY))
		{
			waterz = hs->ceilingplane.ZatPoint(mo);
			if (oldz <= waterz && newz > waterz)
			{ // View went above fake ceiling
				sec->TriggerSectorActions (mo, SECSPAC_EyesAboveC);
			}
			else if (oldz > waterz && newz <= waterz)
			{ // View went below fake ceiling
				sec->TriggerSectorActions (mo, SECSPAC_EyesBelowC);
			}
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, CheckFakeFloorTriggers)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(oldz);
	PARAM_BOOL(oldz_has_viewh);
	P_CheckFakeFloorTriggers(self, oldz, oldz_has_viewh);
	return 0;
}
//===========================================================================
//
// PlayerLandedOnThing
//
//===========================================================================

static void PlayerLandedMakeGruntSound(AActor* self, AActor *onmobj)
{
	IFVIRTUALPTR(self, AActor, PlayerLandedMakeGruntSound)
	{
		VMValue params[2] = { self, onmobj };
		VMCall(func, params, 2, nullptr, 0);
	}
}

static void PlayerSquatView(AActor *self, AActor *onmobj)
{
	IFVIRTUALPTR(self, AActor, PlayerSquatView)
	{
		VMValue params[2] = { self, onmobj };
		VMCall(func, params, 2, nullptr, 0);
	}
}

static void PlayerLandedOnThing (AActor *mo, AActor *onmobj)
{
	PlayerSquatView(mo, onmobj);

	if (mo->player->cheats & CF_PREDICTING)
		return;

	P_FallingDamage (mo);

	PlayerLandedMakeGruntSound(mo, onmobj);

//	mo->player->centering = true;
}

//==========================================================================
//
// AActor :: FallAndSink
//
//==========================================================================

void AActor::FallAndSink(double grav, double oldfloorz)
{
	if (Z() > floorz && !(flags & MF_NOGRAVITY))
	{
		double startvelz = Vel.Z;

		if (waterlevel == 0 || (player &&
			!(player->cmd.ucmd.forwardmove | player->cmd.ucmd.sidemove)))
		{
			// [RH] Double gravity only if running off a ledge. Coming down from
			// an upward thrust (e.g. a jump) should not double it.
			if (Vel.Z == 0 && oldfloorz > floorz && Z() == oldfloorz)
			{
				Vel.Z -= grav + grav;
			}
			else
			{
				Vel.Z -= grav;
			}
		}
		if (player == NULL)
		{
			if (waterlevel >= 1)
			{
				double sinkspeed;

				if ((flags & MF_SPECIAL) && !(flags3 & MF3_ISMONSTER))
				{ // Pickup items don't sink if placed and drop slowly if dropped
					sinkspeed = (flags & MF_DROPPED) ? -WATER_SINK_SPEED / 8 : 0;
				}
				else
				{
					sinkspeed = -WATER_SINK_SPEED;

					// If it's not a player, scale sinkspeed by its mass, with
					// 100 being equivalent to a player.
					if (player == NULL)
					{
						sinkspeed = sinkspeed * clamp(Mass, 1, 4000) / 100;
					}
				}
				if (Vel.Z < sinkspeed)
				{ // Dropping too fast, so slow down toward sinkspeed.
					Vel.Z -= max(sinkspeed * 2, -8.);
					if (Vel.Z > sinkspeed)
					{
						Vel.Z = sinkspeed;
					}
				}
				else if (Vel.Z > sinkspeed)
				{ // Dropping too slow/going up, so trend toward sinkspeed.
					Vel.Z = startvelz + max(sinkspeed / 3, -8.);
					if (Vel.Z < sinkspeed)
					{
						Vel.Z = sinkspeed;
					}
				}
			}
		}
		else
		{
			if (waterlevel > 1)
			{
				double sinkspeed = -WATER_SINK_SPEED;

				if (Vel.Z < sinkspeed)
				{
					Vel.Z = (startvelz < sinkspeed) ? startvelz : sinkspeed;
				}
				else
				{
					Vel.Z = startvelz + ((Vel.Z - startvelz) *
						(waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR));
				}
			}
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, FallAndSink)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(grav);
	PARAM_FLOAT(oldfloorz);
	self->FallAndSink(grav, oldfloorz);
	return 0;
}

void AActor::CallFallAndSink(double grav, double oldfloorz)
{
	IFVIRTUAL(AActor, FallAndSink)
	{
		VMValue params[3] = { (DObject*)this, grav, oldfloorz };
		VMCall(func, params, 3, nullptr, 0);
	}
	else
	{
		FallAndSink(grav, oldfloorz);
	}
}

//
// P_NightmareRespawn
//
void P_NightmareRespawn (AActor *mobj)
{
	double z;
	AActor *mo;
	AActor *info = mobj->GetDefault();

	mobj->skillrespawncount++;

	// spawn the new monster (assume the spawn will be good)
	if (info->flags & MF_SPAWNCEILING)
		z = ONCEILINGZ;
	else if (info->flags2 & MF2_SPAWNFLOAT)
		z = FLOATRANDZ;
	else
		z = ONFLOORZ;

	// spawn it
	mo = AActor::StaticSpawn(mobj->Level, mobj->GetClass(), DVector3(mobj->SpawnPoint.X, mobj->SpawnPoint.Y, z), NO_REPLACE, true);
	mo->health = mobj->SpawnHealth();

	if (z == ONFLOORZ)
	{
		mo->AddZ(mobj->SpawnPoint.Z);
		if (mo->Z() < mo->floorz)
		{ // Do not respawn monsters in the floor, even if that's where they
		  // started. The initial P_ZMovement() call would have put them on
		  // the floor right away, but we need them on the floor now so we
		  // can use P_CheckPosition() properly.
			mo->SetZ(mo->floorz);
		}
		if (mo->Top() > mo->ceilingz)
		{
			mo->SetZ(mo->ceilingz- mo->Height);
		}
	}
	else if (z == ONCEILINGZ)
	{
		mo->AddZ(-mobj->SpawnPoint.Z);
	}

	// If there are 3D floors, we need to find floor/ceiling again.
	P_FindFloorCeiling(mo, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);

	if (z == ONFLOORZ)
	{
		if (mo->Z() < mo->floorz)
		{ // Do not respawn monsters in the floor, even if that's where they
		  // started. The initial P_ZMovement() call would have put them on
		  // the floor right away, but we need them on the floor now so we
		  // can use P_CheckPosition() properly.
			mo->SetZ(mo->floorz);
		}
		if (mo->Top() > mo->ceilingz)
		{ // Do the same for the ceiling.
			mo->SetZ(mo->ceilingz - mo->Height);
		}
	}

	// something is occupying its position?
	if (!P_CheckPosition(mo, mo->Pos().XY(), true))
	{
		//[GrafZahl] MF_COUNTKILL still needs to be checked here.
		mo->ClearCounters();
		mo->Destroy ();
		return;		// no respawn
	}

	z = mo->Z();

	// inherit attributes from deceased one
	mo->SpawnPoint = mobj->SpawnPoint;
	mo->SpawnAngle = mobj->SpawnAngle;
	mo->SpawnFlags = mobj->SpawnFlags & ~MTF_DORMANT;	// It wasn't dormant when it died, so it's not dormant now, either.
	mo->Angles.Yaw = DAngle::fromDeg(mobj->SpawnAngle);

	mo->HandleSpawnFlags ();
	mo->reactiontime = 18;
	mo->CopyFriendliness (mobj, false);
	mo->Translation = mobj->Translation;

	mo->skillrespawncount = mobj->skillrespawncount;

	mo->Prev.Z = z;		// Do not interpolate Z position if we changed it since spawning.

	// spawn a teleport fog at old spot because of removal of the body?
	P_SpawnTeleportFog(mobj, mobj->Pos(), true, true);

	// spawn a teleport fog at the new spot
	P_SpawnTeleportFog(mobj, DVector3(mobj->SpawnPoint.XY(), z), false, true);

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


//
// 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 ()
{
	assert(!(ObjectFlags & OF_EuthanizeMe));

	if (tid == 0)
	{
		iprev = NULL;
		inext = NULL;
		return;
	}
	else
	{
		int hash = TIDHASH (tid);
		auto &slot = Level->TIDHash[hash];

		inext = slot;
		iprev = &slot;
		slot = this;
		if (inext)
		{
			inext->iprev = &inext;
		}
	}
}

//
// P_RemoveMobjFromHash
//
// Removes an mobj from its hash chain.
//
void AActor::RemoveFromHash ()
{
	if (tid != 0 && iprev)
	{
		*iprev = inext;
		if (inext)
		{
			inext->iprev = iprev;
		}
		iprev = NULL;
		inext = NULL;
	}
	tid = 0;
}

void AActor::SetTID (int newTID)
{
	RemoveFromHash();

	if (ObjectFlags & OF_EuthanizeMe)
	{
		// Do not assign TID and do not link actor into the hash
		// if it was already destroyed and will be freed by GC
		return;
	}

	tid = newTID;

	AddToHash();
}


//==========================================================================
//
// P_IsTIDUsed
//
// Returns true if there is at least one actor with the specified TID
// (dead or alive).
//
//==========================================================================

bool FLevelLocals::IsTIDUsed(int tid)
{
	AActor *probe = TIDHash[tid & 127];
	while (probe != NULL)
	{
		if (probe->tid == tid)
		{
			return true;
		}
		probe = probe->inext;
	}
	return false;
}

//==========================================================================
//
// P_FindUniqueTID
//
// Returns an unused TID. If start_tid is 0, then a random TID will be
// chosen. Otherwise, it will perform a linear search starting from
// start_tid. If limit is non-0, then it will not check more than <limit>
// number of TIDs. Returns 0 if no suitable TID was found.
//
//==========================================================================

int FLevelLocals::FindUniqueTID(int start_tid, int limit)
{
	int tid;

	if (start_tid != 0)
	{ // Do a linear search.
		if (start_tid > INT_MAX-limit+1)
		{ // If 'limit+start_tid-1' overflows, clamp 'limit' to INT_MAX
			limit = INT_MAX;
		}
		else
		{
			limit += start_tid-1;
		}
		for (tid = start_tid; tid <= limit; ++tid)
		{
			if (tid != 0 && !IsTIDUsed(tid))
			{
				return tid;
			}
		}
		// Nothing free found.
		return 0;
	}
	// Do a random search. To try and be a bit more performant, this
	// actually does several linear searches. In the case of *very*
	// dense TID usage, this could potentially perform worse than doing
	// a complete linear scan starting at 1. However, you would need
	// to use an absolutely ridiculous number of actors before this
	// becomes a real concern.
	if (limit == 0)
	{
		limit = INT_MAX;
	}
	for (int i = 0; i < limit; i += 5)
	{
		// Use a positive starting TID.
		tid = pr_uniquetid.GenRand32() & INT_MAX;
		tid = FindUniqueTID(tid == 0 ? 1 : tid, 5);
		if (tid != 0)
		{
			return tid;
		}
	}
	// Nothing free found.
	return 0;
}


CCMD(utid)
{
	for (auto Level : AllLevels())
	{
		Printf("%s, %d\n", Level->MapName.GetChars(), Level->FindUniqueTID(argv.argc() > 1 ? atoi(argv[1]) : 0,
			(argv.argc() > 2 && atoi(argv[2]) >= 0) ? atoi(argv[2]) : 0));
	}
}

//==========================================================================
//
// AActor :: GetMissileDamage
//
// If the actor's damage amount is an expression, evaluate it and return
// the result. Otherwise, return ((random() & mask) + add) * damage.
//
//==========================================================================

int AActor::GetMissileDamage (int mask, int add)
{
	if (DamageVal >= 0)
	{
		if (mask == 0)
		{
			return add * DamageVal;
		}
		else
		{
			return ((pr_missiledamage() & mask) + add) * DamageVal;
		}
	}
	if (DamageFunc == nullptr)
	{
		// This should never happen
		assert(false && "No damage function found");
		return 0;
	}
	VMValue param = this;
	VMReturn result;

	int amount;

	result.IntAt(&amount);

	if (VMCall(DamageFunc, &param, 1, &result, 1) < 1)
	{ // No results
		return 0;
	}
	return amount;
}

void AActor::Howl ()
{
	FSoundID howl = SoundVar(NAME_HowlSound);
	if (!S_IsActorPlayingSomething(this, CHAN_BODY, howl))
	{
		S_Sound (this, CHAN_BODY, 0, howl, 1, ATTN_NORM);
	}
}

DEFINE_ACTION_FUNCTION(AActor, Howl)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->Howl();
	return 0;
}

bool AActor::Slam (AActor *thing)
{
	if ((flags8 & MF8_ONLYSLAMSOLID)
		&& !(thing->flags & MF_SOLID) && !(thing->flags & MF_SHOOTABLE))
	{
		return true;
	}

	flags &= ~MF_SKULLFLY;
	Vel.Zero();
	if (health > 0)
	{
		if (!(flags2 & MF2_DORMANT))
		{
			int dam = GetMissileDamage (7, 1);
			int newdam = P_DamageMobj (thing, this, this, dam, NAME_Melee);
			P_TraceBleed (newdam > 0 ? newdam : dam, thing, this);
			// The charging monster may have died by the target's actions here.
			if (health > 0)
			{
				FState *slam = FindState(NAME_Slam);
				if (slam != NULL)
					SetState(slam);
				else if (SeeState != NULL && !(flags8 & MF8_RETARGETAFTERSLAM))
					SetState (SeeState);
				else
					SetIdle();
			}
		}
		else
		{
			SetIdle();
			tics = -1;
		}
	}
	return false;			// stop moving
}

DEFINE_ACTION_FUNCTION(AActor, Slam)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT(thing, AActor);
	ACTION_RETURN_BOOL(self->Slam(thing));
}

bool AActor::CallSlam(AActor *thing)
{
	IFVIRTUAL(AActor, Slam)
	{
		VMValue params[2] = { (DObject*)this, thing };
		VMReturn ret;
		int retval;
		ret.IntAt(&retval);
		VMCall(func, params, 2, &ret, 1);
		return !!retval;

	}
	else return Slam(thing);
}



// This virtual method only exists on the script side.
int AActor::SpecialMissileHit (AActor *victim)
{
	IFVIRTUAL(AActor, SpecialMissileHit)
	{
		VMValue params[2] = { (DObject*)this, victim };
		VMReturn ret;
		int retval;
		ret.IntAt(&retval);
		VMCall(func, params, 2, &ret, 1);
		return retval;
	}
	else return -1;
}

// This virtual method only exists on the script side.
int AActor::SpecialBounceHit(AActor* bounceMobj, line_t* bounceLine, secplane_t* bouncePlane, bool is3DFloor)
{
	IFVIRTUAL(AActor, SpecialBounceHit)
	{
		VMValue params[] = { (DObject*)this, bounceMobj, bounceLine, bouncePlane, is3DFloor };
		VMReturn ret;
		int retval;
		ret.IntAt(&retval);
		VMCall(func, params, 5, &ret, 1);
		return retval;
	}
	else return -1;
}

bool AActor::AdjustReflectionAngle (AActor *thing, DAngle &angle)
{
	if (flags2 & MF2_DONTREFLECT) return true;
	if (thing->flags7 & MF7_THRUREFLECT) return false;
	// Change angle for reflection
	if (thing->flags4&MF4_SHIELDREFLECT)
	{
		// Shield reflection (from the Centaur)
		if ((flags7 & MF7_NOSHIELDREFLECT) || absangle(angle, thing->Angles.Yaw) > DAngle::fromDeg(45))
			return true;	// Let missile explode

		if (pr_reflect () < 128)
			angle += DAngle::fromDeg(45);
		else
			angle -= DAngle::fromDeg(45);

	}
	else if (thing->flags4&MF4_DEFLECT)
	{
		// deflect (like the Heresiarch)
		if(pr_reflect() < 128) 
			angle += DAngle::fromDeg(45);
		else 
			angle -= DAngle::fromDeg(45);
	}
	else
	{
		angle += DAngle::fromDeg((pr_reflect() % 16) - 8);
	}
	//Always check for AIMREFLECT, no matter what else is checked above.
	if (thing->flags7 & MF7_AIMREFLECT)
	{
		if (this->target != NULL)
		{
			A_Face(this, this->target);
		}
		else if (thing->target != NULL)
		{
			A_Face(this, thing->target);
		}
	}
	
	return false;
}

int AActor::AbsorbDamage(int damage, FName dmgtype, AActor *inflictor, AActor *source, int flags)
{
	AActor *next;
	for (AActor *item = Inventory; item != nullptr; item = next)
	{
		// [Player701] Remember the next item now in case the current item is destroyed later
		next = item->Inventory;
		IFVIRTUALPTRNAME(item, NAME_Inventory, AbsorbDamage)
		{
			VMValue params[7] = { item, damage, dmgtype.GetIndex(), &damage, inflictor, source, flags };
			VMCall(func, params, 7, nullptr, 0);
		}
	}
	return damage;
}

void AActor::AlterWeaponSprite(visstyle_t *vis)
{
	int changed = 0;
	TArray<AActor *> items;
	// This needs to go backwards through the items but the list has no backlinks.
	for (AActor *item = Inventory; item != nullptr; item = item->Inventory)
	{
		items.Push(item);
	}
	for(int i=items.Size()-1;i>=0;i--)
	{
		IFVIRTUALPTRNAME(items[i], NAME_Inventory, AlterWeaponSprite)
		{
			VMValue params[3] = { items[i], vis, &changed };
			VMCall(func, params, 3, nullptr, 0);
		}
	}
}

void AActor::PlayActiveSound ()
{
	if (ActiveSound.isvalid() && !S_IsActorPlayingSomething(this, CHAN_VOICE))
	{
		S_Sound (this, CHAN_VOICE, 0, ActiveSound, 1,
			(flags3 & MF3_FULLVOLACTIVE) ? ATTN_NONE : ATTN_IDLE);
	}
}

DEFINE_ACTION_FUNCTION(AActor, PlayActiveSound)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->PlayActiveSound();
	return 0;
}

void AActor::PlayPushSound()
{
	FSoundID push = SoundVar(NAME_PushSound);
	if (!S_IsActorPlayingSomething(this, CHAN_BODY, push))
	{
		S_Sound(this, CHAN_BODY, 0, push, 1, ATTN_NORM);
	}
}

DEFINE_ACTION_FUNCTION(AActor, PlayPushSound)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->PlayPushSound();
	return 0;
}

bool AActor::IsOkayToAttack (AActor *link)
{
	// Standard things to eliminate: an actor shouldn't attack itself,
	// or a non-shootable, dormant, non-player-and-non-monster actor.
	if (link == this)									return false;
	if (!(link->player||(link->flags3 & MF3_ISMONSTER)))return false;
	if (!(link->flags & MF_SHOOTABLE))					return false;
	if (link->flags2 & MF2_DORMANT)						return false;
	if (link->flags7 & MF7_NEVERTARGET)					return false; // NEVERTARGET means just that.

	// An actor shouldn't attack friendly actors. The reference depends
	// on the type of actor: for a player's actor, itself; for a projectile,
	// its target; and for a summoned minion, its tracer.
	AActor * Friend;
	if (flags5 & MF5_SUMMONEDMONSTER)					Friend = tracer;
	else if (flags & MF_MISSILE)						Friend = target;
	else if ((flags & MF_FRIENDLY) && FriendPlayer)		Friend = Level->Players[FriendPlayer-1]->mo;
	else												Friend = this;

	// Friend checks
	if (link == Friend)									return false;
	if (Friend == NULL)									return false;
	if (Friend->IsFriend(link))							return false;
	if ((link->flags5 & MF5_SUMMONEDMONSTER)			// No attack against minions on the same side
		&& (link->tracer == Friend))					return false;
	if (multiplayer && !deathmatch						// No attack against fellow players in coop
		&& link->player && Friend->player)				return false;
	if (((flags & link->flags) & MF_FRIENDLY)			// No friendly infighting amongst minions
		&& IsFriend(link))								return false;

	// Now that all the actor checks are made, the line of sight can be checked
	if (P_CheckSight (this, link))
	{
		// AMageStaffFX2::IsOkayToAttack had an extra check here, generalized with a flag,
		// to only allow the check to succeed if the enemy was in a ~84  FOV of the player
		if (flags3 & MF3_SCREENSEEKER)
		{
			DAngle angle = absangle(Friend->AngleTo(link), Friend->Angles.Yaw);
			if (angle < DAngle::fromDeg(30 * (256./360.)))
			{
				return true;
			}
		}
		// Other actors are not concerned by this check
		else return true;
	}
	// The sight check was failed, or the angle wasn't right for a screenseeker
	return false;
}

void AActor::SetShade (uint32_t rgb)
{
	PalEntry *entry = (PalEntry *)&rgb;
	fillcolor = (rgb & 0xffffff) | (ColorMatcher.Pick (entry->r, entry->g, entry->b) << 24);
}

void AActor::SetShade (int r, int g, int b)
{
	fillcolor = MAKEARGB(ColorMatcher.Pick (r, g, b), r, g, b);
}

DEFINE_ACTION_FUNCTION(AActor, SetShade)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_INT(color);
	self->SetShade(color);
	return 0;
}

// [MC] Helper function for Set(View)Pitch. 
DAngle AActor::ClampPitch(DAngle p)
{	
	// clamp the pitch we set
	DAngle min, max;

	if (player != nullptr)
	{
		min = player->MinPitch;
		max = player->MaxPitch;
	}
	else
	{
		min = DAngle::fromDeg(-89.);
		max = DAngle::fromDeg(89.);
	}
	p = clamp(p, min, max);
	return p;
}

void AActor::SetPitch(DAngle p, int fflags)
{
	if (player != nullptr || (fflags & SPF_FORCECLAMP))
	{
		p = ClampPitch(p);
	}

	if (p != Angles.Pitch)
	{
		if (player != nullptr)
		{
			if (fflags & SPF_SCALEDNOLERP)
			{
				player->angleOffsetTargets.Pitch = deltaangle(Angles.Pitch, p);
				player->cheats |= CF_SCALEDNOLERP;
			}
			else
			{
				Angles.Pitch = p;
				if (fflags & SPF_INTERPOLATE)
					player->cheats |= CF_INTERPVIEW;
			}
		}
		else
		{
			Angles.Pitch = p;
		}
	}
	
}

void AActor::SetAngle(DAngle ang, int fflags)
{
	if (ang != Angles.Yaw)
	{
		if (player != nullptr)
		{
			if (fflags & SPF_SCALEDNOLERP)
			{
				player->angleOffsetTargets.Yaw = deltaangle(Angles.Yaw, ang);
				player->cheats |= CF_SCALEDNOLERP;
			}
			else
			{
				Angles.Yaw = ang;
				if (fflags & SPF_INTERPOLATE)
					player->cheats |= CF_INTERPVIEW;
			}
		}
		else
		{
			Angles.Yaw = ang;
		}
	}
	
}

void AActor::SetRoll(DAngle r, int fflags)
{
	if (r != Angles.Roll)
	{
		if (player != nullptr)
		{
			if (fflags & SPF_SCALEDNOLERP)
			{
				player->angleOffsetTargets.Roll = deltaangle(Angles.Roll, r);
				player->cheats |= CF_SCALEDNOLERP;
			}
			else
			{
				Angles.Roll = r;
				if (fflags & SPF_INTERPOLATE)
					player->cheats |= CF_INTERPVIEW;
			}
		}
		else
		{
			Angles.Roll = r;
		}
	}
}

void AActor::SetViewPitch(DAngle p, int fflags)
{
	if (player != NULL || (fflags & SPF_FORCECLAMP))
	{
		p = ClampPitch(p);
	}

	if (p != ViewAngles.Pitch)
	{
		ViewAngles.Pitch = p;
		if (player != nullptr && (fflags & SPF_INTERPOLATE))
		{
			player->cheats |= CF_INTERPVIEWANGLES;
		}
	}

}

void AActor::SetViewAngle(DAngle ang, int fflags)
{
	if (ang != ViewAngles.Yaw)
	{
		ViewAngles.Yaw = ang;
		if (player != nullptr && (fflags & SPF_INTERPOLATE))
		{
			player->cheats |= CF_INTERPVIEWANGLES;
		}
	}

}

double AActor::GetFOV(double ticFrac)
{
	double fov;
	if (player)
	{
		if (player->cheats & CF_NOFOVINTERP)
			return player->FOV;

		fov = player->FOV;
	}
	else
	{
		fov = CameraFOV;
	}

	return PrevFOV.Degrees() * (1 - ticFrac) + fov * ticFrac;
}

void AActor::SetViewRoll(DAngle r, int fflags)
{
	if (r != ViewAngles.Roll)
	{
		ViewAngles.Roll = r;
		if (player != nullptr && (fflags & SPF_INTERPOLATE))
		{
			player->cheats |= CF_INTERPVIEWANGLES;
		}
	}
}

PClassActor *AActor::GetBloodType(int type) const
{
	IFVIRTUAL(AActor, GetBloodType)
	{
		VMValue params[] = { (DObject*)this, type };
		PClassActor *res;
		VMReturn ret((void**)&res);
		VMCall(func, params, countof(params), &ret, 1);
		return res;
	}
	return nullptr;
}


DVector3 AActor::GetPortalTransition(double byoffset, sector_t **pSec)
{
	bool moved = false;
	sector_t *sec = Sector;
	double testz = Z() + byoffset;
	DVector3 pos = Pos();

	while (!sec->PortalBlocksMovement(sector_t::ceiling))
	{
		if (testz >= sec->GetPortalPlaneZ(sector_t::ceiling))
		{
			pos = PosRelative(sec->GetOppositePortalGroup(sector_t::ceiling));
			sec = Level->PointInSector(pos);
			moved = true;
		}
		else break;
	}
	if (!moved)
	{
		while (!sec->PortalBlocksMovement(sector_t::floor))
		{
			if (testz < sec->GetPortalPlaneZ(sector_t::floor))
			{
				pos = PosRelative(sec->GetOppositePortalGroup(sector_t::floor));
				sec = Level->PointInSector(pos);
			}
			else break;
		}
	}
	if (pSec) *pSec = sec;
	return pos;
}



void AActor::CheckPortalTransition(bool islinked)
{
	bool moved = false;
	FLinkContext ctx;
	while (!Sector->PortalBlocksMovement(sector_t::ceiling))
	{
		if (Z() >= Sector->GetPortalPlaneZ(sector_t::ceiling))
		{
			DVector3 oldpos = Pos();
			if (islinked && !moved) UnlinkFromWorld(&ctx);
			SetXYZ(PosRelative(Sector->GetOppositePortalGroup(sector_t::ceiling)));
			Prev += Pos() - oldpos;
			Sector = Level->PointInSector(Pos());
			PrevPortalGroup = Sector->PortalGroup;
			moved = true;
		}
		else break;
	}
	if (!moved)
	{
		while (!Sector->PortalBlocksMovement(sector_t::floor))
		{
			double portalz = Sector->GetPortalPlaneZ(sector_t::floor);
			if (Z() < portalz && floorz < portalz)
			{
				DVector3 oldpos = Pos();
				if (islinked && !moved) UnlinkFromWorld(&ctx);
				SetXYZ(PosRelative(Sector->GetOppositePortalGroup(sector_t::floor)));
				Prev += Pos() - oldpos;
				Sector = Level->PointInSector(Pos());
				PrevPortalGroup = Sector->PortalGroup;
				moved = true;
			}
			else break;
		}
	}
	if (islinked && moved) LinkToWorld(&ctx);
}

DEFINE_ACTION_FUNCTION(AActor, CheckPortalTransition)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_BOOL(linked);
	self->CheckPortalTransition(linked);
	return 0;
}

//
// P_MobjThinker
//
void AActor::Tick ()
{
	// [RH] Data for Heretic/Hexen scrolling sectors
	static const int8_t HexenCompatSpeeds[] = {-25, 0, -10, -5, 0, 5, 10, 0, 25 };
	static const int8_t HexenScrollies[24][2] =
	{
		{  0,  1 }, {  0,  2 }, {  0,  4 },
		{ -1,  0 }, { -2,  0 }, { -4,  0 },
		{  0, -1 }, {  0, -2 }, {  0, -4 },
		{  1,  0 }, {  2,  0 }, {  4,  0 },
		{  1,  1 }, {  2,  2 }, {  4,  4 },
		{ -1,  1 }, { -2,  2 }, { -4,  4 },
		{ -1, -1 }, { -2, -2 }, { -4, -4 },
		{  1, -1 }, {  2, -2 }, {  4, -4 }
	};

	static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 };
	static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };

	// Check for Actor unmorphing, but only on the thing that is the morphed Actor.
	// Players do their own special checking for this.
	if (alternative != nullptr && player == nullptr)
	{
		if (flags & MF_UNMORPHED)
			return;

		int res = false;
		IFVIRTUAL(AActor, CheckUnmorph)
		{
			VMValue params[] = { this };
			VMReturn ret[] = { &res };
			VMCall(func, params, 1, ret, 1);
		}

		if (res)
			return;
	}

	if (freezetics > 0)
	{
		freezetics--;
		return;
	}

	AActor *onmo;

	//assert (state != NULL);
	if (state == NULL)
	{
		Printf("Actor of type %s at (%f,%f) left without a state\n", GetClass()->TypeName.GetChars(), X(), Y());
		Destroy();
		return;
	}

	if (flags5 & MF5_NOINTERACTION)
	{
		// only do the minimally necessary things here to save time:
		// Check the time freezer
		// apply velocity
		// ensure that the actor is not linked into the blockmap

		//Added by MC: Freeze mode.
		if (isFrozen())
		{
			// Boss cubes shouldn't be accelerated by timefreeze
			if (flags6 & MF6_BOSSCUBE)
			{
				special2++;
			}

			if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & MODELANIM_NONE))
			{
				modelData->curAnim.startTic += 1;
			}

			return;
		}

		if (!Vel.isZero() || !(flags & MF_NOBLOCKMAP))
		{
			FLinkContext ctx;
			UnlinkFromWorld(&ctx);
			flags |= MF_NOBLOCKMAP;
			SetXYZ(Vec3Offset(Vel));
			CheckPortalTransition(false);
			LinkToWorld(&ctx);
		}
		flags8 &= ~MF8_INSCROLLSEC;
	}
	else
	{
		if (player)
			player->crossingPortal = false;
		if (!player || !(player->cheats & CF_PREDICTING))
		{
			// Handle powerup effects here so that the order is controlled
			// by the order in the inventory, not the order in the thinker table
			AActor *item = Inventory;
			
			while (item != NULL)
			{
				IFVIRTUALPTRNAME(item, NAME_Inventory, DoEffect)
				{
					VMValue params[1] = { item };
					VMCall(func, params, 1, nullptr, 0);
				}
				item = item->Inventory;
			}
		}

		if (flags & MF_UNMORPHED)
		{
			return;
		}

		if (isFrozen())
		{
			// Boss cubes shouldn't be accelerated by timefreeze
			if (flags6 & MF6_BOSSCUBE)
			{
				special2++;
			}

			if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & MODELANIM_NONE))
			{
				modelData->curAnim.startTic += 1;
			}

			return;
		}

		if (effects & FX_ROCKET) 
		{
			if (++smokecounter == 4)
			{
				// add some smoke behind the rocket 
				smokecounter = 0;
				AActor *th = Spawn(Level, "RocketSmokeTrail", Vec3Offset(-Vel), ALLOW_REPLACE);
				if (th)
				{
					th->tics -= pr_rockettrail()&3;
					if (th->tics < 1) th->tics = 1;
					if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
				}
			}
		}
		else if (effects & FX_GRENADE) 
		{
			if (++smokecounter == 8)
			{
				smokecounter = 0;
				DAngle moveangle = Vel.Angle();
				double xo = -moveangle.Cos() * radius * 2 + pr_rockettrail() / 64.;
				double yo = -moveangle.Sin() * radius * 2 + pr_rockettrail() / 64.;
				double zo = -Height * Vel.Z / 8. + Height * (2 / 3.);
				AActor * th = Spawn(Level, "GrenadeSmokeTrail", Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
				if (th)
				{
					th->tics -= pr_rockettrail()&3;
					if (th->tics < 1) th->tics = 1;
					if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
				}
			}
		}

		double oldz = Z();

		// [RH] Give the pain elemental vertical friction
		// This used to be in APainElemental::Tick but in order to use
		// A_PainAttack with other monsters it has to be here
		if (flags4 & MF4_VFRICTION)
		{
			if (health >0)
			{
				if (fabs (Vel.Z) < 0.25)
				{
					Vel.Z = 0;
					flags4 &= ~MF4_VFRICTION;
				}
				else
				{
					Vel.Z *= (0xe800 / 65536.);
				}
			}
		}

		// [RH] Pulse in and out of visibility
		if (effects & FX_VISIBILITYPULSE)
		{
			if (visdir > 0)
			{
				Alpha += 1/32.;
				if (Alpha >= 1.)
				{
					Alpha = 1.;
					visdir = -1;
				}
			}
			else
			{
				Alpha -= 1/32.;
				if (Alpha <= 0.25)
				{
					Alpha = 0.25;
					visdir = 1;
				}
			}
		}
		else if (flags & MF_STEALTH)
		{
			// [RH] Fade a stealth monster in and out of visibility
			RenderStyle.Flags &= ~STYLEF_Alpha1;
			if (visdir > 0)
			{
				Alpha += 2./TICRATE;
				if (Alpha > 1.)
				{
					Alpha = 1.;
					visdir = 0;
				}
			}
			else if (visdir < 0)
			{
				Alpha -= 1.5/TICRATE;
				if (Alpha < StealthAlpha)
				{
					Alpha = StealthAlpha;
					visdir = 0;
				}
			}
		}

		if (Level->BotInfo.botnum && !demoplayback &&
			((flags & (MF_SPECIAL|MF_MISSILE)) || (flags3 & MF3_ISMONSTER)))
		{
			Level->BotInfo.BotTick(this);
		}

		// [RH] Consider carrying sectors here
		DVector2 cumm(0, 0);

		if ((((flags8 & MF8_INSCROLLSEC) && Level->Scrolls.Size() > 0) || player != NULL) && !(flags & MF_NOCLIP) && !(flags & MF_NOSECTOR))
		{
			double height, waterheight;	// killough 4/4/98: add waterheight
			const msecnode_t *node;
			int countx, county;

			// Clear the flag for the next frame.
			flags8 &= ~MF8_INSCROLLSEC;

			// killough 3/7/98: Carry things on floor
			// killough 3/20/98: use new sector list which reflects true members
			// killough 3/27/98: fix carrier bug
			// killough 4/4/98: Underwater, carry things even w/o gravity

			// Move objects only if on floor or underwater,
			// non-floating, and clipped.

			countx = county = 0;

			for (node = touching_sectorlist; node; node = node->m_tnext)
			{
				sector_t *sec = node->m_sector;
				DVector2 scrollv;

				if (Level->Scrolls.Size() > unsigned(sec->Index()))
				{
					scrollv = Level->Scrolls[sec->Index()];
				}
				else
				{
					scrollv.Zero();
				}

				if (player != NULL)
				{
					int scrolltype = sec->special;

					if (scrolltype >= Scroll_North_Slow &&
						scrolltype <= Scroll_SouthWest_Fast)
					{ // Hexen scroll special
						scrolltype -= Scroll_North_Slow;
						if (Level->i_compatflags&COMPATF_RAVENSCROLL)
						{
							scrollv.X -= HexenCompatSpeeds[HexenScrollies[scrolltype][0]+4] * (1. / (32 * CARRYFACTOR));
							scrollv.Y += HexenCompatSpeeds[HexenScrollies[scrolltype][1]+4] * (1. / (32 * CARRYFACTOR));

						}
						else
						{
							// Use speeds that actually match the scrolling textures!
							scrollv.X -= HexenScrollies[scrolltype][0] * 0.5;
							scrollv.Y += HexenScrollies[scrolltype][1] * 0.5;
						}
					}
					else if (scrolltype >= Carry_East5 &&
							 scrolltype <= Carry_West35)
					{ // Heretic scroll special
						scrolltype -= Carry_East5;
						uint8_t dir = HereticScrollDirs[scrolltype / 5];
						double carryspeed = HereticSpeedMuls[scrolltype % 5] * (1. / (32 * CARRYFACTOR));
						if (scrolltype < 5 && !(Level->i_compatflags&COMPATF_RAVENSCROLL))
						{
							// Use speeds that actually match the scrolling textures!
							carryspeed = (1 << ((scrolltype % 5) + 15)) / 65536.;
						}
						scrollv.X += carryspeed * ((dir & 3) - 1);
						scrollv.Y += carryspeed * (((dir & 12) >> 2) - 1);
					}
					else if (scrolltype == dScroll_EastLavaDamage)
					{ // Special Heretic scroll special
						if (Level->i_compatflags&COMPATF_RAVENSCROLL)
						{
							scrollv.X += 28. / (32*CARRYFACTOR);
						}
						else
						{
							// Use a speed that actually matches the scrolling texture!
							scrollv.X += 12. / (32 * CARRYFACTOR);
						}
					}
					else if (scrolltype == Scroll_StrifeCurrent)
					{ // Strife scroll special
						int anglespeed = Level->GetFirstSectorTag(sec) - 100;
						double carryspeed = (anglespeed % 10) / (16 * CARRYFACTOR);
						DAngle angle = DAngle::fromDeg(((anglespeed / 10) * 45.));
						scrollv += angle.ToVector(carryspeed);
					}
				}

				if (scrollv.isZero())
				{
					continue;
				}
				sector_t *heightsec = sec->GetHeightSec();
				if (flags & MF_NOGRAVITY && heightsec == NULL)
				{
					continue;
				}
				DVector3 pos = PosRelative(sec);
				height = sec->floorplane.ZatPoint (pos);
				double height2 = sec->floorplane.ZatPoint(this);
				if (isAbove(height))
				{
					if (heightsec == NULL)
					{
						continue;
					}

					waterheight = heightsec->floorplane.ZatPoint (pos);
					if (waterheight > height && Z() >= waterheight)
					{
						continue;
					}
				}

				cumm += scrollv;
				if (scrollv.X) countx++;
				if (scrollv.Y) county++;
			}

			// Some levels designed with Boom in mind actually want things to accelerate
			// at neighboring scrolling sector boundaries. But it is only important for
			// non-player objects.
			if (player != NULL || !(Level->i_compatflags & COMPATF_BOOMSCROLL))
			{
				if (countx > 1)
				{
					cumm.X /= countx;
				}
				if (county > 1)
				{
					cumm.Y /= county;
				}
			}
		}

		// [RH] If standing on a steep slope, fall down it
		if ((flags & MF_SOLID) && !(flags & (MF_NOCLIP|MF_NOGRAVITY)) &&
			!(flags & MF_NOBLOCKMAP) &&
			Vel.Z <= 0 &&
			floorz == Z())
		{
			secplane_t floorplane;

			// Check 3D floors as well
			floorplane = P_FindFloorPlane(floorsector, PosAtZ(floorz));

			if (floorplane.fC() < MaxSlopeSteepness &&
				floorplane.ZatPoint (PosRelative(floorsector)) <= floorz)
			{
				const msecnode_t *node;
				bool dopush = true;

				if (floorplane.fC() > MaxSlopeSteepness*2/3)
				{
					for (node = touching_sectorlist; node; node = node->m_tnext)
					{
						const sector_t *sec = node->m_sector;
						if (sec->floorplane.fC() >= MaxSlopeSteepness)
						{
							if (floorplane.ZatPoint(PosRelative(node->m_sector)) >= Z() - MaxStepHeight)
							{
								dopush = false;
								break;
							}
						}
					}
				}
				if (dopush)
				{
					Vel += floorplane.Normal().XY();
				}
			}
		}

		// [RH] Missiles moving perfectly vertical need some X/Y movement, or they
		// won't hurt anything. Don't do this if damage is 0! That way, you can
		// still have missiles that go straight up and down through actors without
		// damaging anything.
		// (for backwards compatibility this must check for lack of damage function, not for zero damage!)
		if ((flags & MF_MISSILE) && Vel.X == 0 && Vel.Y == 0 && !IsZeroDamage())
		{
			VelFromAngle(MinVel);
		}

		// Handle X and Y velocities
		BlockingMobj = nullptr;
		MovementBlockingLine = nullptr;
		sector_t* oldBlockingCeiling = BlockingCeiling;
		sector_t* oldBlockingFloor = BlockingFloor;
		Blocking3DFloor = nullptr;
		BlockingFloor = nullptr;
		BlockingCeiling = nullptr;
		double oldfloorz = P_XYMovement (this, cumm);
		if (ObjectFlags & OF_EuthanizeMe)
		{ // actor was destroyed
			return;
		}
		// [ZZ] trigger hit floor/hit ceiling actions from XY movement
		if (BlockingFloor && BlockingFloor != oldBlockingFloor && (!player || !(player->cheats & CF_PREDICTING)) && BlockingFloor->SecActTarget)
			BlockingFloor->TriggerSectorActions(this, SECSPAC_HitFloor);
		if (BlockingCeiling && BlockingCeiling != oldBlockingCeiling && (!player || !(player->cheats & CF_PREDICTING)) && BlockingCeiling->SecActTarget)
			BlockingCeiling->TriggerSectorActions(this, SECSPAC_HitCeiling);
		if (Vel.X == 0 && Vel.Y == 0) // Actors at rest
		{
			if (flags2 & MF2_BLASTED)
			{ // Reset to not blasted when velocities are gone
				flags2 &= ~MF2_BLASTED;
			}
			if ((flags6 & MF6_TOUCHY) && !IsSentient())
			{ // Arm a mine which has come to rest
				flags6 |= MF6_ARMED;
			}

		}
		if (Vel.Z != 0 || BlockingMobj || Z() != floorz)
		{	// Handle Z velocity and gravity
			if (((flags2 & MF2_PASSMOBJ) || (flags & MF_SPECIAL)) && !(Level->i_compatflags & COMPATF_NO_PASSMOBJ))
			{
				if (!(onmo = P_CheckOnmobj (this)))
				{
					P_ZMovement (this, oldfloorz);
					flags2 &= ~MF2_ONMOBJ;
				}
				else
				{
					if (player)
					{
						if (Vel.Z < Level->gravity * Sector->gravity * (-1./100)// -655.36f)
							&& !(flags&MF_NOGRAVITY))
						{
							PlayerLandedOnThing (this, onmo);
						}
					}
					if (onmo->Top() - Z() <= MaxStepHeight)
					{
						if (player && player->mo == this)
						{
							player->viewheight -= onmo->Top() - Z();
							double deltaview = player->GetDeltaViewHeight();
							if (deltaview > player->deltaviewheight)
							{
								player->deltaviewheight = deltaview;
							}
						} 
						SetZ(onmo->Top());
					}
					// Check for MF6_BUMPSPECIAL
					// By default, only players can activate things by bumping into them
					// We trigger specials as long as we are on top of it and not just when
					// we land on it. This could be considered as gravity making us continually
					// bump into it, but it also avoids having to worry about walking on to
					// something without dropping and not triggering anything.
					if ((onmo->flags6 & MF6_BUMPSPECIAL) && ((player != NULL)
						|| ((onmo->activationtype & THINGSPEC_MonsterTrigger) && (flags3 & MF3_ISMONSTER))
						|| ((onmo->activationtype & THINGSPEC_MissileTrigger) && (flags & MF_MISSILE))
						) && (Level->maptime > onmo->lastbump)) // Leave the bumper enough time to go away
					{
						if (player == NULL || !(player->cheats & CF_PREDICTING))
						{
							if (P_ActivateThingSpecial(onmo, this))
								onmo->lastbump = Level->maptime + TICRATE;
						}
					}
					if (Vel.Z != 0 && (BounceFlags & BOUNCE_Actors))
					{
						if (flags & MF_MISSILE)
							P_DoMissileDamage(this, onmo);

						// If the bouncer is a missile and has hit the other actor it needs to be exploded here
						// to be in line with the case when an actor's side is hit.
						if (!P_BounceActor(this, onmo, true) && (flags & MF_MISSILE))
							P_ExplodeMissile(this, nullptr, onmo);
					}
					else
					{
						flags2 |= MF2_ONMOBJ;
						Vel.Z = 0;
						Crash();
					}
				}
			}
			else
			{
				P_ZMovement (this, oldfloorz);
			}

			if (ObjectFlags & OF_EuthanizeMe)
				return;		// actor was destroyed
		}
		else if (Z() <= floorz)
		{
			Crash();
			if (ObjectFlags & OF_EuthanizeMe)
				return;		// actor was destroyed
		}

		CheckPortalTransition(true);

		UpdateWaterLevel ();

		// [RH] Don't advance if predicting a player
		if (player && (player->cheats & CF_PREDICTING))
		{
			return;
		}

		// Check for poison damage, but only once per PoisonPeriod tics (or once per second if none).
		if (PoisonDurationReceived && (Level->time % (PoisonPeriodReceived ? PoisonPeriodReceived : TICRATE) == 0))
		{
			P_DamageMobj(this, NULL, Poisoner, PoisonDamageReceived, PoisonDamageTypeReceived != NAME_None ? PoisonDamageTypeReceived : (FName)NAME_Poison, 0);

			--PoisonDurationReceived;

			// Must clear damage when duration is done, otherwise it
			// could be added to with ADDITIVEPOISONDAMAGE.
			if (!PoisonDurationReceived) PoisonDamageReceived = 0;
		}
	}

	assert (state != NULL);
	if (state == NULL)
	{
		Destroy();
		return;
	}
	if (!CheckNoDelay())
		return; // freed itself

	UpdateRenderSectorList();

	if (Sector->Flags & SECF_KILLMONSTERS && Z() == floorz &&
		player == nullptr && (flags & MF_SHOOTABLE) && !(flags & MF_FLOAT))
	{
		P_DamageMobj(this, nullptr, nullptr, TELEFRAG_DAMAGE, NAME_InstantDeath);
		// must have been removed
		if (ObjectFlags & OF_EuthanizeMe) return;
	}
	//[inkoalawetrust] Genericized level damage handling that makes sector, 3D floor, and TERRAIN flat damage affect monsters and other NPCs too.
	P_ActorOnSpecial3DFloor(this); //3D floors must be checked separately to see if their control sector allows non-player damage
	if (checkForSpecialSector(this,Sector))
	{
		P_ActorInSpecialSector(this,Sector);
		if (!isAbove(Sector->floorplane.ZatPoint(this)) || waterlevel) // Actor must be touching the floor for TERRAIN flats.
			P_ActorOnSpecialFlat(this, P_GetThingFloorType(this));
	}

	if (tics != -1)
	{
		// cycle through states, calling action functions at transitions
		// [RH] Use tics <= 0 instead of == 0 so that spawnstates
		// of 0 tics work as expected.
		if (--tics <= 0)
		{
			if (!SetState(state->GetNextState()))
				return; 		// freed itself
		}
	}

	if (tics == -1 || state->GetCanRaise())
	{
		int respawn_monsters = G_SkillProperty(SKILLP_Respawn);
		// check for nightmare respawn
		if (!(flags5 & MF5_ALWAYSRESPAWN))
		{
			if (!respawn_monsters || !(flags3 & MF3_ISMONSTER) || (flags2 & MF2_DORMANT) || (flags5 & MF5_NEVERRESPAWN))
				return;

			int limit = G_SkillProperty (SKILLP_RespawnLimit);
			if (limit > 0 && skillrespawncount >= limit)
				return;
		}

		movecount++;

		if (movecount < respawn_monsters)
			return;

		if (Level->time & 31)
			return;

		if (pr_nightmarerespawn() > 4)
			return;

		P_NightmareRespawn (this);
	}
}

//==========================================================================
//
// AActor :: CheckNoDelay
//
//==========================================================================

bool AActor::CheckNoDelay()
{
	if ((flags7 & MF7_HANDLENODELAY) && !(flags2 & MF2_DORMANT))
	{
		flags7 &= ~MF7_HANDLENODELAY;
		if (state->GetNoDelay())
		{
			// For immediately spawned objects with the NoDelay flag set for their
			// Spawn state, explicitly call the current state's function.
			FState *newstate;
			FStateParamInfo stp = { state, STATE_Actor, PSP_WEAPON };
			if (state->CallAction(this, this, &stp, &newstate))
			{
				if (ObjectFlags & OF_EuthanizeMe)
				{
					return false;		// freed itself
				}
				if (newstate != NULL)
				{
					return SetState(newstate);
				}
			}
		}
	}
	return true;
}

DEFINE_ACTION_FUNCTION(AActor, CheckNoDelay)
{
	PARAM_SELF_PROLOGUE(AActor);
	ACTION_RETURN_BOOL(self->CheckNoDelay());
}

//==========================================================================
//
// AActor :: CheckSectorTransition
//
// Fire off some sector triggers if the actor has changed sectors.
//
//==========================================================================

void AActor::CheckSectorTransition(sector_t *oldsec)
{
	if (oldsec != Sector)
	{
		if (oldsec->SecActTarget != NULL)
		{
			oldsec->TriggerSectorActions(this, SECSPAC_Exit);
		}
		if (Sector->SecActTarget != NULL)
		{
			int act = SECSPAC_Enter;
			if (Z() <= Sector->floorplane.ZatPoint(this))
			{
				act |= SECSPAC_HitFloor;
			}
			if (Top() >= Sector->ceilingplane.ZatPoint(this))
			{
				act |= SECSPAC_HitCeiling;
			}
			if (Sector->heightsec != NULL && Z() == Sector->heightsec->floorplane.ZatPoint(this))
			{
				act |= SECSPAC_HitFakeFloor;
			}
			Sector->TriggerSectorActions(this, act);
		}
		if (Z() == floorz)
		{
			P_CheckFor3DFloorHit(this, Z(), true);
		}
		if (Top() == ceilingz)
		{
			P_CheckFor3DCeilingHit(this, Top(), true);
		}
	}
}

//==========================================================================
//
// UpdateWaterDepth
//
// Updates the actor's current waterlevel and waterdepth.
// Consolidates common code in UpdateWaterLevel and SplashCheck.
//
// Returns the floor height used for the depth check, or -FLT_MAX
// if the actor wasn't in a sector.
//
//==========================================================================

static double UpdateWaterDepth(AActor* actor, bool splash)
{
	double fh = -FLT_MAX;
	bool reset = false;

	actor->waterlevel = 0;
	actor->waterdepth = 0;

	if (actor->Sector == NULL)
	{
		return fh;
	}

	if (actor->Sector->MoreFlags & SECMF_UNDERWATER)	// intentionally not SECMF_UNDERWATERMASK
	{
		actor->waterdepth = actor->Height;
	}
	else
	{
		const sector_t *hsec = actor->Sector->GetHeightSec();
		if (hsec != NULL)
		{
			fh = hsec->floorplane.ZatPoint(actor);

			// splash checks also check Boom-style non-swimmable sectors
			//  as well as non-solid, visible 3D floors (below)
			if (splash || hsec->MoreFlags & SECMF_UNDERWATERMASK)
			{
				actor->waterdepth = fh - actor->Z();

				if (actor->waterdepth <= 0 && !(hsec->MoreFlags & SECMF_FAKEFLOORONLY) && (actor->Top() > hsec->ceilingplane.ZatPoint(actor)))
				{
					actor->waterdepth = actor->Height;
				}
			}
		}
		else
		{
			// Check 3D floors as well!
			for (auto rover : actor->Sector->e->XFloor.ffloors)
			{
				if (!(rover->flags & FF_EXISTS)) continue;
				if (rover->flags & FF_SOLID) continue;

				bool reset = !(rover->flags & FF_SWIMMABLE);
				if (splash) { reset &= rover->alpha == 0; }
				if (reset) continue;

				double ff_bottom = rover->bottom.plane->ZatPoint(actor);
				double ff_top = rover->top.plane->ZatPoint(actor);

				if (ff_top <= actor->Z() || ff_bottom > actor->Center()) continue;

				fh = ff_top;
				actor->waterdepth = ff_top - actor->Z();
				break;
			}
		}
	}

	if (actor->waterdepth < 0) { actor->waterdepth = 0; }

	if (actor->waterdepth > (actor->Height / 2))
	{
		// When noclipping around and going from low to high sector, your view height
		//  can go negative, which is why this is nested inside here
		if ((actor->player && (actor->waterdepth >= actor->player->viewheight)) || (actor->waterdepth >= actor->Height))
		{
			actor->waterlevel = 3;
		}
		else
		{
			actor->waterlevel = 2;
		}
	}
	else if (actor->waterdepth > 0)
	{
		actor->waterlevel = 1;
	}

	return fh;
}

//==========================================================================
//
// AActor::SplashCheck
//
// Returns true if actor should splash
//
//==========================================================================

void AActor::SplashCheck()
{
	double fh = UpdateWaterDepth(this, true);

	// some additional checks to make deep sectors like Boom's splash without setting
	// the water flags. 
	if (boomwaterlevel == 0 && waterlevel != 0)
	{
		P_HitWater(this, Sector, PosAtZ(fh), true);
	}
	boomwaterlevel = waterlevel;
	return;
}

//==========================================================================
//
// AActor::PlayDiveOrSurfaceSounds
//
// Plays diving or surfacing sounds for the player
//
//==========================================================================

void AActor::PlayDiveOrSurfaceSounds(int oldlevel)
{
	IFVIRTUAL(AActor, PlayDiveOrSurfaceSounds)
	{
		VMValue params[2] = { (DObject *)this, oldlevel };
		VMCall(func, params, 2, nullptr, 0);
	}
}

//==========================================================================
//
// AActor::UpdateWaterLevel
//
// Returns true if actor should splash
//
//==========================================================================

bool AActor::UpdateWaterLevel(bool dosplash)
{
	int oldlevel = waterlevel;

	if (dosplash) SplashCheck();
	UpdateWaterDepth(this, false);

	// Play surfacing and diving sounds, as appropriate.
	//
	// (used to be that this code was wrapped around a "Sector != nullptr" check,
	//  but actors should always be within a sector, and besides, this is just
	//  sound stuff)

	if (player != nullptr)
	{
		PlayDiveOrSurfaceSounds(oldlevel);
	}

	return false;	// we did the splash ourselves
}

DEFINE_ACTION_FUNCTION(AActor, UpdateWaterLevel)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_BOOL(splash);
	ACTION_RETURN_BOOL(self->UpdateWaterLevel(splash));
}

//==========================================================================
//
// P_SpawnMobj
//
//==========================================================================

void ConstructActor(AActor *actor, const DVector3 &pos, bool SpawningMapThing)
{
	auto Level = actor->Level;
	actor->SpawnTime = Level->totaltime;
	actor->SpawnOrder = Level->spawnindex++;

	// Set default dialogue
	actor->ConversationRoot = Level->GetConversation(actor->GetClass()->TypeName);
	if (actor->ConversationRoot != -1)
	{
		actor->Conversation = Level->StrifeDialogues[actor->ConversationRoot];
	}
	else
	{
		actor->Conversation = NULL;
	}

	actor->SetXYZ(pos);
	actor->OldRenderPos = { FLT_MAX, FLT_MAX, FLT_MAX };
	actor->picnum.SetInvalid();
	actor->health = actor->SpawnHealth();

	// Actors with zero gravity need the NOGRAVITY flag set.
	if (actor->Gravity == 0) actor->flags |= MF_NOGRAVITY;

	FRandom &rng = Level->BotInfo.m_Thinking ? pr_botspawnmobj : pr_spawnmobj;

	if ((!!G_SkillProperty(SKILLP_InstantReaction) || actor->flags5 & MF5_ALWAYSFAST || !!(dmflags & DF_INSTANT_REACTION))
		&& actor->flags3 & MF3_ISMONSTER)
		actor->reactiontime = 0;

	if (actor->flags3 & MF3_ISMONSTER)
	{
		actor->LastLookPlayerNumber = rng() % MAXPLAYERS;
		actor->TIDtoHate = 0;
	}

	// Set the state, but do not use SetState, because action
	// routines can't be called yet.  If the spawnstate has an action
	// routine, it will not be called.
	FState *st = actor->SpawnState;
	actor->state = st;
	actor->tics = st->GetTics();
	
	actor->sprite = st->sprite;
	actor->frame = st->GetFrame();
	actor->renderflags = (actor->renderflags & ~RF_FULLBRIGHT) | ActorRenderFlags::FromInt (st->GetFullbright());
	actor->touching_sectorlist = nullptr;	// NULL head of sector list // phares 3/13/98
	actor->touching_rendersectors = nullptr;
	if (G_SkillProperty(SKILLP_FastMonsters))
	{
		double f = actor->FloatVar(NAME_FastSpeed);
		if (f >= 0) actor->Speed = f;
	}

	// set subsector and/or block links
	actor->LinkToWorld (nullptr, SpawningMapThing);
	actor->ClearInterpolation();
	actor->ClearFOVInterpolation();

	actor->dropoffz = actor->floorz = actor->Sector->floorplane.ZatPoint(pos);
	actor->ceilingz = actor->Sector->ceilingplane.ZatPoint(pos);

	// The z-coordinate needs to be set once before calling P_FindFloorCeiling
	// For FLOATRANDZ just use the floor here.
	if (pos.Z == ONFLOORZ || pos.Z == FLOATRANDZ)
	{
		actor->SetZ(actor->floorz);
	}
	else if (pos.Z == ONCEILINGZ)
	{
		actor->SetZ(actor->ceilingz - actor->Height);
	}

	if (SpawningMapThing || !actor->IsKindOf (NAME_PlayerPawn))
	{
		// Check if there's something solid to stand on between the current position and the
		// current sector's floor. For map spawns this must be delayed until after setting the
		// z-coordinate.
		if (!SpawningMapThing) 
		{
			P_FindFloorCeiling(actor, FFCF_ONLYSPAWNPOS);
		}
		else
		{
			actor->floorsector = actor->Sector;
			actor->floorpic = actor->floorsector->GetTexture(sector_t::floor);
			actor->floorterrain = actor->floorsector->GetTerrain(sector_t::floor);
			actor->ceilingsector = actor->Sector;
			actor->ceilingpic = actor->ceilingsector->GetTexture(sector_t::ceiling);
		}
	}
	else if (!(actor->flags5 & MF5_NOINTERACTION))
	{
		P_FindFloorCeiling (actor);
	}
	else
	{
		actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
		actor->floorterrain = actor->Sector->GetTerrain(sector_t::floor);
		actor->floorsector = actor->Sector;
		actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
		actor->ceilingsector = actor->Sector;
	}

	actor->SpawnPoint.X = pos.X;
	actor->SpawnPoint.Y = pos.Y;
	// do not copy Z!

	if (pos.Z == ONFLOORZ)
	{
		actor->SetZ(actor->floorz);
	}
	else if (pos.Z == ONCEILINGZ)
	{
		actor->SetZ(actor->ceilingz - actor->Height);
	}
	else if (pos.Z == FLOATRANDZ)
	{
		double space = actor->ceilingz - actor->Height - actor->floorz;
		if (space > 48)
		{
			space -= 40;
			actor->SetZ( space * rng() / 256. + actor->floorz + 40);
		}
		else
		{
			actor->SetZ(actor->floorz);
		}
	}
	else
	{
		actor->SpawnPoint.Z = (actor->Z() - actor->Sector->floorplane.ZatPoint(actor));
	}

	if (actor->FloatBobPhase == (uint8_t)-1) actor->FloatBobPhase = rng();	// Don't make everything bob in sync (unless deliberately told to do)
	if (actor->flags2 & MF2_FLOORCLIP)
	{
		actor->AdjustFloorClip ();
	}
	else
	{
		actor->Floorclip = 0;
	}
	actor->UpdateWaterLevel (false);
	if (!SpawningMapThing)
	{
		actor->CallBeginPlay ();
		if (actor->ObjectFlags & OF_EuthanizeMe)
		{
			return;
		}
	}
	if (Level->flags & LEVEL_NOALLIES && !actor->IsKindOf(NAME_PlayerPawn))
	{
		actor->flags &= ~MF_FRIENDLY;
	}
	// [RH] Count monsters whenever they are spawned.
	if (actor->CountsAsKill())
	{
		Level->total_monsters++;
	}
	// [RH] Same, for items
	if (actor->flags & MF_COUNTITEM)
	{
		Level->total_items++;
	}
	// And for secrets
	if (actor->flags5 & MF5_COUNTSECRET)
	{
		Level->total_secrets++;
	}
	// force scroller check in the first tic.
	actor->flags8 |= MF8_INSCROLLSEC;
}


AActor *AActor::StaticSpawn(FLevelLocals *Level, PClassActor *type, const DVector3 &pos, replace_t allowreplacement, bool SpawningMapThing)
{
	if (type == NULL)
	{
		I_Error("Tried to spawn a class-less actor\n");
	}
	else if (type->bAbstract)
	{
		// [Player701] Abstract actors cannot be spawned by any means
		Printf("Attempt to spawn an instance of abstract actor class %s\n", type->TypeName.GetChars());
		return nullptr;
	}

	if (allowreplacement)
	{
		type = type->GetReplacement(Level);
	}

	AActor *actor;

	actor = static_cast<AActor *>(Level->CreateThinker(type));
	actor->EnableNetworking(true);

	ConstructActor(actor, pos, SpawningMapThing);
	return actor;
}

DEFINE_ACTION_FUNCTION(AActor, Spawn)
{
	PARAM_PROLOGUE;
	PARAM_CLASS_NOT_NULL(type, AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_INT(flags);
	ACTION_RETURN_OBJECT(AActor::StaticSpawn(currentVMLevel, type, DVector3(x, y, z), replace_t(flags)));
}

PClassActor *ClassForSpawn(FName classname)
{
	PClass *cls = PClass::FindClass(classname);
	if (cls == NULL)
	{
		I_Error("Attempt to spawn actor of unknown type '%s'\n", classname.GetChars());
	}
	if (!cls->IsDescendantOf(RUNTIME_CLASS(AActor)))
	{
		I_Error("Attempt to spawn non-actor of type '%s'\n", classname.GetChars());
	}
	return static_cast<PClassActor*>(cls);
}

void AActor::LevelSpawned ()
{
	if (tics > 0 && !(flags4 & MF4_SYNCHRONIZED))
	{
		tics = 1 + (pr_spawnmapthing() % tics);
	}
	// [RH] Clear MF_DROPPED flag if the default version doesn't have it set.
	// (Inventory.BeginPlay() makes all inventory items spawn with it set.)
	if (!(GetDefault()->flags & MF_DROPPED))
	{
		flags &= ~MF_DROPPED;
	}
	HandleSpawnFlags ();
}

void AActor::HandleSpawnFlags ()
{
	if (SpawnFlags & MTF_AMBUSH)
	{
		flags |= MF_AMBUSH;
	}
	if (SpawnFlags & MTF_DORMANT)
	{
		CallDeactivate (NULL);
	}
	if (SpawnFlags & MTF_STANDSTILL)
	{
		flags4 |= MF4_STANDSTILL;
	}
	if (SpawnFlags & MTF_FRIENDLY)
	{
		flags |= MF_FRIENDLY;
		// Friendlies don't count as kills!
		if (flags & MF_COUNTKILL)
		{
			flags &= ~MF_COUNTKILL;
			Level->total_monsters--;
		}
	}
	if (SpawnFlags & MTF_SHADOW)
	{
		flags |= MF_SHADOW;
		RenderStyle = STYLE_Translucent;
		Alpha = 0.25;
	}
	else if (SpawnFlags & MTF_ALTSHADOW)
	{
		RenderStyle = STYLE_None;
	}
	if (SpawnFlags & MTF_SECRET)
	{
		if (!(flags5 & MF5_COUNTSECRET))
		{
			//Printf("Secret %s in sector %i!\n", GetTag(), Sector->sectornum);
			flags5 |= MF5_COUNTSECRET;
			Level->total_secrets++;
		}
	}
	if (SpawnFlags & MTF_NOCOUNT)
	{
		if (flags & MF_COUNTKILL)
		{
			flags &= ~MF_COUNTKILL;
			Level->total_monsters--;
		}
		if (flags & MF_COUNTITEM)
		{
			flags &= ~MF_COUNTITEM;
			Level->total_items--;
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, HandleSpawnFlags)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->HandleSpawnFlags();
	return 0;
}

void AActor::BeginPlay ()
{
	// If the actor is spawned with the dormant flag set, clear it, and use
	// the normal deactivation logic to make it properly dormant.
	if (flags2 & MF2_DORMANT)
	{
		flags2 &= ~MF2_DORMANT;
		CallDeactivate (NULL);
	}
}

DEFINE_ACTION_FUNCTION(AActor, BeginPlay)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->BeginPlay();
	return 0;
}

void AActor::CallBeginPlay()
{
	IFVIRTUAL(AActor, BeginPlay)
	{
		// Without the type cast this picks the 'void *' assignment...
		VMValue params[1] = { (DObject*)this };
		VMCall(func, params, 1, nullptr, 0);
	}
	else BeginPlay();
}


void AActor::PostBeginPlay ()
{
	PrevAngles = Angles;
	flags7 |= MF7_HANDLENODELAY;
	if (GetInfo()->LightAssociations.Size() || (state && state->Light > 0))
	{
		flags8 |= MF8_RECREATELIGHTS;
		Level->flags3 |= LEVEL3_LIGHTCREATED;
	}
}

void AActor::CallPostBeginPlay()
{
	Super::CallPostBeginPlay();
	Level->localEventManager->WorldThingSpawned(this);
}

bool AActor::isFast()
{
	if (flags5&MF5_ALWAYSFAST) return true;
	if (flags5&MF5_NEVERFAST) return false;
	return !!G_SkillProperty(SKILLP_FastMonsters);
}

bool AActor::isSlow()
{
	return !!G_SkillProperty(SKILLP_SlowMonsters);
}

//===========================================================================
//
// Activate
//
//===========================================================================

void AActor::Activate (AActor *activator)
{
	if ((flags3 & MF3_ISMONSTER) && (health > 0 || (flags & MF_ICECORPSE)))
	{
		if (flags2 & MF2_DORMANT)
		{
			flags2 &= ~MF2_DORMANT;
			FState *state = FindState(NAME_Active);
			if (state != NULL) 
			{
				SetState(state);
			}
			else
			{
				tics = 1;
			}
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, Activate)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT(activator, AActor);
	self->Activate(activator);
	return 0;
}

void AActor::CallActivate(AActor *activator)
{
	IFVIRTUAL(AActor, Activate)
	{
		// Without the type cast this picks the 'void *' assignment...
		VMValue params[2] = { (DObject*)this, (DObject*)activator };
		VMCall(func, params, 2, nullptr, 0);
	}
	else Activate(activator);
}


//===========================================================================
//
// Deactivate
//
//===========================================================================

void AActor::Deactivate (AActor *activator)
{
	if ((flags3 & MF3_ISMONSTER) && (health > 0 || (flags & MF_ICECORPSE)))
	{
		if (!(flags2 & MF2_DORMANT))
		{
			flags2 |= MF2_DORMANT;
			FState *state = FindState(NAME_Inactive);
			if (state != NULL) 
			{
				SetState(state);
			}
			else
			{
				tics = -1;
			}
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, Deactivate)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT(activator, AActor);
	self->Deactivate(activator);
	return 0;
}

void AActor::CallDeactivate(AActor *activator)
{
	IFVIRTUAL(AActor, Deactivate)
	{
		// Without the type cast this picks the 'void *' assignment...
		VMValue params[2] = { (DObject*)this, (DObject*)activator };
		VMCall(func, params, 2, nullptr, 0);
	}
	else Deactivate(activator);
}

//===========================================================================
//
// Destroy
//
//===========================================================================

void AActor::OnDestroy ()
{
	// If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as
	// well so it's not stuck in the map.
	if (alternative != nullptr && !(flags & MF_UNMORPHED))
	{
		alternative->ClearCounters();
		alternative->alternative = nullptr;
		alternative->Destroy();
		alternative = nullptr;
	}

	// [ZZ] call destroy event hook.
	//      note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
	//      but you can't really do that without utterly breaking the game, so it's ok.
	//      note: if OnDestroy is ever made optional, E_WorldThingDestroyed should still be called for ANY thing.
	if (Level != nullptr)
	{
		Level->localEventManager->WorldThingDestroyed(this);
	}

	DeleteAttachedLights();
	ClearRenderSectorList();
	ClearRenderLineList();

	// [RH] Destroy any inventory this actor is carrying
	DestroyAllInventory (this);

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

	// unlink from sector and block lists
	UnlinkFromWorld (nullptr);
	flags |= MF_NOSECTOR|MF_NOBLOCKMAP;

	if (ViewPos != nullptr)
	{
		ViewPos->Destroy();
		ViewPos = nullptr;
	}

	// Transform any playing sound into positioned, non-actor sounds.
	S_RelinkSound (this, NULL);

	Super::OnDestroy();
}

//===========================================================================
//
// AdjustFloorClip
//
//===========================================================================

void AActor::AdjustFloorClip ()
{
	if (flags3 & MF3_SPECIALFLOORCLIP)
	{
		return;
	}

	double oldclip = Floorclip;
	double shallowestclip = INT_MAX;
	const msecnode_t *m;

	// [RH] clip based on shallowest floor player is standing on
	// If the sector has a deep water effect, then let that effect
	// do the floorclipping instead of the terrain type.
	for (m = touching_sectorlist; m; m = m->m_tnext)
	{
		DVector3 pos = PosRelative(m->m_sector);
		sector_t* hsec = m->m_sector->GetHeightSec();
		if (hsec == NULL)
		{
			if (m->m_sector->floorplane.ZatPoint(pos) == Z())
			{
				double clip = Terrains[m->m_sector->GetTerrain(sector_t::floor)].FootClip;
				if (clip < shallowestclip)
				{
					shallowestclip = clip;
				}
			}
			else
			{
				for (auto& ff : m->m_sector->e->XFloor.ffloors)
				{
					if ((ff->flags & FF_SOLID) && (ff->flags & FF_EXISTS) && ff->top.plane->ZatPoint(pos) == Z())
					{
						double clip = Terrains[ff->top.model->GetTerrain(ff->top.isceiling)].FootClip;
						if (clip < shallowestclip)
						{
							shallowestclip = clip;
						}
					}
				}

			}
		}
	}
	if (shallowestclip == INT_MAX)
	{
		Floorclip = 0;
	}
	else
	{
		Floorclip = shallowestclip;
	}
	if (player && player->mo == this && oldclip != Floorclip)
	{
		player->viewheight -= (oldclip - Floorclip);
		player->deltaviewheight = player->GetDeltaViewHeight();
	}
}

DEFINE_ACTION_FUNCTION(AActor, AdjustFloorClip)
{
	PARAM_SELF_PROLOGUE(AActor);
	self->AdjustFloorClip();
	return 0;
}

//
// P_SpawnPlayer
// Called when a player is spawned on the level.
// Most of the player structure stays unchanged between levels.
//
EXTERN_CVAR (Bool, chasedemo)
EXTERN_CVAR(Bool, sv_singleplayerrespawn)
EXTERN_CVAR(Float, fov)

extern bool demonew;

//==========================================================================
//
// This function is only designed for swapping player pawns
// over to their new ones upon changing levels or respawning. It SHOULD NOT be
// used for anything else! Do not export this functionality as it's
// meant strictly for internal usage.
//
//==========================================================================

void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld)
{
	if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer
		|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn))
	{
		return;
	}

	// Go through player infos.
	for (int i = 0; i < MAXPLAYERS; ++i)
	{
		if (!oldPlayer->Level->PlayerInGame(i))
			continue;

		auto p = oldPlayer->Level->Players[i];

		if (p->mo == oldPlayer)
			p->mo = newPlayer;
		if (p->poisoner == oldPlayer)
			p->poisoner = newPlayer;
		if (p->attacker == oldPlayer)
			p->attacker = newPlayer;
		if (p->camera == oldPlayer)
			p->camera = newPlayer;
		if (p->ConversationNPC == oldPlayer)
			p->ConversationNPC = newPlayer;
		if (p->ConversationPC == oldPlayer)
			p->ConversationPC = newPlayer;
	}

	// Go through sectors.
	for (auto& sec : oldPlayer->Level->sectors)
	{
		if (sec.SoundTarget == oldPlayer)
			sec.SoundTarget = newPlayer;
	}

	// Update all the remaining object pointers.
	for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
		probe->PointerSubstitution(oldPlayer, newPlayer, removeOld);
}

//==========================================================================
//
// This has some extra barriers compared to PlayerPointerSubstitution to allow
// Actors to freely morph into other Actors which is its main usage.
// It also allows morphing to be more extendable from ZScript.
//
//==========================================================================

int MorphPointerSubstitution(AActor* from, AActor* to)
{
	// Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't
	// allowed to be changed into other things. Anything being morphed into that's considered a player
	// is automatically out of the question to ensure modders aren't swapping clients around.
	if (from == nullptr || to == nullptr || from == to || to->player != nullptr
		|| (from->flags & MF_UNMORPHED)									// Another thing's dummy Actor, unmorphing the wrong way, etc.
		|| (from->alternative == nullptr && to->alternative != nullptr)	// Morphing into something that's already morphed.
		|| (from->alternative != nullptr && from->alternative != to))	// Only allow something morphed to unmorph.
	{
		return false;
	}

	const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn);
	if (from->IsKindOf(NAME_PlayerPawn))
	{
		// Players are only allowed to turn into other valid player pawns. For
		// valid pawns, make sure an actual player is changing into an empty one.
		// Voodoo dolls aren't allowed to morph since that should be passed to
		// the main player directly.
		if (!toIsPlayer || from->player == nullptr || from->player->mo != from)
			return false;
	}
	else if (toIsPlayer || from->player != nullptr
			|| (from->IsKindOf(NAME_Inventory) && from->PointerVar<AActor>(NAME_Owner) != nullptr)
			|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(NAME_Owner) != nullptr))
	{
		// Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from
		// turning into fake players.
		return false;
	}

	// [MC] Had to move this here since ObtainInventory was also moved as well. Should be called
	// before any transference of items since that's what was intended when introduced.
	if (!from->alternative) // Morphing into
	{
		{
			IFVIRTUALPTR(from, AActor, PreMorph)
			{
				VMValue params[] = { from, to, false };
				VMCall(func, params, 3, nullptr, 0);
			}
		}
		{
			IFVIRTUALPTR(to, AActor, PreMorph)
			{
				VMValue params[] = { to, from, true };
				VMCall(func, params, 3, nullptr, 0);
			}
		}
	}
	else // Unmorphing back
	{
		{
			IFVIRTUALPTR(from, AActor, PreUnmorph)
			{
				VMValue params[] = { from, to, false };
				VMCall(func, params, 3, nullptr, 0);
			}
		}
		{
			IFVIRTUALPTR(to, AActor, PreUnmorph)
			{
				VMValue params[] = { to, from, true };
				VMCall(func, params, 3, nullptr, 0);
			}
		}
	}
	// Since the check is good, move the inventory items over. This should always be done when
	// morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their
	// player structs.
	IFVM(Actor, ObtainInventory)
	{
		VMValue params[] = { to, from };
		VMCall(func, params, 2, nullptr, 0);
	}

	// Go through player infos.
	for (int i = 0; i < MAXPLAYERS; ++i)
	{
		if (!from->Level->PlayerInGame(i))
			continue;

		auto p = from->Level->Players[i];

		if (p->mo == from)
			p->mo = to;
		if (p->poisoner == from)
			p->poisoner = to;
		if (p->attacker == from)
			p->attacker = to;
		if (p->camera == from)
			p->camera = to;
		if (p->ConversationNPC == from)
			p->ConversationNPC = to;
		if (p->ConversationPC == from)
			p->ConversationPC = to;
	}

	// Go through sectors.
	for (auto& sec : from->Level->sectors)
	{
		if (sec.SoundTarget == from)
			sec.SoundTarget = to;
	}

	// Replace any object pointers that are safe to swap around.
	for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
		probe->PointerSubstitution(from, to, false);

	// Remaining maintenance related to morphing.
	if (from->player != nullptr)
	{
		to->player = from->player;
		from->player = nullptr;

		// Swap the new body into the right network slot if it's a client (this doesn't
		// really matter for regular Actors since they grab any ID they can get anyway).
		NetworkEntityManager::SetClientNetworkEntity(to, to->player - players);
	}

	if (from->alternative != nullptr)
	{
		to->flags &= ~MF_UNMORPHED;
		to->alternative = from->alternative = nullptr;
	}
	else
	{
		from->flags |= MF_UNMORPHED;
		from->alternative = to;
		to->alternative = from;
	}

	return true;
}

void FLevelLocals::PlayerSpawnPickClass (int playernum)
{
	auto p = Players[playernum];

	if (p->cls == NULL)
	{
		// [GRB] Pick a class from player class list
		if (PlayerClasses.Size () > 1)
		{
			int type;

			if (!deathmatch || !multiplayer)
			{
				type = SinglePlayerClass[playernum];
			}
			else
			{
				type = p->userinfo.GetPlayerClassNum();
				if (type < 0)
				{
					type = pr_multiclasschoice() % PlayerClasses.Size ();
				}
			}
			p->CurrentPlayerClass = type;
		}
		else
		{
			p->CurrentPlayerClass = 0;
		}
		p->cls = PlayerClasses[p->CurrentPlayerClass].Type;
	}
}

AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flags)
{
	player_t *p;
	AActor *mobj, *oldactor;
	uint8_t	  state;
	DVector3 spawn;
	DAngle SpawnAngle;

	if (mthing == NULL)
	{
		return NULL;
	}
	// not playing?
	if ((unsigned)playernum >= (unsigned)MAXPLAYERS || !PlayerInGame(playernum) )
		return NULL;

	// Old lerp data needs to go
	if (playernum == consoleplayer)
	{
		P_PredictionLerpReset();
	}

	p = Players[playernum];

	PlayerSpawnPickClass(playernum);

	if ((dmflags2 & DF2_SAME_SPAWN_SPOT) && !deathmatch
		&& p->mo != nullptr && p->playerstate == PST_REBORN
		&& gameaction != ga_worlddone
		&& !(p->mo->Sector->Flags & SECF_NORESPAWN)
		&& p->LastDamageType != NAME_Suicide)
	{
		spawn = p->LastSafePos;
		SpawnAngle = p->mo->Angles.Yaw;
	}
	else
	{
		spawn.X = mthing->pos.X;
		spawn.Y = mthing->pos.Y;

		// Allow full angular precision
		SpawnAngle = DAngle::fromDeg(mthing->angle);
		if (i_compatflags2 & COMPATF2_BADANGLES)
		{
			SpawnAngle += DAngle::fromDeg(0.01);
		}

		if (GetDefaultByType(p->cls)->flags & MF_SPAWNCEILING)
			spawn.Z = ONCEILINGZ;
		else if (GetDefaultByType(p->cls)->flags2 & MF2_SPAWNFLOAT)
			spawn.Z = FLOATRANDZ;
		else
			spawn.Z = ONFLOORZ;
	}

	mobj = Spawn (this, p->cls, spawn, NO_REPLACE);

	if (this->flags & LEVEL_USEPLAYERSTARTZ)
	{
		if (spawn.Z == ONFLOORZ)
			mobj->AddZ(mthing->pos.Z);
		else if (spawn.Z == ONCEILINGZ)
			mobj->AddZ(-mthing->pos.Z);
		P_FindFloorCeiling(mobj, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
	}

	mobj->FriendPlayer = playernum + 1;	// [RH] players are their own friends
	oldactor = p->mo;
	p->mo = mobj;
	mobj->player = p;
	state = p->playerstate;
	const auto heldWeap = state == PST_REBORN && (dmflags3 & DF3_REMEMBER_LAST_WEAP) ? p->ReadyWeapon : nullptr;
	if (state == PST_REBORN || state == PST_ENTER)
	{
		PlayerReborn (playernum);
	}
	else if (oldactor != NULL && oldactor->player == p && !(flags & SPF_TEMPPLAYER))
	{
		// Move the voodoo doll's inventory to the new player.
		IFVM(Actor, ObtainInventory)
		{
			VMValue params[] = { mobj, oldactor };
			VMCall(func, params, 2, nullptr, 0);
		}
		Behaviors.StopMyScripts (oldactor);	// cancel all ENTER/RESPAWN scripts for the voodoo doll
	}

	// [GRB] Reset skin
	p->userinfo.SkinNumChanged(R_FindSkin (Skins[p->userinfo.GetSkin()].Name.GetChars(), p->CurrentPlayerClass));

	if (!(mobj->flags2 & MF2_DONTTRANSLATE))
	{
		// [RH] Be sure the player has the right translation
		R_BuildPlayerTranslation (playernum);

		// [RH] set color translations for player sprites
		mobj->Translation = TRANSLATION(TRANSLATION_Players,playernum);
	}

	mobj->Angles.Yaw = SpawnAngle;
	mobj->Angles.Pitch = mobj->Angles.Roll = nullAngle;
	mobj->health = p->health;

	// [RH] Set player sprite based on skin
	if (!(mobj->flags4 & MF4_NOSKIN))
	{
		mobj->sprite = Skins[p->userinfo.GetSkin()].sprite;
	}

	p->DesiredFOV = p->FOV = fov;
	p->camera = p->mo;
	p->playerstate = PST_LIVE;
	p->refire = 0;
	p->damagecount = 0;
	p->bonuscount = 0;
	p->morphTics = 0;
	p->MorphedPlayerClass = 0;
	p->MorphStyle = 0;
	p->MorphExitFlash = NULL;
	p->extralight = 0;
	p->fixedcolormap = NOFIXEDCOLORMAP;
	p->fixedlightlevel = -1;
	p->viewheight = p->DefaultViewHeight();
	p->inconsistant = 0;
	p->attacker = nullptr;
	p->spreecount = 0;
	p->multicount = 0;
	p->lastkilltime = 0;
	p->BlendR = p->BlendG = p->BlendB = p->BlendA = 0.f;
	p->Uncrouch();
	p->MinPitch = p->MaxPitch = nullAngle;	// will be filled in by PostBeginPlay()/netcode
	p->MUSINFOactor = nullptr;
	p->MUSINFOtics = -1;
	p->Vel.Zero();	// killough 10/98: initialize bobbing to 0.

	IFVIRTUALPTRNAME(p->mo, NAME_PlayerPawn, ResetAirSupply)
	{
		VMValue params[] = { p->mo, false };
		VMCall(func, params, 2, nullptr, 0);
	}

	for (int ii = 0; ii < MAXPLAYERS; ++ii)
	{
		if (PlayerInGame(ii) && Players[ii]->camera == oldactor)
		{
			Players[ii]->camera = mobj;
		}
	}

	// [RH] Allow chasecam for demo watching
	if ((demoplayback || demonew) && chasedemo)
		p->cheats = CF_CHASECAM;

	// setup gun psprite
	if (!(flags & SPF_TEMPPLAYER))
	{ // This can also start a script so don't do it for the dummy player.
		P_SetupPsprites (p, !!(flags & SPF_WEAPONFULLYUP));
	}

	if (deathmatch)
	{ // Give all cards in death match mode.
		IFVIRTUALPTRNAME(p->mo, NAME_PlayerPawn, GiveDeathmatchInventory)
		{
			VMValue params[1] = { p->mo };
			VMCall(func, params, 1, nullptr, 0);
		}
	}
	else if ((multiplayer || (flags2 & LEVEL2_ALLOWRESPAWN) || sv_singleplayerrespawn ||
		!!G_SkillProperty(SKILLP_PlayerRespawn)) && state == PST_REBORN && oldactor != NULL)
	{ // Special inventory handling for respawning in coop
		IFVM(PlayerPawn, FilterCoopRespawnInventory)
		{
			VMValue params[] = { p->mo, oldactor, ((heldWeap == nullptr || (heldWeap->ObjectFlags & OF_EuthanizeMe)) ? nullptr : heldWeap) };
			VMCall(func, params, 2, nullptr, 0);
		}
	}
	if (oldactor != NULL)
	{ // Remove any inventory left from the old actor. Coop handles
	  // it above, but the other modes don't.
		DestroyAllInventory(oldactor);
	}
	// [BC] Handle temporary invulnerability when respawned
	if (state == PST_REBORN || state == PST_ENTER)
	{
		IFVIRTUALPTRNAME(p->mo, NAME_PlayerPawn, OnRespawn)
		{
			VMValue param = p->mo;
			VMCall(func, &param, 1, nullptr, 0);
		}
	}

	if (StatusBar != NULL && (playernum == consoleplayer || StatusBar->GetPlayer() == playernum))
	{
		StatusBar->AttachToPlayer (p);
	}

	if (multiplayer)
	{
		P_SpawnTeleportFog(mobj, mobj->Vec3Angle(20., mobj->Angles.Yaw, 0.), false, true);
	}

	// "Fix" for one of the starts on exec.wad MAP01: If you start inside the ceiling,
	// drop down below it, even if that means sinking into the floor.
	if (mobj->Top() > mobj->ceilingz)
	{
		mobj->SetZ(mobj->ceilingz - mobj->Height, false);
	}

	// [BC] Do script stuff
	if (!(flags & SPF_TEMPPLAYER))
	{
		if (state == PST_ENTER || (state == PST_LIVE && !savegamerestore))
		{
			Behaviors.StartTypedScripts (SCRIPT_Enter, p->mo, true);
			localEventManager->PlayerSpawned(PlayerNum(p));
		}
		else if (state == PST_REBORN)
		{
			assert (oldactor != NULL);

			// before relocating all pointers to the player all sound targets
			// pointing to the old actor have to be NULLed. Otherwise all
			// monsters who last targeted this player will wake up immediately
			// after the player has respawned.
			AActor *th;
			auto it = GetThinkerIterator<AActor>();
			while ((th = it.Next()))
			{
				if (th->LastHeard == oldactor) th->LastHeard = nullptr;
			}
			for(auto &sec : sectors)
			{
				if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
			}

			PlayerPointerSubstitution (oldactor, p->mo, false);

			localEventManager->PlayerRespawned(PlayerNum(p));
			Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);
		}
	}
	return mobj;
}

//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
// [RH] position is used to weed out unwanted start spots
AActor *FLevelLocals::SpawnMapThing (FMapThing *mthing, int position)
{
	PClassActor *i;
	int mask;
	AActor *mobj;

	bool spawnmulti = G_SkillProperty(SKILLP_SpawnMulti) || !!(dmflags2 & DF2_ALWAYS_SPAWN_MULTI);
	bool spawnmulti_cooponly = G_SkillProperty(SKILLP_SpawnMultiCoopOnly);

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

	// find which type to spawn
	FDoomEdEntry *mentry = mthing->info;

	if (mentry == NULL)
	{
		// [RH] Don't die if the map tries to spawn an unknown thing
		Printf("Unknown type %i at (%.1f, %.1f)\n",
			mthing->EdNum, mthing->pos.X, mthing->pos.Y);
		mentry = DoomEdMap.CheckKey(0);
		if (mentry == NULL)	// we need a valid entry for the rest of this function so if we can't find a default, let's exit right away.
		{
		return NULL;
	}
	}
	if (mentry->Type == NULL && mentry->Special <= 0)
	{
		// has been explicitly set to not spawning anything.
		return NULL;
	}

	// copy args to mapthing so that we have them in one place for the rest of this function	
	if (mentry->ArgsDefined > 0)
	{
		if (mentry->Type!= NULL) mthing->special = mentry->Special;
		memcpy(mthing->args, mentry->Args, sizeof(mthing->args[0]) * mentry->ArgsDefined);
	}

	int pnum = -1;
	if (mentry->Type == NULL)
	{

		switch (mentry->Special)
		{
		case SMT_DeathmatchStart:
		{
			// count deathmatch start positions
			FPlayerStart start(mthing, 0);
			deathmatchstarts.Push(start);
			return NULL;
		}

		case SMT_PolyAnchor:
		case SMT_PolySpawn:
		case SMT_PolySpawnCrush:
		case SMT_PolySpawnHurt:
			return nullptr;

		case SMT_Player1Start:
		case SMT_Player2Start:
		case SMT_Player3Start:
		case SMT_Player4Start:
		case SMT_Player5Start:
		case SMT_Player6Start:
		case SMT_Player7Start:
		case SMT_Player8Start:
			pnum = mentry->Special - SMT_Player1Start;
			break;

		// Sound sequence override will be handled later
		default:
			break;

	}
		}

	if (pnum == -1 || (flags & LEVEL_FILTERSTARTS))
	{
		// check for appropriate game type
		if (deathmatch) 
		{
			mask = MTF_DEATHMATCH;
		}
		else if (multiplayer)
		{
			mask = MTF_COOPERATIVE;
		}
		else if (spawnmulti || spawnmulti_cooponly)
		{
			mask = spawnmulti_cooponly ? MTF_COOPERATIVE : (MTF_COOPERATIVE|MTF_SINGLE);
		}
		else
		{
			mask = MTF_SINGLE;
		}
		if (!(mthing->flags & mask))
		{
			return NULL;
		}

		mask = G_SkillProperty(SKILLP_SpawnFilter);
		if (!(mthing->SkillFilter & mask) && !mentry->NoSkillFlags)
		{
			return NULL;
		}

		// Check class spawn masks. Now with player classes available
		// this is enabled for all games.
		if (!multiplayer)
		{ // Single player
			auto p = GetConsolePlayer();
			if (p)
			{
				int spawnmask = p->GetSpawnClass();
				if (spawnmask != 0 && (mthing->ClassFilter & spawnmask) == 0)
				{ // Not for current class
					return nullptr;
				}
			}
		}
		else if (!deathmatch)
		{ // Cooperative
			mask = 0;
			for (int i = 0; i < MAXPLAYERS; i++)
			{
				if (PlayerInGame(i))
				{
					int spawnmask = Players[i]->GetSpawnClass();
					if (spawnmask != 0)
						mask |= spawnmask;
					else 
						mask = -1;
				}
			}
			if (mask != -1 && (mthing->ClassFilter & mask) == 0)
			{
				return NULL;
			}
		}
	}

	if (pnum != -1)
	{
		// [RH] Only spawn spots that match position.
		if (mthing->args[0] != position)
			return NULL;

		// save spots for respawning in network games
		FPlayerStart start(mthing, pnum+1);
		playerstarts[pnum] = start;
		if (flags2 & LEVEL2_RANDOMPLAYERSTARTS)
		{ // When using random player starts, all starts count
			AllPlayerStarts.Push(start);
		}
		else
		{ // When not using random player starts, later single player
		  // starts should override earlier ones, since the earlier
		  // ones are for voodoo dolls and not likely to be ideal for
		  // spawning regular players.
			unsigned i;
			for (i = 0; i < AllPlayerStarts.Size(); ++i)
			{
				if (AllPlayerStarts[i].type == pnum+1)
				{
					AllPlayerStarts[i] = start;
					break;
				}
			}
			if (i == AllPlayerStarts.Size())
			{
				AllPlayerStarts.Push(start);
			}
		}
		if (!deathmatch && !(flags2 & LEVEL2_RANDOMPLAYERSTARTS))
		{
			return SpawnPlayer(&start, pnum, (flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
		}
		return NULL;
	}

	// [RH] sound sequence overriders
	if (mentry->Type == NULL && mentry->Special == SMT_SSeqOverride)
	{
		int type = mthing->args[0];
		if (type == 255) type = -1;
		if (type > 63)
		{
			Printf ("Sound sequence %d out of range\n", type);
		}
		else
		{
			PointInSector (mthing->pos)->seqType = type;
		}
		return NULL;
	}

	// [RH] If the thing's corresponding sprite has no frames, also map
	//		it to the unknown thing.
		// Handle decorate replacements explicitly here
		// to check for missing frames in the replacement object.
	i = mentry->Type->GetReplacement(this);

		const AActor *defaults = GetDefaultByType (i);
		if (defaults->SpawnState == NULL ||
			sprites[defaults->SpawnState->sprite].numframes == 0)
		{
			// We don't load mods for shareware games so we'll just ignore
			// missing actors. Heretic needs this since the shareware includes
			// the retail weapons in Deathmatch.
			if (gameinfo.flags & GI_SHAREWARE)
				return NULL;

			Printf ("%s at (%.1f, %.1f) has no frames\n",
					i->TypeName.GetChars(), mthing->pos.X, mthing->pos.Y);
			i = PClass::FindActor("Unknown");
			assert(i->IsDescendantOf(RUNTIME_CLASS(AActor)));
		}

	const AActor *info = GetDefaultByType (i);

	// Don't spawn keycards and players in deathmatch.
	if (deathmatch && (info->flags & MF_NOTDMATCH))
		return nullptr;

	// Don't spawn extra things in co-op if desired.
	if (multiplayer && !deathmatch)
	{
		// Don't spawn DM-only things in co-op.
		if ((dmflags2 & DF2_NO_COOP_THING_SPAWN) && (mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
			return nullptr;
		// Having co-op only functionality is a bit odd, but you never know.
		if (!mthing->special && !mthing->thingid && (mthing->flags & (MTF_COOPERATIVE | MTF_SINGLE)) == MTF_COOPERATIVE)
		{
			// Don't spawn co-op only things in general.
			if (dmflags3 & DF3_NO_COOP_ONLY_THINGS)
				return nullptr;
			// Don't spawn co-op only items.
			if ((dmflags3 & DF3_NO_COOP_ONLY_ITEMS) && i->IsDescendantOf(NAME_Inventory))
				return nullptr;
		}
	}

	// [RH] don't spawn extra weapons in coop if so desired
	if (multiplayer && !deathmatch && (dmflags & DF_NO_COOP_WEAPON_SPAWN))
	{
		if (GetDefaultByType(i)->flags7 & MF7_WEAPONSPAWN)
		{
			if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
				return NULL;
		}
	}

	// don't spawn any monsters if -nomonsters
	if (((flags2 & LEVEL2_NOMONSTERS) || (dmflags & DF_NO_MONSTERS)) && info->flags3 & MF3_ISMONSTER )
	{
		return NULL;
	}
	auto it = GetDefaultByType(i);

	IFVIRTUALPTR(it, AActor, ShouldSpawn)
	{
		int ret;
		VMValue param = it;
		VMReturn rett(&ret);
		VMCall(func, &param, 1, &rett, 1);
		if (!ret) return nullptr;
	}

	// spawn it
	double sz;

	if (info->flags & MF_SPAWNCEILING)
		sz = ONCEILINGZ;
	else if (info->flags2 & MF2_SPAWNFLOAT)
		sz = FLOATRANDZ;
	else
		sz = ONFLOORZ;

	mobj = AActor::StaticSpawn (this, i, DVector3(mthing->pos.XY(), sz), NO_REPLACE, true);

	if (sz == ONFLOORZ)
	{
		mobj->AddZ(mthing->pos.Z);
		if ((mobj->flags2 & MF2_FLOATBOB) && (mobj->Level->ib_compatflags & BCOMPATF_FLOATBOB))
		{
			mobj->specialf1 = mthing->pos.Z;
		}
	}
	else if (sz == ONCEILINGZ)
		mobj->AddZ(-mthing->pos.Z);

	if (mobj->flags2 & MF2_FLOORCLIP)
	{
		mobj->AdjustFloorClip();
	}

	mobj->SpawnPoint = mthing->pos;
	mobj->SpawnAngle = mthing->angle;
	mobj->SpawnFlags = mthing->flags;
	if (mthing->friendlyseeblocks > 0)
		mobj->friendlyseeblocks = mthing->friendlyseeblocks;
	if (mthing->FloatbobPhase >= 0 && mthing->FloatbobPhase < 64) mobj->FloatBobPhase = mthing->FloatbobPhase;
	if (mthing->Gravity < 0) mobj->Gravity = -mthing->Gravity;
	else if (mthing->Gravity > 0) mobj->Gravity *= mthing->Gravity;
	else 
	{
		mobj->flags |= MF_NOGRAVITY;
		mobj->Gravity = 0;
	}

	// For Hexen floatbob 'compatibility' we do not really want to alter the floorz.
	if (mobj->specialf1 == 0 || !(mobj->flags2 & MF2_FLOATBOB) || !(mobj->Level->ib_compatflags & BCOMPATF_FLOATBOB))
	{
		P_FindFloorCeiling(mobj, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
	}

	// if the actor got args defined either in DECORATE or MAPINFO we must ignore the map's properties.
	if (!(mobj->flags2 & MF2_ARGSDEFINED))
	{
		// [RH] Set the thing's special
		mobj->special = mthing->special;
		for(int j=0;j<5;j++) mobj->args[j]=mthing->args[j];
	}

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

	mobj->PrevAngles.Yaw = mobj->Angles.Yaw = DAngle::fromDeg(mthing->angle);

	// Check if this actor's mapthing has a conversation defined
	if (mthing->Conversation > 0)
	{
		// Make sure that this does not partially overwrite the default dialogue settings.
		int root = GetConversation(mthing->Conversation);
		if (root != -1)
		{
			mobj->ConversationRoot = root;
			mobj->Conversation = StrifeDialogues[mobj->ConversationRoot];
		}
	}

	// Set various UDMF options
	if (mthing->Alpha >= 0)
		mobj->Alpha = mthing->Alpha;
	if (mthing->RenderStyle != STYLE_Count)
		mobj->RenderStyle = (ERenderStyle)mthing->RenderStyle;
	if (mthing->Scale.X != 0)
		mobj->Scale.X = mthing->Scale.X * mobj->Scale.X;
	if (mthing->Scale.Y != 0)
		mobj->Scale.Y = mthing->Scale.Y * mobj->Scale.Y;
	if (mthing->pitch)
		mobj->Angles.Pitch = DAngle::fromDeg(mthing->pitch);
	if (mthing->roll)
		mobj->Angles.Roll = DAngle::fromDeg(mthing->roll);
	if (mthing->score)
		mobj->Score = mthing->score;
	if (mthing->fillcolor)
		mobj->fillcolor = (mthing->fillcolor & 0xffffff) | (ColorMatcher.Pick((mthing->fillcolor & 0xff0000) >> 16,
			(mthing->fillcolor & 0xff00) >> 8, (mthing->fillcolor & 0xff)) << 24);

	// allow color strings for lights and reshuffle the args for spot lights
	if (i->IsDescendantOf(NAME_DynamicLight))
	{
		if (mthing->arg0str != NAME_None)
		{
			PalEntry color = V_GetColor(mthing->arg0str.GetChars());
			mobj->args[0] = color.r;
			mobj->args[1] = color.g;
			mobj->args[2] = color.b;
		}
		else if (mobj->IntVar(NAME_lightflags) & LF_SPOT)
		{
			mobj->args[0] = RPART(mthing->args[0]);
			mobj->args[1] = GPART(mthing->args[0]);
			mobj->args[2] = BPART(mthing->args[0]);
		}

		if (mobj->IntVar(NAME_lightflags) & LF_SPOT)
		{
			mobj->AngleVar(NAME_SpotInnerAngle) = DAngle::fromDeg(mthing->args[1]);
			mobj->AngleVar(NAME_SpotOuterAngle) = DAngle::fromDeg(mthing->args[2]);
		}
	}

	mobj->CallBeginPlay ();
	if (!(mobj->ObjectFlags & OF_EuthanizeMe))
	{
		mobj->LevelSpawned ();
	}

	if (mthing->Health > 0)
		mobj->health = int(mobj->health * mthing->Health);
	else
		mobj->health = -int(mthing->Health);
	if (mthing->Health == 0)
	{
		// We cannot call 'Die' here directly because the level is not yet fully set up.
		// This needs to be delayed by one tic.
		auto state = mobj->FindState(NAME_DieFromSpawn);
		if (state) mobj->SetState(state, true);
	}
	else if (mthing->Health != 1)
		mobj->StartHealth = mobj->health;

	return mobj;
}

//===========================================================================
//
// SpawnMapThing
//
//===========================================================================
CVAR(Bool, dumpspawnedthings, false, 0)

AActor *FLevelLocals::SpawnMapThing(int index, FMapThing *mt, int position)
{
	AActor *spawned = SpawnMapThing(mt, position);
	if (dumpspawnedthings)
	{
		Printf("%5d: (%5f, %5f, %5f), doomednum = %5d, flags = %04x, type = %s\n",
			index, mt->pos.X, mt->pos.Y, mt->pos.Z, mt->EdNum, mt->flags,
			spawned ? spawned->GetClass()->TypeName.GetChars() : "(none)");
	}
	T_AddSpawnedThing(this, spawned);
	return spawned;
}



//
// GAME SPAWN FUNCTIONS
//


//
// P_SpawnPuff
//

AActor *P_SpawnPuff (AActor *source, PClassActor *pufftype, const DVector3 &pos1, DAngle hitdir, DAngle particledir, int updown, int flags, AActor *vict)
{
	AActor *puff;
	DVector3 pos = pos1;

	if (pufftype == nullptr) return nullptr;

	if (!(flags & PF_NORANDOMZ)) pos.Z += pr_spawnpuff.Random2() / 64.;
	puff = Spawn(source->Level, pufftype, pos, ALLOW_REPLACE);
	if (puff == NULL) return NULL;

	if ((puff->flags4 & MF4_RANDOMIZE) && puff->tics > 0)
	{
		puff->tics -= pr_spawnpuff() & 3;
		if (puff->tics < 1)
			puff->tics = 1;
	}

	//Moved puff creation and target/master/tracer setting to here. 
	if (puff && vict)
	{
		if (puff->flags7 & MF7_HITTARGET)	puff->target = vict;
		if (puff->flags7 & MF7_HITMASTER)	puff->master = vict;
		if (puff->flags7 & MF7_HITTRACER)	puff->tracer = vict;
	}
	// [BB] If the puff came from a player, set the target of the puff to this player.
	if ( puff && (puff->flags5 & MF5_PUFFGETSOWNER))
		puff->target = source;
	
	// [AA] Track the source of the attack unconditionally in a separate field.
	puff->damagesource = source;
	
	// Angle is the opposite of the hit direction (i.e. the puff faces the source.)
	puff->Angles.Yaw = hitdir + DAngle::fromDeg(180);

	// [AA] Mark the spawned actor as a puff with a flag.
	puff->flags9 |= MF9_ISPUFF;

	// If a puff has a crash state and an actor was not hit,
	// it will enter the crash state. This is used by the StrifeSpark
	// and BlasterPuff.
	FState *crashstate;
	if ((flags & PF_HITSKY) && (crashstate = puff->FindState(NAME_Death, NAME_Sky, true)) != NULL)
	{
		puff->SetState (crashstate);
	}
	else if (!(flags & PF_HITTHING) && (crashstate = puff->FindState(NAME_Crash)) != NULL)
	{
		puff->SetState (crashstate);
	}
	else if ((flags & PF_HITTHINGBLEED) && (crashstate = puff->FindState(NAME_Death, NAME_Extreme, true)) != NULL)
	{
		puff->SetState (crashstate);
	}
	else if ((flags & PF_MELEERANGE) && puff->MeleeState != NULL)
	{
		// handle the hard coded state jump of Doom's bullet puff
		// in a more flexible manner.
		puff->SetState (puff->MeleeState);
	}

	if (!(flags & PF_TEMPORARY))
	{
		if (cl_pufftype && updown != 3 && (puff->flags4 & MF4_ALLOWPARTICLES))
		{
			P_DrawSplash2 (source->Level, 32, pos, particledir, updown, 1);
			if (cl_pufftype == 1) puff->renderflags |= RF_INVISIBLE;
		}

		if ((flags & PF_HITTHING) && puff->SeeSound.isvalid())
		{ // Hit thing sound
			S_Sound (puff, CHAN_BODY, 0, puff->SeeSound, 1, ATTN_NORM);
		}
		else if (puff->AttackSound.isvalid())
		{
			S_Sound (puff, CHAN_BODY, 0, puff->AttackSound, 1, ATTN_NORM);
		}
	}

	return puff;
}

DEFINE_ACTION_FUNCTION(AActor, SpawnPuff)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_CLASS(pufftype, AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_ANGLE(hitdir);
	PARAM_ANGLE(particledir);
	PARAM_INT(updown);
	PARAM_INT(flags);
	PARAM_OBJECT(victim, AActor);
	ACTION_RETURN_OBJECT(P_SpawnPuff(self, pufftype, DVector3(x, y, z), hitdir, particledir, updown, flags, victim));
}

//---------------------------------------------------------------------------
//
// P_SpawnBlood
// 
//---------------------------------------------------------------------------

void P_SpawnBlood (const DVector3 &pos1, DAngle dir, int damage, AActor *originator)
{
	AActor *th;
	PClassActor *bloodcls = originator->GetBloodType();
	DVector3 pos = pos1;
	pos.Z += pr_spawnblood.Random2() / 64.;

	int bloodtype = cl_bloodtype;
	
	if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
		bloodtype = 0;

	if (bloodcls != NULL)
	{
		th = Spawn(originator->Level, bloodcls, pos, NO_REPLACE); // GetBloodType already performed the replacement
		th->Vel.Z = 2;
		th->Angles.Yaw = dir;
		// [NG] Applying PUFFGETSOWNER to the blood will make it target the owner
		if (th->flags5 & MF5_PUFFGETSOWNER) th->target = originator;
		if (gameinfo.gametype & GAME_DoomChex)
		{
			th->tics -= pr_spawnblood() & 3;

			if (th->tics < 1)
				th->tics = 1;
		}
		// colorize the blood
		if (!(th->flags2 & MF2_DONTTRANSLATE))
		{
			th->Translation = originator->BloodTranslation;
		}
		
		// Moved out of the blood actor so that replacing blood is easier
		if (gameinfo.gametype & GAME_DoomStrifeChex)
		{
			if (gameinfo.gametype == GAME_Strife)
			{
				if (damage > 13)
				{
					FState *state = th->FindState(NAME_Spray);
					if (state != NULL)
					{
						th->SetState (state);
						goto statedone;
					}
				}
				else damage += 2;
			}
			int advance = 0;
			if (damage <= 12 && damage >= 9)
			{
				advance = 1;
			}
			else if (damage < 9)
			{
				advance = 2;
			}

			PClassActor *cls = th->GetClass();

			while (cls != RUNTIME_CLASS(AActor))
			{
				int checked_advance = advance;
				if (cls->OwnsState(th->SpawnState))
				{
					for (; checked_advance > 0; --checked_advance)
					{
						// [RH] Do not set to a state we do not own.
						if (cls->OwnsState(th->SpawnState + checked_advance))
						{
							th->SetState(th->SpawnState + checked_advance);
							goto statedone;
						}
					}
				}
				// We can safely assume the ParentClass is of type PClassActor
				// since we stop when we see the Actor base class.
				cls = static_cast<PClassActor *>(cls->ParentClass);
			}
		}

	statedone:
		if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE;
	}

	if (bloodtype >= 1)
		P_DrawSplash2 (originator->Level, 40, pos, dir, 2, originator->BloodColor);
}

DEFINE_ACTION_FUNCTION(AActor, SpawnBlood)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_ANGLE(dir);
	PARAM_INT(damage);
	P_SpawnBlood(DVector3(x, y, z), dir, damage, self);
	return 0;
}


//---------------------------------------------------------------------------
//
// PROC P_BloodSplatter
//
//---------------------------------------------------------------------------

void P_BloodSplatter (const DVector3 &pos, AActor *originator, DAngle hitangle)
{
	PClassActor *bloodcls = originator->GetBloodType(1); 

	int bloodtype = cl_bloodtype;
	
	if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
		bloodtype = 0;

	if (bloodcls != NULL)
	{
		AActor *mo;

		mo = Spawn(originator->Level, bloodcls, pos, NO_REPLACE); // GetBloodType already performed the replacement
		mo->target = originator;
		mo->Vel.X = pr_splatter.Random2 () / 64.;
		mo->Vel.Y = pr_splatter.Random2() / 64.;
		mo->Vel.Z = 3;

		// colorize the blood!
		if (!(mo->flags2 & MF2_DONTTRANSLATE)) 
		{
			mo->Translation = originator->BloodTranslation;
		}

		if (!(bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
	}
	if (bloodtype >= 1)
	{
		P_DrawSplash2 (originator->Level, 40, pos, hitangle - DAngle::fromDeg(180.), 2, originator->BloodColor);
	}
}

//===========================================================================
//
//  P_BloodSplatter2
//
//===========================================================================

void P_BloodSplatter2 (const DVector3 &pos, AActor *originator, DAngle hitangle)
{
	PClassActor *bloodcls = originator->GetBloodType(2);

	int bloodtype = cl_bloodtype;
	
	if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
		bloodtype = 0;

	DVector2 add;
	add.X = (pr_splat() - 128) / 32.;
	add.Y = (pr_splat() - 128) / 32.;

	if (bloodcls != NULL)
	{
		AActor *mo;


		mo = Spawn (originator->Level, bloodcls, pos + add, NO_REPLACE); // GetBloodType already performed the replacement
		mo->target = originator;

		// colorize the blood!
		if (!(mo->flags2 & MF2_DONTTRANSLATE))
		{
			mo->Translation = originator->BloodTranslation;
		}

		if (!(bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
	}
	if (bloodtype >= 1)
	{
		P_DrawSplash2(originator->Level, 40, pos + add, hitangle - DAngle::fromDeg(180.), 2, originator->BloodColor);
	}
}

DEFINE_ACTION_FUNCTION(AActor, BloodSplatter)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_ANGLE(dir);
	PARAM_BOOL(axe);
	if (axe) P_BloodSplatter2(DVector3(x, y, z), self, dir);
	else P_BloodSplatter(DVector3(x, y, z), self, dir);
	return 0;
}


//---------------------------------------------------------------------------
//
// PROC P_RipperBlood
//
//---------------------------------------------------------------------------

void P_RipperBlood (AActor *mo, AActor *bleeder)
{
	PClassActor *bloodcls = bleeder->GetBloodType();

	double xo = pr_ripperblood.Random2() / 16.;
	double yo = pr_ripperblood.Random2() / 16.;
	double zo = pr_ripperblood.Random2() / 16.;
	DVector3 pos = mo->Vec3Offset(xo, yo, zo);

	int bloodtype = cl_bloodtype;
	
	if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
		bloodtype = 0;

	if (bloodcls != NULL)
	{
		AActor *th;
		th = Spawn (bleeder->Level, bloodcls, pos, NO_REPLACE); // GetBloodType already performed the replacement
		// [NG] Applying PUFFGETSOWNER to the blood will make it target the owner
		if (th->flags5 & MF5_PUFFGETSOWNER) th->target = bleeder;
		if (gameinfo.gametype == GAME_Heretic)
			th->flags |= MF_NOGRAVITY;
		th->Vel.X = mo->Vel.X / 2;
		th->Vel.Y = mo->Vel.Y / 2;
		th->tics += pr_ripperblood () & 3;

		// colorize the blood!
		if (!(th->flags2 & MF2_DONTTRANSLATE))
		{
			th->Translation = bleeder->BloodTranslation;
		}

		if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE;
	}
	if (bloodtype >= 1)
	{
		P_DrawSplash2(bleeder->Level, 28, pos, bleeder->AngleTo(mo) + DAngle::fromDeg(180.), 0, bleeder->BloodColor);
	}
}

//---------------------------------------------------------------------------
//
// FUNC P_GetThingFloorType
//
//---------------------------------------------------------------------------

int P_GetThingFloorType (AActor *thing)
{
	if (thing->floorterrain >= 0)
	{		
		return thing->floorterrain;
	}
	else
	{
		return thing->Sector->GetTerrain(sector_t::floor);
	}
}


//---------------------------------------------------------------------------
//
// FUNC P_HitWater
//
// Returns true if hit liquid and splashed, false if not.
//---------------------------------------------------------------------------

enum HitWaterFlags
{
	THW_SMALL	= 1 << 0,
	THW_NOVEL	= 1 << 1,
};

bool P_HitWater (AActor * thing, sector_t * sec, const DVector3 &pos, bool checkabove, bool alert, bool force, int flags)
{
	if (thing->player && (thing->player->cheats & CF_PREDICTING))
		return false;

	AActor *mo = NULL;
	FSplashDef *splash;
	int terrainnum;
	sector_t *hsec = NULL;
	
	// don't splash above the object
	if (checkabove)
	{
		double compare_z = thing->Center();
		// Missiles are typically small and fast, so they might
		// end up submerged by the move that calls P_HitWater.
		if (thing->flags & MF_MISSILE)
			compare_z -= thing->Vel.Z;
		if (pos.Z > compare_z) 
			return false;
	}

#if 0 // needs some rethinking before activation

	// This avoids spawning splashes on invisible self referencing sectors.
	// For network consistency do this only in single player though because
	// it is not guaranteed that all players have GL nodes loaded.
	if (!multiplayer && thing->subsector->sector != thing->subsector->render_sector)
	{
		double zs = thing->subsector->sector->floorplane.ZatPoint(pos);
		double zr = thing->subsector->render_sector->floorplane.ZatPoint(pos);

		if (zs > zr && thing->Z() >= zs) return false;
	}
#endif

	// 'force' means, we want this sector's terrain, no matter what.
	if (!force)
	{
		for (unsigned int i = 0; i<sec->e->XFloor.ffloors.Size(); i++)
		{
			F3DFloor * rover = sec->e->XFloor.ffloors[i];
			if (!(rover->flags & FF_EXISTS)) continue;
			double planez = rover->top.plane->ZatPoint(pos);
				if (pos.Z > planez - 0.5 && pos.Z < planez + 0.5)	// allow minor imprecisions
			{
				if ((rover->flags & (FF_SOLID | FF_SWIMMABLE)) || rover->alpha > 0)
				{
					terrainnum = rover->model->GetTerrain(rover->top.isceiling);
					goto foundone;
				}
			}
			planez = rover->bottom.plane->ZatPoint(pos);
			if (planez < pos.Z && !(planez < thing->floorz)) return false;
		}
	}
	hsec = sec->GetHeightSec();
	if (force || hsec == NULL || !(hsec->MoreFlags & SECMF_CLIPFAKEPLANES))
	{
		terrainnum = sec->GetTerrain(sector_t::floor);
	}
	else
	{
		terrainnum = hsec->GetTerrain(sector_t::floor);
	}
foundone:

	int splashnum = Terrains[terrainnum].Splash;
	bool smallsplash = false;
	const secplane_t *plane;

	if (splashnum == -1)
		return Terrains[terrainnum].IsLiquid;

	const bool dealDamageOnLand = thing->player
		&& Terrains[terrainnum].DamageOnLand
		&& Terrains[terrainnum].DamageAmount
		&& (thing->Level->time & Terrains[terrainnum].DamageTimeMask);
	if (dealDamageOnLand)
		P_DamageMobj(thing, nullptr, nullptr, Terrains[terrainnum].DamageAmount, Terrains[terrainnum].DamageMOD);

	// don't splash when touching an underwater floor
	if (thing->waterlevel >= 1 && pos.Z <= thing->floorz) return Terrains[terrainnum].IsLiquid;

	plane = hsec != NULL? &sec->heightsec->floorplane : &sec->floorplane;

	// Don't splash for living things with small vertical velocities.
	// There are levels where the constant splashing from the monsters gets extremely annoying
	if (!(flags & THW_NOVEL) && ((thing->flags3 & MF3_ISMONSTER || thing->player) && thing->Vel.Z >= -6) && !force)
		return Terrains[terrainnum].IsLiquid;

	splash = &Splashes[splashnum];

	// Small splash for small masses
	if (flags & THW_SMALL || thing->Mass < 10)
		smallsplash = true;

	if (!(thing->flags3 & MF3_DONTSPLASH))
	{
		if (smallsplash && splash->SmallSplash)
		{
			mo = Spawn(sec->Level, splash->SmallSplash, pos, ALLOW_REPLACE);
			mo->target = thing;
			if (mo) mo->Floorclip += splash->SmallSplashClip;
		}
		else
		{
			if (splash->SplashChunk)
			{
				mo = Spawn(sec->Level, splash->SplashChunk, pos, ALLOW_REPLACE);
				mo->target = thing;
				if (splash->ChunkXVelShift != 255)
				{
					mo->Vel.X = (pr_chunk.Random2() << splash->ChunkXVelShift) / 65536.;
				}
				if (splash->ChunkYVelShift != 255)
				{
					mo->Vel.Y = (pr_chunk.Random2() << splash->ChunkYVelShift) / 65536.;
				}
				mo->Vel.Z = splash->ChunkBaseZVel + (pr_chunk() << splash->ChunkZVelShift) / 65536.;
			}
			if (splash->SplashBase)
			{
				mo = Spawn(sec->Level, splash->SplashBase, pos, ALLOW_REPLACE);
				mo->target = thing;
			}
			if (thing->player && !splash->NoAlert && alert)
			{
				P_NoiseAlert(thing, thing, true);
			}
		}
		if (mo)
		{
			S_Sound(mo, CHAN_ITEM, 0, smallsplash ?
				splash->SmallSplashSound : splash->NormalSplashSound,
				1, ATTN_IDLE);
		}
		else
		{
			S_Sound(thing->Level, pos, CHAN_ITEM, 0, smallsplash ?
				splash->SmallSplashSound : splash->NormalSplashSound,
				1, ATTN_IDLE);
		}
	}

	// Don't let deep water eat missiles
	return plane == &sec->floorplane ? Terrains[terrainnum].IsLiquid : false;
}

DEFINE_ACTION_FUNCTION(AActor, HitWater)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_POINTER_NOT_NULL(sec, sector_t);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_BOOL(checkabove);
	PARAM_BOOL(alert);
	PARAM_BOOL(force);
	PARAM_INT(flags);
	ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force, flags));
}


//---------------------------------------------------------------------------
//
// FUNC P_HitFloor
//
// Returns true if hit liquid and splashed, false if not.
//---------------------------------------------------------------------------

bool P_HitFloor (AActor *thing)
{
	const msecnode_t *m;

	// killough 11/98: touchy objects explode on impact
	// Allow very short drops to be safe, so that a touchy can be summoned without exploding.
	if (thing->flags6 & MF6_TOUCHY && ((thing->flags6 & MF6_ARMED) || thing->IsSentient()) && thing->Vel.Z < -5)
	{
		thing->flags6 &= ~MF6_ARMED; // Disarm
		P_DamageMobj (thing, NULL, NULL, thing->health, NAME_Crush, DMG_FORCED);  // kill object
		return false;
	}

	// don't splash if landing on the edge above water/lava/etc....
	DVector3 pos;
	for (m = thing->touching_sectorlist; m; m = m->m_tnext)
	{
		pos = thing->PosRelative(m->m_sector);
		if (thing->Z() == m->m_sector->floorplane.ZatPoint(pos))
		{
			break;
		}

		// Check 3D floors
		for(unsigned int i=0;i<m->m_sector->e->XFloor.ffloors.Size();i++)
		{		
			F3DFloor * rover = m->m_sector->e->XFloor.ffloors[i];
			if (!(rover->flags & FF_EXISTS)) continue;
			if (rover->flags & (FF_SOLID|FF_SWIMMABLE))
			{
				if (rover->top.plane->ZatPoint(pos) == thing->Z())
				{
					return P_HitWater (thing, m->m_sector, pos);
				}
			}
		}
	}
	if (m == NULL || m->m_sector->GetHeightSec() != NULL)
	{ 
		return false;
	}

	return P_HitWater (thing, m->m_sector, pos);
}

DEFINE_ACTION_FUNCTION(AActor, HitFloor)
{
	PARAM_SELF_PROLOGUE(AActor);
	ACTION_RETURN_BOOL(P_HitFloor(self));
}

//---------------------------------------------------------------------------
//
// FUNC P_CheckMissileSpawn
//
// Returns true if the missile is at a valid spawn point, otherwise
// explodes it and returns false.
//
//---------------------------------------------------------------------------

bool P_CheckMissileSpawn (AActor* th, double maxdist)
{
	// [RH] Don't decrement tics if they are already less than 1
	if ((th->flags4 & MF4_RANDOMIZE) && th->tics > 0)
	{
		th->tics -= pr_checkmissilespawn() & 3;
		if (th->tics < 1)
			th->tics = 1;
	}

	DVector3 newpos = { 0,0,0 };

	if (maxdist > 0)
	{
		// move a little forward so an angle can be computed if it immediately explodes
		DVector3 advance = th->Vel;
		double maxsquared = maxdist*maxdist;

		// Keep halving the advance vector until we get something less than maxdist
		// units away, since we still want to spawn the missile inside the shooter.
		do
		{
			advance *= 0.5f;
		}
		while (advance.XY().LengthSquared() >= maxsquared);
		newpos += advance;
	}

	newpos = th->Vec3Offset(newpos);
	th->SetXYZ(newpos);
	th->Sector = th->Level->PointInSector(th->Pos());

	FCheckPosition tm(!!(th->flags2 & MF2_RIP));

	// killough 8/12/98: for non-missile objects (e.g. grenades)
	// 
	// [GZ] MBF excludes non-missile objects from the P_TryMove test
	// and subsequent potential P_ExplodeMissile call. That is because
	// in MBF, a projectile is not an actor with the MF_MISSILE flag
	// but an actor with either or both the MF_MISSILE and MF_BOUNCES
	// flags, and a grenade is identified by not having MF_MISSILE.
	// Killough wanted grenades not to explode directly when spawned,
	// therefore they can be fired safely even when humping a wall as
	// they will then just drop on the floor at their shooter's feet.
	//
	// However, ZDoom does allow non-missiles to be shot as well, so
	// Killough's check for non-missiles is inadequate here. So let's
	// replace it by a check for non-missile and MBF bounce type.
	// This should allow MBF behavior where relevant without altering
	// established ZDoom behavior for crazy stuff like a cacodemon cannon.
	bool MBFGrenade = (!(th->flags & MF_MISSILE) || (th->BounceFlags & BOUNCE_MBF));

	// killough 3/15/98: no dropoff (really = don't care for missiles)
	auto oldf2 = th->flags2;
	th->flags2 &= ~(MF2_MCROSS|MF2_PCROSS);	// The following check is not supposed to activate missile triggers.
	if (!(P_TryMove (th, newpos.XY(), false, NULL, tm, true)))
	{
		// [RH] Don't explode ripping missiles that spawn inside something
		if (th->BlockingMobj == NULL || !(th->flags2 & MF2_RIP) || (th->BlockingMobj->flags5 & MF5_DONTRIP))
		{
			// If this is a monster spawned by A_SpawnProjectile subtract it from the counter.
			th->ClearCounters();
			// [RH] Don't explode missiles that spawn on top of horizon lines
			if (th->BlockingLine != NULL && th->BlockingLine->special == Line_Horizon)
			{
				th->Destroy ();
			}
			else if (MBFGrenade && th->BlockingLine != NULL)
			{
				P_BounceWall(th);
			}
			else
			{
				P_ExplodeMissile (th, th->BlockingLine, th->BlockingMobj);
			}
			return false;
		}
	}
	th->flags2 = oldf2;
	th->ClearInterpolation();
	return true;
}

DEFINE_ACTION_FUNCTION(AActor, CheckMissileSpawn)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(add);
	ACTION_RETURN_BOOL(P_CheckMissileSpawn(self, add));
}


//---------------------------------------------------------------------------
//
// FUNC P_PlaySpawnSound
//
// Plays a missiles spawn sound. Location depends on the
// MF_SPAWNSOUNDSOURCE flag.
//
//---------------------------------------------------------------------------

void P_PlaySpawnSound(AActor *missile, AActor *spawner)
{
	if (missile->SeeSound != NO_SOUND)
	{
		if (!(missile->flags & MF_SPAWNSOUNDSOURCE))
		{
			S_Sound (missile, CHAN_VOICE, 0, missile->SeeSound, 1, ATTN_NORM);
		}
		else if (spawner != NULL)
		{
			S_Sound (spawner, CHAN_WEAPON, 0, missile->SeeSound, 1, ATTN_NORM);
		}
		else
		{
			// If there is no spawner use the spawn position.
			// But not in a silenced sector.
			if (!(missile->Sector->Flags & SECF_SILENT))
				S_Sound (missile->Level, missile->Pos(), CHAN_WEAPON, 0, missile->SeeSound, 1, ATTN_NORM);
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, PlaySpawnSound)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(missile, AActor);
	P_PlaySpawnSound(missile, self);
	return 0;
}



double GetDefaultSpeed(PClassActor *type)
{
	if (type == NULL)
		return 0;

	auto def = GetDefaultByType(type);
	if (G_SkillProperty(SKILLP_FastMonsters))
	{
		double f = def->FloatVar(NAME_FastSpeed);
		if (f >= 0) return f;
	}
	return def->Speed;
}

//---------------------------------------------------------------------------
//
// FUNC P_SpawnMissile
//
// Returns NULL if the missile exploded immediately, otherwise returns
// a mobj_t pointer to the missile.
//
//---------------------------------------------------------------------------

AActor *P_SpawnMissileXYZ (DVector3 pos, AActor *source, AActor *dest, PClassActor *type, bool checkspawn, AActor *owner)
{
	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}

	if (dest == NULL)
	{
		Printf ("P_SpawnMissileXYZ: Tried to shoot %s from %s with no destination\n",
			type->TypeName.GetChars(), source->GetClass()->TypeName.GetChars());
		return NULL;
	}

	if (pos.Z != ONFLOORZ && pos.Z != ONCEILINGZ)
	{
		pos.Z -= source->Floorclip;
	}

	AActor *th = Spawn (source->Level, type, pos, ALLOW_REPLACE);
	
	P_PlaySpawnSound(th, source);

	// record missile's originator
	if (owner == NULL) owner = source;
	th->target = owner;

	double speed = th->Speed;

	// [RH]
	// Hexen calculates the missile velocity based on the source's location.
	// Would it be more useful to base it on the actual position of the
	// missile?
	// Answer: No, because this way, you can set up sets of parallel missiles.

	DVector3 velocity = source->Vec3To(dest);
	// Floor and ceiling huggers should never have a vertical component to their velocity
	if (th->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
	{
		velocity.Z = 0;
	}
	// [RH] Adjust the trajectory if the missile will go over the target's head.
	else if (pos.Z - source->Z() >= dest->Height)
	{
		velocity.Z += (dest->Height - pos.Z + source->Z());
	}
	th->Vel = velocity.Resized(speed);

	P_SpawnMissileXYZ_ShadowHandling(source,dest,th,pos);
	
	th->AngleFromVel();

	if (th->flags4 & MF4_SPECTRAL)
	{
		th->SetFriendPlayer(owner->player);
	}

	return (!checkspawn || P_CheckMissileSpawn (th, source->radius)) ? th : NULL;
}

DEFINE_ACTION_FUNCTION(AActor, SpawnMissileXYZ)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_OBJECT_NOT_NULL(dest, AActor);
	PARAM_CLASS(type, AActor);
	PARAM_BOOL(check);
	PARAM_OBJECT(owner, AActor);
	ACTION_RETURN_OBJECT(P_SpawnMissileXYZ(DVector3(x,y,z), self, dest, type, check, owner));
}

AActor *P_SpawnMissile(AActor *source, AActor *dest, PClassActor *type, AActor *owner)
{
	if (source == nullptr)
	{
		return nullptr;
	}
	return P_SpawnMissileXYZ(source->PosPlusZ(32 + source->GetBobOffset()), source, dest, type, true, owner);
}

DEFINE_ACTION_FUNCTION(AActor, SpawnMissile)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(dest, AActor);
	PARAM_CLASS(type, AActor);
	PARAM_OBJECT(owner, AActor);
	ACTION_RETURN_OBJECT(P_SpawnMissile(self, dest, type, owner));
}

AActor *P_SpawnMissileZ(AActor *source, double z, AActor *dest, PClassActor *type)
{
	if (source == nullptr)
	{
		return nullptr;
	}
	return P_SpawnMissileXYZ(source->PosAtZ(z), source, dest, type);
}

DEFINE_ACTION_FUNCTION(AActor, SpawnMissileZ)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(z);
	PARAM_OBJECT_NOT_NULL(dest, AActor);
	PARAM_CLASS(type, AActor);
	ACTION_RETURN_OBJECT(P_SpawnMissileZ(self, z, dest, type));
}



AActor *P_OldSpawnMissile(AActor *source, AActor *owner, AActor *dest, PClassActor *type)
{
	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}
	AActor *th = Spawn (source->Level, type, source->PosPlusZ(32.), ALLOW_REPLACE);

	P_PlaySpawnSound(th, source);
	th->target = owner;		// record missile's originator

	th->Angles.Yaw = source->AngleTo(dest);
	th->VelFromAngle();


	double dist = source->DistanceBySpeed(dest, max(1., th->Speed));
	th->Vel.Z = (dest->Z() - source->Z()) / dist;

	if (th->flags4 & MF4_SPECTRAL)
	{
		th->SetFriendPlayer(owner->player);
	}

	P_CheckMissileSpawn(th, source->radius);
	return th;
}

DEFINE_ACTION_FUNCTION(AActor, OldSpawnMissile)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(dest, AActor);
	PARAM_CLASS(type, AActor);
	PARAM_OBJECT(owner, AActor);
	ACTION_RETURN_OBJECT(P_OldSpawnMissile(self, owner, dest, type));
}


//---------------------------------------------------------------------------
//
// FUNC P_SpawnMissileAngle
//
// Returns NULL if the missile exploded immediately, otherwise returns
// a mobj_t pointer to the missile.
//
//---------------------------------------------------------------------------

AActor *P_SpawnMissileZAimed (AActor *source, double z, AActor *dest, PClassActor *type)
{
	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}
	DAngle an;
	double dist;
	double speed;
	double vz;

	an = source->Angles.Yaw;

	dist = source->Distance2D (dest);
	speed = GetDefaultSpeed (type);
	dist /= speed;
	vz = dist != 0 ? (dest->Z() - source->Z())/dist : speed;
	an += P_SpawnMissileZAimed_ShadowHandling(source, dest, vz, speed, source->PosAtZ(z));
	return P_SpawnMissileAngleZSpeed (source, z, type, an, vz, speed);
}

DEFINE_ACTION_FUNCTION(AActor, SpawnMissileZAimed)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(z);
	PARAM_OBJECT_NOT_NULL(dest, AActor);
	PARAM_CLASS(type, AActor);
	ACTION_RETURN_OBJECT(P_SpawnMissileZAimed(self, z, dest, type));
}

//---------------------------------------------------------------------------
//
// FUNC P_SpawnMissileAngleZSpeed
//
// Returns NULL if the missile exploded immediately, otherwise returns
// a mobj_t pointer to the missile.
//
//---------------------------------------------------------------------------

AActor *P_SpawnMissileAngleZSpeed (AActor *source, double z,
	PClassActor *type, DAngle angle, double vz, double speed, AActor *owner, bool checkspawn)
{
	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}
	AActor *mo;

	if (z != ONFLOORZ && z != ONCEILINGZ) 
	{
		z -= source->Floorclip;
	}

	mo = Spawn (source->Level, type, source->PosAtZ(z), ALLOW_REPLACE);

	P_PlaySpawnSound(mo, source);
	if (owner == NULL) owner = source;
	mo->target = owner;
	mo->Angles.Yaw = angle;
	mo->VelFromAngle(speed);
	mo->Vel.Z = vz;

	if (mo->flags4 & MF4_SPECTRAL)
	{
		mo->SetFriendPlayer(owner->player);
	}

	return (!checkspawn || P_CheckMissileSpawn(mo, source->radius)) ? mo : NULL;
}

DEFINE_ACTION_FUNCTION(AActor, SpawnMissileAngleZSpeed)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(z);
	PARAM_CLASS(type, AActor);
	PARAM_ANGLE(angle);
	PARAM_FLOAT(vz);
	PARAM_FLOAT(speed);
	PARAM_OBJECT(owner, AActor);
	PARAM_BOOL(checkspawn);
	ACTION_RETURN_OBJECT(P_SpawnMissileAngleZSpeed(self, z, type, angle, vz, speed, owner, checkspawn));
}


AActor *P_SpawnSubMissile(AActor *source, PClassActor *type, AActor *target)
{
	AActor *other = Spawn(source->Level, type, source->Pos(), ALLOW_REPLACE);

	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}

	other->target = target;
	other->Angles.Yaw = source->Angles.Yaw;
	other->VelFromAngle();

	if (other->flags4 & MF4_SPECTRAL)
	{
		if (source->flags & MF_MISSILE && source->flags4 & MF4_SPECTRAL)
		{
			other->FriendPlayer = source->FriendPlayer;
		}
		else
		{
			other->SetFriendPlayer(target->player);
		}
	}

	if (P_CheckMissileSpawn(other, source->radius))
	{
		DAngle pitch = P_AimLineAttack(source, source->Angles.Yaw, 1024.);
		other->Vel.Z = -other->Speed * pitch.Sin();
		return other;
	}
	return NULL;
}

DEFINE_ACTION_FUNCTION(AActor, SpawnSubMissile)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_CLASS(cls, AActor);
	PARAM_OBJECT_NOT_NULL(target, AActor);
	ACTION_RETURN_OBJECT(P_SpawnSubMissile(self, cls, target));
}
/*
================
=
= P_SpawnPlayerMissile
=
= Tries to aim at a nearby monster
================
*/

AActor *P_SpawnPlayerMissile (AActor *source, double x, double y, double z,
							  PClassActor *type, DAngle angle, FTranslatedLineTarget *pLineTarget, AActor **pMissileActor,
							  bool nofreeaim, bool noautoaim, int aimflags)
{
	if (source == nullptr || type == nullptr)
	{
		return nullptr;
	}
	aimflags &= ~ALF_IGNORENOAUTOAIM; // just to be safe.

	static const double angdiff[3] = { -5.625, 5.625, 0 };
	DAngle an = angle;
	DAngle pitch;
	FTranslatedLineTarget scratch;
	AActor *defaultobject = GetDefaultByType(type);
	DAngle vrange = DAngle::fromDeg(nofreeaim ? 35. : 0.);

	if (!pLineTarget) pLineTarget = &scratch;
	if (!(aimflags & ALF_NOWEAPONCHECK) && source->player && source->player->ReadyWeapon && ((source->player->ReadyWeapon->IntVar(NAME_WeaponFlags) & WIF_NOAUTOAIM) || noautoaim))
	{
		// Keep exactly the same angle and pitch as the player's own aim
		an = angle;
		pitch = source->Angles.Pitch;
		pLineTarget->linetarget = NULL;
	}
	else // see which target is to be aimed at
	{
		// [XA] If MaxTargetRange is defined in the spawned projectile, use this as the
		//      maximum range for the P_AimLineAttack call later; this allows MaxTargetRange
		//      to function as a "maximum tracer-acquisition range" for seeker missiles.
		double linetargetrange = defaultobject->maxtargetrange > 0 ? defaultobject->maxtargetrange*64 : 16*64.;

		int i = 2;
		do
		{
			an = angle + DAngle::fromDeg(angdiff[i]);
			pitch = P_AimLineAttack (source, an, linetargetrange, pLineTarget, vrange, aimflags);
	
			if (source->player != NULL &&
				!nofreeaim &&
				source->Level->IsFreelookAllowed() &&
				source->player->userinfo.GetAimDist() <= 0.5)
			{
				break;
			}
		} while (pLineTarget->linetarget == NULL && --i >= 0);

		if (pLineTarget->linetarget == NULL)
		{
			an = angle;
			if (nofreeaim || !source->Level->IsFreelookAllowed())
			{
				pitch = nullAngle;
			}
		}
	}

	if (z != ONFLOORZ && z != ONCEILINGZ)
	{
		// Doom spawns missiles 4 units lower than hitscan attacks for players.
		z += source->Center() - source->Floorclip + source->AttackOffset(-4);
		// Do not fire beneath the floor.
		if (z < source->floorz)
		{
			z = source->floorz;
		}
	}
	DVector3 pos = source->Vec2OffsetZ(x, y, z);
	AActor *MissileActor = Spawn (source->Level, type, pos, ALLOW_REPLACE);
	if (pMissileActor) *pMissileActor = MissileActor;
	P_PlaySpawnSound(MissileActor, source);
	MissileActor->target = source;
	MissileActor->Angles.Yaw = an;
	if (MissileActor->flags3 & (MF3_FLOORHUGGER | MF3_CEILINGHUGGER))
	{
		MissileActor->VelFromAngle();
	}
	else
	{
		MissileActor->Vel3DFromAngle(pitch, MissileActor->Speed);
	}

	if (MissileActor->flags4 & MF4_SPECTRAL)
	{
		MissileActor->SetFriendPlayer(source->player);
	}
	if (P_CheckMissileSpawn (MissileActor, source->radius))
	{
		return MissileActor;
	}
	return NULL;
}

DEFINE_ACTION_FUNCTION(AActor, SpawnPlayerMissile)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_CLASS(type, AActor);
	PARAM_ANGLE(angle);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	PARAM_OUTPOINTER(lt, FTranslatedLineTarget);
	PARAM_BOOL(nofreeaim);
	PARAM_BOOL(noautoaim);
	PARAM_INT(aimflags);
	AActor *missileactor;
	if (angle == DAngle::fromDeg(1e37)) angle = self->Angles.Yaw;
	AActor *misl = P_SpawnPlayerMissile(self, x, y, z, type, angle, lt, &missileactor, nofreeaim, noautoaim, aimflags);
	if (numret > 0) ret[0].SetObject(misl);
	if (numret > 1) ret[1].SetObject(missileactor), numret = 2;
	return numret;
}


int AActor::GetTeam()
{
	if (player)
	{
		return player->userinfo.GetTeam();
	}

	int myTeam = DesignatedTeam;

	// Check for monsters that belong to a player on the team but aren't part of the team themselves.
	if (myTeam == TEAM_NONE && FriendPlayer != 0)
	{
		myTeam = Level->Players[FriendPlayer - 1]->userinfo.GetTeam();
	}
	return myTeam;

}

bool AActor::IsTeammate (AActor *other)
{
	if (!other)
	{
		return false;
	}
	else if (!deathmatch && player && other->player)
	{
		return (!((flags ^ other->flags) & MF_FRIENDLY));
	}
	else if (teamplay)
	{
		int myTeam = GetTeam();
		int otherTeam = other->GetTeam();

		return (myTeam != TEAM_NONE && myTeam == otherTeam);
	}
	return false;
}

//==========================================================================
//
// AActor :: GetSpecies
//
// Species is defined as the lowest base class that is a monster
// with no non-monster class in between. If the actor specifies an explicit
// species (i.e. not 'None'), that is used. This is virtualized, so special
// monsters can change this behavior if they like.
//
//==========================================================================

FName AActor::GetSpecies()
{
	if (Species != NAME_None)
	{
		return Species;
	}

	PClassActor *thistype = GetClass();

	if (GetDefaultByType(thistype)->flags3 & MF3_ISMONSTER)
	{
		while (thistype->ParentClass)
		{
			if (GetDefaultByType(thistype->ParentClass)->flags3 & MF3_ISMONSTER)
				thistype = static_cast<PClassActor *>(thistype->ParentClass);
			else 
				break;
		}
	}
	return Species = thistype->TypeName; // [GZ] Speeds up future calls.
}

//==========================================================================
//
// AActor :: IsFriend
//
// Checks if two monsters have to be considered friendly.
//
//==========================================================================

bool AActor::IsFriend (AActor *other)
{
	if (flags & other->flags & MF_FRIENDLY)
	{
		if (deathmatch && teamplay)
			return IsTeammate(other) ||
				(FriendPlayer != 0 && other->FriendPlayer != 0 &&
					Level->Players[FriendPlayer-1]->mo->IsTeammate(Level->Players[other->FriendPlayer-1]->mo));

		return !deathmatch ||
			FriendPlayer == other->FriendPlayer ||
			FriendPlayer == 0 ||
			other->FriendPlayer == 0 ||
			Level->Players[FriendPlayer-1]->mo->IsTeammate(Level->Players[other->FriendPlayer-1]->mo);
	}
	// [SP] If friendly flags match, then they are on the same team.
	/*if (!((flags ^ other->flags) & MF_FRIENDLY))
		return true;*/
	return false;
}

//==========================================================================
//
// AActor :: IsHostile
//
// Checks if two monsters have to be considered hostile under any circumstances
//
//==========================================================================

bool AActor::IsHostile (AActor *other)
{
	// Both monsters are non-friendlies so hostilities depend on infighting settings
	if (!((flags | other->flags) & MF_FRIENDLY)) return false;

	// Both monsters are friendly and belong to the same player if applicable.
	if (flags & other->flags & MF_FRIENDLY)
	{
		if (deathmatch && teamplay)
			return !IsTeammate(other) &&
				!(FriendPlayer != 0 && other->FriendPlayer != 0 &&
					Level->Players[FriendPlayer-1]->mo->IsTeammate(Level->Players[other->FriendPlayer-1]->mo));

		return deathmatch &&
			FriendPlayer != other->FriendPlayer &&
			FriendPlayer !=0 &&
			other->FriendPlayer != 0 &&
			!Level->Players[FriendPlayer-1]->mo->IsTeammate(Level->Players[other->FriendPlayer-1]->mo);
	}
	return true;
}

//==========================================================================
//
// AActor :: DoSpecialDamage
//
// override this for special damage effects.
//
//==========================================================================

int AActor::DoSpecialDamage (AActor *target, int damage, FName damagetype)
{
	if (target->player && target->player->mo == target && damage < 1000 &&
		(target->player->cheats & CF_GODMODE || target->player->cheats & CF_GODMODE2))
	{
		return -1;
	}
	else
	{
		if (target->player)
		{
			// Only do this for old style poison damage.
			if (PoisonDamage > 0 && PoisonDuration == INT_MIN)
			{
				P_PoisonPlayer (target->player, this, this->target, PoisonDamage);
				damage >>= 1;
			}
		}
	
		return damage;
	}
}

DEFINE_ACTION_FUNCTION(AActor, DoSpecialDamage)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT_NOT_NULL(target, AActor);
	PARAM_INT(damage);
	PARAM_NAME(damagetype);
	ACTION_RETURN_INT(self->DoSpecialDamage(target, damage, damagetype));
}

int AActor::CallDoSpecialDamage(AActor *target, int damage, FName damagetype)
{
	IFVIRTUAL(AActor, DoSpecialDamage)
	{
		// Without the type cast this picks the 'void *' assignment...
		VMValue params[4] = { (DObject*)this, (DObject*)target, damage, damagetype.GetIndex() };
		VMReturn ret;
		int retval;
		ret.IntAt(&retval);
		VMCall(func, params, 4, &ret, 1);
		return retval;
	}
	else return DoSpecialDamage(target, damage, damagetype);

}

//==========================================================================
//
// AActor :: TakeSpecialDamage
//
//==========================================================================

int AActor::TakeSpecialDamage (AActor *inflictor, AActor *source, int damage, FName damagetype)
{
	FState *death;

	// If the actor does not have a corresponding death state, then it does not take damage.
	// Note that DeathState matches every kind of damagetype, so an actor has that, it can
	// be hurt with any type of damage. Exception: Massacre damage always succeeds, because
	// it needs to work.

	// Always kill if there is a regular death state or no death states at all.
	if (FindState (NAME_Death) != NULL || !HasSpecialDeathStates() || damagetype == NAME_Massacre)
	{
		return damage;
	}
	
	if (inflictor && inflictor->DeathType != NAME_None)
		damagetype = inflictor->DeathType;

	if (damagetype == NAME_Ice)
	{
		death = FindState (NAME_Death, NAME_Ice, true);
		if (death == NULL && !deh.NoAutofreeze && !(flags4 & MF4_NOICEDEATH) &&
			(player || (flags3 & MF3_ISMONSTER)))
		{
			death = FindState(NAME_GenericFreezeDeath);
		}
	}
	else
	{
		death = FindState (NAME_Death, damagetype);
	}
	return (death == NULL) ? -1 : damage;
}

DEFINE_ACTION_FUNCTION(AActor, TakeSpecialDamage)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_OBJECT(inflictor, AActor);
	PARAM_OBJECT(source, AActor);
	PARAM_INT(damage);
	PARAM_NAME(damagetype);
	ACTION_RETURN_INT(self->TakeSpecialDamage(inflictor, source, damage, damagetype));
}


int AActor::CallTakeSpecialDamage(AActor *inflictor, AActor *source, int damage, FName damagetype)
{
	IFVIRTUAL(AActor, TakeSpecialDamage)
	{
		VMValue params[5] = { (DObject*)this, inflictor, source, damage, damagetype.GetIndex() };
		VMReturn ret;
		int retval;
		ret.IntAt(&retval);
		VMCall(func, params, 5, &ret, 1);
		return retval;
	}
	else return TakeSpecialDamage(inflictor, source, damage, damagetype);

}

void AActor::Crash()
{
	// [RC] Weird that this forces the Crash state regardless of flag.
	if(!(flags6 & MF6_DONTCORPSE))
	{
	if (((flags & MF_CORPSE) || (flags6 & MF6_KILLED)) &&
		!(flags3 & MF3_CRASHED) &&
		!(flags & MF_ICECORPSE))
	{
		FState *crashstate = NULL;
		
		int gibh = GetGibHealth();
		if (DamageType != NAME_None)
		{
			if (health < gibh)
			{ // Extreme death
				FName labels[] = { NAME_Crash, NAME_Extreme, DamageType };
				crashstate = FindState (3, labels, true);
			}
			if (crashstate == NULL)
			{ // Normal death
				crashstate = FindState(NAME_Crash, DamageType, true);
			}
		}
		if (crashstate == NULL)
		{
			if (health < gibh)
			{ // Extreme death
				crashstate = FindState(NAME_Crash, NAME_Extreme);
			}
			else
			{ // Normal death
				crashstate = FindState(NAME_Crash);
			}
		}
		if (crashstate != NULL) SetState(crashstate);
		// Set MF3_CRASHED regardless of the presence of a crash state
		// so this code doesn't have to be executed repeatedly.
		flags3 |= MF3_CRASHED;
	}
	}
}

void AActor::SetIdle(bool nofunction)
{
	FState *idle = FindState (NAME_Idle);
	if (idle == NULL) idle = SpawnState;
	SetState(idle, nofunction);
}


int AActor::SpawnHealth() const
{
	int defhealth = StartHealth ? StartHealth : GetDefault()->health;
	if (!(flags3 & MF3_ISMONSTER) || defhealth == 0)
	{
		return defhealth;
	}
	else if (flags & MF_FRIENDLY)
	{
		int adj = int(defhealth * G_SkillProperty(SKILLP_FriendlyHealth));
		return (adj <= 0) ? 1 : adj;
	}
	else
	{
		int adj = int(defhealth * G_SkillProperty(SKILLP_MonsterHealth));
		return (adj <= 0) ? 1 : adj;
	}
}

int AActor::GetMaxHealth(bool withupgrades) const
{
	int ret = 100;
	IFVIRTUAL(AActor, GetMaxHealth)
	{
		VMValue param[] = { const_cast<AActor*>(this), withupgrades };
		VMReturn r(&ret);
		VMCall(func, param, 2, &r, 1);
	}
	return ret;
}

FState *AActor::GetRaiseState()
{
	if (!(flags & MF_CORPSE))
	{
		return NULL;	// not a monster
	}

	if (tics != -1 && // not lying still yet
		!state->GetCanRaise()) // or not ready to be raised yet
	{
		return NULL;
	}

	if (IsKindOf(NAME_PlayerPawn))
	{
		return NULL;	// do not resurrect players
	}

	return FindState(NAME_Raise);
}

void AActor::Revive()
{
	AActor *info = GetDefault();
	FLinkContext ctx;

	bool flagchange = (flags & (MF_NOBLOCKMAP | MF_NOSECTOR)) != (info->flags & (MF_NOBLOCKMAP | MF_NOSECTOR));

	if (flagchange) UnlinkFromWorld(&ctx);
	flags = info->flags;
	if (flagchange) LinkToWorld(&ctx);
	flags2 = info->flags2;
	flags3 = info->flags3;
	flags4 = info->flags4;
	flags5 = info->flags5;
	flags6 = info->flags6;
	flags7 = info->flags7;
	flags8 = info->flags8; 
	if (SpawnFlags & MTF_FRIENDLY) flags |= MF_FRIENDLY;
	DamageType = info->DamageType;
	health = SpawnHealth();
	target = nullptr;
	lastenemy = nullptr;

	// Make sure to clear poison damage.
	PoisonDamageReceived = 0;
	PoisonDamageTypeReceived = NAME_None;
	PoisonDurationReceived = 0;
	PoisonPeriodReceived = 0;
	Poisoner = nullptr;
	if (player != nullptr)
	{
		player->poisoncount = 0;
		player->poisoner = nullptr;
		player->poisontype = player->poisonpaintype = NAME_None;
	}

	// [RH] If it's a monster, it gets to count as another kill
	if (CountsAsKill())
	{
		Level->total_monsters++;
	}

	// [ZZ] resurrect hook
	Level->localEventManager->WorldThingRevived(this);
}

int AActor::GetGibHealth() const
{
	IFVIRTUAL(AActor, GetGibHealth)
	{
		VMValue params[] = { (DObject*)this };
		int h;
		VMReturn ret(&h);
		VMCall(func, params, 1, &ret, 1);
		return h;
	}
	return -SpawnHealth();
}


// killough 11/98:
// Whether an object is "sentient" or not. Used for environmental influences.
// (left precisely the same as MBF even though it doesn't make much sense.)
bool AActor::IsSentient() const
{
	return health > 0 && SeeState != NULL;
}


FSharedStringArena AActor::mStringPropertyData;

const char *AActor::GetTag(const char *def) const
{
	if (Tag != NULL)
	{
		const char *tag = Tag->GetChars();
		if (tag[0] == '$')
		{
			return GStrings.GetString(tag + 1);
		}
		else
		{
			return tag;
		}
	}
	else if (def)
	{
		return def;
	}
	else
	{
		return GetClass()->TypeName.GetChars();
	}
}

void AActor::SetTag(const char *def)
{
	if (def == NULL || *def == 0) Tag = nullptr;
	else Tag = mStringPropertyData.Alloc(def);
}

const char *AActor::GetCharacterName() const
{
	if (Conversation && Conversation->SpeakerName.Len() != 0)
	{
		const char *cname = Conversation->SpeakerName.GetChars();
		if (cname[0] == '$')
		{
			return GStrings.GetString(cname + 1);
		}
		else return cname;
	}
	return GetTag();
}

void AActor::ClearCounters()
{
	if (CountsAsKill() && health > 0)
	{
		Level->total_monsters--;
		flags &= ~MF_COUNTKILL;
	}
	// Same, for items
	if (flags & MF_COUNTITEM)
	{
		Level->total_items--;
		flags &= ~MF_COUNTITEM;
	}
	// And finally for secrets
	if (flags5 & MF5_COUNTSECRET)
	{
		Level->total_secrets--;
		flags5 &= ~MF5_COUNTSECRET;
	}
}

int AActor::GetModifiedDamage(FName damagetype, int damage, bool passive, AActor *inflictor, AActor *source, int flags)
{
	auto inv = Inventory;
	while (inv != nullptr && !(inv->ObjectFlags & OF_EuthanizeMe))
	{
		auto nextinv = inv->Inventory;
		IFVIRTUALPTRNAME(inv, NAME_Inventory, ModifyDamage)
		{
			VMValue params[8] = { (DObject*)inv, damage, damagetype.GetIndex(), &damage, passive, inflictor, source, flags };
			VMCall(func, params, 8, nullptr, 0);
		}
		inv = nextinv;
	}
	return damage;
}


int AActor::ApplyDamageFactor(FName damagetype, int damage) const
{
	damage = int(damage * DamageFactor);
	if (damage > 0)
	{
		damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, damagetype, &GetInfo()->DamageFactors);
	}
	return damage;
}

void AActor::SetTranslation(FName trname)
{
	// There is no constant for the empty name...
	if (trname.GetChars()[0] == 0)
	{
		// an empty string resets to the default
		Translation = GetDefault()->Translation;
		return;
	}

	auto tnum = R_FindCustomTranslation(trname);
	if (tnum != INVALID_TRANSLATION)
	{
		Translation = tnum;
	}
	// silently ignore if the name does not exist, this would create some insane message spam otherwise.
}

//---------------------------------------------------------------------------
//
// PROP A_RestoreSpecialPosition
//
//---------------------------------------------------------------------------
static FRandom pr_restore("RestorePos");

void AActor::RestoreSpecialPosition()
{
	// Move item back to its original location
	DVector2 sp = SpawnPoint.XY();

	FLinkContext ctx;
	UnlinkFromWorld(&ctx);
	SetXY(sp);
	LinkToWorld(&ctx, true);
	SetZ(Sector->floorplane.ZatPoint(sp));
	P_FindFloorCeiling(this, FFCF_ONLYSPAWNPOS | FFCF_NOPORTALS);	// no portal checks here so that things get spawned in this sector.

	if (flags & MF_SPAWNCEILING)
	{
		SetZ(ceilingz - Height - SpawnPoint.Z);
	}
	else if (flags2 & MF2_SPAWNFLOAT)
	{
		double space = ceilingz - Height - floorz;
		if (space > 48)
		{
			space -= 40;
			SetZ((space * pr_restore()) / 256. + floorz + 40);
		}
		else
		{
			SetZ(floorz);
		}
	}
	else
	{
		SetZ(SpawnPoint.Z + floorz);
	}
	// Redo floor/ceiling check, in case of 3D floors and portals
	P_FindFloorCeiling(this, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
	if (Z() < floorz)
	{ // Do not reappear under the floor, even if that's where we were for the
	  // initial spawn.
		SetZ(floorz);
	}
	if ((flags & MF_SOLID) && (Top() > ceilingz))
	{ // Do the same for the ceiling.
		SetZ(ceilingz - Height);
	}
	// Do not interpolate from the position the actor was at when it was
	// picked up, in case that is different from where it is now.
	ClearInterpolation();
}

//----------------------------------------------------------------------------
//
// DropItem handling
//
//----------------------------------------------------------------------------

DEFINE_FIELD(FDropItem, Next)
DEFINE_FIELD(FDropItem, Name)
DEFINE_FIELD(FDropItem, Probability)
DEFINE_FIELD(FDropItem, Amount)

void PrintMiscActorInfo(AActor *query)
{
	if (query)
	{
		int flagi;
		int querystyle = STYLE_Count;
		for (int style = STYLE_None; style < STYLE_Count; ++style)
		{ // Check for a legacy render style that matches.
			if (LegacyRenderStyles[style] == query->RenderStyle)
			{
				querystyle = style;
				break;
			}
		}
		static const char * renderstyles[]= {"None", "Normal", "Fuzzy", "SoulTrans",
			"OptFuzzy", "Stencil", "Translucent", "Add", "Shaded", "TranslucentStencil",
			"Shadow", "Subtract", "AddStencil", "AddShaded"};

		FLineSpecial *spec = P_GetLineSpecialInfo(query->special);

		Printf("%s @ %p has the following flags:\n   flags: %x", query->GetTag(), query, query->flags.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags & ActorFlags::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags));
		Printf("\n   flags2: %x", query->flags2.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags2 & ActorFlags2::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags2));
		Printf("\n   flags3: %x", query->flags3.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags3 & ActorFlags3::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags3));
		Printf("\n   flags4: %x", query->flags4.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags4 & ActorFlags4::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags4));
		Printf("\n   flags5: %x", query->flags5.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags5 & ActorFlags5::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags5));
		Printf("\n   flags6: %x", query->flags6.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags6 & ActorFlags6::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags6));
		Printf("\n   flags7: %x", query->flags7.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags7 & ActorFlags7::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags7));
		Printf("\n   flags8: %x", query->flags8.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags8 & ActorFlags8::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags8));
		Printf("\n   flags9: %x", query->flags9.GetValue());
		for (flagi = 0; flagi <= 31; flagi++)
			if (query->flags9 & ActorFlags9::FromInt(1 << flagi)) Printf(" %s", FLAG_NAME(1 << flagi, flags9));
		Printf("\nBounce flags: %x\nBounce factors: f:%f, w:%f", 
			query->BounceFlags.GetValue(), query->bouncefactor,
			query->wallbouncefactor);
		/*for (flagi = 0; flagi < 31; flagi++)
			if (query->BounceFlags & 1<<flagi) Printf(" %s", flagnamesb[flagi]);*/
		Printf("\nRender style = %i:%s, alpha %f\nRender flags: %x", 
			querystyle, ((unsigned)querystyle < countof(renderstyles) ? renderstyles[querystyle] : "Custom"),
			query->Alpha, query->renderflags.GetValue());
		/*for (flagi = 0; flagi < 31; flagi++)
			if (query->renderflags & 1<<flagi) Printf(" %s", flagnamesr[flagi]);*/
		Printf("\nSpecial+args: %s(%i, %i, %i, %i, %i)\nspecial1: %i, special2: %i.",
			(spec ? spec->name : "None"),
			query->args[0], query->args[1], query->args[2], query->args[3], 
			query->args[4],	query->special1, query->special2);
		Printf("\nTID: %d", query->tid);
		Printf("\nCoord= x: %f, y: %f, z:%f, floor:%f, ceiling:%f, height= %f",
			query->X(), query->Y(), query->Z(),
			query->floorz, query->ceilingz, query->Height);
		Printf("\nSpeed= %f, velocity= x:%f, y:%f, z:%f, combined:%f.\n",
			query->Speed, query->Vel.X, query->Vel.Y, query->Vel.Z, query->Vel.Length());
		Printf("Scale: x:%f, y:%f\n", query->Scale.X, query->Scale.Y);
		Printf("FriendlySeeBlocks: %d\n", query->friendlyseeblocks);
		Printf("Target: %s\n", query->target ? query->target->GetClass()->TypeName.GetChars() : "-");
		Printf("Last enemy: %s\n", query->lastenemy ? query->lastenemy->GetClass()->TypeName.GetChars() : "-");
		auto sn = FState::StaticGetStateName(query->state);
		Printf("State:%s, Tics: %d\n", sn.GetChars(), query->tics);
	}
}
