/*
+------+
|Client|
+------+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+
| Scratch                                      Http://www.admdev.com/scratch |
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+
| Handle's "clients" (eg, Players) connecting, disconnecting, etc.           |
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+
*/

void() set_disconnect_frame;
void() ImpulseCommands;
void() FireWeapons;
void() PlayerDie;
void() PlayerPain;
void() respawn;
void (vector org) spawn_tfog;
void (vector org, entity death_owner) spawn_tdeath;

/*
=============================================================================

				LEVEL CHANGING / INTERMISSION

=============================================================================
*/

/*QUAKED info_intermission (1 0.5 0.5) (-16 -16 -16) (16 16 16)
This is the camera point for the intermission.
Use mangle instead of angle, so you can set pitch or roll as well as yaw.  'pitch roll yaw'
*/
void() info_intermission = {};

/*
============
FindIntermission

Returns the entity to view from
============
*/
entity() FindIntermission =
{
	local	entity spot;
	local	float cyc;

	// look for info_intermission first
	spot = find (world, classname, "info_intermission");
	if (spot)
	{	// pick a random one
		cyc = random() * 4;
		while (cyc > 1)
		{
			spot = find (spot, classname, "info_intermission");
			if (!spot)
				spot = find (spot, classname, "info_intermission");
			cyc = cyc - 1;
		}
		return spot;
	}

	// then look for the start position
	spot = find (world, classname, "info_player_start");
	if (spot)
		return spot;
	
	// testinfo_player_start is only found in regioned levels
	spot = find (world, classname, "testplayerstart");
	if (spot)
		return spot;
	
	objerror ("FindIntermission: no spot");
};


string nextmap;
void() GotoNextMap =
{
	if (cvar("samelevel"))	// if samelevel is set, stay on same level
		changelevel (mapname);
	else
		changelevel (nextmap);
};


void() ExitIntermission =
{
	// skip any text in deathmatch
	if (deathmatch)
	{
		GotoNextMap ();
		return;
	}
	
	intermission_exittime = time + 1;
	intermission_running = intermission_running + 1;

	GotoNextMap();
};

/*
============
IntermissionThink

When the player presses attack or jump, change to the next level
============
*/
void() IntermissionThink =
{
	if (time < intermission_exittime)
		return;

	if (!self.button0 && !self.button1 && !self.button2)
		return;
	
	ExitIntermission ();
};

void() execute_changelevel =
{
	local entity	pos;

	intermission_running = 1;
	
	// enforce a wait time before allowing changelevel
	intermission_exittime = time + 5;
	
	pos = FindIntermission ();

	other = find (world, classname, "player");
	while (other != world)
	{
		other.view_ofs = '0 0 0';
		other.angles = other.v_angle = pos.mangle;
		other.fixangle = TRUE;		// turn this way immediately
		other.nextthink = time + 0.5;
		other.takedamage = DAMAGE_NO;
		other.solid = SOLID_NOT;
		other.movetype = MOVETYPE_NONE;
		other.modelindex = 0;
		setorigin (other, pos.origin);
		other = find (other, classname, "player");
	}	
	WriteByte (MSG_ALL, SVC_INTERMISSION);
};


void() changelevel_touch =
{
	local entity	pos;

	if (other.classname != "player")
		return;

	if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start")))
	{
		T_Damage (other, self, self, 50000);
		return;
	}

	if (coop || deathmatch)
	{
		bprint (other.netname);
		bprint (" exited the area\n");
	}
	
	nextmap = self.map;

	SUB_UseTargets ();

	if ( (self.spawnflags & 1) && (deathmatch == 0) )
	{	// NO_INTERMISSION
		GotoNextMap();
		return;
	}
	
	self.touch = SUB_Null;

	// we can't move people right now, because touch functions are called
	// in the middle of C movement code, so set a think time to do it
	self.think = execute_changelevel;
	self.nextthink = time + 0.1;
};

/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION
When the player touches this, he gets sent to the map listed in the "map" variable.  Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats.
*/
void() trigger_changelevel =
{
	if (!self.map) objerror ("changelevel trigger doesn't have map");
	
	InitTrigger ();
	self.touch = changelevel_touch;
};

/*
go to the next level for deathmatch
only called if a time or frag limit has expired
*/
void() NextLevel =
{
	local entity o;

	// find a trigger changelevel
	o = find(world, classname, "trigger_changelevel");

	// go back to start if no trigger_changelevel
	if (!o)
	{
		mapname = "start";
		o = spawn();
		o.map = mapname;
	}

	nextmap = o.map;
	gameover = TRUE;
	
	if (o.nextthink < time)
	{
		o.think = execute_changelevel;
		o.nextthink = time + 0.1;
	}
};

/*
============
CheckRules

Exit deathmatch games upon conditions
============
*/
void() CheckRules =
{
	local	float		timelimit;
	local	float		fraglimit;
	
	if (gameover)	// someone else quit the game already
		return;
		
	timelimit = cvar("timelimit") * 60;
	fraglimit = cvar("fraglimit");
	
	if (timelimit && time >= timelimit)
	{
		NextLevel ();
		return;
	}
	
	if (fraglimit && self.frags >= fraglimit)
	{
		if (fraglimit != 0)
			NextLevel ();
		return;
	}	
};

void() ClientKill = {};

void() ClientConnect =
{
	ClientInRankings(); // FrikBot
	bprint (self.netname);

	if (random() < 0.25)
		bprint (" comes to prove his killing skills.\n");
	else if (random() < 0.50)
		bprint (" wants to take all the glory.\n");
	else if (random() < 0.75)
		bprint (" decides to brush up on his skills.\n");
	else
		bprint (" joins in for victory.\n");

	// a client connecting during an intermission can cause problems
	if (intermission_running) ExitIntermission ();
};

void() ClientDisconnect =
{
	ClientDisconnected(); // FrikBot

	self.solid = SOLID_NOT;
	self.classname = string_null;
	setmodel(self, string_null);

	bprint (self.netname);
	if (random() < 0.50)
		bprint (" leaves the battle ground with ");
	else
		bprint (" gets away clean with ");

	bprint (ftos(self.frags));
	bprint (" kills.\n");
	self.netname = string_null;

	self.think = SUB_Null;
	self.nextthink = -1;
};

/*
============
PlayerJump
============
*/
void() PlayerJump =
{
	local string tempp;

	if (self.deadflag != 0)
		return;
	if (!(self.flags & FL_ONGROUND))
		return;

     	if (self.waterlevel == 0)
	{
		if (self.jcount == 135) // 135 is max
		{
			sound (self, CHAN_VOICE, "player/jump.wav", 1, ATTN_NORM);
			self.punchangle_x = -5;
            }
		sound (self, CHAN_BODY, "player/fjump.wav", 1, ATTN_NORM);
	}
	self.flags = self.flags - FL_ONGROUND;	// don't stairwalk	
	self.button2 = 0;
      self.velocity_z = self.velocity_z + 170 + self.jcount;
};

/*
===========
WaterMove

============
*/
.float	dmgtime;

void() WaterMove =
{
	if (self.movetype == MOVETYPE_NOCLIP)
		return;
	if (self.health < 0)
		return;

	if (self.waterlevel != 3)
	{
		if (self.air_finished < time)
			sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM);
		else if (self.air_finished < time + 9)
			sound (self, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM);
		self.air_finished = time + 12;
		self.dmg = 2;
	}
	else if (self.air_finished < time)
	{	// drown!
		if (self.pain_finished < time)
		{
			self.dmg = self.dmg + 2;
			if (self.dmg > 15)
				self.dmg = 10;
			T_Damage (self, world, world, self.dmg);
			self.pain_finished = time + 1;
		}
	}
	
	if (!self.waterlevel)
	{
		if (self.flags & FL_INWATER)
		{	
			// play leave water sound
			sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM);
			self.flags = self.flags - FL_INWATER;
		}
		return;
	}

	if (self.watertype == CONTENT_LAVA)
	{	// do damage
		if (self.dmgtime < time)
		{
			self.dmgtime = time + 0.2;
			T_Damage (self, world, world, 10*self.waterlevel);
		}
	}
	else if (self.watertype == CONTENT_SLIME)
	{	// do damage
		if (self.dmgtime < time)
		{
			self.dmgtime = time + 1;
			T_Damage (self, world, world, 4*self.waterlevel);
		}
	}
	
	if (!(self.flags & FL_INWATER))
	{	
		// player enter water sound
		if (self.watertype == CONTENT_LAVA)
			sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM);
		if (self.watertype == CONTENT_WATER)
			sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM);
		if (self.watertype == CONTENT_SLIME)
			sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM);

		self.flags = self.flags + FL_INWATER;
		self.dmgtime = 0;
	}
	
	if (! (self.flags & FL_WATERJUMP) )
		self.velocity = self.velocity - 0.8*self.waterlevel*frametime*self.velocity;
};

void() CheckWaterJump =
{
	local vector start, end;

	// check for a jump-out-of-water
	makevectors (self.angles);
	start = self.origin;
	start_z = start_z + 8; 
	v_forward_z = 0;
	normalize(v_forward);
	end = start + v_forward*24;
	traceline (start, end, TRUE, self);
	if (trace_fraction < 1)
	{	// solid at waist
		start_z = start_z + self.maxs_z - 8;
		end = start + v_forward*24;
		self.movedir = trace_plane_normal * -50;
		traceline (start, end, TRUE, self);
		if (trace_fraction == 1)
		{	// open at eye level
			self.flags = self.flags | FL_WATERJUMP;
			self.velocity_z = 225;
			self.flags = self.flags - (self.flags & FL_JUMPRELEASED);
			self.teleport_time = time + 2;	// safety net
			return;
		}
	}
};

/*
============
JumpBar
============
*/

void(float percent, float width) JumpBar =
{
	local float i, f;

	if (!self.ishuman)
		return;

	//open
	msg_entity = self;
	WriteChar(MSG_ONE, SVC_CENTERPRINT);

	//move down 5 spaces
	WriteChar(MSG_ONE, 10);
	WriteChar(MSG_ONE, 10);
	WriteChar(MSG_ONE, 10);
	WriteChar(MSG_ONE, 10);
	WriteChar(MSG_ONE, 10);

	WriteChar(MSG_ONE, 128);
	i = 0;
	percent = floor(percent * width * 8);
	while(i < width)
	{
		f = percent - (i * 8);
		if (f > 7)
			WriteChar(MSG_ONE, 129);
		else if (f < 1)
			WriteByte (MSG_ONE, 139);
		else
			WriteByte (MSG_ONE, 139 - f);
		i = i + 1;
	}
	WriteChar(MSG_ONE, 130);
	WriteChar(MSG_ONE, 0); // close
};

/*
============
PlayerPrethink
============
*/
void() PlayerPreThink = 
{
	if (self.spawntime)
	{
		if (time > self.spawntime)
		{
			stuffcmd(self, "exec startup.cfg\n");
			self.spawntime = 0;
		}
	}

	if (world.model == "maps/start.bsp")
	{
		if (self.impulse)
			ImpulseCommands();
		MainMenu();
		return;
	}

	if (BotPreFrame()) // FrikBot
		return;

	if (self.respawn_finished < time && !intermission_running && self.deadflag == DEAD_DEAD)
		respawn(); // Restart the player

	if (self.aflag == TRUE)
		FirstPersonCamera_Chase();

	if (intermission_running)
	{
		IntermissionThink ();	// otherwise a button could be missed between
		return;
	}

	CheckRules ();
	WaterMove ();
	Controls_Think();

	if (self.waterlevel == 2)
		CheckWaterJump ();
	if (self.button0)
		FireWeapons();
	else
	{
		self.subgunspread = 0;
		self.holdfire = FALSE;
	}

	if (self.button2)
	{
		if (!(self.flags & FL_ONGROUND) || self.deadflag != DEAD_NO)
			return;
		local float perc;

		self.jcount = self.jcount + 270 * frametime;
		if (self.jcount > 135)
			self.jcount = 135;
		if (self.ishuman)
		{
			perc = (self.jcount / 135);
			JumpBar(perc, 8); // jump bar
		}
	}
	else
	{
		if (self.jcount > 0)
		{
			centerprint (self, "\n"); // clear jbar
			PlayerJump();
			self.jcount = 0;
		}
	}

	if (self.impulse)
		ImpulseCommands();
};

/*
============
PlayerPostThink
============
*/
void() PlayerPostThink = 
{
	if (BotPostFrame()) // FrikBot
		return;

	if (self.shield_finished < time && self.shield == TRUE) 
		{self.effects = 0;self.shield = FALSE;}
	if (timedelay_finished < time && timedelay == TRUE)
		{localcmd ("host_framerate 0\n");timedelay = FALSE;}

	// check to see if player landed and play landing sound
	if ((self.jump_flag < -200) && (self.flags & FL_ONGROUND) && (self.health > 0))
	{
			sound (self, CHAN_BODY, "player/fland.wav", 1, ATTN_NORM);
			self.jump_flag = 0;
	}
	if (!(self.flags & FL_ONGROUND))
		self.jump_flag = self.velocity_z;

	if (world.model != "maps/start.bsp" && self.health > 0)
		self.angles_x = self.v_angle_x * -0.4;
};

/*
============
Respawn
============
*/

void() respawn =
{
	if (deathmatch)
	{
		SetNewParms ();
		PutClientInServer ();
	}
	else // single play
	{
		localcmd ("host_framerate 0\n");
		localcmd ("restart\n");
	}
};

float(vector v) CheckSpawnPoint =
{
	return FALSE;
};

/*
============
SelectSpawnPoint

Returns the entity to spawn at
============
*/
entity() SelectSpawnPoint =
{
	local	entity spot;
	local	entity thing;
	local	float  pcount;
		
	if (coop)
	{
		lastspawn = find(lastspawn, classname, "info_player_coop");
		if (lastspawn == world)
			lastspawn = find (lastspawn, classname, "info_player_start");
		if (lastspawn != world)
			return lastspawn;
	}
	else if (deathmatch)
	{
		if (world.model != "maps/start.bsp")
		{
			spot = lastspawn;
			while (1)
			{
				spot = find(spot, classname, "info_player_deathmatch");
				if (spot != world)
				{
					if (spot == lastspawn)
						return lastspawn;
					pcount = 0;
					thing = findradius(spot.origin, 32);
					while(thing)
					{
						if (thing.classname == "player")
							pcount = pcount + 1;
						thing = thing.chain;
					}
					if (pcount == 0)
					{
						lastspawn = spot;
						return spot;
					}
				}
			}
		}
		else
		{
			spot = find (world, classname, "info_player_start"); // Put player at
			// info player start position at start.bsp even in a deathmatch game.
		}
	}
	
	spot = find (world, classname, "info_player_start");
	if (!spot)
		error ("PutClientInServer: no info_player_start on level");
	
	return spot;
};

/*
============
View Weapons
============
*/

void() VWep_Think =
{
	if (!self.owner.netname)
		remove(self);
	if (intermission_running)
	{
		setmodel(self, string_null);
		return;
	}
	if (self.owner.frame >= 74) // remove vwep on certain player frames
		setmodel(self, string_null);
	else if (self.owner.checkwepstop_finished < time)
	{
		if (self.owner.weapon == W_PISTOL) setmodel(self, "progs/vw_pist.mdl");
		else if (self.owner.weapon == W_KNIFE) setmodel(self, "progs/vw_knife.mdl");
		else if (self.owner.weapon == W_DOUBLEPISTOL) setmodel(self, "progs/vw_2pist.mdl");
		else if (self.owner.weapon == W_SHOTGUN) setmodel(self, "progs/vw_shotg.mdl");
		else if (self.owner.weapon == W_SUBMACHINE) setmodel(self, "progs/vw_subm.mdl");
		else setmodel(self, string_null);
	}
	if (self.owner.dropwep == TRUE) // create another vwep at respawn if dropwep is true
		remove(self);

	self.frame = self.owner.frame;
	if (world.model != "maps/start.bsp")
	{
		self.origin = self.owner.origin;
	}
	else
		self.origin = self.owner.origin - v_up*8;

	if (self.health > 0)
		self.angles_x = self.owner.angles_x;
	self.angles_y = self.owner.angles_y;
	self.nextthink = time + 0.01;
};

void() VWep =
{
	if (world.model == "maps/start.bsp")
		return;

	local entity vwep;

	vwep = spawn();
	vwep.owner = self;
	vwep.classname = "vwep";
	vwep.movetype  = MOVETYPE_NONE;
	vwep.solid     = SOLID_NOT;
	vwep.think = VWep_Think;
	vwep.nextthink = time + 0.1;
};

/*
============
PutClientInServer
============
*/
void() W_SetCurrentAmmo;
void() targetter;
void() player_stand;
void() Chase_cam_level_start;

void() PutClientInServer =
{
	local	entity spot;

	if (!self.netname) return; // No more player

	spot = SelectSpawnPoint ();

	self.classname = "player";
	self.health = self.max_health = 100;
	self.takedamage = DAMAGE_AIM;
	self.solid = SOLID_SLIDEBOX;
	if (world.model == "maps/start.bsp")
	{
		self.movetype = MOVETYPE_NONE;
		Menu_Toggle();
	}
	else
		self.movetype = MOVETYPE_WALK;
	self.flags = FL_CLIENT;

	self.origin = spot.origin + '0 0 1';
	self.angles = spot.angles;
	self.fixangle = TRUE;
	self.armorvalue = 0;

	setmodel (self, "progs/corptop.mdl");
	setsize (self, VEC_HULL_MIN, VEC_HULL_MAX);

	self.air_finished = time + 12;
	self.effects = 0;

	if (world.model != "maps/start.bsp")
		self.view_ofs = '0 0 22';
	self.velocity = '0 0 0';
      self.th_die = PlayerDie;
	self.th_pain = PlayerPain;
	self.items = 0;
	self.deadflag = 0;
	self.smacked = FALSE; //for shotgun, check corpdeth.qc
	self.exploded = FALSE; //for grenades, check damage.qc

	DecodeLevelParms();

	if (world.model != "maps/start.bsp")
		player_stand();

	self.weapon = W_PISTOL;

	if (self.dropwep == TRUE) // VWep was eliminated from world on death
	{
		VWep(); // spawn another!
		self.dropwep = FALSE;
	}

	if (self.dethtype > 0)
	{
		self.dethtype = 0;
		CorpLegs();
	}

	// Cheat code - Ranger skin
	if (self.netname == "Ranger")
		self.skin = 5;
	else
		self.skin = 0;

	if (self.spawned == FALSE) // first time spawning
	{
		VWep();
		CorpLegs();
	      if (self.ishuman)
		{
			if (world.model != "maps/start.bsp")
			{
				if (self.netname != "inbodyview")
				{
					Chase_cam_level_start();
					Start_chase_cam(self); // Bots don't need a camera			
				}
				else
					FirstPersonCamera();
				targetter(); // or a targetter
			}
			else
			{
					Chase_cam_level_start();
					Start_chase_cam(self);
			}
		}

		self.spawntime = time + 0.1;
		self.spawned = TRUE;
	}
	W_SetCurrentAmmo();
	makevectors(self.angles);
	spawn_tfog (self.origin + v_forward*20);
	if (world.model != "maps/start.bsp")
		spawn_tdeath (self.origin, self);
};

/*
===========
ClientObituary

called when a player dies
============
*/
void(entity targ, entity attacker) ClientObituary =
{
	local	float rnum;
	local	string deathstring, deathstring2;
	rnum = random();

	if (targ.classname == "player")
	{
		if (attacker.classname == "teledeath")
		{
			bprint (targ.netname);
			bprint (" was telefragged by ");
			bprint (attacker.owner.netname);
			bprint ("\n");
			attacker.owner.frags = attacker.owner.frags + 1;
			return;
		}

		if (attacker.classname == "player")
		{
			if (targ == attacker)
			{
				// killed self
				attacker.frags = attacker.frags - 1;
				bprint (targ.netname);
				bprint (" didn't feel the need to live.\n");
			}
			else if ( (teamplay == 2) && (targ.team > 0)&&(targ.team == attacker.team) )
			{
				if (rnum < 0.25)
					deathstring = " kills his friend.\n";
				else if (rnum < 0.50)
					deathstring = " hates his own team.\n";
				else if (rnum < 0.75)
					deathstring = " needs to learn to aim.\n";
				else
					deathstring = " shouldn't be using a gun.\n";
				bprint (attacker.netname);
				bprint (deathstring);
				attacker.frags = attacker.frags - 1;
				return;
			}
			else
			{
				attacker.frags = attacker.frags + 1;
				if (targ.exploded == TRUE)
				{
					deathstring = " couldn't avoid ";
					deathstring2 = "'s grenade.\n";
				}
				else
				{
					rnum = attacker.weapon;
					if (rnum == W_KNIFE)
					{
						deathstring = " was knifed to death by ";
						deathstring2 = "\n";
					}
					if (rnum == W_PISTOL)
					{
						deathstring = " was riddled with bullets by ";
						deathstring2 = ".\n";
					}
					if (rnum == W_DOUBLEPISTOL)
					{
						deathstring = " got shot up akimbo style by ";
						deathstring2 = ".\n";
					}
					if (rnum == W_SHOTGUN)
					{
						deathstring = " was blown away by ";
						deathstring2 = "'s shotgun skills.\n";
					}
					if (rnum == W_SUBMACHINE)
					{
						deathstring = " was flayed by ";
						deathstring2 = "'s Sub-Machinegun.\n";
					}
				}
				bprint (targ.netname);
				bprint (deathstring);
				bprint (attacker.netname);
				bprint (deathstring2);
			}
			return;
		}
		else
		{
			targ.frags = targ.frags - 1;
			bprint (targ.netname);

			// killed by a montser?
			if (attacker.flags & FL_MONSTER)
			{
				if (attacker.classname == "none")
					bprint (" monster death message\n");

				return;
			}

			if (attacker.solid == SOLID_BSP && attacker != world)
			{	
				bprint (" got caught up between walls.\n");
				return;
			}
			if (attacker.classname == "fireball")
			{
				bprint (" was smoked from a fireball.\n");
				return;
			}
			if (attacker.classname == "trigger_changelevel")
			{
				bprint (" thinks he can leave.\n");
				return;
			}

			// in-water deaths
			rnum = targ.watertype;
			if (rnum == -3)
			{
				if (random() < 0.5)
					bprint (" can't breath underwater.\n");
				else
					bprint (" drank too much.\n");
				return;
			}
			else if (rnum == -4)
			{
				if (random() < 0.5)
					bprint (" dies by acid.\n");
				else
					bprint (" realizes that acid burns.\n");
				return;
			}
			else if (rnum == -5)
			{
				if (targ.health < -15)
				{
					bprint (" was melted on contact.\n");
					return;
				}
				if (random() < 0.5)
					bprint (" learns about lava first hand.\n");
				else
					bprint (" falls into lava.\n");
				return;
			}

			// fell to their death?
			if (targ.deathtype == "falling")
			{
				targ.deathtype = "";
				bprint (" fell to his death\n");
				return;
			}

			// hell if I know; he's just dead!!!
			bprint (" died\n");
		}
	}
};
