/*
chasecam file
player entity variables used:
.dest_x      (HK WEAP)
.dest1_x     (HUD)
.dest1_y      (MSKIN PRO)
.dest2_x,y,z  (CAM)

 #####################
 ### chase cam mod ###
 Rob Albin, 09 Sep 96

 orig functions modified:
	WEAPONS.QC
		W_SetCurrentAmmo
		ImpulseCommands
	CLIENT.QC
		SetChangeParms
		SetNewParms
		DecodeLevelParms
		PutClientInServer

// ref. defs.qc

// message protocol defines
float SVC_SETVIEWPORT	= 5;
float SVC_SETVIEWANGLES	= 10;

// free player entity variable
// ('.float speed'  bit-flag defines:) changed:
// player.dest2_x = bit-flag variable
// player.dest2_y = chasecam distance
// player.dest2_z = chasecam zmult (height offset)
float HUD_ON          = 16;
*float CHSCAM_MONSTER  = 8;* deleted *
float CHSCAM_ON       = 4;
float LASERTARG_LIT   = 2;
float LASERTARG_ON    = 1;
*/

void (entity targ_owner) LaserTargeterToggle;

// free player entity variable
// '.float speed'  bit-flag defines:
//float CHSCAM_MONSTER  = ;

void( entity m_e, entity v_e) set_viewport =
{
	msg_entity = m_e;                         // target of message
	WriteByte (MSG_ONE, SVC_SETVIEWPORT);
	WriteEntity (MSG_ONE, v_e);           // view port
};

void() Keep_cam_chasing_owner;
void( float opt ) Remove_chase_cam;

// (moved to defs.qc)
//float chasecam_dist = 118, chasecam_zmult = 0.30000;
// changed to player.dest2_y (distance)
// player.dest2_z (zmult)
// set in client.qc for initial values

// Resets weapon model after changing view
// called by chase cam or player entities
void( entity player_ent ) Chase_cam_setweapon =
{
	local entity e;

	e = self;
	self = player_ent;
	if ( (self.dest2_x & CHSCAM_ON) ) self.weaponmodel = "";
	else                            W_SetCurrentAmmo ();
	self = e;
};

// called either by player or chase cam entities (to restart)
// vpos:
// '0 0 0' = start from player
// (else use as cam origin)
void( entity cam_owner, vector vpos ) Start_chase_cam =
{

	local entity	chase_cam;

	stuffcmd( cam_owner, "fov 80\n" );

	chase_cam = spawn();

	chase_cam.owner=cam_owner;

	// turn on bit-flag
	chase_cam.owner.dest2_x = chase_cam.owner.dest2_x | CHSCAM_ON;

	chase_cam.solid = SOLID_NOT;
	chase_cam.movetype = MOVETYPE_FLY;


	chase_cam.angles = chase_cam.owner.angles;

	setmodel (chase_cam, "progs/eyes.mdl" );
	setsize (chase_cam, '0 0 0', '0 0 0');

	if (vpos == '0 0 0')
		setorigin( chase_cam, chase_cam.owner.origin + '0 0 22' );
	else
		setorigin( chase_cam, vpos );

	chase_cam.classname = "chase_cam";

	chase_cam.nextthink = time + 0.1;
	chase_cam.think = Keep_cam_chasing_owner;

	// set CLOSE to 0 (ref. PlayerPreThink())
	//chase_cam.owner.view_ofs_z = -1;
	//set_viewport( cam_owner, chase_cam);
	chase_cam.skin = 1; // let track function handle the remote view
							  // part, start with "temp. disabled" flag set

	Chase_cam_setweapon( cam_owner );
	// distance clipping
	chase_cam.ammo_shells = cam_owner.dest2_y / 3;

};

// secondary think for cam entities
void() Reable_chase_cam =
{
	self.nextthink = time + 0.1;

	//if (self.owner.health <= 0)
	if (self.owner.solid == SOLID_NOT)
	{
		//owner died in water, reset .dest2_x flag for respawn
		self.owner.dest2_x = self.owner.dest2_x | CHSCAM_ON;
		remove( self );
		return;
	}

	if (self.owner.waterlevel)
		return;

	self.owner.dest2_x = self.owner.dest2_x | CHSCAM_ON;
	Chase_cam_setweapon( self.owner );

	self.skin = 1; // flag temp. disabled flag so track function
						// will reset remote view
	self.solid = SOLID_NOT;
	self.movetype = MOVETYPE_FLY;
	setmodel (self, "progs/eyes.mdl" );
	setsize (self, '0 0 0', '0 0 0');
	setorigin( self, self.owner.origin);
	self.ammo_shells = self.owner.dest2_y / 3; // distance clipping
	self.think = Keep_cam_chasing_owner;
	//Start_chase_cam( self.owner, '0 0 0' );
	//remove( self );

};


// called only by chase cam entities
// opt values
// TRUE = remove completely
// FALSE = remove view but keep alive with Reable_chase_cam();
void( float opt ) Remove_chase_cam =
{
	stuffcmd( self.owner, "fov 90\n" );

	// turn off bit-flag
	self.owner.dest2_x = self.owner.dest2_x - (self.owner.dest2_x & CHSCAM_ON);

	// set view-point back to normal
	set_viewport( self.owner, self.owner);


	setmodel( self, "" );
	self.velocity = '0 0 0';

	self.owner.view_ofs_z = 22;

	Chase_cam_setweapon( self.owner );

	if ( !opt )
	{
		self.nextthink = time + 0.1;
		self.think = Reable_chase_cam;
	}
	else
		remove( self );

};

void () cam_deathview =
{
	self.nextthink = time + 0.1;

	if (self.owner.solid != SOLID_NOT)
	{
		remove( self);
		return;
	}

	traceline( self.owner.origin, self.origin, TRUE, self.owner);
	if (trace_inwater)
	{
		set_viewport( self.owner, self.owner);
		self.owner.health = -1;
		remove( self);
		return;
	}
	if (trace_fraction < 1)
	{
		setorigin( self, trace_endpos - self.dest * 8);
		self.velocity = '0 0 0';
	}

	if (self.velocity != '0 0 0')
	{
		traceline( self.origin, self.origin + self.dest * 20, TRUE, self.owner);
		if (trace_fraction < 1 || trace_inwater ||
			 vlen( self.origin - self.owner.origin) > 200)
			self.velocity = '0 0 0';
	}

};

// called only by player ents immediately after being killed, if
// not already in chaseview, and not in water
void() cam_startdeathview =
{
	local entity e;
	local vector goal, ang;
	ang = self.v_angle;
	ang_x = 30; // pretend was a slight down aim
	self.weaponmodel = "";

	e = spawn();
	e.owner = self;
	e.solid = SOLID_NOT;
	e.movetype = MOVETYPE_FLY;
	e.angles = e.owner.angles;
	setmodel (e, "progs/eyes.mdl" );
	setsize (e, '0 0 0', '0 0 0');

	makevectors( ang );
	goal = (self.origin + '0 0 18') - (v_forward * 120);
	traceline( self.origin + '0 0 18', goal, TRUE, self);
	if (trace_inwater)
	{
		remove( e);
		return;
	}
	setorigin( e, trace_endpos + (v_forward * 8));
	e.velocity = '0 0 0' - v_forward * 20;
	e.dest = '0 0 0' - v_forward; // save forward direction, used in cam_deathview
	e.nextthink = time + 0.1;
	e.think = cam_deathview;

	set_viewport( self, e);

};


/*
	main think function for cam entities
		self.ammo_shells = distance clipping
		self.ammo_nails = hang-up flag
		self.dmg = lifts fix
		self.frags = lifts fix
		self.impulse = lifts fix
		self.dest = deathview vector to save 'v_forward' of fade direction
		self.skin = close proximity temporary disableing

	.v_angle_x:
		78.8 (max down aim)
		-68.9 (max up aim)
*/
void() Keep_cam_chasing_owner =
{
	local vector vtmp1, vtmp2;
	local vector owner_vec, goal, dir;
	local float  dist, cap, f_f;


	//debug var
	//local string s;

	//bprint( "\n\n\nz velocity = " );
	//s = ftos( self.owner.velocity_z);
	//bprint( s);
	//bprint( "\n");
	// (results) velocity_z does move when running on sloped ground

	self.nextthink = time + 0.1;

	// check if player toggled
	if (! (self.owner.dest2_x & CHSCAM_ON))
	{
		Remove_chase_cam( TRUE );
		return;
	}

	if (self.owner.solid == SOLID_NOT)
	{
		stuffcmd( self.owner, "fov 90\n" );

		if (intermission_running)
		{
			set_viewport( self.owner, self.owner);
			remove( self );
		}
		else
		{  // deathview
			self.velocity = '0 0 0' - normalize( self.owner.origin - self.origin) * 20;
			self.angles = vectoangles( self.velocity);
			makevectors( self.angles);
			self.dest = v_forward; // save forward direction, used in cam_deathview
			self.think = cam_deathview;
			if (self.skin) // temp disable flag is set
			{
				self.owner.view_ofs_z = -1;
				set_viewport( self.owner, self);
			}
		}
		return;
	}


	if ( self.owner.waterlevel )
	{
		Remove_chase_cam( FALSE );
		return;
	}

	owner_vec = self.owner.origin + '0 0 22';

	// get player velocity relative to player's
	// current yaw
	// f_f (based on running calcs (maxspeed = 400)
	//  (back ~= 640, forward ~= 0)
	dir_y = self.owner.v_angle_y;
	makevectors( dir );
	f_f = vlen( (v_forward * 320) - self.owner.velocity );

	// held for use after second makevectors call for
	// pulling forward on a down aim
	dir = v_forward;

//   local string s;
//   sprint( self.owner, "\n\n f_f = " );
//   s = ftos( f_f );
//   sprint( self.owner, s );
//   sprint( self.owner, "\n f_r = " );
//   s = ftos( f_r );
//   sprint( self.owner, s );
//   sprint( self.owner, "\n" );

	// increment fade-back variable
	self.ammo_shells = self.ammo_shells + 4.5;
	if (self.ammo_shells > self.owner.dest2_y)
		self.ammo_shells = self.owner.dest2_y;

	makevectors( self.owner.v_angle );

	// set spot before clipping
	//.v_angle_x:
	//	78.8 (max down aim)
	//	-68.9 (max up aim)

	goal = owner_vec - (v_forward * self.ammo_shells);
	goal_z = goal_z + 14 + (self.ammo_shells * self.owner.dest2_z);

	// adjust initial goal
	//////////////////////////////////////////////////
	if (self.owner.v_angle_x > 0)
	{
		// pull forward
		cap = self.owner.v_angle_x;
		if (cap > 56) cap = 56;
		goal = goal + dir * cap;

		// pull down
		goal_z = goal_z - (self.owner.v_angle_x * (self.ammo_shells * 0.01));
		cap = self.owner.origin_z + 28;
		if (goal_z < cap)
			goal_z = cap;
	}

	// increase height if getting under the player
	if (goal_z < owner_vec_z)
	{
		vtmp1 = goal; vtmp1_z = 0;
		vtmp2 = owner_vec; vtmp2_z = 0;
		cap = vlen( vtmp1 - vtmp2 );
		if (cap < 100)
		{
			goal_z = goal_z + ((100 - cap) * 0.6);
			if ( goal_z > owner_vec_z )
				goal_z = owner_vec_z;
		}
	}
	//////////////////////////////////////

	// clip for brushes
	//////////////////////////////////////
	traceline (owner_vec, goal, FALSE, self.owner);
	// pull a little forward, avoids most hang-ups along walls
	goal = trace_endpos + ( v_forward * 2 );

	// clip fade-back variable
	traceline( owner_vec, owner_vec - (v_forward * self.ammo_shells), FALSE, self.owner);
	if (trace_fraction < 1)
	{
		self.ammo_shells = vlen(trace_endpos - owner_vec);
		cap = fabs( self.owner.v_angle_x ) - 10;
		if (cap > 0)
			self.ammo_shells = self.ammo_shells + cap;
	}

	traceline (self.origin, goal, FALSE, self.owner );
	if (trace_fraction < 1)
	{
		// half it if blocked
		dir = normalize(goal - owner_vec);
		dist = vlen(goal - owner_vec) * 0.5;
		goal =  owner_vec + dir * dist;
	}

	// pad for floors and ceilings
	traceline (goal, goal + '0 0 6', FALSE, self.owner );
	if (trace_fraction < 1 ) goal = trace_endpos - '0 0 6';
	else
	{
		traceline (goal, goal - '0 0 6', FALSE, self.owner );
		if (trace_fraction < 1 ) goal = trace_endpos + '0 0 6';
	}
	////////////////////////////////////////

	self.angles = self.owner.angles;

	traceline (self.origin, owner_vec, FALSE, self.owner );
	if (trace_fraction < 1)
	{
		// vtmp1_z:  0 = LOS to head
		//				-1 = LOS to feet, but not head
		//				-2 = no LOS
		traceline (self.origin, self.owner.origin - '0 0 24', FALSE, self.owner );
		if (trace_fraction == 1)
			vtmp1_z = -1;
		else
			vtmp1_z = -2;
	}
	else
		vtmp1_z = 0;
	if (vtmp1_z > -2)
	{
		dir = normalize(goal - self.origin);
		dist = vlen(goal - self.origin);

		cap = dist * 0.16;

		if (cap > 5.2)
			self.velocity = dir * dist * 5.2;
		else if (cap > 1)
			self.velocity = dir * dist * cap;
		else
			self.velocity = dir * dist;

		if (vtmp1_z == -1)
		{
			self.velocity_z = self.velocity_z - 400;
		}

		// tighten up if owner running backwards
		if (f_f > 560)
		{
			cap = self.velocity_z;
			self.velocity = self.velocity * 2;
			self.velocity_z = cap;
		}

		if (! self.owner.velocity_z)
		{
			// rising/lowering lifts fix,
			// quick and dirty hack, player.velocity_z stays at zero when
			// moving on a lift, so check through an alternate route for the
			// condition and amplify the current z velocity.
			if (self.owner.origin_z != self.frags)
				//if (self.dmg &&  (self.owner.flags & FL_ONGROUND)) // avoid landing on ground events that can happen between thinks
					// wierd setup here with the .impulse var, forces a skip for first occurance,
					// avoids logic catching vel_z same/org_z dif. instance that happens when player
					// transitions between sloping to level ground while running, (also took care
					// of the same type of thing for landing after a jump)
					if (self.impulse)
						self.velocity_z = self.velocity_z * 3;
					else
						self.impulse = TRUE;
		}
		else if (self.impulse)
			self.impulse = FALSE;

		// try to avoid hangups common to ceiling clips when player
		// is running
		if( self.oldorigin == self.origin )
		{
			if( dist > 16 )
			{
				self.ammo_nails = self.ammo_nails + 1;
				if( self.ammo_nails > 2 )
				{
					self.ammo_nails = 0;
					self.velocity = '0 0 0';
					setorigin( self, goal );
				}
			}
			else
				self.ammo_nails = 0;
		}
	}
	else
	{
		self.velocity = '0 0 0';
		setorigin( self, goal );
	}

	if (! self.skin) // .skin is 1 if in a temp disable state
	{
		if (vlen(self.owner.origin - self.origin) < 30)
		{
			self.skin = 1;
			self.owner.view_ofs_z = 22;
			set_viewport( self.owner, self.owner);
		}
	}
	else
	{
		if (vlen(self.owner.origin - self.origin) > 39)
		{
			self.skin = 0;
			self.owner.view_ofs_z = -1;
			set_viewport( self.owner, self);
		}
	}


	// hacks (only) section :-)

	// keep flag clear so internal C won't drop movement checks
	if (self.flags & FL_ONGROUND)
		self.flags = self.flags - FL_ONGROUND;

	self.oldorigin = self.origin; // cam hang-up hack
	self.frags = self.owner.origin_z; // 'on lift' hack
	self.dmg = self.owner.flags & FL_ONGROUND; // 'on lift' hack
};


// called by player only
void() Toggle_chase_cam =
{

	if (self.waterlevel)
		return;

	if( (self.dest2_x & CHSCAM_ON) )
	{
		// will be noticed by next think
		// of player's chase cam entity
		self.dest2_x = self.dest2_x - CHSCAM_ON;
	}
	else
		Start_chase_cam( self, '0 0 0' );

};

/*
////////////////////////////////////////////
// laser targeter functions

// targeter ent. think function
void() LaserTargeterTrack = {
	local vector src;

	self.nextthink = time + 0.05;

	if (! (self.owner.dest2_x & LASERTARG_ON))
	{
		if( (self.owner.dest2_x & LASERTARG_LIT) )
		{
			self.owner.dest2_x = self.owner.dest2_x | LASERTARG_ON;
			self.effects = self.effects | EF_DIMLIGHT;
		}
		else
		{
			remove( self );
			return;
		}
	}

	if (self.owner.solid == SOLID_NOT)
	{
		// leave .dest2_x flags alone
		remove( self );
		return;
	}

	if (self.owner.weapon == IT_AXE)
	{
		if (self.model != "")
			setmodel( self, "" );
	}
	else if (self.model == "")
		//setmodel( self, "progs/cross1.mdl" );
		setmodel( self, "progs/s_bubble.spr" );

	makevectors( self.owner.v_angle );
	self.angles = vectoangles( v_forward );

	src = self.owner.origin + v_forward * 10;
	src_z = self.owner.absmin_z + self.owner.size_z * 0.7;

	traceline( src,  src + v_forward * 2048, FALSE, self.owner);

	setorigin( self, (0.1 * src + 0.9 * trace_endpos) );


};

void( entity targ_owner ) LaserTargeterToggle =
{

	local entity e;

	if( (targ_owner.dest2_x & LASERTARG_ON) )
	{
		// don't activate glow in multi-player
		if (! deathmatch  && ! coop)
		{
			if( (targ_owner.dest2_x & LASERTARG_LIT) )
				targ_owner.dest2_x = targ_owner.dest2_x - LASERTARG_LIT;
			else
				targ_owner.dest2_x = targ_owner.dest2_x | LASERTARG_LIT;
		}

		targ_owner.dest2_x = targ_owner.dest2_x - LASERTARG_ON;
	}
	else
	{
		targ_owner.dest2_x = targ_owner.dest2_x | LASERTARG_ON;

		e = spawn();
		e.owner = targ_owner;

		e.movetype = MOVETYPE_NONE;
		e.solid = SOLID_NOT;

		setmodel( e, "progs/s_bubble.spr" );
		//setmodel( e, "progs/cross1.mdl" );
		setsize( e, VEC_ORIGIN, VEC_ORIGIN );
		setorigin( e, e.owner.origin );

		if( (e.owner.dest2_x & LASERTARG_LIT) )
			e.effects = e.effects | EF_DIMLIGHT;

		e.nextthink = time + 0.1;
		e.think = LaserTargeterTrack;

	}

};
////////////////////////////////////////////
*/

void () Spawn_extra_changelevels;
void() Chase_cam_lvlstart_think =
{

	self.owner.skin = self.owner.dest1_y; //mskin pro stuff

	if ( (self.owner.dest2_x & CHSCAM_ON) )
		Start_chase_cam( self.owner, '0 0 0' );

	if ( (self.owner.dest2_x & LASERTARG_ON) )
	{
		self.owner.dest2_x = self.owner.dest2_x - LASERTARG_ON;
		LaserTargeterToggle( self.owner );
	}

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

	remove( self );
};


// called in CLIENT.QC by void() PutClientInServer
// player.dest2_x is set and saved between levels using parm16
// in CLIENT.QC
void() Chase_cam_level_start =
{

	local entity e;

	e = spawn();
	e.owner = self;
	e.nextthink = time + 0.2;
	e.think = Chase_cam_lvlstart_think;

};


// opt:
// 0 = minus
// 1 = plus
// 2 = read cvar temp1 for value
// (player.dest2_y holds cam distance)
void(float opt) Chase_cam_change_dist =
{
	local string s;

	if (!opt)
	{
		self.dest2_y = self.dest2_y - 2;
		if (self.dest2_y < 10)
			self.dest2_y = 10;
	}
	else if (opt == 1)
		self.dest2_y = self.dest2_y + 2;
	else
		self.dest2_y = cvar( "temp1" );

	sprint( self, "chasecam distance = " );
	s = ftos( self.dest2_y );
	sprint( self, s );
	sprint( self, "\n" );


};

// opt:
// 0 = minus
// 1 = plus
// 2 = read cvar temp1 for value
// (player.dest2_z holds zmult)
void(float opt) Chase_cam_change_zmult =
{
	local string str;
	local float f;

	if (!opt)
	{
		self.dest2_z = self.dest2_z - 0.02;
		if (self.dest2_z < 0)
			self.dest2_z = 0;
	}
	else if (opt == 1)
		self.dest2_z = self.dest2_z + 0.02;

	else
	{
		f = cvar( "temp1" );
		self.dest2_z = f * 0.01;
	}

	sprint( self, "chasecam verticle offset = " );

	// convert and strip for display
	// apparantly,
	//	.dest2_z = .dest2_z +- 0.02;
	// don't _exactly_ add/sub 0.02
	f = self.dest2_z * 100;
	f = rint( f );
	str = ftos( f );

	sprint( self, str );
	sprint( self, "\n" );


};

/*
Reads current values of cvar temp1:
opt:
	0 = into player.dest2_y (chasecam distance)
	1 = into player.dest2_z (zmult)
*/
void(float opt) Chase_cam_read_temp1 =
{
	if (!opt)
		Chase_cam_change_dist(2);
	else
		Chase_cam_change_zmult(2);
};

// ### chase cam mod ###
// #####################

void () S_E_CLVLS_marquee =
{
	self.nextthink = time + 1;
	self.ammo_shells = self.ammo_shells + 1;
	//centerprint( self.owner, "­ Γθασεγαν ΙΙ QuakeC mod ­\n\n­  written by Rob Albin  ­" );
	centerprint(self.owner,"Sυπεςδυπες Qυαλε 3.0\n\nWritten by BΟNΔO\nhondobondo@yahoo.com\n");

	if (self.ammo_shells > 3)
		remove( self );
};

void () Spawn_extra_changelevels =
{
	if (deathmatch)
		return;
		
	local float i;
	local entity e;

/*
	if (time < 5)
	{
		e = spawn();
		e.owner = self.owner;
		e.nextthink = time + 3;
		e.think = S_E_CLVLS_marquee;
	}
*/

};


// replacement code for laser targeter
void () LaserTargeterTrack = {

   local vector dir;
   local vector src;

   if ( !(self.owner.speed & LASERTARG_ON) ) {

      remove (self);
      return ;

   }
   makevectors (self.owner.v_angle);
   src = self.owner.origin;
   src = (self.owner.origin + (v_forward * MOVETYPE_BOUNCE));
   src_z = (self.owner.absmin_z + (self.owner.size_z * 0.700));
   dir = aim (self.owner,100000.000);
   traceline (src,(src + (dir * 320.000)),FALSE,self.owner);
   src = ((0.100 * src) + (0.900 * trace_endpos));
   setorigin (self,src);
   self.nextthink = (time + 0.100);

};

void (entity targ_owner) LaserTargeterToggle = {

   local entity e;

   if ( (targ_owner.speed & LASERTARG_ON) ) {

      targ_owner.speed = (targ_owner.speed - LASERTARG_ON);

   } else {

      targ_owner.speed = (targ_owner.speed | LASERTARG_ON);
      e = spawn ();
      e.owner = targ_owner;
      e.movetype = MOVETYPE_NONE;
      e.solid = SOLID_NOT;
      setmodel (e,"progs/s_bubble.spr");
      setsize (e,VEC_ORIGIN,VEC_ORIGIN);
      setorigin (e,e.owner.origin);
      e.nextthink = (time + 0.100);
      e.think = LaserTargeterTrack;

   }

};
