/*
	server/player.qc

	Various stuff done for the player, including per-frame functions 
	like PlayerPreThink and PlayerPostThink, also client specific 
	stuff like PutClientInServer etc.

	Copyright (C) 2021-2022 NZ:P Team

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/

void(entity e) Light_None;
void() Spawns_Init;
void() Perk_UpdateQuickRevive;
void() SUB_UseTargets;
void() rec_downed;

#define PLAYER_START_HEALTH		100

// 32x32x72
#define PLAYER_MINS_STANDING 	'-16 -16 -32'
#define PLAYER_MAXS_STANDING 	'16 16 40'

// 32x32x36
#define PLAYER_MINS_CROUCHING	'-16 -16 -32'
#define PLAYER_MAXS_CROUCHING 	'16 16 4'

#define PLAYER_CROUCH_HEIGHT 	25
#define PLAYER_PRONE_HEIGHT 	23

#define PLAYER_ANIM_WALK 		1
#define PLAYER_ANIM_SPRINT		2

//
// Player 3rd Person Animations
//

// Walking
void() PAnim_Walk		=[   1,     PAnim_Walk1			] {self.frame = 0; self.tp_anim_time = time + 0.5;}
void() PAnim_Walk1		=[   2,     PAnim_Walk2			] {self.frame = 1;}
void() PAnim_Walk2		=[   3,     PAnim_Walk3			] {self.frame = 2;}
void() PAnim_Walk3		=[   4,     PAnim_Walk4			] {self.frame = 3;}
void() PAnim_Walk4		=[   5,     PAnim_Walk5			] {self.frame = 4;}
void() PAnim_Walk5		=[   6,     PAnim_Walk6			] {self.frame = 5;}
void() PAnim_Walk6		=[   7,     PAnim_Walk7			] {self.frame = 6;}
void() PAnim_Walk7		=[   8,     PAnim_Walk8			] {self.frame = 7;}
void() PAnim_Walk8		=[   9,     SUB_Null			] {self.frame = 8;}

// Sprinting
void() PAnim_Sprint 	=[	1,		PAnim_Sprint1		] {self.frame = 25; self.tp_anim_time = time + 0.65;}
void() PAnim_Sprint1 	=[	2,		PAnim_Sprint2		] {self.frame = 26;}
void() PAnim_Sprint2	=[	3,		PAnim_Sprint3		] {self.frame = 27;}
void() PAnim_Sprint3 	=[	4,		PAnim_Sprint4		] {self.frame = 28;}
void() PAnim_Sprint4 	=[	5,		PAnim_Sprint5		] {self.frame = 29;}
void() PAnim_Sprint5 	=[	6,		PAnim_Sprint6		] {self.frame = 30;}
void() PAnim_Sprint6 	=[	7,		SUB_Null			] {self.frame = 31;}

// Reloading
void() PAnim_Reload   	=[	1,		PAnim_Reload1	  	] {self.frame = 11; self.tp_anim_time = time + 1;}
void() PAnim_Reload1  	=[	2,		PAnim_Reload2	  	] {self.frame = 12;}
void() PAnim_Reload2  	=[	3,		PAnim_Reload3	  	] {self.frame = 13;}
void() PAnim_Reload3  	=[	4,		PAnim_Reload4	  	] {self.frame = 14;}
void() PAnim_Reload4  	=[	5,		PAnim_Reload5	  	] {self.frame = 15;}
void() PAnim_Reload5  	=[	6,		PAnim_Reload6	  	] {self.frame = 16;}
void() PAnim_Reload6  	=[	7,		PAnim_Reload7	  	] {self.frame = 17;}
void() PAnim_Reload7  	=[	8,		PAnim_Reload8	 	] {self.frame = 18;}
void() PAnim_Reload8  	=[	9,		PAnim_Reload9 		] {self.frame = 19;}
void() PAnim_Reload9   	=[	10,		PAnim_Reload10		] {self.frame = 20;}
void() PAnim_Reload10 	=[	11,		PAnim_Reload11		] {self.frame = 21;}
void() PAnim_Reload11 	=[	12,		PAnim_Reload12		] {self.frame = 22;}
void() PAnim_Reload12 	=[	13,		PAnim_Reload13		] {self.frame = 23;}
void() PAnim_Reload13 	=[	14,		SUB_Null			] {self.frame = 24;}

// Firing
void() PAnim_Fire 		=[	1,		PAnim_Fire1	  		] {self.frame = 9; self.tp_anim_time = time + 0.25;}
void() PAnim_Fire1 		=[	2,		SUB_Null	  		] {self.frame = 10;}

// Melee
void() PAnim_Melee 		=[	1,		PAnim_Melee1	  	] {self.frame = 49; self.tp_anim_time = time + 0.65;}
void() PAnim_Melee1 	=[	2,		PAnim_Melee2	  	] {self.frame = 50;}
void() PAnim_Melee2 	=[	3,		PAnim_Melee3	  	] {self.frame = 51;}
void() PAnim_Melee3 	=[	4,		PAnim_Melee4	  	] {self.frame = 52;}
void() PAnim_Melee4 	=[	5,		PAnim_Melee5	  	] {self.frame = 53;}
void() PAnim_Melee5 	=[	6,		PAnim_Melee6	  	] {self.frame = 54;}
void() PAnim_Melee6 	=[	7,		SUB_Null	  		] {self.frame = 55;}

// Weapon Swap
void() PAnim_Swap 		=[	1,		PAnim_Swap1	  		] {self.frame = 56; self.tp_anim_time = time + 1;}
void() PAnim_Swap1 		=[	2,		PAnim_Swap2	  		] {self.frame = 57;}
void() PAnim_Swap2 		=[	3,		PAnim_Swap3	  		] {self.frame = 58;}
void() PAnim_Swap3 		=[	4,		PAnim_Swap4	  		] {self.frame = 59;}
void() PAnim_Swap4 		=[	5,		PAnim_Swap5	  		] {self.frame = 60;}
void() PAnim_Swap5 		=[	6,		PAnim_Swap6	  		] {self.frame = 61;}
void() PAnim_Swap6 		=[	7,		PAnim_Swap7	  		] {self.frame = 62;}
void() PAnim_Swap7 		=[	8,		PAnim_Swap8	  		] {self.frame = 63;}
void() PAnim_Swap8 		=[	9,		SUB_Null	  		] {self.frame = 64;}

// Enter Dolphin Dive 
void() PAnim_EnterDive 	=[	1,		PAnim_EnterDive1	] {self.frame = 203; self.tp_anim_time = time + 5;}
void() PAnim_EnterDive1 =[	2,		PAnim_EnterDive2	] {self.frame = 204;}
void() PAnim_EnterDive2 =[	3,		PAnim_EnterDive3	] {self.frame = 205;}
void() PAnim_EnterDive3 =[	4,		PAnim_EnterDive4	] {self.frame = 206;}
void() PAnim_EnterDive4 =[	5,		SUB_Null			] {self.frame = 207;}

// Flop from Dive
void() PAnim_Flop 		=[	1,		PAnim_Flop1			] {self.frame = 208; self.tp_anim_time = time + 1;}
void() PAnim_Flop1 		=[	2,		PAnim_Flop2			] {self.frame = 209;}
void() PAnim_Flop2 		=[	3,		PAnim_Flop3			] {self.frame = 210;}
void() PAnim_Flop3 		=[	4,		SUB_Null			] {self.frame = 211;}

// Enter Stand
void() PAnim_Stand 		=[  1, 		PAnim_Stand1		] {self.frame = 114; self.tp_anim_time = time + 1;}
void() PAnim_Stand1 	=[  2, 		PAnim_Stand2		] {self.frame = 113;}
void() PAnim_Stand2 	=[  3, 		SUB_Null			] {self.frame = 0;}

// Enter Crouch
void() PAnim_Crouch 	=[  1, 		PAnim_Crouch1		] {self.frame = 113; self.tp_anim_time = time + 0.5;}
void() PAnim_Crouch1 	=[  2, 		PAnim_Crouch2		] {self.frame = 114;}
void() PAnim_Crouch2 	=[  3, 		SUB_Null			] {self.frame = 115;}

// Walking, while Crouch
void() PAnim_CrouchWalk =[  1, 		PAnim_CrouchWalk1	] {self.frame = 116; self.tp_anim_time = time + 0.5;}
void() PAnim_CrouchWalk1=[  2, 		PAnim_CrouchWalk2	] {self.frame = 117;}
void() PAnim_CrouchWalk2=[  3, 		PAnim_CrouchWalk3	] {self.frame = 118;}
void() PAnim_CrouchWalk3=[  4, 		PAnim_CrouchWalk4	] {self.frame = 119;}
void() PAnim_CrouchWalk4=[  5, 		PAnim_CrouchWalk5	] {self.frame = 120;}
void() PAnim_CrouchWalk5=[  6, 		PAnim_CrouchWalk6	] {self.frame = 121;}
void() PAnim_CrouchWalk6=[  7, 		PAnim_CrouchWalk7	] {self.frame = 122;}
void() PAnim_CrouchWalk7=[  8, 		PAnim_CrouchWalk8	] {self.frame = 123;}
void() PAnim_CrouchWalk8=[  9, 		SUB_Null			] {self.frame = 124;}

// Reloading, while Crouch
void() PAnim_CrReload   =[	1,		PAnim_CrReload1	  	] {self.frame = 128; self.tp_anim_time = time + 1;}
void() PAnim_CrReload1  =[	2,		PAnim_CrReload2	  	] {self.frame = 129;}
void() PAnim_CrReload2  =[	3,		PAnim_CrReload3	  	] {self.frame = 130;}
void() PAnim_CrReload3  =[	4,		PAnim_CrReload4	  	] {self.frame = 131;}
void() PAnim_CrReload4  =[	5,		PAnim_CrReload5	  	] {self.frame = 132;}
void() PAnim_CrReload5  =[	6,		PAnim_CrReload6	  	] {self.frame = 133;}
void() PAnim_CrReload6  =[	7,		PAnim_CrReload7	  	] {self.frame = 134;}
void() PAnim_CrReload7  =[	8,		PAnim_CrReload8	 	] {self.frame = 135;}
void() PAnim_CrReload8  =[	9,		PAnim_CrReload9 	] {self.frame = 136;}
void() PAnim_CrReload9  =[	10,		SUB_Null			] {self.frame = 115;}

// Firing, while Crouch
void() PAnim_CrouchFire =[	1,		PAnim_CrouchFire1	] {self.frame = 126; self.tp_anim_time = time + 0.25;}
void() PAnim_CrouchFire1=[	2,		SUB_Null	  		] {self.frame = 127;}

// Enter Prone
void() PAnim_Prone 		=[  1, 		PAnim_Prone1		] {self.frame = 154; self.tp_anim_time = time + 1.5;}
void() PAnim_Prone1 	=[  2, 		PAnim_Prone2		] {self.frame = 155;}
void() PAnim_Prone2 	=[  3, 		PAnim_Prone3		] {self.frame = 156;}
void() PAnim_Prone3 	=[  4, 		PAnim_Prone4		] {self.frame = 157;}
void() PAnim_Prone4 	=[  5, 		PAnim_Prone5		] {self.frame = 158;}
void() PAnim_Prone5 	=[  6, 		PAnim_Prone6		] {self.frame = 159;}
void() PAnim_Prone6 	=[  7, 		PAnim_Prone7		] {self.frame = 160;}
void() PAnim_Prone7 	=[  8, 		PAnim_Prone8		] {self.frame = 161;}
void() PAnim_Prone8 	=[  9, 		SUB_Null			] {self.frame = 162;}

// Walking, while Prone
void() PAnim_ProneWalk	=[  1, 		PAnim_ProneWalk1	] {self.frame = 162; self.tp_anim_time = time + 1.5;}
void() PAnim_ProneWalk1	=[  2, 		PAnim_ProneWalk2	] {self.frame = 163;}
void() PAnim_ProneWalk2	=[  3, 		PAnim_ProneWalk3	] {self.frame = 164;}
void() PAnim_ProneWalk3	=[  4, 		PAnim_ProneWalk4	] {self.frame = 165;}
void() PAnim_ProneWalk4	=[  5, 		PAnim_ProneWalk5	] {self.frame = 166;}
void() PAnim_ProneWalk5	=[  6, 		PAnim_ProneWalk6	] {self.frame = 167;}
void() PAnim_ProneWalk6	=[  7, 		PAnim_ProneWalk7	] {self.frame = 168;}
void() PAnim_ProneWalk7	=[  8, 		PAnim_ProneWalk8	] {self.frame = 169;}
void() PAnim_ProneWalk8	=[  9, 		PAnim_ProneWalk9	] {self.frame = 170;}
void() PAnim_ProneWalk9	=[  10, 	PAnim_ProneWalk10	] {self.frame = 171;}
void() PAnim_ProneWalk10=[  11, 	PAnim_ProneWalk11	] {self.frame = 172;}
void() PAnim_ProneWalk11=[  12, 	PAnim_ProneWalk12	] {self.frame = 173;}
void() PAnim_ProneWalk12=[  13, 	SUB_Null			] {self.frame = 174;}

// Reloading, while Prone
void() PAnim_PrReload   =[	1,		PAnim_PrReload1	  	] {self.frame = 176; self.tp_anim_time = time + 1;}
void() PAnim_PrReload1  =[	2,		PAnim_PrReload2	  	] {self.frame = 177;}
void() PAnim_PrReload2  =[	3,		PAnim_PrReload3	  	] {self.frame = 178;}
void() PAnim_PrReload3  =[	4,		PAnim_PrReload4	  	] {self.frame = 179;}
void() PAnim_PrReload4  =[	5,		PAnim_PrReload5	  	] {self.frame = 180;}
void() PAnim_PrReload5  =[	6,		PAnim_PrReload6	  	] {self.frame = 181;}
void() PAnim_PrReload6  =[	7,		SUB_Null	  		] {self.frame = 162;}

// Enter Crouch, from Prone
void() PAnim_UpCrouch 	=[  1, 		PAnim_UpCrouch1		] {self.frame = 161; self.tp_anim_time = time + 1;}
void() PAnim_UpCrouch1 	=[  2, 		PAnim_UpCrouch2		] {self.frame = 160;}
void() PAnim_UpCrouch2 	=[  3, 		PAnim_UpCrouch3		] {self.frame = 159;}
void() PAnim_UpCrouch3 	=[  4, 		PAnim_UpCrouch4		] {self.frame = 158;}
void() PAnim_UpCrouch4 	=[  5, 		PAnim_UpCrouch5		] {self.frame = 157;}
void() PAnim_UpCrouch5 	=[  6, 		PAnim_UpCrouch6		] {self.frame = 156;}
void() PAnim_UpCrouch6 	=[  7, 		PAnim_UpCrouch7		] {self.frame = 155;}
void() PAnim_UpCrouch7 	=[  8, 		PAnim_UpCrouch8		] {self.frame = 154;}
void() PAnim_UpCrouch8 	=[  9, 		SUB_Null			] {self.frame = 115;}

// Enter Last Stand
void() PAnim_GetDown	=[	1, 		PAnim_GetDown1		] {self.frame = 32; self.tp_anim_time = time + 1;};
void() PAnim_GetDown1	=[	2, 		PAnim_GetDown2		] {self.frame = 33;};
void() PAnim_GetDown2	=[	3, 		PAnim_GetDown3		] {self.frame = 34;};
void() PAnim_GetDown3	=[	4, 		PAnim_GetDown4		] {self.frame = 35;};
void() PAnim_GetDown4	=[	5, 		PAnim_GetDown5		] {self.frame = 36;};
void() PAnim_GetDown5	=[	6, 		SUB_Null			] {self.frame = 37; rec_downed();};

// Firing, while in Last Stand
void() PAnim_LastFire 	=[	1,		PAnim_LastFire1		] {self.frame = 36; self.tp_anim_time = time + 0.25;}
void() PAnim_LastFire1  =[	2,		SUB_Null			] {self.frame = 37;} 

// Leave Last Stand
void() PAnim_GetUp 		=[	1,		PAnim_GetUp1		] {self.frame = 39; self.tp_anim_time = time + 1;}
void() PAnim_GetUp1		=[	2,		PAnim_GetUp2		] {self.frame = 40;}
void() PAnim_GetUp2		=[	3,		PAnim_GetUp3		] {self.frame = 41;}
void() PAnim_GetUp3		=[	4,		PAnim_GetUp4		] {self.frame = 42;}
void() PAnim_GetUp4		=[	5,		PAnim_GetUp5		] {self.frame = 43;}
void() PAnim_GetUp5		=[	6,		PAnim_GetUp6		] {self.frame = 44;}
void() PAnim_GetUp6		=[	7,		PAnim_GetUp7		] {self.frame = 45;}
void() PAnim_GetUp7		=[	8,		PAnim_GetUp8		] {self.frame = 46;}
void() PAnim_GetUp8		=[	9,		PAnim_GetUp9		] {self.frame = 47;}
void() PAnim_GetUp9		=[	10,		SUB_Null			] {self.frame = 48;}


#define forward 	0
#define backward 	1
#define left 		2
#define right 		3
#define all_move    -1

float(float dir) checkMovement =
{	
	switch(dir) {
		case forward:
			if (self.movement_x > 0)
				return 1;
		break;
		case backward:
			if (self.movement_x < 0)
				return 1;
		break;
		case right:
			if (self.movement_y > 0)
				return 1;
		break;
		case left:
			if (self.movement_y < 0)
				return 1;
		break;
		case all_move:
			if (self.movement_x || self.movement_y)
				return 1;
		break;
		default:
			return 0;
	}
}

//
// Player_CanStandHere
// Performs a tracebox and will return true
// if the player can stance in their current
// space.
//
float(entity who) Player_CanStandHere =
{
	tracebox(who.origin, PLAYER_MINS_STANDING, PLAYER_MAXS_STANDING, who.origin, true, who);

	return !trace_startsolid;
}

void() PlayerJump =
{
	if (!(self.flags & FL_ONGROUND) 
	    || !(self.flags & FL_JUMPRELEASED)
		|| self.downed 
		|| self.dive ) {
		return;
	}
			
	self.flags = self.flags - (self.flags & FL_JUMPRELEASED);

	if (self.button2)
		self.button2 = 0;
	
	self.oldz = self.origin_z;

	self.velocity_z = 230;
}

void(float override) JumpCheck =
{

#ifndef FTE

	override = 0;

#endif // FTE

	if(self.button2 || override) {
		if (self.downed)
			return;
			
		if (self.stance == 2) {
      // naievil -- stop sprinting if we jump, which is a real mechanic from the game that we never implemented 
		  if (self.sprinting) {
			  W_SprintStop();
		  }
			PlayerJump();
		} else if (self.view_ofs_z == self.new_ofs_z && (self.flags & FL_ONGROUND) && Player_CanStandHere(self)) {
			switch(self.stance) {
				case 0:
					self.new_ofs_z = self.view_ofs_z + PLAYER_CROUCH_HEIGHT + PLAYER_PRONE_HEIGHT;
					self.stance = 2;
					break;
				case 1:
					self.new_ofs_z = self.view_ofs_z + PLAYER_CROUCH_HEIGHT;
					self.stance = 2;
					break;
				default: break;
			}
			setsize(self, PLAYER_MINS_STANDING, PLAYER_MAXS_STANDING);
		}
	} else
		self.flags = self.flags | FL_JUMPRELEASED;
}


void() PlayerPreThink =
{
	if (self.downed) {
		self.maxspeed = 30;
	} else {
		self.maxspeed = 190;
		if (self.sprinting) {

#ifdef FTE

			if (self.viewzoom > 0.97)
				self.viewzoom -= 0.015;
			else if (self.viewzoom < 0.97)
				self.viewzoom = 0.97;
				
			// viewbob when running 
			self.punchangle_y = 0.6*sin(time*10);

#endif // FTE

			//playrun1();
			self.maxspeed *= 1.5; // down from 1.66x, to match WaW
		} else if (!self.sprinting && !self.zoom) {

#ifdef FTE

			if (self.viewzoom < 1)
				self.viewzoom += 0.015;
			else 
				self.viewzoom = 1;
				
			if (checkMovement(-1))
				self.punchangle_x = 0.25*sin(time*15);	

#endif // FTE

		} else if (self.zoom != 3) {
			self.maxspeed *= 0.5;
		} 

		// Speed Penalty
		if (self.speed_penalty_time > time)
		{
			self.maxspeed *= self.speed_penalty;
		} else {
			self.speed_penalty = 1;
		}
		
		switch(self.stance) {
			case 1:
				self.maxspeed *= 0.65;
				break;
			case 0:
				self.maxspeed *= 0.25;
				break;		
		}

#ifdef FTE

		if (checkMovement(backward)) {
			self.maxspeed *= 0.7;
		} else if (checkMovement(left) || checkMovement(right)) {
			self.maxspeed *= 0.8;
		}

#endif // FTE

		if (!self.isBuying)
			self.maxspeed *= GetWeaponWalkSpeed(self.perks, self.weapon);
	}
	if(self.isspec != 0 && !self.downed)
	{
		if(self.button0)
		{
			self.aiment = find(self.aiment, classname, "player");
			if(self.aiment != world)
			{
				sprint(self, PRINT_HIGH, "Now spectating ");
				sprint(self, PRINT_HIGH, self.aiment.netname);
				sprint(self, PRINT_HIGH, "\n");
				
				self.solid = SOLID_NOT;
				self.movetype = MOVETYPE_NONE;
			}
			else
			{
				sprint(self, PRINT_HIGH, "Freefly spectate\n");
				self.movetype = MOVETYPE_FLY;
			}
		}
		if(self.aiment != world)
		{
			self.origin = self.aiment.origin;
			self.angles = self.aiment.v_angle;
			self.velocity = self.aiment.velocity;
			self.fixangle = TRUE;
		}
		return;
	}
	
	if (cvar("waypoint_mode")) {
		Waypoint_Logic();
	} else {
		Weapon_Logic();
	}

	JumpCheck(0);

	// refuel/cool flamethrowers
	if (self.ltime < time) {
		if (self.weapons[0].weapon_magazine == 0 && !self.cooldown) {
			self.cooldown = true;
		}

		if (self.cooldown && self.weapons[0].weapon_magazine > 20)
			self.cooldown = false;
		
		if (GetFiretype(self.weapon) == FIRETYPE_FLAME && 
		self.weapons[0].weapon_magazine < getWeaponMag(self.weapon)) {
			self.weapons[0].weapon_magazine += 1;
		}

		self.ltime = time + 0.1;
	}
};

float player_trace_time;
void() PlayerPostThink =
{

#ifdef FTE

	// Network everything
	self.SendFlags = 1;

#endif // FTE

	if(self.isspec)
		return;

#ifndef FTE

	if (time < 5 && self.name != "")
		nzp_setplayername(self, self.name);

#endif // FTE

	//landsound
	if((self.oldvelocity_z < -212) && (self.flags & FL_ONGROUND))
	{
		sound(self, CHAN_BODY, "sounds/player/land.wav", 0.6, 1.75);
		self.lastsound_time = time - 0.15;
	}

	// Walking Animations
	if (self.tp_anim_time < time && self.reload_delay < time && self.reload_delay2 < time 
	&& self.fire_delay < time && self.fire_delay2 < time && self.knife_delay < time) {
		if (vlen(self.velocity) > 20) {
			if (self.sprinting) {
				PAnim_Sprint();
				self.tp_anim_type = PLAYER_ANIM_SPRINT;
			} else {
				self.tp_anim_type = PLAYER_ANIM_WALK;
				switch(self.stance) {
					case 2: PAnim_Walk(); break;
					case 1: PAnim_CrouchWalk(); break;
					case 0: PAnim_ProneWalk(); break;
				}
			}
		} else {
			// Stand still so Crouch Walk isn't stuck in place
			switch(self.stance) {
				case 2: self.frame = 0; break;
				case 1: self.frame = 115; break;
				case 0: self.frame = 162; break;
			}
		}
	}
	
#ifdef FTE

	//footsteps
	if((vlen(self.velocity) > 20) &&(( time - self.lastsound_time > 0.4) || (time - self.lastsound_time > 0.3 && self.sprinting)) && (self.flags & FL_ONGROUND))
	{
		float movelen = vlen(input_movevalues);
		if(movelen > 300)
		{		
			local float ran = random();
			if(ran > 0.8)
				sound(self, CHAN_BODY, "sounds/player/footstep1.wav", 0.6, 2.5);
			else if(ran > 0.6)
				sound(self, CHAN_BODY, "sounds/player/footstep2.wav", 0.6, 2.5);
			else if(ran > 0.4)
				sound(self, CHAN_BODY, "sounds/player/footstep3.wav", 0.6, 2.5);
			else if(ran > 0.2)
				sound(self, CHAN_BODY, "sounds/player/footstep4.wav", 0.6, 2.5);
			else
				sound(self, CHAN_BODY, "sounds/player/footstep5.wav", 0.6, 2.5);
			self.lastsound_time = time;
		}
	}

#endif // FTE
	
	// Health Regeneration
	if (self.health_delay < time && self.health != self.max_health && !self.downed)
	{
		// If we weren't super low on health, regen is instant.
		if (self.health_was_very_low == true) {
			self.health += 120 * frametime;
		} else {
			// Was 100% instant, but this delays it by only a few
			// frames so the graphic doesn't just vanish when healed.
			self.health += 300 * frametime;
		}

		// Make sure we mark that we weren't just super hurt anymore.
		if (self.max_health <= self.health) {
			self.health = self.max_health;
			self.health_was_very_low = false;
		}
	}
	
	if (self.progress_bar) {
		if (self.progress_bar < time) {
			if (self.downed)
				GetUp();
			
			if (self.reviving)
				self.revived = 1;
			
			self.progress_bar = 0;
			self.progress_bar_time = 0;
			self.progress_bar_percent = 0;
			
				
		} else {
			float remaining = self.progress_bar - time;
			self.progress_bar_percent = invertfloat((remaining / self.progress_bar_time));
		}
	}

	if (self.sprinting) {
		self.sprint_timer = self.sprint_duration + (time - self.sprint_start_time);
		
#ifndef FTE

		if (!self.velocity || self.stance != 2) {
			W_SprintStop();
		}

#else

		if (!self.velocity || !checkMovement(0) || self.stance != 2) { //moto (FIXME) -- move me!
			W_SprintStop();
		}

#endif // FTE
		
		if (self.perks & P_STAMIN) {
			if (self.sprint_timer > sprint_max_time * 2) {
				W_SprintStop();
			}
		}
		else
		{
			if (self.sprint_timer > sprint_max_time) {
				W_SprintStop();
			}
		}

	} else if (self.sprint_duration > 0.0) 	{
		self.sprint_rest_time = (time - self.sprint_stop_time);
	}

	self.oldvelocity = self.velocity;

	// Perform a traceline to keep track of entities directly
	// in front of the player.
	// Also don't do it every frame because that's really tough
	// on perf.
	if (player_trace_time < time) {
		vector source;

		makevectors (self.v_angle);
		source = self.origin + self.view_ofs;

#ifdef FTE

		self.dimension_hit = HITBOX_DIM_LIMBS | HITBOX_DIM_ZOMBIES;

#endif // FTE

		traceline (source, source + v_forward*800*1.2, 0, self);

#ifdef FTE

		self.dimension_hit = HITBOX_DIM_ZOMBIES;

#endif // FTE


		// use .head here to avoid expanding ent struct
		self.head = trace_ent;

		// check whether we're looking at an entity separately to communicate
		// with the client more reasonably
		if (trace_ent.classname == "ai_zombie" || trace_ent.classname == "ai_zombie_head" 
		|| trace_ent.classname == "ai_zombie_rarm" ||trace_ent.classname == "ai_zombie_larm" 
		|| trace_ent.classname == "ai_dog")
			self.facingenemy = true;
		else
			self.facingenemy = false;

		// 1/4 of a second is enough of a delay to not kill the effect.
		player_trace_time = time + 0.25;
	}
};

void() ClientKill = {};

//called when a client connects to the server
void() ClientConnect =
{	
	if(cvar("developer") || player_count > 1) {
		bprint(PRINT_HIGH, self.netname); //print player name
		bprint(PRINT_HIGH, " connected.\n");
	}	
};

void() PollPlayerPoints =
{
	float i, breakpoint;
	entity pollent;
	breakpoint = 0;
	
	for (i = 1; i <= 4 && !breakpoint; i++)
	{
		pollent = findfloat(world, playernum, i);
		
		if (pollent == world) {
			breakpoint = 1;
			break;
		}
		UpdatePlayerPoints(i, pollent.points, pollent.kills, 0, pollent.netname, pollent);
	}
}

//
// Player_PickSpawnPoint()
// Picks a valid spawn point for the
// newly spawning player, as well as
// assigns any spawn-specific fields
// (e.g., starting weapon).
//
void() Player_PickSpawnPoint =
{
	entity spawn_point = world;
	float found_viable_spawn = false;

	// Assign a location
	while(!found_viable_spawn) {
		float index = random();

		// assign one of the spawnpoints
		if (index < 0.25)
			spawn_point = find(world, classname, SPAWN_1_CLASS);
		else if (index < 0.50)
			spawn_point = find(world, classname, SPAWN_2_CLASS);
		else if (index < 0.75)
			spawn_point = find(world, classname, SPAWN_3_CLASS);
		else
			spawn_point = find(world, classname, SPAWN_4_CLASS);

		float found_player_here = false;

		entity ents_in_spawn_range = findradius(spawn_point.origin, 32);

		// check if there's a player in the way
		while(ents_in_spawn_range != world) {
			if (ents_in_spawn_range.classname == "player")
				found_player_here = true;

			ents_in_spawn_range = ents_in_spawn_range.chain;
		}

		// no player in the way, this spawn is good.
		if (found_player_here == false)
			found_viable_spawn = true;
	}

	// Give them their name
	self.name = spawn_point.name;

	// Set their location
	self.origin = spawn_point.origin + '0 0 1';
	self.angles = spawn_point.angles;

	// Assign their starting weapon
	Weapon_GiveWeapon(spawn_point.weapon, spawn_point.currentmag, spawn_point.currentammo);

	// Activate anything the Spawn was targeting
	entity tempe = self;
	self = spawn_point;
	SUB_UseTargets();
	self = tempe;
};

void() PlayerSpawn =
{	
	entity spawnpoint = world;
	local_client = self;

	self.isspec = FALSE;
	self.classname = "player";
	self.solid = SOLID_BBOX;

	// We can only collide with zombies (and not their limbs)

#ifdef FTE

	self.dimension_hit = HITBOX_DIM_ZOMBIES;

#endif // FTE

	setmodel(self, "models/player.mdl");
	self.movetype = MOVETYPE_WALK;
	self.max_health = self.health = PLAYER_START_HEALTH;

	entity who = find(world,classname,"player");
	while(who != self && !self.playernum)
	{
		if(who)
		{
			player_count++;
			break;
		}
	}

	if (!self.playernum) {
		self.playernum = player_count + 1;
		if (self.playernum == 1)
			pl1 = self;
	}

	// Assign them a spawn location.
	Player_PickSpawnPoint();
	
	self.fixangle = true;
	setsize(self, PLAYER_MINS_STANDING, PLAYER_MAXS_STANDING);
	self.view_ofs = VEC_VIEW_OFS; // naievil -- set view_ofs to 32 to maintain half life (64) sizes
	self.stance = 2;
	self.new_ofs_z = self.view_ofs_z;
	self.oldz = self.origin_z;
	self.grenades = self.grenades | 1;  // add frag grenades to player inventory
	
	if (rounds)
		self.primary_grenades = 2;
	else
		self.primary_grenades = 0; 		// start off without grenades
		
	self.pri_grenade_state = 0; 		// defines that frag grenades are for player first, not betty
	self.secondary_grenades = -1;		// shows that we both don't have betties AND shouldn't draw the image onscreen

	if (!self.points)
		addmoney(self, G_STARTPOINTS, 0);

	self.weaponmodel = GetWeaponModel(self.weapon, 0);// Give weapon model
	self.weapon2model = GetWeapon2Model(self.weapon);

	SwitchWeapon(self.weapon);

	self.stamina = 3;
	self.reviving = 0;
	self.perks = G_PERKS;
	SetPerk(self, self.perks);

	if (rounds < 1 && player_count == 0) {
		sound(self, CHAN_AUTO, "sounds/rounds/splash.wav", 1, ATTN_NONE);
		sound(self, CHAN_ITEM, "sounds/rounds/nround.wav", 0.75, ATTN_NONE);
	}

	PromptLevelChange(self.nextthink + 3, 1, self);	
	UpdatePlayerCount(player_count);
	
#ifdef FTE

	PollPlayerPoints();
	UpdateV2model("", 0);
	stuffcmd(self, "cl_gunx 8;cl_guny 16;cl_gunz 25\n");
	SetRound(self, G_STARTROUND);
	self.viewzoom = 1;
	self.SendEntity = Player_SendEntity;
	self.weapon_animduration = getWeaponDelay(self.weapon, FIRE);

	if (G_WORLDTEXT)
		WorldText(world.chaptertitle, world.location, world.date, world.person, self);

#else 

	self.Weapon_Name = GetWeaponName(self.weapon);
	self.Flash_Offset = GetWeaponFlash_Offset(self.weapon);
	self.Flash_Size = GetWeaponFlash_Size(self.weapon);

#endif // FTE

	if (G_STARTROUND != 1) {
		rounds = G_STARTROUND - 1;
	}
};

void() SpectatorSpawn =
{
	local entity spawnpoint;
	spawnpoint = find(world, classname, "info_player_start");
	
	self.isspec = TRUE;
	self.health = 420;
	self.classname = "spectator";
	self.solid = SOLID_NOT;
	setmodel(self, "");
	self.movetype = MOVETYPE_FLY;
	self.origin = spawnpoint.origin + [0,0,1];
	self.fixangle = TRUE;
	setsize(self, PLAYER_MINS_STANDING, PLAYER_MAXS_STANDING);
	self.view_ofs = '0 0 22';
	self.aiment = world;
};

//called when a client loads a map
float spawns_initialized;
void() PutClientInServer =
{
	// Init Spawn Points
	if (!spawns_initialized) {
		Spawns_Init();
		spawns_initialized = true;
	}

	if(cvar("developer") || player_count > 1) {
		bprint(PRINT_HIGH, self.netname);
		bprint(PRINT_HIGH, " has joined the game.\n");
	}
	
	if (spawn_time > time || !rounds)
		PlayerSpawn();

#ifdef FTE

	else 
		SpectatorSpawn();

#endif // FTE

	Perk_UpdateQuickRevive();
};

//called when client disconnects from the server
void() ClientDisconnect =
{
	bprint(PRINT_HIGH, self.netname);
	bprint(PRINT_HIGH, " has left the game.\n");
	player_count--;
	UpdatePlayerCount(player_count);
	
	self.classname = "disconnected";
	self.solid = SOLID_NOT;
	self.movetype = MOVETYPE_TOSS;
	self.nextthink = -1;
	setmodel(self, "models/sprites/null.spr");

	Perk_UpdateQuickRevive();
};


void() SetNewParms =
{
};

void() SetChangeParms =
{
};

#ifdef FTE

void() SV_RunClientCommand =
{
	runstandardplayerphysics(self);
}

#endif // FTE