#include "common.qh"

#include "damage.qh"
#include "mdl.qh"
#include "client.qh"
#include "server.qh"
#include "misc.qh"
#include "items.qh"

#include "weapon.qh"
#include "equip.qh"

#include "act.qh"
#include "act_player.qh"

#include "override.qh"

void ()
act_player_init =
{
	precache_sound ("player/plyrjmp8.wav");
	precache_sound ("player/h2ojump.wav");

	precache_sound ("player/land.wav");
	precache_sound ("player/land2.wav");
	
	precache_sound ("player/inh2o.wav");
	precache_sound ("player/slimbrn2.wav");
	precache_sound ("player/inlava.wav");

	precache_sound ("misc/water1.wav");
	precache_sound ("misc/water2.wav");
	
	precache_sound ("player/drown1.wav");
	precache_sound ("player/drown2.wav");

	precache_sound ("player/lburn1.wav");
	precache_sound ("player/lburn2.wav");

	precache_sound ("player/gasp1.wav");
	precache_sound ("player/gasp2.wav");
	
	precache_sound ("misc/outwater.wav");

	precache_sound ("player/pain1.wav");
	precache_sound ("player/pain2.wav");
	precache_sound ("player/pain3.wav");
	precache_sound ("player/pain4.wav");
	precache_sound ("player/pain5.wav");
	precache_sound ("player/pain6.wav");

	precache_sound ("player/death1.wav");
	precache_sound ("player/death2.wav");
	precache_sound ("player/death3.wav");
	precache_sound ("player/death4.wav");
	precache_sound ("player/death5.wav");

	precache_sound ("player/gib.wav");
	precache_sound ("player/udeath.wav");
	
	precache_sound ("player/h2odeath.wav");

	precache_model ("progs/backpack.mdl");
	precache_sound ("weapons/lock4.wav");
	precache_sound ("items/itembk2.wav");
};

// ========================================================================

void (float scl)
player_throw_ammo =
{
	local entity player;

	player = self;

	self = spawn ("BACKPACK");

	if (scl > 1) {
		/* FIXME: This is actually a hack to avoid powerups */
#define qg(eid)					\
		if (equip_query (player, eid))	\
			equip_grant (self, eid)	\

		qg (EQUIPID_SUPER_SHOTGUN);
		qg (EQUIPID_NAILGUN);
		qg (EQUIPID_SUPER_NAILGUN);
		qg (EQUIPID_GRENADE_LAUNCHER);
		qg (EQUIPID_ROCKET_LAUNCHER);
		qg (EQUIPID_LIGHTNING_GUN);
#undef qg

		player.itemfield_1 = 0;
		player.itemfield_2 = 0;
		player.itemfield_3 = 0;
		player.itemfield_4 = 0;

		scl = 1;
	}

	self.ammo_shells = player.ammo_shells * scl;
	player.ammo_shells -= self.ammo_shells;
	self.ammo_nails = player.ammo_nails * scl;
	player.ammo_nails -= self.ammo_nails;
	self.ammo_rockets = player.ammo_rockets * scl;
	player.ammo_rockets -= self.ammo_rockets;
	self.ammo_cells = player.ammo_cells * scl;
	player.ammo_cells -= self.ammo_cells;

	if (!self.ammo_shells && !self.ammo_nails && !self.ammo_rockets
		&& !self.ammo_cells && !self.itemfield_1 && !self.itemfield_2
		&& !self.itemfield_3 && !self.itemfield_4) {
		remove (self);
		self = player;
		return;
	}

	self.origin = center (player);

	self.movetype = MOVETYPE_TOSS;
	self.velocity_x = crandom () * 100;
	self.velocity_y = crandom () * 100;
	self.velocity_z = 200;

	self.mins = '-16 -16 0';
	self.maxs = '16 16 56';

	self.model = "progs/backpack.mdl";
	self.noise2 = "weapons/lock4.wav";
	self.noise7 = "items/itembk2.wav";

	self.wait = -1;

	self.think = SUB_remove;
	self.nextthink = time + 20 + random () *20;

	item_generic ();

	self = player;
};

void ()
player_die =
{
	local float r;

	if (self.health > CONFIG_GIB_HEALTH) {
		if (self.waterlevel >= 3) {
			self.noise = "player/h2odeath.wav";
		} else {
			r = random () * 5;
			if (r < 1)
				self.noise = "player/death1.wav";
			else if (r < 2)
				self.noise = "player/death2.wav";
			else if (r < 3)
				self.noise = "player/death3.wav";
			else if (r < 4)
				self.noise = "player/death4.wav";
			else
				self.noise = "player/death5.wav";
		}
		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE);

		player_throw_ammo (2);
		mdl_bodyque_and_func (MDL_FUNC_DIE, floor (random () * 5));
	} else {
		r = random () * 2;
		if (r < 1)
			self.noise = "player/gib.wav";
		else
			self.noise = "player/udeath.wav";

		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE);
		mdl_bodyque_and_func (MDL_FUNC_GIB, 0);
	}

	makevectors (self.angles);	/* Not v_angle! */
	self.velocity = (v_forward * -200) + (v_up * 100);

	if (self.v_angle_x < 75 && self.v_angle_x > -75) {
		self.angles = self.v_angle;
		self.angles_x += 20;
		self.fixangle = TRUE;
	}

	/* FIXME: Hack. */
	setorigin (self, vieworigin (self));
	RemoveClientFromGame ();
};

// ========================================================================

float (float d)
_player_takedamage_core =
{
	self.health -= d;
	self.dmg_take += d;

	if (floor (self.health) <= 0) {
		deathmsg_display ();
		player_die ();
	} else {
		deathmsg_nodisplay ();
		self.mdl_func (MDL_FUNC_PAIN, d);
	}

	return TRUE;
};

float (float d)
player_takedamage =
{
	local float taked, r;

	damage_push (d);

	taked = damage_armor (d);
	self.dmg_save += (d - taked);

	if (taked <= 0)
		return FALSE;

	if (self.pain_finished < time) {
		if ((self.mdl_mod & MDL_MOD_SWIM_IN) && self.air_finished < (time + random () * 9)) {
			r = random () * 2;
			if (r < 1)
				self.noise = "player/drown2.wav";
			else
				self.noise = "player/drown1.wav";
		} else {
			r = random () * 5;
			if (r < 1)
				self.noise = "player/pain1.wav";
			else if (r < 2)
				self.noise = "player/pain2.wav";
			else if (r < 3)
				self.noise = "player/pain3.wav";
			else if (r < 4)
				self.noise = "player/pain4.wav";
			else if (r < 5)
				self.noise = "player/pain5.wav";
			else
				self.noise = "player/pain6.wav";
		}
		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);

		self.pain_finished = time + 1;
	}
	
	return _player_takedamage_core (taked);
};

// ========================================================================

float (float d)
_player_takedamage_drown =
{
	local float r;

	if (d <= 0)
		return FALSE;

	if (self.pain_finished < time) {
		r = random () * 2;
		if (r < 1)
			self.noise = self.noise3;
		else
			self.noise = self.noise4;

		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);

		self.pain_finished = time + 1;
	}

	return _player_takedamage_core (d);
};

float (float d)
_player_takedamage_melt =
{
	local float taked, r;

	taked = damage_armor (d);
	self.dmg_save += d - taked;

	if (taked <= 0)
		return FALSE;

	if (self.pain_finished < time) {
		r = random () * 2;
		if (r < 1)
			self.noise = self.noise3;
		else
			self.noise = self.noise4;

		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);

		self.pain_finished = time + 1;
	}

	return _player_takedamage_core (taked);
};

float (float d, float (float d) damg_func, void () deathmessage)
_player_damage_custom =
{
	local float ret;

	override_set_th_takedamage (self, damg_func);
	ret = damage (self, self, world, d, deathmessage);
	override_set_th_takedamage (self, player_takedamage);
	
	return ret;
};

// ========================================================================

void ()
_deathmsg_player_liquid =
{
	local string def_nname;
	local float r;

	def_nname = name (self);

	switch (self.watertype) {
	case CONTENT_WATER:
		r = random () * 2;
		if (r < 1)
			bprint (PRINT_DEATH, def_nname, " sleeps with the fishes.\n");
		else
			bprint (PRINT_DEATH, def_nname, " hunts for air.\n");
		break;
	case CONTENT_SLIME:
		r = random () * 2;
		if (r < 1)
			bprint (PRINT_DEATH, def_nname, " can't exist on slime alone.\n");
		else
			bprint (PRINT_DEATH, def_nname, " floats in slime.\n");
		break;
	case CONTENT_LAVA:
		r = random () * 3;
		if (r < 1)
			bprint (PRINT_DEATH, def_nname, " swam in a volcano.\n");
		else if (r < 2)
			bprint (PRINT_DEATH, def_nname, " turned into hot slag.\n");
		else
			bprint (PRINT_DEATH, def_nname, " parties with Chthon.\n");
		break;
	default:
		bprint (PRINT_DEATH, def_nname, " gulps it down.\n");
		break;
	}
};

void ()
_deathmsg_player_fall =
{
	local string def_name;
	local float r;

	def_name = name (self);

	r = random () * 3;
	if (r < 1)
		bprint (PRINT_DEATH, def_name, " landed head-first.\n");
	else if (r < 2)
		bprint (PRINT_DEATH, def_name, " cratered.\n");
	else
		bprint (PRINT_DEATH, def_name,
				" took a nose dive into the ground.\n");
};

// ========================================================================

/* This is so stupid. */
float ()
_player_jump =
{
	if (!self.button2) {
		self.flags |= FL_JUMPRELEASED;
		return FALSE;
	}

	if (!(self.flags & FL_ONGROUND))
		return FALSE;

	if (!(self.flags & FL_JUMPRELEASED))
		return FALSE;

	self.mdl_func (MDL_FUNC_JUMP, 0);

	self.flags &= ~FL_JUMPRELEASED;
	self.button2 = 0;

	return TRUE;
};

void ()
_player_water_jump =
{
	if (self.waterlevel <= 1) {
		if (_player_jump())
			sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
		return;
	}

	if (self.waterlevel <= 2) {
		if (_player_jump ())
			sound (self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM);
	}

	/* Yeah.  The engine is a piece of crap. */
	if (self.button2) {
		if (self.flags & FL_ONGROUND) {
			self.flags &= ~FL_ONGROUND;
			self.origin_z++;
		}
	}
};

/* This is called every frame... */
void ()
player_prethink =
{
	/* Gasp for air if we weren't swimming..
	/* Checking here rather than later, with mdl_mod is a HACK */
	/* It avoids the gasp when the player hasn't communicated
	   with the server in a while (it happens..) */
	if (self.waterlevel < 3) {
		if (self.mdl_mod & MDL_MOD_SWIM_IN) {
			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;
	}

	/* Let the MDL (and us) know we're swimming */
	self.mdl_mod &= ~MDL_MOD_SWIM;
	if (self.waterlevel >= 3)
		self.mdl_mod |= MDL_MOD_SWIM_IN;
	else if (self.waterlevel >= 1)
		self.mdl_mod |= MDL_MOD_SWIM_OVER;

	/* Handle environmental damage */
	if (self.watertype == CONTENT_SLIME) {
		self.noise3 = "player/lburn1.wav";
		self.noise4 = "player/lburn2.wav";
		_player_damage_custom (4 * self.waterlevel * frametime,
							   _player_takedamage_melt,
							   _deathmsg_player_liquid);
	} else if (self.watertype == CONTENT_LAVA) {
		self.noise3 = "player/lburn1.wav";
		self.noise4 = "player/lburn2.wav";
		_player_damage_custom (50 * self.waterlevel * frametime,
							   _player_takedamage_melt,
							   _deathmsg_player_liquid);
	}

	/* Try to breathe :) */
	if (self.air_finished < time) {
		local float damg;

		damg = (time - self.air_finished) * 0.2;
		if (damg > 1.5)
			damg = 1.5;

		self.noise3 = "player/drown1.wav";
		self.noise4 = "player/drown2.wav";
		_player_damage_custom (damg, _player_takedamage_drown,
							   _deathmsg_player_liquid);
	}

	/* Enter/exit water, swim sound */
	if (!self.waterlevel) {
		if (self.flags & FL_INWATER) {
			sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM);
			self.flags &= ~FL_INWATER;
		}

		if (_player_jump())
			sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
	} else {
		if (!(self.flags & FL_INWATER)) {
			if (self.watertype == CONTENT_WATER)
				self.noise = "player/inh2o.wav";
			else if (self.watertype == CONTENT_SLIME)
				self.noise = "player/slimbrn2.wav";
			else if (self.watertype == CONTENT_LAVA)
				self.noise = "player/inlava.wav";
			sound (self, CHAN_BODY, self.noise, 1, ATTN_NORM);

			self.flags = (self.flags & ~FL_JUMPRELEASED) | FL_INWATER;
			
			self.water_finished = 0;
		}

		if (self.mdl_mod&MDL_MOD_SWIM) {
			local float increment, mxspeed;

			increment = vlen (self.origin - self.pos1);
			mxspeed = calc_max_speed (self);
			if (increment > mxspeed)
				increment = mxspeed;

			self.water_finished += increment;
			if (self.water_finished >= mxspeed) {
				self.water_finished = 0;
				if (random () < 0.5)
					self.noise = "misc/water1.wav";
				else
					self.noise = "misc/water2.wav";
				sound (self, CHAN_BODY, self.noise, 1, ATTN_NORM);
			}
		}

		_player_water_jump ();
	}

	self.pos1 = self.origin;

#if DEBUG
	debug_impulse ();
#endif
	weapon_player_impulse ();
};

/* This is not called every frame */
void ()
player_think =
{	// The player does not think.
};

/* FIXME: We should check to see if we actually collided.
          I don't know how to at the moment... */
void ()
player_velocity_damage =
{
	local vector vel, dir;
	local float v1, v2;

	vel = self.movedir;
	self.movedir = self.velocity;

	/* Did we just teleport? */
	if (self.teleport_time)
		return;

	/* Did we slow down a lot? */
	v1 = vlen (vel);
	v2 = vlen (self.velocity);
	if (v2 >= v1)
		return;

	vel = vel - self.velocity;
	vel_z = vel_z * 2;
	v1 = vlen (vel);

	/* Find the largest magnitude dir */
	vel_x = fabs (vel_x);
	vel_y = fabs (vel_y);
	vel_z = fabs (vel_z);

	dir = '0 0 1';
	if (vel_y > (vel * dir))
		dir = '0 1 0';
	if (vel_x > (vel * dir))
		dir = '1 0 0';

	/* Play sounds, apply damage */
	if (v1 > CONFIG_LAND_SOUND) {
		if (dir_z && (self.watertype == CONTENT_WATER)) {
			self.noise = "player/h2ojump.wav";
		} else if (v1 > CONFIG_LAND_HURT) {
			self.noise = "player/land2.wav";
			self.pain_finished = time + 1;
			damage (self, self, world, 5, _deathmsg_player_fall);
		} else {
			self.noise = "player/land.wav";
		}

		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
	}
};

/* This is also called every frame... */
void ()
player_postthink =
{
	player_velocity_damage ();

	self.items &= ~(IT_ARMOR1|IT_ARMOR2|IT_ARMOR3);
	if (self.armortype >= 0.8)
		self.items |= IT_ARMOR3;
	else if (self.armortype >= 0.6)
		self.items |= IT_ARMOR2;
	else if (self.armortype >= 0.3)
		self.items |= IT_ARMOR1;

	update_weapon_flags ();
	weapon_set_ammo ();
};

// ========================================================================

void (float nitem, string str1, string str2)
_item_xprint_strs =
{
	if (!is_cl (self))
		return;
	if (!str1)
		return;

	if (nitem == 1) {
		stuffcmd (self, "bf\n");
		xprint_start (self, PRINT_LOW);
		xprint_str ("You get ");
	} else if (nitem > 1)
		xprint_str (", ");

	xprint_str (str1);
	if (str2)
		xprint_str (str2);
};

void (float nitem, string str1, string str2)
_item_xprint_strs_last =
{
	if (!is_cl(self))
		return;
	if (!str1)
		return;

	if (nitem == 1) {
		stuffcmd (self, "bf\n");
		xprint_start (self, PRINT_LOW);
		xprint_str ("You get ");
	}

	if (nitem == 2)
		xprint_str (" and ");
	else if (nitem > 2)
		xprint_str (", and ");

	xprint_str (str1);
	if (str2)
		xprint_str (str2);

	xprint_str ("\n");
};

float nitem;
string str1, str2;
void (.float fld, float max, string sing, string plur)
_player_takefield =
{
	local float new, diff;

	new = increase_bound (self.fld, other.fld, max);
	if (new > 999)
		new = 999;
	if (new > self.fld) {
		diff = floor (new) - floor (self.fld);
		if (!diff)
			diff = new - self.fld;

		_item_xprint_strs (nitem, str1, str2);
		str1 = ftos (diff);
		if (diff == 1)
			str2 = sing;
		else
			str2 = plur;
		nitem++;

		self.fld = new;
	}
};

float ()
player_takeitem =
{
	local float eid;

	nitem = 0;
	str1 = NIL;
	str2 = NIL;

	while ((eid = equip_iter (other)) != -1) {
		if (equip_query (self, eid))
			continue;

		equip_grant (self, eid);

		_item_xprint_strs (nitem, str1, str2);
		str1 = "the ";
		str2 = equip_id_to_string (eid);
		nitem++;
	}

	_player_takefield (ammo_shells, self.max_ammo_shells, " shell", " shells");
	_player_takefield (ammo_nails, self.max_ammo_nails, " nail", " nails");
	_player_takefield (ammo_rockets, self.max_ammo_rockets, " rocket",
					   " rockets");
	_player_takefield (ammo_cells, self.max_ammo_cells, " cell", " cells");

	if (other.armorvalue <= self.max_armor) {
		if ((other.armorvalue * other.armortype)
			> (self.armorvalue * self.armortype)) {
			self.armorvalue = other.armorvalue;
			self.armortype = other.armortype;

			_item_xprint_strs (nitem, str1, str2);
			str2 = NIL;
			if (self.armortype >= 0.8)
				str1 = "strong armor";
			else if (self.armortype >= 0.6)
				str1 = "medium armor";
			else if (self.armortype >= 0.3)
				str1 = "light armor";
			else
				str1 = "really crappy armor";
			nitem++;
		}
	}

	_player_takefield (health, self.max_health, " health", " health");

	_item_xprint_strs_last (nitem, str1, str2);

	if (nitem)
		return TRUE;
	return FALSE;
};

// ===================================================================== //

void ()
act_setup_player ={
	act_setup ();

	self.movedir = '0 0 0';

	self.solid = SOLID_SLIDEBOX;
	self.takedamage = DAMAGE_AIM;
	self.movetype = MOVETYPE_WALK;

	self.deadflag = DEAD_NO;

	self.gravity = 1;
	self.maxspeed = sv_maxspeed;
	self.mass = 1000;

	self.air_finished = time + 12;
	self.pain_finished = 0;
	self.water_finished = 0;

	override_set_th_takeitem (self, player_takeitem);

	override_set_th_takedamage (self, player_takedamage);

	override_set_prethink (self, player_prethink);
	override_set_actthink (self, player_think);
	override_set_postthink (self, player_postthink);
};
