// Emacs style mode select	 -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
//		Player related stuff.
//		Bobbing POV/weapon, movement.
//		Pending weapon.
//
//-----------------------------------------------------------------------------




#include "doomdef.h"
#include "d_event.h"
#include "p_local.h"
#include "doomstat.h"
#include "s_sound.h"
#include "i_system.h"
#include "d_ticcmd.h"	// [BC] New #include's
#include "m_random.h"
#include "dstrings.h"
#include "p_inter.h"
#include "hu_stuff.h"
#include "r_main.h"
#include "sv_main.h"
#include "v_video.h"


//
// Movement.
//

// 16 pixels of bob
#define MAXBOB			0x100000

BOOL onground;
BOOL onceiling;	// [BC]

// This function supplements the pointer cleanup in dobject.cpp, because
// player_s is not derived from DObject. (I tried it, and DestroyScan was
// unable to properly determine the player object's type--possibly
// because it gets staticly allocated in an array.)
//
// This function checks all the DObject pointers in a player_s and NULLs any
// that match the pointer passed in. If you add any pointers that point to
// DObject (or a subclass), add them here too.

void player_s::FixPointers (const DObject *obj)
{
	if (mo == obj)
		mo = NULL;
}


//
// P_Thrust
// Moves the given origin along a given angle.
//
void P_SideThrust (player_t *player, angle_t angle, fixed_t move)
{
	angle = (angle - ANGLE_90) >> ANGLETOFINESHIFT;

	player->mo->momx += FixedMul (move, finecosine[angle]);
	player->mo->momy += FixedMul (move, finesine[angle]);
}

void P_ForwardThrust (player_t *player, angle_t angle, fixed_t move)
{
	angle >>= ANGLETOFINESHIFT;

	if ((player->mo->waterlevel || (player->mo->flags2 & MF2_FLY))
		&& player->mo->pitch != 0)
	{
		angle_t pitch = (angle_t)player->mo->pitch >> ANGLETOFINESHIFT;
		fixed_t zpush = FixedMul (move, finesine[pitch]);
		if (player->mo->waterlevel && player->mo->waterlevel < 2 && zpush < 0)
			zpush = 0;
		player->mo->momz -= zpush;
		move = FixedMul (move, finecosine[pitch]);
	}
	player->mo->momx += FixedMul (move, finecosine[angle]);
	player->mo->momy += FixedMul (move, finesine[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 P_Bob (player_t *player, angle_t angle, fixed_t move)
{
	angle >>= ANGLETOFINESHIFT;

	player->momx += FixedMul(move,finecosine[angle]);
	player->momy += FixedMul(move,finesine[angle]);
}

//
// P_CalcHeight
// Calculate the walking / running height adjustment
//
void P_CalcHeight (player_t *player) 
{
	int 		angle;
	fixed_t 	bob;

	// Regular movement bobbing
	// (needs to be calculated for gun swing even if not on ground)
	// OPTIMIZE: tablify angle

	// killough 10/98: Make bobbing depend only on player-applied motion.
	//
	// Note: don't reduce bobbing here if on ice: if you reduce bobbing here,
	// it causes bobbing jerkiness when the player moves from ice to non-ice,
	// and vice-versa.

	if ((player->mo->flags2 & MF2_FLY) && (!onground || !onceiling))	// [BC]
	{
		player->bob = FRACUNIT / 2;
	}
	else
	{
		player->bob = FixedMul (player->momx, player->momx)
					+ FixedMul (player->momy, player->momy);
		player->bob >>= 2;

		if (player->bob > MAXBOB)
			player->bob = MAXBOB;
	}

	if ((!onceiling && !onground) || (player->cheats & CF_NOMOMENTUM))	// [BC]
	{
//		if (!(player->mo->flags2 & MF2_REVERSEGRAV))	// [BC]
			player->viewz = player->mo->z + VIEWHEIGHT;
//		else	// [BC]
//			player->viewz = player->mo->z;

		if (player->viewz > player->mo->ceilingz-4*FRACUNIT)
			player->viewz = player->mo->ceilingz-4*FRACUNIT;

		return;
	}
				
	angle = (FINEANGLES/20*level.time) & FINEMASK;
	bob = FixedMul (player->bob>>(player->mo->waterlevel > 1 ? 2 : 1), finesine[angle]);

	// move viewheight
	if (player->playerstate == PST_LIVE)
	{
		player->viewheight += player->deltaviewheight;

		if (player->viewheight > VIEWHEIGHT)
		{
			player->viewheight = VIEWHEIGHT;
			player->deltaviewheight = 0;
		}
		else if (player->viewheight < VIEWHEIGHT/2)
		{
			player->viewheight = VIEWHEIGHT/2;
			if (player->deltaviewheight <= 0)
				player->deltaviewheight = 1;
		}
		
		if (player->deltaviewheight)	
		{
			player->deltaviewheight += FRACUNIT/4;
			if (!player->deltaviewheight)
				player->deltaviewheight = 1;
		}
	}
	player->viewz = player->mo->z + player->viewheight + bob;

	if (player->viewz > player->mo->ceilingz-4*FRACUNIT)
		player->viewz = player->mo->ceilingz-4*FRACUNIT;
}



//
// P_MovePlayer
//
void P_MovePlayer (player_t *player)
{
	ticcmd_t *cmd = &player->cmd;
	AActor *mo = player->mo;

	if (levelTics && netstate == NS_CLIENT)
		memset (cmd, 0, sizeof(ticcmd_t));
	
	if (netstate == NS_CLIENT)
	{
		if (player->jumpTics)
			player->jumpTics--;

		// Move around.
		// Reactiontime is used to prevent movement
		//	for a bit after a teleport.
		if (player->mo->reactiontime)
		{
			player->mo->reactiontime--;
			return;
		}

		// [RH] check for jump
		if ((cmd->ucmd.buttons & BT_JUMP) == BT_JUMP)
		{
			onground = (player->mo->z <= player->mo->floorz) || (player->mo->flags2 & MF2_ONMOBJ);

			if (player->mo->waterlevel >= 2)
			{
				player->mo->momz = 4*FRACUNIT;
				if (player->rune == ru_highjump)	// [BC]
					player->mo->momz *= 2;
			}
			else if (player->mo->flags2 & MF2_FLY)	
					player->mo->momz = 3*FRACUNIT;
			else if (!(dmflags & DF_NO_JUMP) && onground && !player->jumpTics && !oldschool.value)
			{
				if (player->mo->subsector->sector->springpadzone)
					player->mo->momz += 4*FRACUNIT;
				else
					player->mo->momz += 8*FRACUNIT;
				if (player->rune == ru_highjump)	// [BC]
					player->mo->momz *= 2;
				S_Sound (player->mo, CHAN_BODY, "*jump1", 1, ATTN_NORM);
				player->mo->flags2 &= ~MF2_ONMOBJ;
				player->jumpTics = 18;
				if (player->mo->subsector->sector->springpadzone)
					player->jumpTics /= 2;
				if (player->rune == ru_highjump)	// [BC]
					player->jumpTics *= 2;
			}
			else if (!(dmflags & DF_NO_JUMP) && onceiling && !player->jumpTics && !oldschool.value) // [BC]
			{
				if (player->mo->subsector->sector->springpadzone)
					player->mo->momz -= 4*FRACUNIT;
				else
					player->mo->momz -= 8*FRACUNIT;
				if (player->rune == ru_highjump)
					player->mo->momz *= 2;
				S_Sound (player->mo, CHAN_BODY, "*jump1", 1, ATTN_NORM);
				player->jumpTics = 18;
				if (player->mo->subsector->sector->springpadzone)
					player->jumpTics /= 2;
				if (player->rune == ru_highjump)
					player->jumpTics *= 2;
			}
		}

		if (cmd->ucmd.upmove &&
			(player->mo->waterlevel >= 2 || player->mo->flags2 & MF2_FLY))
		{
			player->mo->momz = cmd->ucmd.upmove << 8;
		}

		if (player->mo->subsector->sector->special || player->mo->subsector->sector->damage
			|| player->mo->subsector->sector->SSpecial)
			P_PlayerInSpecialSector (player);
	}

	mo->angle += cmd->ucmd.yaw << 16;

	onground = (mo->z <= mo->floorz) || (mo->flags2 & MF2_ONMOBJ);
	onceiling = ((mo->z + mo->height) >= mo->ceilingz);	// [BC]

	// [RH] Don't let frozen players move
	if (player->cheats & CF_FROZEN)
		return;

	// 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->ucmd.forwardmove | cmd->ucmd.sidemove)
	{
		fixed_t forwardmove, sidemove;
		int bobfactor;
		int friction, movefactor;

		movefactor = P_GetMoveFactor (mo, &friction);
		bobfactor = friction < ORIG_FRICTION ? movefactor : ORIG_FRICTION_FACTOR;
		if (!onground && !onceiling && // [BC] Allow normal movement on ceiling
			!(player->mo->flags2 & MF2_FLY) && !player->mo->waterlevel)
		{
			// [RH] allow very limited movement if not on ground.
			// [BC] Bah! Changed.
			movefactor /= 4;
			bobfactor /= 4;
		}

		forwardmove = (cmd->ucmd.forwardmove * movefactor) >> 8;
		sidemove = (cmd->ucmd.sidemove * movefactor) >> 8;

		if (forwardmove)
		{
			// [BC] Handle haste
			if (player->rune == ru_haste)
				forwardmove += ((cmd->ucmd.forwardmove * movefactor) >> 8)/4;
			if (player->powers[pw_turbosphere])
				forwardmove += ((cmd->ucmd.forwardmove * movefactor) >> 8)/2;

			if (player && player->grab && player->usedown)	// [BC]
			{
				// [BC] Don't run faster than you would normally if pulling a light object
//				if (player->touch->info->mass >= 50)
				forwardmove /= (player->grab->info->mass / 50);
				P_ForwardThrust (player, mo->angle, forwardmove);	// [BC] Don't bob when pulling
				player->grab->momx = player->mo->momx;
				player->grab->momy = player->mo->momy;
			}
			else
			{
				P_Bob (player, mo->angle, (cmd->ucmd.forwardmove * bobfactor) >> 8);
				P_ForwardThrust (player, mo->angle, forwardmove);
			}
		}
		if (sidemove)
		{
			if (player->rune == ru_haste)
				sidemove += ((cmd->ucmd.sidemove * movefactor) >> 8)/4;
			if (player->powers[pw_turbosphere])
				sidemove += ((cmd->ucmd.sidemove * movefactor) >> 8)/2;

			if (player && player->grab && player->usedown)	// [BC]
			{
				sidemove /= (player->grab->info->mass / 50);
				P_SideThrust (player, mo->angle, sidemove);
				player->grab->momx = player->mo->momx;
				player->grab->momy = player->mo->momy;
			}
			else
			{
				P_Bob (player, mo->angle-ANG90, (cmd->ucmd.sidemove * bobfactor) >> 8);
				P_SideThrust (player, mo->angle, sidemove);
			}
		}

		if (mo->state == &states[S_PLAY])
		{
			P_SetMobjState (player->mo, S_PLAY_RUN1);
		}

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

// [RH] (Adapted from Q2)
// P_FallingDamage
//
void P_FallingDamage (AActor *ent)
{
	float	delta;
	int		damage;

	if (!ent->player)
		return;		// not a player

	if (ent->flags & MF_NOCLIP)
		return;

	if ((ent->player->oldvelocity[2] < 0)
		&& (ent->momz > ent->player->oldvelocity[2])
		&& (!(ent->flags2 & MF2_ONMOBJ)
			|| !(ent->z <= ent->floorz)))
	{
		delta = (float)ent->player->oldvelocity[2];
	}
	else
	{
		if (!(ent->flags2 & MF2_ONMOBJ))
			return;
		delta = (float)(ent->momz - ent->player->oldvelocity[2]);
	}
	delta = delta*delta * 2.03904313e-11f;

	if (delta < 1)
		return;

	if (delta < 15)
	{
		//ent->s.event = EV_FOOTSTEP;
		return;
	}

	if (delta > 30)
	{
		damage = (int)((delta-30)/2);
		if (damage < 1)
			damage = 1;

		if (dmflags & DF_YES_FALLING || level.flags & LEVEL_FORCEFALLINGDAMAGE)	// [BC]
			P_DamageMobj (ent, NULL, NULL, damage, MOD_FALLING);
	}
	else
	{
		//ent->s.event = EV_FALLSHORT;
		return;
	}

}


//
// P_DeathThink
// Fall on your face when dying.
// Decrease POV height to floor height.
//
#define ANG5	(ANG90/18)

void P_DeathThink (player_t *player)
{
	angle_t 			angle;
	angle_t 			delta;

	P_MovePsprites (player);
	onground = (player->mo->z <= player->mo->floorz);
	onceiling = ((player->mo->z + player->mo->height) >= player->mo->ceilingz);	// [BC]
	
	// fall to the ground
	if (player->viewheight > 6*FRACUNIT)
		player->viewheight -= FRACUNIT;

	if (player->viewheight < 6*FRACUNIT)
		player->viewheight = 6*FRACUNIT;

	player->deltaviewheight = 0;
	P_CalcHeight (player);
		
	if (player->attacker && player->attacker != player->mo)
	{
		angle = R_PointToAngle2 (player->mo->x,
								 player->mo->y,
								 player->attacker->x,
								 player->attacker->y);
		
		delta = angle - player->mo->angle;
		
		if (delta < ANG5 || delta > (unsigned)-ANG5)
		{
			// Looking at killer,
			//	so fade damage flash down.
			player->mo->angle = angle;

			if (player->damagecount)
				player->damagecount--;
		}
		else if (delta < ANG180)
			player->mo->angle += ANG5;
		else
			player->mo->angle -= ANG5;
	}
	else if (player->damagecount)
		player->damagecount--;
		

	if (netstate == NS_CLIENT)
		return;

	// [RH] Delay rebirth slightly
	if (level.time >= player->respawn_time&& !(dmflags2 & DF2_FORCE_NORESPAWN))	// [BC]
	{
		if (player->cmd.ucmd.buttons & BT_USE || ((deathmatch.value || teamgame.value || alwaysapplydmflags.value) && dmflags & DF_FORCE_RESPAWN)) // [BC]
			player->playerstate = PST_REBORN;
	}
}

// P_PlayerLookUpDown
//
void P_PlayerLookUpDown (player_t *p)
{
	ticcmd_t *cmd = &p->cmd;

	if (levelTics)
		memset (cmd, 0, sizeof(ticcmd_t));
		
	// [RH] Look up/down stuff
	if (dmflags & DF_NO_FREELOOK || oldschool.value)
	{
		p->mo->pitch = 0;
	}
	else
	{
		int look = cmd->ucmd.pitch << 16;

		// 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.
		if (look)
		{
			if (look == -32768 << 16)
			{ // center view
				p->mo->pitch = 0;
			}
			else if (look > 0)
			{ // look up
				p->mo->pitch -= look;
				if (p->mo->pitch < -ANGLE_1*32)
					p->mo->pitch = -ANGLE_1*32;
			}
			else
			{ // look down
				p->mo->pitch -= look;
				if (p->mo->pitch > ANGLE_1*56)
					p->mo->pitch = ANGLE_1*56;
			}
		}
	}
}

//
// P_PlayerThink
//
void P_PlayerThink (player_t *player)
{
	ticcmd_t *cmd;
	weapontype_t newweapon;

	// [RH] Error out if player doesn't have an mobj, but just make
	//		it a warning if the player trying to spawn is a bot
	if (!player->mo)
	{
		if (player->skullb)	// [BC]
		{
			Printf (PRINT_HIGH, "%s left: No player %d start\n",
				player->userinfo.netname, player-players+1);
			ClearPlayer (player - players);
			return;
		}
		else
		{
			I_Error ("No player %ld start\n", player - players + 1);
		}
	}

	player->xviewshift = 0;		// [RH] Make sure view is in right place

	if (netstate == NS_CLIENT)
	{
		// chain saw run forward
		cmd = &player->cmd;

		if (levelTics)
			memset (cmd, 0, sizeof(ticcmd_t));

		if (player->mo->flags & MF_JUSTATTACKED)
		{
			cmd->ucmd.yaw = 0;
			cmd->ucmd.forwardmove = 0xc800/2;
			cmd->ucmd.sidemove = 0;
			player->mo->flags &= ~MF_JUSTATTACKED;
		}

		// [BC] Handle medals
		P_MedalThink (player);

		// Check for weapon change.

		// A special event has no other buttons.
		if (cmd->ucmd.buttons & BT_SPECIAL)
			cmd->ucmd.buttons = 0;

		// check for use
		if (cmd->ucmd.buttons & BT_USE)
		{
			if (!player->usedown)
			{
				P_UseLines (player);
				P_UseItems (player);	// [BC]
				player->usedown = true;
			}
		}
		else
			player->usedown = false;

		// cycle psprites
		P_MovePsprites (player);

		// Counters, time dependent power ups.

		// Strength counts up to diminish fade.
		if (player->powers[pw_strength])
			player->powers[pw_strength]++;	
				
		if (player->powers[pw_invulnerability])
			player->powers[pw_invulnerability]--;

		if (player->powers[pw_invisibility])
			if (! --player->powers[pw_invisibility] )
				player->mo->flags &= ~MF_SHADOW;
						
		if (player->powers[pw_infrared])
			player->powers[pw_infrared]--;
				
		if (player->powers[pw_ironfeet])
			player->powers[pw_ironfeet]--;
				
		if ((player->powers[pw_turbosphere]) == 4*32)
			Printf (PRINT_HIGH, "Turbosphere wearing off...\n");

		if (player->powers[pw_turbosphere])
			player->powers[pw_turbosphere]--;

		if (player->powers[pw_floatingfeet] == 4*32)
			Printf (PRINT_HIGH, "Anti-gravity belt wearing off...\n");

		if (player->powers[pw_floatingfeet])
			player->powers[pw_floatingfeet]--;

		if (player->powers[pw_infragoggles])
			player->powers[pw_infragoggles]--;

		if (player->powers[pw_translucency])
			if (! --player->powers[pw_translucency] )
				player->mo->translucency = FRACUNIT;

		if (player->powers[pw_doomsphere])
			player->powers[pw_doomsphere]--;

		if (player->damagecount)
			player->damagecount--;
				
		if (player->bonuscount)
			player->bonuscount--;

		if (player->runeTics)
			player->runeTics--;

		if (player->nameTics)
			player->nameTics--;

		if ((cmd->ucmd.buttons & BT_TAUNT) && !((cmd->ucmd.buttons & BT_JUMP) == BT_JUMP) && !(cmd->ucmd.buttons & BT_CHANGE))
		{
			if (!player->tauntdown)
			{
				P_Taunt (player);
				player->tauntdown = true;
			}
		}
		else
			player->tauntdown = false;

		// Handling colormaps.
		if (player->powers[pw_freezer] > 4*32 || (player->powers[pw_freezer]&8) ) 	// [BC]
			player->mo->fixedcolormap = GOLDCOLORMAP;
		else if (player->powers[pw_invulnerability] > 4*32 || (player->powers[pw_invulnerability]&8) )
			player->mo->fixedcolormap = INVERSECOLORMAP;
		else if (player->powers[pw_doomsphere] > 4*32 || (player->powers[pw_doomsphere]&8) ) 	// [BC]
			player->mo->fixedcolormap = REDCOLORMAP;
		else if (player->powers[pw_infrared] > 4*32	|| (player->powers[pw_infrared]&8) )
			player->mo->fixedcolormap = 1;			// almost full bright
		else
			player->mo->fixedcolormap = 0;

		// Handle air supply
		if (player->mo->waterlevel < 3 || player->powers[pw_ironfeet])
		{
			player->air_finished = level.time + 10*TICRATE;
		}
		else if (player->air_finished <= level.time && !(level.time & 31))
		{
			P_DamageMobj (player->mo, NULL, NULL, 2 + 2*((level.time-player->air_finished)/TICRATE), MOD_WATER, DMG_NO_ARMOR);
		}
	}
	else
	{
		// fixme: do this in the cheat code
		if (player->cheats & CF_NOCLIP)
			player->mo->flags |= MF_NOCLIP;
		else
			player->mo->flags &= ~MF_NOCLIP;
/*		if (player->cheats & CF_FLY)
			player->mo->flags |= MF_NOGRAVITY, player->mo->flags2 |= MF2_FLY;
		else
			player->mo->flags &= ~MF_NOGRAVITY, player->mo->flags2 &= ~MF2_FLY;*/

		// chain saw run forward
		cmd = &player->cmd;

		// [BC] Don't move once fraglimit, etc. has been hit
		if (levelTics)
			memset (cmd, 0, sizeof(ticcmd_t));

		if (player->mo->flags & MF_JUSTATTACKED)
		{
			cmd->ucmd.yaw = 0;
			cmd->ucmd.forwardmove = 0xc800/2;
			cmd->ucmd.sidemove = 0;
			player->mo->flags &= ~MF_JUSTATTACKED;
		}

		// [BC] Handle medals
		P_MedalThink (player);

		if (player->playerstate == PST_DEAD)
		{
			P_DeathThink (player);
			return;
		}

		// [BC] Handle degeneration
		if (dmflags2 & DF2_YES_DEGENERATION)
		{
			if (!(level.time&0x7f) && player->health > player->maxlowhealth)
			{
				player->health--;
				player->mo->health--;
			}
		}

		// [BC] Handle regeneration
		if (!(level.time&0x1f) && player->rune == ru_regeneration)
		{
			if (P_GiveBody(player, 5))
				S_Sound (player->mo, CHAN_ITEM, "misc/i_pkup", 1, ATTN_NORM);
		}

		if (player->jumpTics)
			player->jumpTics--;

		// [BC]
		if (player->runeTics)
			player->runeTics--;

		// [BC]
		if (player->nameTics)
			player->nameTics--;

		if ((cmd->ucmd.buttons & BT_TAUNT) && !((cmd->ucmd.buttons & BT_JUMP) == BT_JUMP) && !(cmd->ucmd.buttons & BT_CHANGE))
		{
			if (!player->tauntdown)
			{
				P_Taunt (player);
				player->tauntdown = true;
			}
		}
		else
			player->tauntdown = false;

		// [RH] Look up/down stuff
		if (dmflags & DF_NO_FREELOOK || oldschool.value)
		{
			player->mo->pitch = 0;
		}
		else
		{
			int look = cmd->ucmd.pitch << 16;

			// 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.
			if (look)
			{
				if (look == -32768 << 16)
				{ // center view
					player->mo->pitch = 0;
				}
				else if (look > 0)
				{ // look up
					player->mo->pitch -= look;
					if (player->mo->pitch < -ANGLE_1*32)
						player->mo->pitch = -ANGLE_1*32;
				}
				else
				{ // look down
					player->mo->pitch -= look;
					if (player->mo->pitch > ANGLE_1*56)
						player->mo->pitch = ANGLE_1*56;
				}
			}
		}

		// Move around.
		// Reactiontime is used to prevent movement
		//	for a bit after a teleport.
		if (player->mo->reactiontime)
			player->mo->reactiontime--;
		else
		{
			// [RH] check for jump
			if ((cmd->ucmd.buttons & BT_JUMP) == BT_JUMP)
			{
				onground = (player->mo->z <= player->mo->floorz) || (player->mo->flags2 & MF2_ONMOBJ);

				if (player->mo->waterlevel >= 2)
				{
					player->mo->momz = 4*FRACUNIT;
					if (player->rune == ru_highjump)	// [BC]
						player->mo->momz *= 2;
				}
				else if (player->mo->flags2 & MF2_FLY)	
						player->mo->momz = 3*FRACUNIT;
				else if (!(dmflags & DF_NO_JUMP) && onground && !player->jumpTics && !oldschool.value)
				{
					if (player->mo->subsector->sector->springpadzone)
						player->mo->momz += 4*FRACUNIT;
					else
						player->mo->momz += 8*FRACUNIT;
					if (player->rune == ru_highjump)	// [BC]
						player->mo->momz *= 2;
					S_Sound (player->mo, CHAN_BODY, "*jump1", 1, ATTN_NORM);
					player->mo->flags2 &= ~MF2_ONMOBJ;
					player->jumpTics = 18;
					if (player->mo->subsector->sector->springpadzone)
						player->jumpTics /= 2;
					if (player->rune == ru_highjump)	// [BC]
						player->jumpTics *= 2;
				}
				else if (!(dmflags & DF_NO_JUMP) && onceiling && !player->jumpTics && !oldschool.value) // [BC]
				{
					if (player->mo->subsector->sector->springpadzone)
						player->mo->momz -= 4*FRACUNIT;
					else
						player->mo->momz -= 8*FRACUNIT;
					if (player->rune == ru_highjump)
						player->mo->momz *= 2;
					S_Sound (player->mo, CHAN_BODY, "*jump1", 1, ATTN_NORM);
					player->jumpTics = 18;
					if (player->mo->subsector->sector->springpadzone)
						player->jumpTics /= 2;
					if (player->rune == ru_highjump)
						player->jumpTics *= 2;
				}
			}

			if (cmd->ucmd.upmove &&
				(player->mo->waterlevel >= 2 || player->mo->flags2 & MF2_FLY))
			{
				player->mo->momz = cmd->ucmd.upmove << 8;
			}

			P_MovePlayer (player);
		}

		P_CalcHeight (player);

		if (player->mo->subsector->sector->special || player->mo->subsector->sector->damage
			|| player->mo->subsector->sector->SSpecial)	// [BC]
			P_PlayerInSpecialSector (player);

		// Check for weapon change.

		// A special event has no other buttons.
		if (cmd->ucmd.buttons & BT_SPECIAL)
			cmd->ucmd.buttons = 0;						
	
		if ((cmd->ucmd.buttons & BT_CHANGE) || cmd->ucmd.impulse >= 50)
		{
			// [RH] Support direct weapon changes
			if (cmd->ucmd.impulse) {
				newweapon = (weapontype_t)(cmd->ucmd.impulse - 50);
			} else {
				// The actual changing of the weapon is done
				//	when the weapon psprite can do it
				//	(read: not in the middle of an attack).
				newweapon = (weapontype_t)((cmd->ucmd.buttons&BT_WEAPONMASK)>>BT_WEAPONSHIFT);
			
				if (newweapon == wp_fist
					&& player->weaponowned[wp_chainsaw]
					&& !(player->readyweapon == wp_chainsaw
						 && player->powers[pw_strength]))
				{
					newweapon = wp_chainsaw;
				}
				
				if (newweapon == wp_shotgun 
					&& player->weaponowned[wp_supershotgun]
					&& player->readyweapon != wp_supershotgun)
				{
					newweapon = wp_supershotgun;
				}
	// [BC] RL and GL are paired, and so are the plasmagun and railgun. Players
	// are allowed to choose which weapon they want to come up first. Personally,
	// I like to have the grenade launcher and railgun come up first :)

				if (newweapon == wp_missile)
				{
					if (player->weaponowned[wp_grenlauncher] && player->weaponowned[wp_missile])
					{
						if (player->readyweapon != wp_missile && player->readyweapon != wp_grenlauncher)
							newweapon = player->userinfo.rocketpreferred ? wp_missile : wp_grenlauncher;
						else
						{
							if (player->readyweapon == wp_missile)
								newweapon = wp_grenlauncher;
							else
								newweapon = wp_missile;
						}
					}
					else if (player->weaponowned[wp_grenlauncher])
						newweapon = wp_grenlauncher;
					else
						newweapon = wp_missile;
				}

				if (newweapon == wp_plasma)
				{
					if (player->weaponowned[wp_railgun] && player->weaponowned[wp_plasma])
					{
						if (player->readyweapon != wp_plasma && player->readyweapon != wp_railgun)
							newweapon = player->userinfo.plasmapreferred ? wp_plasma : wp_railgun;
						else
						{
							if (player->readyweapon == wp_plasma)
								newweapon = wp_railgun;
							else
								newweapon = wp_plasma;
						}
					}
					else if (player->weaponowned[wp_railgun])
						newweapon = wp_railgun;
					else
						newweapon = wp_plasma;
				}

				if (newweapon == wp_bfg 
					&& player->weaponowned[wp_bfg10k] && player->readyweapon != wp_bfg10k)
					newweapon = wp_bfg10k;

			}

			if ((newweapon >= 0 && newweapon < NUMWEAPONS)
				&& player->weaponowned[newweapon]
				&& newweapon != player->readyweapon)
			{
				player->pendingweapon = newweapon;
				if (netstate == NS_SERVER)
				{
					int playernum;
					for (playernum=0; playernum<MAXPLAYERS; playernum++)
						if (player == &players[playernum])
							break;
			
					MSG_WriteByte (&clients[playernum].reliablebuf, svc_changeweapon);
					MSG_WriteByte (&clients[playernum].reliablebuf, (byte)player->pendingweapon);
				}
			}
		}

		// check for use
		if (cmd->ucmd.buttons & BT_USE)
		{
			if (!player->usedown)
			{
				P_UseLines (player);
				P_UseItems (player);	// [BC]
				player->usedown = true;
			}
		}
		else
			player->usedown = false;

		// cycle psprites
		P_MovePsprites (player);

		// Counters, time dependent power ups.

		// Strength counts up to diminish fade.
		if (player->powers[pw_strength])
			player->powers[pw_strength]++;	
				
		if (player->powers[pw_invulnerability])
			player->powers[pw_invulnerability]--;

		if (player->powers[pw_invisibility])
			if (! --player->powers[pw_invisibility] )
				player->mo->flags &= ~MF_SHADOW;
						
		if (player->powers[pw_infrared])
			player->powers[pw_infrared]--;
				
		if (player->powers[pw_ironfeet])
			player->powers[pw_ironfeet]--;

	// [BC] Handle new powerups
		if ((player->powers[pw_turbosphere]) == 4*32)
			Printf (PRINT_HIGH, "Turbosphere wearing off...\n");

		if (player->powers[pw_turbosphere])
			player->powers[pw_turbosphere]--;

		if (player->powers[pw_floatingfeet] == 4*32)
			Printf (PRINT_HIGH, "Anti-gravity belt wearing off...\n");

		if (player->powers[pw_floatingfeet])
			player->powers[pw_floatingfeet]--;

		if (player->powers[pw_infragoggles])
			player->powers[pw_infragoggles]--;

		if (player->powers[pw_translucency])
			if (! --player->powers[pw_translucency] )
				player->mo->translucency = FRACUNIT;

		if (player->powers[pw_doomsphere])
			player->powers[pw_doomsphere]--;

		if (player->powers[pw_floatingfeet])
		{
			if (player->mo->subsector->sector->antigravityzone)
			{
				player->mo->flags2 &= ~MF2_REVERSEGRAV;
				if (! --player->powers[pw_floatingfeet])
					player->mo->flags2 |= MF2_REVERSEGRAV;
			}
			else
			{
				player->mo->flags2 |= MF2_REVERSEGRAV;
				if (! --player->powers[pw_floatingfeet])
					player->mo->flags2 &= ~MF2_REVERSEGRAV;	
			}
		}

		if (player->powers[pw_infratracker])
			player->powers[pw_infratracker]--;

		if (player->powers[pw_freezer])
		{
			S_PauseSound ();
			if (! --player->powers[pw_freezer])
			{
				S_ResumeSound ();
				player->cheats &= ~CF_FREEZE;
				freeze = false;
			}
		}

		if (player->powers[pw_freezer])
		{
			if (!(player->cheats & CF_FREEZE))
			{
				if (player->powers[pw_freezer] > 4*32 
					|| ((player->powers[pw_freezer] > 3*32 && player->powers[pw_freezer] <= 4*32) && player->powers[pw_freezer] % 16 != 0)
					|| ((player->powers[pw_freezer] > 2*32 && player->powers[pw_freezer] <= 3*32) && player->powers[pw_freezer] % 8 != 0) 
					|| ((player->powers[pw_freezer] > 32 && player->powers[pw_freezer] <= 2*32) && player->powers[pw_freezer] % 4 != 0)
					|| ((player->powers[pw_freezer] > 0 && player->powers[pw_freezer] <= 1*32) && player->powers[pw_freezer] % 2 != 0))
					freeze = true;
				else
					freeze = false;
			}
		}

		if (player->damagecount)
			player->damagecount--;
				
		if (player->bonuscount)
			player->bonuscount--;


		// Handling colormaps.
		// [BC] Cleaned up this part a bit... although it's hard to tell :)

		player->mo->fixedcolormap = 0;

		if (player->powers[pw_invulnerability] > 4*32 || (player->powers[pw_invulnerability]&8) )
			player->mo->fixedcolormap = INVERSECOLORMAP;
		if (player->powers[pw_freezer] > 4*32 || (player->powers[pw_freezer]&8) ) 	// [BC]
			player->mo->fixedcolormap = GOLDCOLORMAP;
		if (player->powers[pw_doomsphere] > 4*32 || (player->powers[pw_doomsphere]&8) ) 	// [BC]
			player->mo->fixedcolormap = REDCOLORMAP;
		// [BC] Lowest priority, so it's down here now
		if (player->powers[pw_infrared] > 4*32	|| (player->powers[pw_infrared]&8) )
			player->mo->fixedcolormap = 1;			// almost full bright

		// Handle air supply
		if (player->mo->waterlevel < 3 || player->powers[pw_ironfeet])
		{
			player->air_finished = level.time + 10*TICRATE;
		}
		else if (player->air_finished <= level.time && !(level.time & 31))
		{
			P_DamageMobj (player->mo, NULL, NULL, 2 + 2*((level.time-player->air_finished)/TICRATE), MOD_WATER, DMG_NO_ARMOR);
		}
	}
}

// [BC]
void P_MedalThink (player_t *player)
{
	if (oldschool.value)
		return;

	if (player->medalTics)
		player->medalTics--;

	if (!player->icon)
	{
		// Draw a skull over the person if they're carrying one
/*		if (teamgame.value && player->team != TM_NONE && player->cards[teams[player->team].theirkey])
		{
			player->icon = new AActor (player->mo->x, player->mo->y, player->mo->z, MT_FLOATYICON);
			P_SetMobjState (player->icon, teams[player->team].theirstate);
			player->icon->special1 = 0;
			player->icon->tracer = player->mo;
		}

		// Draw the terminator ball over the terminator
		else*/ if (terminator.value && player->powers[pw_terminator])
		{
			player->icon = new AActor (player->mo->x, player->mo->y, player->mo->z, MT_FLOATYICON);
			P_SetMobjState (player->icon, S_ARNOLD);
			player->icon->special1 = 0;
			player->icon->tracer = player->mo;
		}

		// Draw a chat icon over the player if they're in the process
		// of typing... of in the case of a bot, "typing"
		else if (player->chatting ||
			(player->skullb && player->skullb->t_chat && player->skullb->chatline &&
			 player->skullb->chatline->string && botchat.value))
		{
			player->icon = new AActor (player->mo->x, player->mo->y, player->mo->z, MT_FLOATYICON);
			P_SetMobjState (player->icon, S_CHAT);
			player->icon->special1 = 0;
			player->icon->tracer = player->mo;
		}
	}
	// Get rid of any flag/skull icons
	else if (teamgame.value && player->team != TM_NONE)
	{
		if (!player->cards[teams[player->team].theirs] && player->icon->state == &states[teams[player->team].theirstate])
		{
			delete player->icon;
			player->icon = NULL;
		}
	}
}

//
// [BC] P_BuildColor
//
void P_BuildColor (int player)
{
	player_t *p = &players[player];

	if (oldschool.value)
	{
		switch (player)
		{
		case 0:
			R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, "00 ff 00"));
			break;
		case 1:
			R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, "4f 50 af"));
			break;
		case 2:
			R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, "af 60 10"));
			break;
		case 3:
			R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, "bf 00 00"));
			break;
		default:
			R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, "00 ff 00"));
			break;
		}

		return;
	}

	if (teamgame.value && (p->team == TM_BLUE || p->team == TM_RED))
		R_BuildPlayerTranslation (player, V_GetColorFromString(NULL, teams[p->team].color));
	else
		R_BuildPlayerTranslation (player, p->userinfo.color);
}

void player_s::Serialize (FArchive &arc)
{
	int i;

	if (arc.IsStoring ())
	{ // saving to archive
		arc << mo
			<< playerstate
			<< cmd
			<< userinfo
			<< viewz
			<< viewheight
			<< deltaviewheight
			<< bob
			<< momx
			<< momy
			<< health
			<< maxlowhealth		// [BC]
			<< maxhighhealth	// [BC]
			<< armorpoints
			<< maxarmor			// [BC]
			<< armortype
			<< rune				// [BC]
			<< lastrune			// [BC]
			<< slot1			// [BC]
			<< backpack
			<< railhits			// [BC]
			<< consechits		// [BC]
			<< unanskills		// [BC]
			<< railshots		// [BC]
			<< medalTics		// [BC]
			<< medalString		// [BC]
			<< hits				// [BC]
			<< totalshots		// [BC]
			<< fragcount
			<< lastkilltime		// [BC]
			<< pointcount		// [BC]
			<< deaths			// [BC]
			<< readyweapon
			<< pendingweapon
			<< attackdown
			<< usedown
			<< cheats
			<< refire
			<< inconsistant
			<< killcount
			<< itemcount
			<< secretcount
			<< damagecount
			<< bonuscount
			<< attacker
			<< extralight
			<< xviewshift
			<< jumpTics
			<< runeTics		// [BC]
			<< invulTics	// [BC]
			<< respawn_time
			<< air_finished
			<< grab		// [BC]
			<< icon		// [BC]
			<< team		// [BC]
			<< scored	// [BC]
			<< skullb;	// [BC]
		for (i = 0; i < NUMPOWERS; i++)
			arc << powers[i];
		for (i = 0; i < NUMCARDS; i++)
			arc << cards[i];
		for (i = 0; i < NUMMEDALS; i++)	// [BC]
			arc << medals[i];
		for (i = 0; i < NUMWEAPONS; i++)
			arc << weaponowned[i];
		for (i = 0; i < NUMAMMO; i++)
			arc << ammo[i] << maxammo[i];
		for (i = 0; i < NUMPSPRITES; i++)
			arc << psprites[i];
		for (i = 0; i < 3; i++)
			arc << oldvelocity[i];
	}
	else
	{ // Restoring from archive
		userinfo_t dummyuserinfo;


		arc >> mo
			>> playerstate
			>> cmd
			>> dummyuserinfo // Q: Would it be better to restore the userinfo from the archive?
			>> viewz
			>> viewheight
			>> deltaviewheight
			>> bob
			>> momx
			>> momy
			>> health
			>> maxlowhealth		// [BC]
			>> maxhighhealth	// [BC]
			>> armorpoints
			>> maxarmor			// [BC]
			>> armortype
			>> rune				// [BC]
			>> lastrune			// [BC]
			>> slot1			// [BC]
			>> backpack
			>> railhits			// [BC]
			>> consechits		// [BC]
			>> unanskills		// [BC]
			>> railshots		// [BC]
			>> medalTics		// [BC]
			>> medalString		// [BC]
			>> hits				// [BC]
			>> totalshots		// [BC]
			>> fragcount
			>> lastkilltime		// [BC]
			>> pointcount		// [BC]
			>> deaths			// [BC]
			>> readyweapon
			>> pendingweapon
			>> attackdown
			>> usedown
			>> cheats
			>> refire
			>> inconsistant
			>> killcount
			>> itemcount
			>> secretcount
			>> damagecount
			>> bonuscount
			>> attacker
			>> extralight
			>> xviewshift
			>> jumpTics
			>> runeTics		// [BC]
			>> invulTics	// [BC]
			>> respawn_time
			>> air_finished
			>> grab		// [BC]
			>> icon		// [BC]
			>> team		// [BC]
			>> scored	// [BC]
			>> skullb;	// [BC]

		for (i = 0; i < NUMPOWERS; i++)
			arc >> powers[i];
		for (i = 0; i < NUMCARDS; i++)
			arc >> cards[i];
		for (i = 0; i < NUMMEDALS; i++)	// [BC]
			arc >> medals[i];
		for (i = 0; i < NUMWEAPONS; i++)
			arc >> weaponowned[i];
		for (i = 0; i < NUMAMMO; i++)
			arc >> ammo[i] >> maxammo[i];
		for (i = 0; i < NUMPSPRITES; i++)
			arc >> psprites[i];
		for (i = 0; i < 3; i++)
			arc >> oldvelocity[i];

		camera = mo;

		if (consoleplayer != this - players)
			userinfo = dummyuserinfo;
	}
}
