
//**************************************************************************
//**
//** p_pspr.c : Heretic 2 : Raven Software, Corp.
//**
//** $RCSfile: p_pspr.c,v $
//** $Revision: 1.105 $
//** $Date: 96/01/06 03:23:35 $
//** $Author: bgokey $
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include <stdlib.h>

#include "doomdef.h"
#include "d_event.h"
#include "c_cvars.h"
#include "m_random.h"
#include "p_enemy.h"
#include "p_local.h"
#include "s_sound.h"
#include "doomstat.h"
#include "gi.h"
#include "p_pspr.h"
#include "templates.h"
#include "g_level.h"
#include "d_player.h"
#include "serializer.h"
#include "v_text.h"
#include "cmdlib.h"


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

#define LOWERSPEED				6.
#define RAISESPEED				6.

// TYPES -------------------------------------------------------------------

struct FGenericButtons
{
	int ReadyFlag;			// Flag passed to A_WeaponReady
	int StateFlag;			// Flag set in WeaponState
	int ButtonFlag;			// Button to press
	ENamedName StateName;	// Name of the button/state
};

enum EWRF_Options
{
	WRF_NoBob			= 1,
	WRF_NoSwitch		= 1 << 1,
	WRF_NoPrimary		= 1 << 2,
	WRF_NoSecondary		= 1 << 3,
	WRF_NoFire = WRF_NoPrimary | WRF_NoSecondary,
	WRF_AllowReload		= 1 << 4,
	WRF_AllowZoom		= 1 << 5,
	WRF_DisableSwitch	= 1 << 6,
	WRF_AllowUser1		= 1 << 7,
	WRF_AllowUser2		= 1 << 8,
	WRF_AllowUser3		= 1 << 9,
	WRF_AllowUser4		= 1 << 10,
};

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

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

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

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

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

// [SO] 1=Weapons states are all 1 tick
//		2=states with a function 1 tick, others 0 ticks.
CVAR(Int, sv_fastweapons, false, CVAR_SERVERINFO);

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

static FRandom pr_wpnreadysnd ("WpnReadySnd");

static const FGenericButtons ButtonChecks[] =
{
	{ WRF_AllowZoom,	WF_WEAPONZOOMOK,	BT_ZOOM,	NAME_Zoom },
	{ WRF_AllowReload,	WF_WEAPONRELOADOK,	BT_RELOAD,	NAME_Reload },
	{ WRF_AllowUser1,	WF_USER1OK,			BT_USER1,	NAME_User1 },
	{ WRF_AllowUser2,	WF_USER2OK,			BT_USER2,	NAME_User2 },
	{ WRF_AllowUser3,	WF_USER3OK,			BT_USER3,	NAME_User3 },
	{ WRF_AllowUser4,	WF_USER4OK,			BT_USER4,	NAME_User4 },
};

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

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

IMPLEMENT_CLASS(DPSprite, false, true)

IMPLEMENT_POINTERS_START(DPSprite)
	IMPLEMENT_POINTER(Caller)
	IMPLEMENT_POINTER(Next)
IMPLEMENT_POINTERS_END

DEFINE_FIELD_NAMED(DPSprite, State, CurState)	// deconflict with same named type
DEFINE_FIELD(DPSprite, Caller)
DEFINE_FIELD(DPSprite, Next)
DEFINE_FIELD(DPSprite, Owner)
DEFINE_FIELD(DPSprite, Sprite)
DEFINE_FIELD(DPSprite, Frame)
DEFINE_FIELD(DPSprite, ID)
DEFINE_FIELD(DPSprite, processPending)
DEFINE_FIELD(DPSprite, x)
DEFINE_FIELD(DPSprite, y)
DEFINE_FIELD(DPSprite, oldx)
DEFINE_FIELD(DPSprite, oldy)
DEFINE_FIELD(DPSprite, firstTic)
DEFINE_FIELD(DPSprite, Tics)
DEFINE_FIELD_BIT(DPSprite, Flags, bAddWeapon, PSPF_ADDWEAPON)
DEFINE_FIELD_BIT(DPSprite, Flags, bAddBob, PSPF_ADDBOB)
DEFINE_FIELD_BIT(DPSprite, Flags, bPowDouble, PSPF_POWDOUBLE)
DEFINE_FIELD_BIT(DPSprite, Flags, bCVarFast, PSPF_CVARFAST)
DEFINE_FIELD_BIT(DPSprite, Flags, bFlip, PSPF_FLIP)

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

DPSprite::DPSprite(player_t *owner, AActor *caller, int id)
: x(.0), y(.0),
  oldx(.0), oldy(.0),
  firstTic(true),
  Sprite(0),
  Flags(0),
  Caller(caller),
  Owner(owner),
  ID(id),
  processPending(true)
{
	DPSprite *prev = nullptr;
	DPSprite *next = Owner->psprites;
	while (next != nullptr && next->ID < ID)
	{
		prev = next;
		next = next->Next;
	}
	Next = next;
	GC::WriteBarrier(this, next);
	if (prev == nullptr)
	{
		Owner->psprites = this;
		GC::WriteBarrier(this);
	}
	else
	{
		prev->Next = this;
		GC::WriteBarrier(prev, this);
	}

	if (Next && Next->ID == ID && ID != 0)
		Next->Destroy(); // Replace it.

	if (Caller->IsKindOf(RUNTIME_CLASS(AWeapon)) || Caller->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
		Flags = (PSPF_ADDWEAPON|PSPF_ADDBOB|PSPF_POWDOUBLE|PSPF_CVARFAST);
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

DPSprite *player_t::FindPSprite(int layer)
{
	if (layer == 0)
		return nullptr;

	DPSprite *pspr = psprites;
	while (pspr)
	{
		if (pspr->ID == layer)
			break;

		pspr = pspr->Next;
	}

	return pspr;
}

DEFINE_ACTION_FUNCTION(_PlayerInfo, FindPSprite)	// the underscore is needed to get past the name mangler which removes the first clas name character to match the class representation (needs to be fixed in a later commit)
{
	PARAM_SELF_STRUCT_PROLOGUE(player_t);
	PARAM_INT(id);
	ACTION_RETURN_OBJECT(self->FindPSprite((PSPLayers)id));
}


//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

void P_SetPsprite(player_t *player, PSPLayers id, FState *state, bool pending)
{
	if (player == nullptr) return;
	player->GetPSprite(id)->SetState(state, pending);
}

DEFINE_ACTION_FUNCTION(_PlayerInfo, SetPSprite)	// the underscore is needed to get past the name mangler which removes the first clas name character to match the class representation (needs to be fixed in a later commit)
{
	PARAM_SELF_STRUCT_PROLOGUE(player_t);
	PARAM_INT(id);
	PARAM_POINTER(state, FState);
	PARAM_BOOL_DEF(pending);
	P_SetPsprite(self, (PSPLayers)id, state, pending);
	return 0;
}

DPSprite *player_t::GetPSprite(PSPLayers layer)
{
	AActor *oldcaller = nullptr;
	AActor *newcaller = nullptr;

	if (layer >= PSP_TARGETCENTER)
	{
		if (mo != nullptr)
		{
			newcaller = mo->FindInventory(RUNTIME_CLASS(APowerTargeter), true);
		}
	}
	else if (layer == PSP_STRIFEHANDS)
	{
		newcaller = mo;
	}
	else
	{
		newcaller = ReadyWeapon;
	}

	assert(newcaller != nullptr);

	DPSprite *pspr = FindPSprite(layer);
	if (pspr == nullptr)
	{
		pspr = new DPSprite(this, newcaller, layer);
	}
	else
	{
		oldcaller = pspr->Caller;
	}

	// Always update the caller here in case we switched weapon
	// or if the layer was being used by something else before.
	pspr->Caller = newcaller;

	if (newcaller != oldcaller)
	{ // Only reset stuff if this layer was created now or if it was being used before.
		if (layer >= PSP_TARGETCENTER)
		{ // The targeter layers were affected by those.
			pspr->Flags = (PSPF_CVARFAST|PSPF_POWDOUBLE);
		}
		else
		{
			pspr->Flags = (PSPF_ADDWEAPON|PSPF_ADDBOB|PSPF_CVARFAST|PSPF_POWDOUBLE);
		}
		if (layer == PSP_STRIFEHANDS)
		{
			// Some of the old hacks rely on this layer coming from the FireHands state.
			// This is the ONLY time a psprite's state is actually null.
			pspr->State = nullptr;
			pspr->y = WEAPONTOP;
		}

		pspr->firstTic = true;
	}

	return pspr;
}

DEFINE_ACTION_FUNCTION(_PlayerInfo, GetPSprite)	// the underscore is needed to get past the name mangler which removes the first clas name character to match the class representation (needs to be fixed in a later commit)
{
	PARAM_SELF_STRUCT_PROLOGUE(player_t);
	PARAM_INT(id);
	ACTION_RETURN_OBJECT(self->GetPSprite((PSPLayers)id));
}


//---------------------------------------------------------------------------
//
// PROC P_NewPspriteTick
//
//---------------------------------------------------------------------------

void DPSprite::NewTick()
{
	// This function should be called after the beginning of a tick, before any possible
	// prprite-event, or near the end, after any possible psprite event.
	// Because data is reset for every tick (which it must be) this has no impact on savegames.
	for (int i = 0; i < MAXPLAYERS; i++)
	{
		if (playeringame[i])
		{
			DPSprite *pspr = players[i].psprites;
			while (pspr)
			{
				pspr->processPending = true;
				pspr->ResetInterpolation();

				pspr = pspr->Next;
			}
		}
	}
}

//---------------------------------------------------------------------------
//
// PROC P_SetPsprite
//
//---------------------------------------------------------------------------

void DPSprite::SetState(FState *newstate, bool pending)
{
	if (ID == PSP_WEAPON)
	{ // A_WeaponReady will re-set these as needed
		Owner->WeaponState &= ~(WF_WEAPONREADY | WF_WEAPONREADYALT | WF_WEAPONBOBBING | WF_WEAPONSWITCHOK | WF_WEAPONRELOADOK | WF_WEAPONZOOMOK |
								WF_USER1OK | WF_USER2OK | WF_USER3OK | WF_USER4OK);
	}

	processPending = pending;

	do
	{
		if (newstate == nullptr)
		{ // Object removed itself.
			Destroy();
			return;
		}

		if (!(newstate->UseFlags & (SUF_OVERLAY|SUF_WEAPON)))	// Weapon and overlay are mostly the same, the main difference is that weapon states restrict the self pointer to class Actor.
		{
			auto so = FState::StaticFindStateOwner(newstate);
			Printf(TEXTCOLOR_RED "State %s.%d not flagged for use in overlays or weapons\n", so->TypeName.GetChars(), int(newstate - so->OwnedStates));
			State = nullptr;
			Destroy();
			return;
		}
		else if (!(newstate->UseFlags & SUF_WEAPON))
		{
			if (Caller->IsKindOf(RUNTIME_CLASS(AWeapon)))
			{
				auto so = FState::StaticFindStateOwner(newstate);
				Printf(TEXTCOLOR_RED "State %s.%d not flagged for use in weapons\n", so->TypeName.GetChars(), int(newstate - so->OwnedStates));
				State = nullptr;
				Destroy();
				return;
			}
		}

		State = newstate;

		if (newstate->sprite != SPR_FIXED)
		{ // okay to change sprite and/or frame
			if (!newstate->GetSameFrame())
			{ // okay to change frame
				Frame = newstate->GetFrame();
			}
			if (newstate->sprite != SPR_NOCHANGE)
			{ // okay to change sprite
				Sprite = newstate->sprite;
			}
		}

		Tics = newstate->GetTics(); // could be 0

		if (Flags & PSPF_CVARFAST)
		{
			if (sv_fastweapons == 2 && ID == PSP_WEAPON)
				Tics = newstate->ActionFunc == nullptr ? 0 : 1;
			else if (sv_fastweapons == 3)
				Tics = (newstate->GetTics() != 0);
			else if (sv_fastweapons)
				Tics = 1;		// great for producing decals :)
		}

		if (ID != PSP_FLASH)
		{ // It's still possible to set the flash layer's offsets with the action function.
			// Anything going through here cannot be reliably interpolated so this has to reset the interpolation coordinates if it changes the values.
			if (newstate->GetMisc1())
			{ // Set coordinates.
				oldx = x = newstate->GetMisc1();
			}
			if (newstate->GetMisc2())
			{
				oldy = y = newstate->GetMisc2();
			}
		}

		if (Owner->mo != nullptr)
		{
			FState *nextstate;
			FStateParamInfo stp = { newstate, STATE_Psprite, ID };
			if (newstate->ActionFunc != nullptr && newstate->ActionFunc->Unsafe)
			{
				// If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash.
				auto owner = FState::StaticFindStateOwner(newstate);
				Printf(TEXTCOLOR_RED "Unsafe state call in state %s.%d to %s which accesses user variables. The action function has been removed from this state\n",
					owner->TypeName.GetChars(), int(newstate - owner->OwnedStates), newstate->ActionFunc->PrintableName.GetChars());
				newstate->ActionFunc = nullptr;
			}
			if (newstate->CallAction(Owner->mo, Caller, &stp, &nextstate))
			{
				// It's possible this call resulted in this very layer being replaced.
				if (ObjectFlags & OF_EuthanizeMe)
				{
					return;
				}
				if (nextstate != nullptr)
				{
					newstate = nextstate;
					Tics = 0;
					continue;
				}
				if (State == nullptr)
				{
					Destroy();
					return;
				}
			}
		}

		newstate = State->GetNextState();
	} while (!Tics); // An initial state of 0 could cycle through.

	return;
}

DEFINE_ACTION_FUNCTION(DPSprite, SetState)
{
	PARAM_SELF_PROLOGUE(DPSprite);
	PARAM_POINTER(state, FState);
	PARAM_BOOL_DEF(pending);
	self->SetState(state, pending);
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC P_BringUpWeapon
//
// Starts bringing the pending weapon up from the bottom of the screen.
// This is only called to start the rising, not throughout it.
//
//---------------------------------------------------------------------------

void P_BringUpWeapon (player_t *player)
{
	AWeapon *weapon;

	if (player->PendingWeapon == WP_NOCHANGE)
	{
		if (player->ReadyWeapon != nullptr)
		{
			player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP;
			P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetReadyState());
		}
		return;
	}

	weapon = player->PendingWeapon;

	// If the player has a tome of power, use this weapon's powered up
	// version, if one is available.
	if (weapon != nullptr &&
		weapon->SisterWeapon &&
		weapon->SisterWeapon->WeaponFlags & WIF_POWERED_UP &&
		player->mo->FindInventory (RUNTIME_CLASS(APowerWeaponLevel2), true))
	{
		weapon = weapon->SisterWeapon;
	}

	player->PendingWeapon = WP_NOCHANGE;
	player->ReadyWeapon = weapon;
	player->mo->weaponspecial = 0;

	if (weapon != nullptr)
	{
		if (weapon->UpSound)
		{
			S_Sound (player->mo, CHAN_WEAPON, weapon->UpSound, 1, ATTN_NORM);
		}
		player->refire = 0;

		player->GetPSprite(PSP_WEAPON)->y = player->cheats & CF_INSTANTWEAPSWITCH
			? WEAPONTOP : WEAPONBOTTOM;
		// make sure that the previous weapon's flash state is terminated.
		// When coming here from a weapon drop it may still be active.
		P_SetPsprite(player, PSP_FLASH, nullptr);
		P_SetPsprite(player, PSP_WEAPON, weapon->GetUpState());
	}
}

//---------------------------------------------------------------------------
//
// PROC P_FireWeapon
//
//---------------------------------------------------------------------------

void P_FireWeapon (player_t *player, FState *state)
{
	AWeapon *weapon;

	// [SO] 9/2/02: People were able to do an awful lot of damage
	// when they were observers...
	if (player->Bot == nullptr && bot_observer)
	{
		return;
	}

	weapon = player->ReadyWeapon;
	if (weapon == nullptr || !weapon->CheckAmmo (AWeapon::PrimaryFire, true))
	{
		return;
	}

	player->mo->PlayAttacking ();
	weapon->bAltFire = false;
	if (state == nullptr)
	{
		state = weapon->GetAtkState(!!player->refire);
	}
	P_SetPsprite(player, PSP_WEAPON, state);
	if (!(weapon->WeaponFlags & WIF_NOALERT))
	{
		P_NoiseAlert (player->mo, player->mo, false);
	}
}

//---------------------------------------------------------------------------
//
// PROC P_FireWeaponAlt
//
//---------------------------------------------------------------------------

void P_FireWeaponAlt (player_t *player, FState *state)
{
	AWeapon *weapon;

	// [SO] 9/2/02: People were able to do an awful lot of damage
	// when they were observers...
	if (player->Bot == nullptr && bot_observer)
	{
		return;
	}

	weapon = player->ReadyWeapon;
	if (weapon == nullptr || weapon->FindState(NAME_AltFire) == nullptr || !weapon->CheckAmmo (AWeapon::AltFire, true))
	{
		return;
	}

	player->mo->PlayAttacking ();
	weapon->bAltFire = true;

	if (state == nullptr)
	{
		state = weapon->GetAltAtkState(!!player->refire);
	}

	P_SetPsprite(player, PSP_WEAPON, state);
	if (!(weapon->WeaponFlags & WIF_NOALERT))
	{
		P_NoiseAlert (player->mo, player->mo, false);
	}
}

//---------------------------------------------------------------------------
//
// PROC P_DropWeapon
//
// The player died, so put the weapon away.
//
//---------------------------------------------------------------------------

void P_DropWeapon (player_t *player)
{
	if (player == nullptr)
	{
		return;
	}
	// Since the weapon is dropping, stop blocking switching.
	player->WeaponState &= ~WF_DISABLESWITCH;
	if ((player->ReadyWeapon != nullptr) && (player->health > 0 || !(player->ReadyWeapon->WeaponFlags & WIF_NODEATHDESELECT)))
	{
		P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetDownState());
	}
}

//============================================================================
//
// P_BobWeapon
//
// [RH] Moved this out of A_WeaponReady so that the weapon can bob every
// tic and not just when A_WeaponReady is called. Not all weapons execute
// A_WeaponReady every tic, and it looks bad if they don't bob smoothly.
//
// [XA] Added new bob styles and exposed bob properties. Thanks, Ryan Cordell!
// [SP] Added new user option for bob speed
//
//============================================================================

void P_BobWeapon (player_t *player, float *x, float *y, double ticfrac)
{
	static float curbob;
	double xx[2], yy[2];

	AWeapon *weapon;
	float bobtarget;

	weapon = player->ReadyWeapon;

	if (weapon == nullptr || weapon->WeaponFlags & WIF_DONTBOB)
	{
		*x = *y = 0;
		return;
	}

	// [XA] Get the current weapon's bob properties.
	int bobstyle = weapon->BobStyle;
	float BobSpeed = (weapon->BobSpeed * 128);
	float Rangex = weapon->BobRangeX;
	float Rangey = weapon->BobRangeY;

	for (int i = 0; i < 2; i++)
	{
		// Bob the weapon based on movement speed. ([SP] And user's bob speed setting)
		FAngle angle = (BobSpeed * player->userinfo.GetWBobSpeed() * 35 /
			TICRATE*(level.time - 1 + i)) * (360.f / 8192.f);

		// [RH] Smooth transitions between bobbing and not-bobbing frames.
		// This also fixes the bug where you can "stick" a weapon off-center by
		// shooting it when it's at the peak of its swing.
		bobtarget = float((player->WeaponState & WF_WEAPONBOBBING) ? player->bob : 0.);
		if (curbob != bobtarget)
		{
			if (fabsf(bobtarget - curbob) <= 1)
			{
				curbob = bobtarget;
			}
			else
			{
				float zoom = MAX(1.f, fabsf(curbob - bobtarget) / 40);
				if (curbob > bobtarget)
				{
					curbob -= zoom;
				}
				else
				{
					curbob += zoom;
				}
			}
		}

		if (curbob != 0)
		{
			//[SP] Added in decorate player.viewbob checks
			float bobx = float(player->bob * Rangex * (float)player->mo->ViewBob);
			float boby = float(player->bob * Rangey * (float)player->mo->ViewBob);
			switch (bobstyle)
			{
			case AWeapon::BobNormal:
				xx[i] = bobx * angle.Cos();
				yy[i] = boby * fabsf(angle.Sin());
				break;

			case AWeapon::BobInverse:
				xx[i] = bobx*angle.Cos();
				yy[i] = boby * (1.f - fabsf(angle.Sin()));
				break;

			case AWeapon::BobAlpha:
				xx[i] = bobx * angle.Sin();
				yy[i] = boby * fabsf(angle.Sin());
				break;

			case AWeapon::BobInverseAlpha:
				xx[i] = bobx * angle.Sin();
				yy[i] = boby * (1.f - fabsf(angle.Sin()));
				break;

			case AWeapon::BobSmooth:
				xx[i] = bobx*angle.Cos();
				yy[i] = 0.5f * (boby * (1.f - ((angle * 2).Cos())));
				break;

			case AWeapon::BobInverseSmooth:
				xx[i] = bobx*angle.Cos();
				yy[i] = 0.5f * (boby * (1.f + ((angle * 2).Cos())));
			}
		}
		else
		{
			xx[i] = 0;
			yy[i] = 0;
		}
	}
	*x = (float)(xx[0] * (1. - ticfrac) + xx[1] * ticfrac);
	*y = (float)(yy[0] * (1. - ticfrac) + yy[1] * ticfrac);
}

//============================================================================
//
// PROC A_WeaponReady
//
// Readies a weapon for firing or bobbing with its three ancillary functions,
// DoReadyWeaponToSwitch(), DoReadyWeaponToFire() and DoReadyWeaponToBob().
// [XA] Added DoReadyWeaponToReload() and DoReadyWeaponToZoom()
//
//============================================================================

void DoReadyWeaponToSwitch (AActor *self, bool switchable)
{
	// Prepare for switching action.
	player_t *player;
	if (self && (player = self->player))
	{
		if (switchable)
		{
			player->WeaponState |= WF_WEAPONSWITCHOK | WF_REFIRESWITCHOK;
		}
		else
		{
			// WF_WEAPONSWITCHOK is automatically cleared every tic by P_SetPsprite().
			player->WeaponState &= ~WF_REFIRESWITCHOK;
		}
	}
}

void DoReadyWeaponDisableSwitch (AActor *self, INTBOOL disable)
{
	// Discard all switch attempts?
	player_t *player;
	if (self && (player = self->player))
	{
		if (disable)
		{
			player->WeaponState |= WF_DISABLESWITCH;
			player->WeaponState &= ~WF_REFIRESWITCHOK;
		}
		else
		{
			player->WeaponState &= ~WF_DISABLESWITCH;
		}
	}
}

void DoReadyWeaponToFire (AActor *self, bool prim, bool alt)
{
	player_t *player;
	AWeapon *weapon;

	if (!self || !(player = self->player) || !(weapon = player->ReadyWeapon))
	{
		return;
	}

	// Change player from attack state
	if (self->InStateSequence(self->state, self->MissileState) ||
		self->InStateSequence(self->state, self->MeleeState))
	{
		static_cast<APlayerPawn *>(self)->PlayIdle ();
	}

	// Play ready sound, if any.
	if (weapon->ReadySound && player->GetPSprite(PSP_WEAPON)->GetState() == weapon->FindState(NAME_Ready))
	{
		if (!(weapon->WeaponFlags & WIF_READYSNDHALF) || pr_wpnreadysnd() < 128)
		{
			S_Sound (self, CHAN_WEAPON, weapon->ReadySound, 1, ATTN_NORM);
		}
	}

	// Prepare for firing action.
	player->WeaponState |= ((prim ? WF_WEAPONREADY : 0) | (alt ? WF_WEAPONREADYALT : 0));
	return;
}

void DoReadyWeaponToBob (AActor *self)
{
	if (self && self->player && self->player->ReadyWeapon)
	{
		// Prepare for bobbing action.
		self->player->WeaponState |= WF_WEAPONBOBBING;
		self->player->GetPSprite(PSP_WEAPON)->x = 0;
		self->player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP;
	}
}

void DoReadyWeaponToGeneric(AActor *self, int paramflags)
{
	int flags = 0;

	for (size_t i = 0; i < countof(ButtonChecks); ++i)
	{
		if (paramflags & ButtonChecks[i].ReadyFlag)
		{
			flags |= ButtonChecks[i].StateFlag;
		}
	}
	if (self != NULL && self->player != NULL)
	{
		self->player->WeaponState |= flags;
	}
}

// This function replaces calls to A_WeaponReady in other codepointers.
void DoReadyWeapon(AActor *self)
{
	DoReadyWeaponToBob(self);
	DoReadyWeaponToFire(self);
	DoReadyWeaponToSwitch(self);
	DoReadyWeaponToGeneric(self, ~0);
}

DEFINE_ACTION_FUNCTION(AStateProvider, A_WeaponReady)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);
	PARAM_INT_DEF(flags);

													DoReadyWeaponToSwitch(self, !(flags & WRF_NoSwitch));
	if ((flags & WRF_NoFire) != WRF_NoFire)			DoReadyWeaponToFire(self, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary));
	if (!(flags & WRF_NoBob))						DoReadyWeaponToBob(self);
													DoReadyWeaponToGeneric(self, flags);
	DoReadyWeaponDisableSwitch(self, flags & WRF_DisableSwitch);
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC P_CheckWeaponFire
//
// The player can fire the weapon.
// [RH] This was in A_WeaponReady before, but that only works well when the
// weapon's ready frames have a one tic delay.
//
//---------------------------------------------------------------------------

void P_CheckWeaponFire (player_t *player)
{
	AWeapon *weapon = player->ReadyWeapon;

	if (weapon == NULL)
		return;

	// Check for fire. Some weapons do not auto fire.
	if ((player->WeaponState & WF_WEAPONREADY) && (player->cmd.ucmd.buttons & BT_ATTACK))
	{
		if (!player->attackdown || !(weapon->WeaponFlags & WIF_NOAUTOFIRE))
		{
			player->attackdown = true;
			P_FireWeapon (player, NULL);
			return;
		}
	}
	else if ((player->WeaponState & WF_WEAPONREADYALT) && (player->cmd.ucmd.buttons & BT_ALTATTACK))
	{
		if (!player->attackdown || !(weapon->WeaponFlags & WIF_NOAUTOFIRE))
		{
			player->attackdown = true;
			P_FireWeaponAlt (player, NULL);
			return;
		}
	}
	else
	{
		player->attackdown = false;
	}
}

//---------------------------------------------------------------------------
//
// PROC P_CheckWeaponSwitch
//
// The player can change to another weapon at this time.
// [GZ] This was cut from P_CheckWeaponFire.
//
//---------------------------------------------------------------------------

void P_CheckWeaponSwitch (player_t *player)
{
	if (player == NULL)
	{
		return;
	}
	if ((player->WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled.
		player->morphTics != 0)					// Morphed classes cannot change weapons.
	{ // ...so throw away any pending weapon requests.
		player->PendingWeapon = WP_NOCHANGE;
	}

	// Put the weapon away if the player has a pending weapon or has died, and
	// we're at a place in the state sequence where dropping the weapon is okay.
	if ((player->PendingWeapon != WP_NOCHANGE || player->health <= 0) &&
		player->WeaponState & WF_WEAPONSWITCHOK)
	{
		P_DropWeapon(player);
	}
}

//---------------------------------------------------------------------------
//
// PROC P_CheckWeaponButtons
//
// Check extra button presses for weapons.
//
//---------------------------------------------------------------------------

static void P_CheckWeaponButtons (player_t *player)
{
	if (player->Bot == nullptr && bot_observer)
	{
		return;
	}
	AWeapon *weapon = player->ReadyWeapon;
	if (weapon == nullptr)
	{
		return;
	}
	// The button checks are ordered by precedence. The first one to match a
	// button press and affect a state change wins.
	for (size_t i = 0; i < countof(ButtonChecks); ++i)
	{
		if ((player->WeaponState & ButtonChecks[i].StateFlag) &&
			(player->cmd.ucmd.buttons & ButtonChecks[i].ButtonFlag))
		{
			FState *state = weapon->GetStateForButtonName(ButtonChecks[i].StateName);
			// [XA] don't change state if still null, so if the modder
			// sets WRF_xxx to true but forgets to define the corresponding
			// state, the weapon won't disappear. ;)
			if (state != nullptr)
			{
				P_SetPsprite(player, PSP_WEAPON, state);
				return;
			}
		}
	}
}

//---------------------------------------------------------------------------
//
// PROC A_ReFire
//
// The player can re-fire the weapon without lowering it entirely.
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AStateProvider, A_ReFire)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);
	PARAM_STATE_ACTION_DEF(state);
	A_ReFire(self, state);
	return 0;
}

void A_ReFire(AActor *self, FState *state)
{
	player_t *player = self->player;
	bool pending;

	if (NULL == player)
	{
		return;
	}
	pending = player->PendingWeapon != WP_NOCHANGE && (player->WeaponState & WF_REFIRESWITCHOK);
	if ((player->cmd.ucmd.buttons & BT_ATTACK)
		&& !player->ReadyWeapon->bAltFire && !pending && player->health > 0)
	{
		player->refire++;
		P_FireWeapon (player, state);
	}
	else if ((player->cmd.ucmd.buttons & BT_ALTATTACK)
		&& player->ReadyWeapon->bAltFire && !pending && player->health > 0)
	{
		player->refire++;
		P_FireWeaponAlt (player, state);
	}
	else
	{
		player->refire = 0;
		player->ReadyWeapon->CheckAmmo (player->ReadyWeapon->bAltFire
			? AWeapon::AltFire : AWeapon::PrimaryFire, true);
	}
}

DEFINE_ACTION_FUNCTION(AStateProvider, A_ClearReFire)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);
	player_t *player = self->player;

	if (NULL != player)
	{
		player->refire = 0;
	}
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC A_CheckReload
//
// Present in Doom, but unused. Also present in Strife, and actually used.
// This and what I call A_XBowReFire are actually the same thing in Strife,
// not two separate functions as I have them here.
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AStateProvider, A_CheckReload)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);

	if (self->player != NULL)
	{
		self->player->ReadyWeapon->CheckAmmo (
			self->player->ReadyWeapon->bAltFire ? AWeapon::AltFire
			: AWeapon::PrimaryFire, true);
	}
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC A_OverlayOffset
//
//---------------------------------------------------------------------------
enum WOFFlags
{
	WOF_KEEPX =		1,
	WOF_KEEPY =		1 << 1,
	WOF_ADD =		1 << 2,
	WOF_INTERPOLATE = 1 << 3,
};

void A_OverlayOffset(AActor *self, int layer, double wx, double wy, int flags)
{
	if ((flags & WOF_KEEPX) && (flags & WOF_KEEPY))
	{
		return;
	}

	player_t *player = self->player;
	DPSprite *psp;

	if (player && (player->playerstate != PST_DEAD))
	{
		psp = player->FindPSprite(layer);

		if (psp == nullptr)
			return;

		if (!(flags & WOF_KEEPX))
		{
			if (flags & WOF_ADD)
			{
				psp->x += wx;
			}
			else
			{
				psp->x = wx;
				if (!(flags & WOF_INTERPOLATE)) psp->oldx = psp->x;
			}
		}
		if (!(flags & WOF_KEEPY))
		{
			if (flags & WOF_ADD)
			{
				psp->y += wy;
			}
			else
			{
				psp->y = wy;
				if (!(flags & WOF_INTERPOLATE)) psp->oldy = psp->y;
			}
		}
	}
}

DEFINE_ACTION_FUNCTION(AActor, A_OverlayOffset)
{
	PARAM_ACTION_PROLOGUE(AActor);
	PARAM_INT_DEF(layer)
	PARAM_FLOAT_DEF(wx)	
	PARAM_FLOAT_DEF(wy)	
	PARAM_INT_DEF(flags)
	A_OverlayOffset(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), wx, wy, flags);
	return 0;
}

DEFINE_ACTION_FUNCTION(AActor, A_WeaponOffset)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT_DEF(wx)	
	PARAM_FLOAT_DEF(wy)	
	PARAM_INT_DEF(flags)
	A_OverlayOffset(self, PSP_WEAPON, wx, wy, flags);
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC A_OverlayFlags
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AActor, A_OverlayFlags)
{
	PARAM_ACTION_PROLOGUE(AActor);
	PARAM_INT(layer);
	PARAM_INT(flags);
	PARAM_BOOL(set);

	if (!ACTION_CALL_FROM_PSPRITE())
		return 0;

	DPSprite *pspr = self->player->FindPSprite(((layer != 0) ? layer : stateinfo->mPSPIndex));

	if (pspr == nullptr)
		return 0;

	if (set)
		pspr->Flags |= flags;
	else
		pspr->Flags &= ~flags;

	return 0;
}

//---------------------------------------------------------------------------
//
// PROC OverlayX/Y
// Action function to return the X/Y of an overlay.
//---------------------------------------------------------------------------

static double GetOverlayPosition(AActor *self, int layer, bool gety)
{
	if (layer)
	{
		DPSprite *pspr = self->player->FindPSprite(layer);

		if (pspr != nullptr)
		{
			return gety ? (pspr->y) : (pspr->x);
		}
	}
	return 0.;
}

DEFINE_ACTION_FUNCTION(AActor, OverlayX)
{
	PARAM_ACTION_PROLOGUE(AActor);
	PARAM_INT_DEF(layer);

	if (ACTION_CALL_FROM_PSPRITE())
	{
		double res = GetOverlayPosition(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), false);
		ACTION_RETURN_FLOAT(res);	
	}
	ACTION_RETURN_FLOAT(0.);
}

DEFINE_ACTION_FUNCTION(AActor, OverlayY)
{
	PARAM_ACTION_PROLOGUE(AActor);
	PARAM_INT_DEF(layer);

	if (ACTION_CALL_FROM_PSPRITE())
	{
		double res = GetOverlayPosition(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), true);
		ACTION_RETURN_FLOAT(res);
	}
	ACTION_RETURN_FLOAT(0.);
}

//---------------------------------------------------------------------------
//
// PROC OverlayID
// Because non-action functions cannot acquire the ID of the overlay...
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AActor, OverlayID)
{
	PARAM_ACTION_PROLOGUE(AActor);

	if (ACTION_CALL_FROM_PSPRITE())
	{
		ACTION_RETURN_INT(stateinfo->mPSPIndex);
	}
	ACTION_RETURN_INT(0);
}



//---------------------------------------------------------------------------
//
// PROC A_Lower
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AStateProvider, A_Lower)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);

	player_t *player = self->player;
	DPSprite *psp;

	if (nullptr == player)
	{
		return 0;
	}
	if (nullptr == player->ReadyWeapon)
	{
		P_BringUpWeapon(player);
		return 0;
	}
	psp = player->GetPSprite(PSP_WEAPON);
	if (player->morphTics || player->cheats & CF_INSTANTWEAPSWITCH)
	{
		psp->y = WEAPONBOTTOM;
	}
	else
	{
		psp->y += LOWERSPEED;
	}
	if (psp->y < WEAPONBOTTOM)
	{ // Not lowered all the way yet
		return 0;
	}
	if (player->playerstate == PST_DEAD)
	{ // Player is dead, so don't bring up a pending weapon
		// Player is dead, so keep the weapon off screen
		P_SetPsprite(player, PSP_FLASH, nullptr);
		psp->SetState(player->ReadyWeapon->FindState(NAME_DeadLowered));
		return 0;
	}
	// [RH] Clear the flash state. Only needed for Strife.
	P_SetPsprite(player, PSP_FLASH, nullptr);
	P_BringUpWeapon (player);
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC A_Raise
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AStateProvider, A_Raise)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);

	if (self == nullptr)
	{
		return 0;
	}
	player_t *player = self->player;
	DPSprite *psp;

	if (nullptr == player)
	{
		return 0;
	}
	if (player->PendingWeapon != WP_NOCHANGE)
	{
		P_DropWeapon(player);
		return 0;
	}
	if (player->ReadyWeapon == nullptr)
	{
		return 0;
	}
	psp = player->GetPSprite(PSP_WEAPON);
	psp->y -= RAISESPEED;
	if (psp->y > WEAPONTOP)
	{ // Not raised all the way yet
		return 0;
	}
	psp->y = WEAPONTOP;
	psp->SetState(player->ReadyWeapon->GetReadyState());
	return 0;
}

//---------------------------------------------------------------------------
//
// PROC A_Overlay
//
//---------------------------------------------------------------------------

DEFINE_ACTION_FUNCTION(AActor, A_Overlay)
{
	PARAM_ACTION_PROLOGUE(AActor);
	PARAM_INT		(layer);
	PARAM_STATE_ACTION_DEF(state);
	PARAM_BOOL_DEF(dontoverride);

	player_t *player = self->player;

	if (player == nullptr || (dontoverride && (player->FindPSprite(layer) != nullptr)))
	{
		ACTION_RETURN_BOOL(false);
	}

	DPSprite *pspr;
	pspr = new DPSprite(player, stateowner, layer);
	pspr->SetState(state);
	ACTION_RETURN_BOOL(true);
}

DEFINE_ACTION_FUNCTION(AActor, A_ClearOverlays)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_INT_DEF(start);
	PARAM_INT_DEF(stop);
	PARAM_BOOL_DEF(safety)

	if (self->player == nullptr)
		ACTION_RETURN_INT(0);

	if (!start && !stop)
	{
		start = INT_MIN;
		stop = safety ? PSP_TARGETCENTER - 1 : INT_MAX;
	}

	unsigned int count = 0;
	int id;

	for (DPSprite *pspr = self->player->psprites; pspr != nullptr; pspr = pspr->GetNext())
	{
		id = pspr->GetID();

		if (id < start || id == 0)
			continue;
		else if (id > stop)
			break;

		if (safety)
		{
			if (id >= PSP_TARGETCENTER)
				break;
			else if (id == PSP_STRIFEHANDS || id == PSP_WEAPON || id == PSP_FLASH)
				continue;
		}

		pspr->SetState(nullptr);
		count++;
	}

	ACTION_RETURN_INT(count);
}

//
// A_GunFlash
//
enum GF_Flags
{
	GFF_NOEXTCHANGE = 1,
};

DEFINE_ACTION_FUNCTION(AStateProvider, A_GunFlash)
{
	PARAM_ACTION_PROLOGUE(AStateProvider);
	PARAM_STATE_ACTION_DEF(flash);
	PARAM_INT_DEF(flags);

	player_t *player = self->player;

	if (nullptr == player)
	{
		return 0;
	}
	if (!(flags & GFF_NOEXTCHANGE))
	{
		player->mo->PlayAttacking2 ();
	}
	if (flash == nullptr)
	{
		if (player->ReadyWeapon->bAltFire)
		{
			flash = player->ReadyWeapon->FindState(NAME_AltFlash);
		}
		if (flash == nullptr)
		{
			flash = player->ReadyWeapon->FindState(NAME_Flash);
		}
	}
	P_SetPsprite(player, PSP_FLASH, flash);
	return 0;
}



//
// WEAPON ATTACKS
//

//
// P_BulletSlope
// Sets a slope so a near miss is at aproximately
// the height of the intended target
//

DAngle P_BulletSlope (AActor *mo, FTranslatedLineTarget *pLineTarget, int aimflags)
{
	static const double angdiff[3] = { -5.625f, 5.625f, 0 };
	int i;
	DAngle an;
	DAngle pitch;
	FTranslatedLineTarget scratch;

	if (pLineTarget == NULL) pLineTarget = &scratch;
	// see which target is to be aimed at
	i = 2;
	do
	{
		an = mo->Angles.Yaw + angdiff[i];
		pitch = P_AimLineAttack (mo, an, 16.*64, pLineTarget, 0., aimflags);

		if (mo->player != NULL &&
			level.IsFreelookAllowed() &&
			mo->player->userinfo.GetAimDist() <= 0.5)
		{
			break;
		}
	} while (pLineTarget->linetarget == NULL && --i >= 0);

	return pitch;
}

DEFINE_ACTION_FUNCTION(AActor, BulletSlope)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_POINTER_DEF(t, FTranslatedLineTarget);
	PARAM_INT_DEF(aimflags);
	ACTION_RETURN_FLOAT(P_BulletSlope(self, t, aimflags).Degrees);
}


AActor *P_AimTarget(AActor *mo)
{
	FTranslatedLineTarget t;
	P_BulletSlope(mo, &t, ALF_PORTALRESTRICT);
	return t.linetarget;
}

DEFINE_ACTION_FUNCTION(AActor, AimTarget)
{
	PARAM_SELF_PROLOGUE(AActor);
	ACTION_RETURN_OBJECT(P_AimTarget(self));
}

//------------------------------------------------------------------------
//
// PROC P_SetupPsprites
//
// Called at start of level for each player
//
//------------------------------------------------------------------------

void P_SetupPsprites(player_t *player, bool startweaponup)
{
	// Remove all psprites
	player->DestroyPSprites();

	// Spawn the ready weapon
	player->PendingWeapon = !startweaponup ? player->ReadyWeapon : WP_NOCHANGE;
	P_BringUpWeapon (player);
}

//------------------------------------------------------------------------
//
// PROC P_MovePsprites
//
// Called every tic by player thinking routine
//
//------------------------------------------------------------------------

void player_t::TickPSprites()
{
	DPSprite *pspr = psprites;
	while (pspr)
	{
		// Destroy the psprite if it's from a weapon that isn't currently selected by the player
		// or if it's from an inventory item that the player no longer owns. 
		if ((pspr->Caller == nullptr ||
			(pspr->Caller->IsKindOf(RUNTIME_CLASS(AInventory)) && barrier_cast<AInventory *>(pspr->Caller)->Owner != pspr->Owner->mo) ||
			(pspr->Caller->IsKindOf(RUNTIME_CLASS(AWeapon)) && pspr->Caller != pspr->Owner->ReadyWeapon)))
		{
			pspr->Destroy();
		}
		else
		{
			pspr->Tick();
		}

		pspr = pspr->Next;
	}

	if ((health > 0) || (ReadyWeapon != nullptr && !(ReadyWeapon->WeaponFlags & WIF_NODEATHINPUT)))
	{
		if (ReadyWeapon == nullptr)
		{
			if (PendingWeapon != WP_NOCHANGE)
				P_BringUpWeapon(this);
		}
		else
		{
			P_CheckWeaponSwitch(this);
			if (WeaponState & (WF_WEAPONREADY | WF_WEAPONREADYALT))
			{
				P_CheckWeaponFire(this);
			}
			// Check custom buttons
			P_CheckWeaponButtons(this);
		}
	}
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

void DPSprite::Tick()
{
	if (processPending)
	{
		// drop tic count and possibly change state
		if (Tics != -1)	// a -1 tic count never changes
		{
			Tics--;

			// [BC] Apply double firing speed.
			if ((Flags & PSPF_POWDOUBLE) && Tics && (Owner->cheats & CF_DOUBLEFIRINGSPEED))
				Tics--;

			if (!Tics)
				SetState(State->GetNextState());
		}
	}
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

void DPSprite::Serialize(FSerializer &arc)
{
	Super::Serialize(arc);

	arc("next", Next)
		("caller", Caller)
		("owner", Owner)
		("flags", Flags)
		("state", State)
		("tics", Tics)
		.Sprite("sprite", Sprite, nullptr)
		("frame", Frame)
		("id", ID)
		("x", x)
		("y", y)
		("oldx", oldx)
		("oldy", oldy);
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

void player_t::DestroyPSprites()
{
	DPSprite *pspr = psprites;
	psprites = nullptr;
	while (pspr)
	{
		DPSprite *next = pspr->Next;
		pspr->Next = nullptr;
		pspr->Destroy();
		pspr = next;
	}
}

//------------------------------------------------------------------------------------
//
// Setting a random flash like some of Doom's weapons can easily crash when the
// definition is overridden incorrectly so let's check that the state actually exists.
// Be aware though that this will not catch all DEHACKED related problems. But it will
// find all DECORATE related ones.
//
//------------------------------------------------------------------------------------

void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int index)
{
	if (flashstate != nullptr)
	{
		PClassActor *cls = weapon->GetClass();
		while (cls != RUNTIME_CLASS(AWeapon))
		{
			if (flashstate >= cls->OwnedStates && flashstate < cls->OwnedStates + cls->NumOwnedStates)
			{
				// The flash state belongs to this class.
				// Now let's check if the actually wanted state does also
				if (flashstate + index < cls->OwnedStates + cls->NumOwnedStates)
				{
					// we're ok so set the state
					P_SetPsprite(player, PSP_FLASH, flashstate + index, true);
					return;
				}
				else
				{
					// oh, no! The state is beyond the end of the state table so use the original flash state.
					P_SetPsprite(player, PSP_FLASH, flashstate, true);
					return;
				}
			}
			// try again with parent class
			cls = static_cast<PClassActor *>(cls->ParentClass);
		}
		// if we get here the state doesn't seem to belong to any class in the inheritance chain
		// This can happen with Dehacked if the flash states are remapped. 
		// The only way to check this would be to go through all Dehacked modifiable actors, convert
		// their states into a single flat array and find the correct one.
		// Rather than that, just check to make sure it belongs to something.
		if (FState::StaticFindStateOwner(flashstate + index) == NULL)
		{ // Invalid state. With no index offset, it should at least be valid.
			index = 0;
		}
	}
	P_SetPsprite(player, PSP_FLASH, flashstate + index, true);
}

DEFINE_ACTION_FUNCTION(_PlayerInfo, SetSafeFlash)
{
	PARAM_SELF_STRUCT_PROLOGUE(player_t);
	PARAM_OBJECT_NOT_NULL(weapon, AWeapon);
	PARAM_POINTER(state, FState);
	PARAM_INT(index);
	P_SetSafeFlash(weapon, self, state, index);
	return 0;
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

void DPSprite::Destroy()
{
	// Do not crash if this gets called on partially initialized objects.
	if (Owner != nullptr && Owner->psprites != nullptr)
	{
		if (Owner->psprites != this)
		{
			DPSprite *prev = Owner->psprites;
			while (prev != nullptr && prev->Next != this)
				prev = prev->Next;

			if (prev != nullptr && prev->Next == this)
			{
				prev->Next = Next;
				GC::WriteBarrier(prev, Next);
			}
		}
		else
		{
			Owner->psprites = Next;
			GC::WriteBarrier(Next);
		}
	}
	Super::Destroy();
}

//------------------------------------------------------------------------
//
//
//
//------------------------------------------------------------------------

ADD_STAT(psprites)
{
	FString out;
	DPSprite *pspr;
	for (int i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i])
			continue;

		out.AppendFormat("[psprites] player: %d | layers: ", i);

		pspr = players[i].psprites;
		while (pspr)
		{
			out.AppendFormat("%d, ", pspr->GetID());

			pspr = pspr->GetNext();
		}

		out.AppendFormat("\n");
	}

	return out;
}
