
struct UserCmd native
{
	native uint	buttons;
	native int16	pitch;			// up/down
	native int16	yaw;			// left/right
	native int16	roll;			// "tilt"
	native int16	forwardmove;
	native int16	sidemove;
	native int16	upmove;
}

class PlayerPawn : Actor native
{
	const CROUCHSPEED = (1./12);
	// [RH] # of ticks to complete a turn180
	const TURN180_TICKS = ((TICRATE / 4) + 1);
	
	native int			crouchsprite;
	native int			MaxHealth;
	native int			BonusHealth;
	native int			MugShotMaxHealth;
	native int			RunHealth;
	native int			PlayerFlags;
	native clearscope Inventory	InvFirst;		// first inventory item displayed on inventory bar
	native clearscope Inventory	InvSel;			// selected inventory item
	native Name 		SoundClass;		// Sound class
	native Name 		Face;			// Doom status bar face (when used)
	native Name 		Portrait;
	native Name 		Slot[10];
	native double 		HexenArmor[5];
	native uint8		ColorRangeStart;	// Skin color range
	native uint8		ColorRangeEnd;

	// [GRB] Player class properties
	native double		JumpZ;
	native double		GruntSpeed;
	native double		FallingScreamMinSpeed, FallingScreamMaxSpeed;
	native double		ViewHeight;
	native double		ForwardMove1, ForwardMove2;
	native double		SideMove1, SideMove2;
	native TextureID	ScoreIcon;
	native int			SpawnMask;
	native Name			MorphWeapon;
	native double		AttackZOffset;			// attack height, relative to player center
	native double		UseRange;				// [NS] Distance at which player can +use
	native double		AirCapacity;			// Multiplier for air supply underwater.
	native Class<Actor> FlechetteType;
	native color 		DamageFade;				// [CW] Fades for when you are being damaged.
	native double		ViewBob;				// [SP] ViewBob Multiplier
	native double		FullHeight;

	meta Name HealingRadiusType;
	meta Name InvulMode;
	
	property prefix: Player;
	property HealRadiusType: HealingradiusType;
	property InvulnerabilityMode: InvulMode;
	property AttackZOffset: AttackZOffset;
	property JumpZ: JumpZ;
	property GruntSpeed: GruntSpeed;
	property FallingScreamSpeed: FallingScreamMinSpeed, FallingScreamMaxSpeed;
	property ViewHeight: ViewHeight;
	property UseRange: UseRange;
	property AirCapacity: AirCapacity;
	property MaxHealth: MaxHealth;
	property MugshotMaxHealth: MugshotMaxHealth;
	property RunHealth: RunHealth;
	property MorphWeapon: MorphWeapon;
	property FlechetteType: FlechetteType;
	property Portrait: Portrait;
	
	Default
	{
		Health 100;
		Radius 16;
		Height 56;
		Mass 100;
		Painchance 255;
		Speed 1;
		+SOLID
		+SHOOTABLE
		+DROPOFF
		+PICKUP
		+NOTDMATCH
		+FRIENDLY
		+SLIDESONWALLS
		+CANPASS
		+CANPUSHWALLS
		+FLOORCLIP
		+WINDTHRUST
		+TELESTOMP
		+NOBLOCKMONST
		Player.AttackZOffset 8;
		Player.JumpZ 8;
		Player.GruntSpeed 12;
		Player.FallingScreamSpeed 35,40;
		Player.ViewHeight 41;
		Player.UseRange 64;
		Player.ForwardMove 1,1;
		Player.SideMove 1,1;
		Player.ColorRange 0,0;
		Player.SoundClass "player";
		Player.DamageScreenColor "ff 00 00";
		Player.MugShotMaxHealth 0;
		Player.FlechetteType "ArtiPoisonBag3";
		Player.AirCapacity 1;
		Player.ViewBob 1;
		Obituary "$OB_MPDEFAULT";
	}
	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	virtual void PlayIdle ()
	{
		if (InStateSequence(CurState, SeeState))
			SetState (SpawnState);
	}

	virtual void PlayRunning ()
	{
		if (InStateSequence(CurState, SpawnState) && SeeState != NULL)
			SetState (SeeState);
	}
	
	virtual void PlayAttacking ()
	{
		if (MissileState != null) SetState (MissileState);
	}

	virtual void PlayAttacking2 ()
	{
		if (MeleeState != null) SetState (MeleeState);
	}
	
	virtual void MorphPlayerThink()
	{
	}
	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	virtual void OnRespawn()
	{
		if (sv_respawnprotect && (multiplayer || alwaysapplydmflags))
		{
			let invul = Powerup(Spawn("PowerInvulnerable"));
			invul.EffectTics = 3 * TICRATE;
			invul.BlendColor = 0;			// don't mess with the view
			invul.bUndroppable = true;		// Don't drop this
			bRespawnInvul = true;			// [RH] special effect
		}
	}
	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack)
	{
		if (victim.player != player && victim.IsTeammate(self))
		{
			victim = self;
			return String.Format("$OB_FRIENDLY%c", random[Obituary](49, 53));
		}
		else
		{
			if (mod == 'Telefrag') return "$OB_MPTELEFRAG";

			String message;
			if (inflictor != NULL && inflictor != self)
			{
				message = inflictor.GetObituary(victim, inflictor, mod, playerattack);
			}
			if (message.Length() == 0 && playerattack && player.ReadyWeapon != NULL)
			{
				message = player.ReadyWeapon.GetObituary(victim, inflictor, mod, playerattack);
			}
			if (message.Length() == 0)
			{
				if (mod == 'BFGSplash') return "$OB_MPBFG_SPLASH";
				if (mod == 'Railgun') return "$OB_RAILGUN";
				message = Obituary;
			}
			return message;
		}
	}
	
	//----------------------------------------------------------------------------
	//
	// This is for SBARINFO.
	//
	//----------------------------------------------------------------------------

	clearscope int, int GetEffectTicsForItem(class<Inventory> item) const 
	{
		let pg = (class<PowerupGiver>)(item);
		if (pg != null)
		{
			let powerupType = (class<Powerup>)(GetDefaultByType(pg).PowerupType);
			let powerup = Powerup(FindInventory(powerupType));
			if(powerup != null) 
			{
				let maxtics = GetDefaultByType(pg).EffectTics;
				if (maxtics == 0) maxtics = powerup.default.EffectTics;
				return powerup.EffectTics, maxtics;
			}
		}
		return -1, -1;
	}
	

	//===========================================================================
	//
	// APlayerPawn :: CheckWeaponSwitch
	//
	// Checks if weapons should be changed after picking up ammo
	//
	//===========================================================================

	void CheckWeaponSwitch(Class<Ammo> ammotype)
	{
		let player = self.player;
		if (!player.GetNeverSwitch() &&	player.PendingWeapon == WP_NOCHANGE && 
			(player.ReadyWeapon == NULL || player.ReadyWeapon.bWimpy_Weapon))
		{
			let best = BestWeapon (ammotype);
			if (best != NULL && (player.ReadyWeapon == NULL ||
				best.SelectionOrder < player.ReadyWeapon.SelectionOrder))
			{
				player.PendingWeapon = best;
			}
		}
	}

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

	virtual void FireWeapon (State stat)
	{
		let player = self.player;
		
		// [SO] 9/2/02: People were able to do an awful lot of damage
		// when they were observers...
		if (player.Bot == null && bot_observer)
		{
			return;
		}

		let weapn = player.ReadyWeapon;
		if (weapn == null || !weapn.CheckAmmo (Weapon.PrimaryFire, true))
		{
			return;
		}

		player.WeaponState &= ~WF_WEAPONBOBBING;
		PlayAttacking ();
		weapn.bAltFire = false;
		if (stat == null)
		{
			stat = weapn.GetAtkState(!!player.refire);
		}
		player.SetPsprite(PSP_WEAPON, stat);
		if (!weapn.bNoAlert)
		{
			SoundAlert (self, false);
		}
	}

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

	virtual void FireWeaponAlt (State stat)
	{
		// [SO] 9/2/02: People were able to do an awful lot of damage
		// when they were observers...
		if (player.Bot == null && bot_observer)
		{
			return;
		}

		let weapn = player.ReadyWeapon;
		if (weapn == null || weapn.FindState('AltFire') == null || !weapn.CheckAmmo (Weapon.AltFire, true))
		{
			return;
		}

		player.WeaponState &= ~WF_WEAPONBOBBING;
		PlayAttacking ();
		weapn.bAltFire = true;

		if (stat == null)
		{
			stat = weapn.GetAltAtkState(!!player.refire);
		}

		player.SetPsprite(PSP_WEAPON, stat);
		if (!weapn.bNoAlert)
		{
			SoundAlert (self, false);
		}
	}

	//---------------------------------------------------------------------------
	//
	// 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 CheckWeaponFire ()
	{
		let player = self.player;
		let weapon = player.ReadyWeapon;

		if (weapon == NULL)
			return;

		// Check for fire. Some weapons do not auto fire.
		if ((player.WeaponState & WF_WEAPONREADY) && (player.cmd.buttons & BT_ATTACK))
		{
			if (!player.attackdown || !weapon.bNoAutofire)
			{
				player.attackdown = true;
				FireWeapon (NULL);
				return;
			}
		}
		else if ((player.WeaponState & WF_WEAPONREADYALT) && (player.cmd.buttons & BT_ALTATTACK))
		{
			if (!player.attackdown || !weapon.bNoAutofire)
			{
				player.attackdown = true;
				FireWeaponAlt (NULL);
				return;
			}
		}
		else
		{
			player.attackdown = false;
		}
	}

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

	virtual void CheckWeaponChange ()
	{
		let player = self.player;
		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)
		{
			player.DropWeapon();
		}
	}
	
	//------------------------------------------------------------------------
	//
	// PROC P_MovePsprites
	//
	// Called every tic by player thinking routine
	//
	//------------------------------------------------------------------------

	virtual void TickPSprites()
	{
		let player = self.player;
		let pspr = player.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 == null ||
				(pspr.Caller is "Inventory" && Inventory(pspr.Caller).Owner != pspr.Owner.mo) ||
				(pspr.Caller is "Weapon" && pspr.Caller != pspr.Owner.ReadyWeapon)))
			{
				pspr.Destroy();
			}
			else
			{
				pspr.Tick();
			}

			pspr = pspr.Next;
		}

		if ((health > 0) || (player.ReadyWeapon != null && !player.ReadyWeapon.bNoDeathInput))
		{
			if (player.ReadyWeapon == null)
			{
				if (player.PendingWeapon != WP_NOCHANGE)
					player.BringUpWeapon();
			}
			else
			{
				CheckWeaponChange();
				if (player.WeaponState & (WF_WEAPONREADY | WF_WEAPONREADYALT))
				{
					CheckWeaponFire();
				}
				// Check custom buttons
				CheckWeaponButtons();
			}
		}
	}

	//==========================================================================
	//
	// P_DeathThink
	//
	//==========================================================================

	virtual void DeathThink ()
	{
		let player = self.player;
		int dir;
		double delta;

		player.Uncrouch();
		TickPSprites();

		player.onground = (pos.Z <= floorz);
		if (self is "PlayerChunk")
		{ // Flying bloody skull or flying ice chunk
			player.viewheight = 6;
			player.deltaviewheight = 0;
			if (player.onground)
			{
				if (Pitch > -19.)
				{
					double lookDelta = (-19. - Pitch) / 8;
					Pitch += lookDelta;
				}
			}
		}
		else if (!bIceCorpse)
		{ // Fall to ground (if not frozen)
			player.deltaviewheight = 0;
			if (player.viewheight > 6)
			{
				player.viewheight -= 1;
			}
			if (player.viewheight < 6)
			{
				player.viewheight = 6;
			}
			if (Pitch < 0)
			{
				Pitch += 3;
			}
			else if (Pitch > 0)
			{
				Pitch -= 3;
			}
			if (abs(Pitch) < 3)
			{
				Pitch = 0.;
			}
		}
		player.mo.CalcHeight ();
			
		if (player.attacker && player.attacker != self)
		{ // Watch killer
			double diff = deltaangle(angle, AngleTo(player.attacker));
			double delta = abs(diff);
	
			if (delta < 10)
			{ // Looking at killer, so fade damage and poison counters
				if (player.damagecount)
				{
					player.damagecount--;
				}
				if (player.poisoncount)
				{
					player.poisoncount--;
				}
			}
			delta /= 8;
			Angle += clamp(diff, -5., 5.);
		}
		else
		{
			if (player.damagecount)
			{
				player.damagecount--;
			}
			if (player.poisoncount)
			{
				player.poisoncount--;
			}
		}		

		if ((player.cmd.buttons & BT_USE ||
			((multiplayer || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn)
		{
			if (level.time >= player.respawn_time || ((player.cmd.buttons & BT_USE) && player.Bot == NULL))
			{
				player.cls = NULL;		// Force a new class if the player is using a random class
				player.playerstate = (multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn)) ? PST_REBORN : PST_ENTER;
				if (special1 > 2)
				{
					special1 = 0;
				}
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckFOV
	//
	//----------------------------------------------------------------------------

	virtual void CheckFOV()
	{
		let player = self.player;

		// [RH] Zoom the player's FOV
		float desired = player.DesiredFOV;
		// Adjust FOV using on the currently held weapon.
		if (player.playerstate != PST_DEAD &&		// No adjustment while dead.
			player.ReadyWeapon != NULL &&			// No adjustment if no weapon.
			player.ReadyWeapon.FOVScale != 0)		// No adjustment if the adjustment is zero.
		{
			// A negative scale is used to prevent G_AddViewAngle/G_AddViewPitch
			// from scaling with the FOV scale.
			desired *= abs(player.ReadyWeapon.FOVScale);
		}
		if (player.FOV != desired)
		{
			if (abs(player.FOV - desired) < 7.)
			{
				player.FOV = desired;
			}
			else
			{
				float zoom = MAX(7., abs(player.FOV - desired) * 0.025);
				if (player.FOV > desired)
				{
					player.FOV = player.FOV - zoom;
				}
				else
				{
					player.FOV = player.FOV + zoom;
				}
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckCheats
	//
	//----------------------------------------------------------------------------

	virtual void CheckCheats()
	{
		let player = self.player;
		// No-clip cheat
		if ((player.cheats & (CF_NOCLIP | CF_NOCLIP2)) == CF_NOCLIP2)
		{ // No noclip2 without noclip
			player.cheats &= ~CF_NOCLIP2;
		}
		bNoClip = (player.cheats & (CF_NOCLIP | CF_NOCLIP2) || Default.bNoClip);
		if (player.cheats & CF_NOCLIP2)
		{
			bNoGravity = true;
		}
		else if (!bFly && !Default.bNoGravity)
		{
			bNoGravity = false;
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckFrozen
	//
	//----------------------------------------------------------------------------

	virtual bool CheckFrozen()
	{
		let player = self.player;
		UserCmd cmd = player.cmd;
		bool totallyfrozen = player.IsTotallyFrozen();

		// [RH] Being totally frozen zeros out most input parameters.
		if (totallyfrozen)
		{
			if (gamestate == GS_TITLELEVEL)
			{
				cmd.buttons = 0;
			}
			else
			{
				cmd.buttons &= BT_USE;
			}
			cmd.pitch = 0;
			cmd.yaw = 0;
			cmd.roll = 0;
			cmd.forwardmove = 0;
			cmd.sidemove = 0;
			cmd.upmove = 0;
			player.turnticks = 0;
		}
		else if (player.cheats & CF_FROZEN)
		{
			cmd.forwardmove = 0;
			cmd.sidemove = 0;
			cmd.upmove = 0;
		}
		return totallyfrozen;
	}

	virtual bool CanCrouch() const
	{
		return player.morphTics == 0 || bCrouchableMorph;
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CrouchMove
	//
	//----------------------------------------------------------------------------

	virtual void CrouchMove(int direction)
	{
		let player = self.player;
		
		double defaultheight = FullHeight;
		double savedheight = Height;
		double crouchspeed = direction * CROUCHSPEED;
		double oldheight = player.viewheight;

		player.crouchdir = direction;
		player.crouchfactor += crouchspeed;

		// check whether the move is ok
		Height  = defaultheight * player.crouchfactor;
		if (!TryMove(Pos.XY, false, NULL))
		{
			Height = savedheight;
			if (direction > 0)
			{
				// doesn't fit
				player.crouchfactor -= crouchspeed;
				return;
			}
		}
		Height = savedheight;

		player.crouchfactor = clamp(player.crouchfactor, 0.5, 1.);
		player.viewheight = ViewHeight * player.crouchfactor;
		player.crouchviewdelta = player.viewheight - ViewHeight;

		// Check for eyes going above/below fake floor due to crouching motion.
		CheckFakeFloorTriggers(pos.Z + oldheight, true);
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckCrouch
	//
	//----------------------------------------------------------------------------

	virtual void CheckCrouch(bool totallyfrozen)
	{
		let player = self.player;
		UserCmd cmd = player.cmd;

		if (cmd.buttons & BT_JUMP)
		{
			cmd.buttons &= ~BT_CROUCH;
		}
		if (CanCrouch() && player.health > 0 && level.IsCrouchingAllowed())
		{
			if (!totallyfrozen)
			{
				int crouchdir = player.crouching;

				if (crouchdir == 0)
				{
					crouchdir = (cmd.buttons & BT_CROUCH) ? -1 : 1;
				}
				else if (cmd.buttons & BT_CROUCH)
				{
					player.crouching = 0;
				}
				if (crouchdir == 1 && player.crouchfactor < 1 && pos.Z + height < ceilingz)
				{
					CrouchMove(1);
				}
				else if (crouchdir == -1 && player.crouchfactor > 0.5)
				{
					CrouchMove(-1);
				}
			}
		}
		else
		{
			player.Uncrouch();
		}

		player.crouchoffset = -(ViewHeight) * (1 - player.crouchfactor);
	}

	//----------------------------------------------------------------------------
	//
	// P_Thrust
	//
	// moves the given origin along a given angle
	//
	//----------------------------------------------------------------------------

	void ForwardThrust (double move, double angle)
	{
		if ((waterlevel || bNoGravity) && Pitch != 0 && !player.GetClassicFlight())
		{
			double zpush = move * sin(Pitch);
			if (waterlevel && waterlevel < 2 && zpush < 0) zpush = 0;
			Vel.Z -= zpush;
			move *= cos(Pitch);
		}
		Thrust(move, angle);
	}

	//----------------------------------------------------------------------------
	//
	// P_Bob
	// Same as P_Thrust, but only affects bobbing.
	//
	// killough 10/98: We apply thrust separately between the real physical player
	// and the part which affects bobbing. This way, bobbing only comes from player
	// motion, nothing external, avoiding many problems, e.g. bobbing should not
	// occur on conveyors, unless the player walks on one, and bobbing should be
	// reduced at a regular rate, even on ice (where the player coasts).
	//
	//----------------------------------------------------------------------------

	void Bob (double angle, double move, bool forward)
	{
		if (forward && (waterlevel || bNoGravity) && Pitch != 0)
		{
			move *= cos(Pitch);
		}
		player.Vel += AngleToVector(angle, move);
	}
	
	//===========================================================================
	//
	// APlayerPawn :: TweakSpeeds
	//
	//===========================================================================

	double, double TweakSpeeds (double forward, double side)
	{
		// Strife's player can't run when its health is below 10
		if (health <= RunHealth)
		{
			forward = clamp(forward, -0x1900, 0x1900);
			side = clamp(side, -0x1800, 0x1800);
		}

		// [GRB]
		if (abs(forward) < 0x3200)
		{
			forward *= ForwardMove1;
		}
		else
		{
			forward *= ForwardMove2;
		}

		if (abs(side) < 0x2800)
		{
			side *= SideMove1;
		}
		else
		{
			side *= SideMove2;
		}

		if (!player.morphTics)
		{
			double factor = 1.;
			for(let it = Inv; it != null; it = it.Inv)
			{
				factor *= it.GetSpeedFactor ();
			}
			forward *= factor;
			side *= factor;
		}
		return forward, side;
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_MovePlayer
	//
	//----------------------------------------------------------------------------

	virtual void MovePlayer ()
	{
		let player = self.player;
		UserCmd cmd = player.cmd;

		// [RH] 180-degree turn overrides all other yaws
		if (player.turnticks)
		{
			player.turnticks--;
			Angle += (180. / TURN180_TICKS);
		}
		else
		{
			Angle += cmd.yaw * (360./65536.);
		}

		player.onground = (pos.z <= floorz) || bOnMobj || bMBFBouncer || (player.cheats & CF_NOCLIP2);

		// killough 10/98:
		//
		// We must apply thrust to the player and bobbing separately, to avoid
		// anomalies. The thrust applied to bobbing is always the same strength on
		// ice, because the player still "works just as hard" to move, while the
		// thrust applied to the movement varies with 'movefactor'.

		if (cmd.forwardmove | cmd.sidemove)
		{
			double forwardmove, sidemove;
			double bobfactor;
			double friction, movefactor;
			double fm, sm;

			[friction, movefactor] = GetFriction();
			bobfactor = friction < ORIG_FRICTION ? movefactor : ORIG_FRICTION_FACTOR;
			if (!player.onground && !bNoGravity && !waterlevel)
			{
				// [RH] allow very limited movement if not on ground.
				movefactor *= level.aircontrol;
				bobfactor*= level.aircontrol;
			}

			fm = cmd.forwardmove;
			sm = cmd.sidemove;
			[fm, sm] = TweakSpeeds (fm, sm);
			fm *= Speed / 256;
			sm *= Speed / 256;

			// When crouching, speed and bobbing have to be reduced
			if (CanCrouch() && player.crouchfactor != 1)
			{
				fm *= player.crouchfactor;
				sm *= player.crouchfactor;
				bobfactor *= player.crouchfactor;
			}

			forwardmove = fm * movefactor * (35 / TICRATE);
			sidemove = sm * movefactor * (35 / TICRATE);

			if (forwardmove)
			{
				Bob(Angle, cmd.forwardmove * bobfactor / 256., true);
				ForwardThrust(forwardmove, Angle);
			}
			if (sidemove)
			{
				let a = Angle - 90;
				Bob(a, cmd.sidemove * bobfactor / 256., false);
				Thrust(sidemove, a);
			}

			if (!(player.cheats & CF_PREDICTING) && (forwardmove != 0 || sidemove != 0))
			{
				PlayRunning ();
			}

			if (player.cheats & CF_REVERTPLEASE)
			{
				player.cheats &= ~CF_REVERTPLEASE;
				player.camera = player.mo;
			}
		}
	}		

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckPitch
	//
	//----------------------------------------------------------------------------

	virtual void CheckPitch()
	{
		let player = self.player;
		// [RH] Look up/down stuff
		if (!level.IsFreelookAllowed())
		{
			Pitch = 0.;
		}
		else
		{
			// The player's view pitch is clamped between -32 and +56 degrees,
			// which translates to about half a screen height up and (more than)
			// one full screen height down from straight ahead when view panning
			// is used.
			int clook = player.cmd.pitch;
			if (clook != 0)
			{
				if (clook == -32768)
				{ // center view
					player.centering = true;
				}
				else if (!player.centering)
				{
					// no more overflows with floating point. Yay! :)
					Pitch = clamp(Pitch - clook * (360. / 65536.), player.MinPitch, player.MaxPitch);
				}
			}
		}
		if (player.centering)
		{
			if (abs(Pitch) > 2.)
			{
				Pitch *= (2. / 3.);
			}
			else
			{
				Pitch = 0.;
				player.centering = false;
				if (PlayerNumber() == consoleplayer)
				{
					LocalViewPitch = 0;
				}
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckJump
	//
	//----------------------------------------------------------------------------

	virtual void CheckJump()
	{
		let player = self.player;
		// [RH] check for jump
		if (player.cmd.buttons & BT_JUMP)
		{
			if (player.crouchoffset != 0)
			{
				// Jumping while crouching will force an un-crouch but not jump
				player.crouching = 1;
			}
			else if (waterlevel >= 2)
			{
				Vel.Z = 4 * Speed;
			}
			else if (bNoGravity)
			{
				Vel.Z = 3.;
			}
			else if (level.IsJumpingAllowed() && player.onground && player.jumpTics == 0)
			{
				double jumpvelz = JumpZ * 35 / TICRATE;
				double jumpfac = 0;

				// [BC] If the player has the high jump power, double his jump velocity.
				// (actually, pick the best factors from all active items.)
				for (let p = Inv; p != null; p = p.Inv)
				{
					let pp = PowerHighJump(p);
					if (pp)
					{
						double f = pp.Strength;
						if (f > jumpfac) jumpfac = f;
					}
				}
				if (jumpfac > 0) jumpvelz *= jumpfac;

				Vel.Z += jumpvelz;
				bOnMobj = false;
				player.jumpTics = -1;
				if (!(player.cheats & CF_PREDICTING)) A_PlaySound("*jump", CHAN_BODY);
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckMoveUpDown
	//
	//----------------------------------------------------------------------------

	virtual void CheckMoveUpDown()
	{
		let player = self.player;
		UserCmd cmd = player.cmd;

		if (cmd.upmove == -32768)
		{ // Only land if in the air
			if (bNoGravity && waterlevel < 2)
			{
				bNoGravity = false;
			}
		}
		else if (cmd.upmove != 0)
		{
			// Clamp the speed to some reasonable maximum.
			cmd.upmove = clamp(cmd.upmove, -0x300, 0x300);
			if (waterlevel >= 2 ||  bFly || (player.cheats & CF_NOCLIP2))
			{
				Vel.Z = Speed * cmd.upmove / 128.;
				if (waterlevel < 2 && !bNoGravity)
				{
					bFly = true;
					bNoGravity = true;
					if ((Vel.Z <= -39) && !(player.cheats & CF_PREDICTING))
					{ // Stop falling scream
						A_StopSound(CHAN_VOICE);
					}
				}
			}
			else if (cmd.upmove > 0 && !(player.cheats & CF_PREDICTING))
			{
				let fly = FindInventory("ArtiFly");
				if (fly != NULL)
				{
					UseInventory(fly);
				}
			}
		}
	}


	//----------------------------------------------------------------------------
	//
	// PROC P_HandleMovement
	//
	//----------------------------------------------------------------------------

	virtual void HandleMovement()
	{
		let player = self.player;
		// [RH] Check for fast turn around
		if (player.cmd.buttons & BT_TURN180 && !(player.oldbuttons & BT_TURN180))
		{
			player.turnticks = TURN180_TICKS;
		}

		// Handle movement
		if (reactiontime)
		{ // Player is frozen
			reactiontime--;
		}
		else
		{
			MovePlayer();
			CheckJump();
			CheckMoveUpDown();
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckUndoMorph
	//
	//----------------------------------------------------------------------------

	virtual void CheckUndoMorph()
	{
		let player = self.player;
		// Morph counter
		if (player.morphTics)
		{
			if (player.chickenPeck)
			{ // Chicken attack counter
				player.chickenPeck -= 3;
			}
			if (!--player.morphTics)
			{ // Attempt to undo the chicken/pig
				player.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT);
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckPoison
	//
	//----------------------------------------------------------------------------

	virtual void CheckPoison()
	{
		let player = self.player;
		if (player.poisoncount && !(level.time & 15))
		{
			player.poisoncount -= 5;
			if (player.poisoncount < 0)
			{
				player.poisoncount = 0;
			}
			player.PoisonDamage(player.poisoner, 1, true);
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckDegeneration
	//
	//----------------------------------------------------------------------------

	virtual void CheckDegeneration()
	{
		// Apply degeneration.
		if (sv_degeneration)
		{
			let player = self.player;
			int maxhealth = GetMaxHealth(true);
			if ((level.time % TICRATE) == 0 && player.health > maxhealth)
			{
				if (player.health - 5 < maxhealth)
					player.health = maxhealth;
				else
					player.health--;

				health = player.health;
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_CheckAirSupply
	//
	//----------------------------------------------------------------------------

	virtual void CheckAirSupply()
	{
		// Handle air supply
		//if (level.airsupply > 0)
		{
			let player = self.player;
			if (waterlevel < 3 || (bInvulnerable) || (player.cheats & (CF_GODMODE | CF_NOCLIP2)) ||	(player.cheats & CF_GODMODE2))
			{
				ResetAirSupply();
			}
			else if (player.air_finished <= level.time && !(level.time & 31))
			{
				DamageMobj(NULL, NULL, 2 + ((level.time - player.air_finished) / TICRATE), 'Drowning');
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC P_PlayerThink
	//
	//----------------------------------------------------------------------------
	
	virtual void PlayerThink()
	{
		let player = self.player;
		UserCmd cmd = player.cmd;
		
		CheckFOV();

		if (player.inventorytics)
		{
			player.inventorytics--;
		}
		CheckCheats();

		if (bJustAttacked)
		{ // Chainsaw/Gauntlets attack auto forward motion
			cmd.yaw = 0;
			cmd.forwardmove = 0xc800/2;
			cmd.sidemove = 0;
			bJustAttacked = false;
		}

		bool totallyfrozen = CheckFrozen();

		// Handle crouching
		CheckCrouch(totallyfrozen);
		CheckMusicChange();

		if (player.playerstate == PST_DEAD)
		{
			DeathThink ();
			return;
		}
		if (player.jumpTics != 0)
		{
			player.jumpTics--;
			if (player.onground && player.jumpTics < -18)
			{
				player.jumpTics = 0;
			}
		}
		if (player.morphTics && !(player.cheats & CF_PREDICTING))
		{
			MorphPlayerThink ();
		}

		CheckPitch();
		HandleMovement();
		CalcHeight ();

		if (!(player.cheats & CF_PREDICTING))
		{
			CheckEnvironment();
			CheckUse();
			CheckUndoMorph();
			// Cycle psprites.
			// Note that after this point the PlayerPawn may have changed due to getting unmorphed so 'self' is no longer safe to use.
			player.mo.TickPSprites();
			// Other Counters
			if (player.damagecount)	player.damagecount--;
			if (player.bonuscount) player.bonuscount--;

			if (player.hazardcount)
			{
				player.hazardcount--;
				if (!(level.time % player.hazardinterval) && player.hazardcount > 16*TICRATE)
					player.mo.DamageMobj (NULL, NULL, 5, player.hazardtype);
			}
			player.mo.CheckPoison();
			player.mo.CheckDegeneration();
			player.mo.CheckAirSupply();
		}
	}
	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	native clearscope int GetMaxHealth(bool withupgrades = false) const;
	native bool ResetAirSupply (bool playgasp = true);
	native clearscope static String GetPrintableDisplayName(Class<Actor> cls);
	native void CheckMusicChange();
	native void CalcHeight ();
	native void CheckEnvironment();
	native void CheckUse();
	native void CheckWeaponButtons();
	native Weapon BestWeapon(class<Ammo> ammotype);

}

class PlayerChunk : PlayerPawn
{
	Default
	{
		+NOSKIN
		-SOLID
		-SHOOTABLE
		-PICKUP
		-NOTDMATCH
		-FRIENDLY
		-SLIDESONWALLS
		-CANPUSHWALLS
		-FLOORCLIP
		-WINDTHRUST
		-TELESTOMP
	}
}

class PSprite : Object native play
{
	enum PSPLayers
	{
		STRIFEHANDS = -1,
		WEAPON = 1,
		FLASH = 1000,
		TARGETCENTER = 0x7fffffff - 2,
		TARGETLEFT,
		TARGETRIGHT,
	};

	native readonly State CurState; 
	native Actor Caller;
	native readonly PSprite Next;
	native readonly PlayerInfo Owner;
	native SpriteID Sprite;
	native int Frame;
	//native readonly int RenderStyle;	had to be blocked because the internal representation was not ok. Renderstyle is still pending a proper solution.
	native readonly int ID;
	native Bool processPending;
	native double x;
	native double y;
	native double oldx;
	native double oldy;
	native double alpha;
	native Bool firstTic;
	native int Tics;
	native bool bAddWeapon;
	native bool bAddBob;
	native bool bPowDouble;
	native bool bCVarFast;
	native bool bFlip;	
	
	native void SetState(State newstate, bool pending = false);

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

	void 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 (bPowDouble && Tics && (Owner.mo.FindInventory ("PowerDoubleFiringSpeed", true))) Tics--;
				if (!Tics) SetState(CurState.NextState);
			}
		}
	}
}

enum EPlayerState
{
	PST_LIVE,	// Playing or camping.
	PST_DEAD,	// Dead on the ground, view follows killer.
	PST_REBORN,	// Ready to restart/respawn???
	PST_ENTER,	// [BC] Entered the game
	PST_GONE	// Player has left the game
}

struct PlayerInfo native play	// this is what internally is known as player_t
{
	// technically engine constants but the only part of the playsim using them is the player.
	const NOFIXEDCOLORMAP = -1;
	const NUMCOLORMAPS = 32;
	
	native PlayerPawn mo;
	native uint8 playerstate;
	native readonly uint buttons;
	native uint original_oldbuttons;
	native Class<PlayerPawn> cls;
	native float DesiredFOV;
	native float FOV;
	native double viewz;
	native double viewheight;
	native double deltaviewheight;
	native double bob;
	native vector2 vel;
	native bool centering;
	native uint8 turnticks;
	native bool attackdown;
	native bool usedown;
	native uint oldbuttons;
	native int health;
	native clearscope int inventorytics;
	native uint8 CurrentPlayerClass;
	native int frags[MAXPLAYERS];
	native int fragcount;
	native int lastkilltime;
	native uint8 multicount;
	native uint8 spreecount;
	native uint16 WeaponState;
	native Weapon ReadyWeapon;
	native Weapon PendingWeapon;
	native PSprite psprites;
	native int cheats;
	native int timefreezer;
	native int16 refire;
	native int16 inconsistent;
	native bool waiting;
	native int killcount;
	native int itemcount;
	native int secretcount;
	native int damagecount;
	native int bonuscount;
	native int hazardcount;
	native int hazardinterval;
	native Name hazardtype;
	native int poisoncount;
	native Name poisontype;
	native Name poisonpaintype;
	native Actor poisoner;
	native Actor attacker;
	native int extralight;
	native int16 fixedcolormap;
	native int16 fixedlightlevel;
	native int morphtics;
	native Class<PlayerPawn>MorphedPlayerClass;
	native int MorphStyle;
	native Class<Actor> MorphExitFlash;
	native Class<Weapon> PremorphWeapon;
	native int chickenPeck;
	native int jumpTics;
	native bool onground;
	native int respawn_time;
	native Actor camera;
	native int air_finished;
	native Name LastDamageType;
	native Actor MUSINFOactor;
	native int8 MUSINFOtics;
	native bool settings_controller;
	native int8 crouching;
	native int8 crouchdir;
	native Bot bot;
	native float BlendR;
	native float BlendG;
	native float BlendB;
	native float BlendA;
	native String LogText;
	native double MinPitch;
	native double MaxPitch;
	native double crouchfactor;
	native double crouchoffset;
	native double crouchviewdelta;
	native Actor ConversationNPC;
	native Actor ConversationPC;
	native double ConversationNPCAngle;
	native bool ConversationFaceTalker;
	native @WeaponSlots weapons;
	native @UserCmd cmd;
	native readonly @UserCmd original_cmd;


	native bool MorphPlayer(playerinfo p, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null);
	native bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false);
	native bool PoisonPlayer(Actor poisoner, Actor source, int poison);
	native void PoisonDamage(Actor source, int damage, bool playPainSound);
	native void SetPsprite(int id, State stat, bool pending = false);
	native void SetSafeFlash(Weapon weap, State flashstate, int index);
	native PSprite GetPSprite(int id) const;
	native PSprite FindPSprite(int id) const;
	native void SetLogNumber (int text);
	native void SetLogText (String text);
	native void DropWeapon();
	native void BringUpWeapon();
	native bool Resurrect();

	native String GetUserName() const;
	native Color GetColor() const;
	native Color GetDisplayColor() const;
	native int GetColorSet() const;
	native int GetPlayerClassNum() const;
	native int GetSkin() const;
	native bool GetNeverSwitch() const;
	native int GetGender() const;
	native int GetTeam() const;
	native float GetAutoaim() const;
	native bool GetNoAutostartMap() const;
	native void SetFOV(float fov);
	native bool GetClassicFlight() const;
	native clearscope bool HasWeaponsInSlot(int slot) const;

	bool IsTotallyFrozen()
	{
		return
			gamestate == GS_TITLELEVEL ||
			(cheats & CF_TOTALLYFROZEN) ||
			(level.frozen && timefreezer == 0);
	}

	void Uncrouch()
	{
		if (crouchfactor != 1)
		{
			crouchfactor = 1;
			crouchoffset = 0;
			crouchdir = 0;
			crouching = 0;
			crouchviewdelta = 0;
			viewheight = mo.ViewHeight;
		}
	}
	

	clearscope int fragSum () const
	{
		int i;
		int allfrags = 0;
		int playernum = mo.PlayerNumber();
	
		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (playeringame[i]
				&& i!=playernum)
			{
				allfrags += frags[i];
			}
		}
		
		// JDC hack - negative frags.
		allfrags -= frags[playernum];
		return allfrags;
	}
	
}

struct PlayerClass native
{
	native class<Actor> Type;
	native uint Flags;
	native Array<int> Skins;
	
	native bool CheckSkin(int skin);
	native void EnumColorsets(out Array<int> data);
	native Name GetColorsetName(int setnum);
}

struct PlayerSkin native
{
	native readonly String		SkinName;
	native readonly String		Face;
	native readonly uint8		gender;
	native readonly uint8		range0start;
	native readonly uint8		range0end;
	native readonly bool		othergame;
	native readonly Vector2		Scale;
	native readonly int			sprite;
	native readonly int			crouchsprite;
	native readonly int			namespc;
};

struct Team native
{
	const NoTeam = 255;
	const Max = 16;
	native String mName;
}
