//====================================================================
//
// SWINGING GRAPPLING HOOK			by: Perecli Manole AKA Bort
//
//====================================================================
// Aside from this new file, the following are the modifications
// done to id's original source files:
//--------------------------------------------------------------------
// File: Progs.src
// Location: before the "weapons.qc" line
// Added: hook.qc
//--------------------------------------------------------------------
// File: Client.qc
// Procedure: PlayerPostThink
// Location: before line "W_WeaponFrame ();"
// Added: CheckGrapHook ();
//--------------------------------------------------------------------
// File: World.qc
// Procedure: worldspawn
// Location: after line "precache_model ("progs/s_spike.mdl");"
// Added: precache_model ("progs/hook.mdl");
//        precache_model ("progs/chain.mdl");
//--------------------------------------------------------------------
// File: Weapons.qc
// Procedure: W_Precache
// Location: end of procedure
// Added: precache_sound ("shambler/smack.wav");
//        precache_sound ("blob/land1.wav");
//        precache_sound ("hook/chain1.wav");
//        precache_sound ("hook/chain2.wav");
//        precache_sound ("hook/retract.wav");
//--------------------------------------------------------------------
// File: Defs.qc
// Declaration group: player only fields
// Location: after line ".float pain_finished;"
// Added: .float hook;	
//--------------------------------------------------------------------


void(vector org, vector vel, float damage) SpawnBlood;	// prototype
float () crandom;   							// prototype


float	HOOK_OUT 	= 1;		// bit flag on if hook has been launched
float	HOOK_ON 	= 2;		// bit flag on if hook command is active
float SHRINK_ON 	= 4;		// bit flag on if shrink chain is active 
float GROW_ON 	= 8;		// bit flag on if grow chain is active

// impulse constants
float I_HOOK 		= 98;
float	I_GROW 		= 97;
float	I_SHRINK	 	= 96;
float	I_STOP	 	= 95;

float MIN_CHAIN_LEN 	= 40;		// minimum chain length
float MAX_CHAIN_LEN 	= 1000;	// maximum chain length
float CHAIN_LINK_LEN	= 55;		// length between chain link origins
float GROW_SHRINK_RATE 	= 40;		// units of lengthen/shrink chain in 0.1 sec


//--------------------------------------------------------------------
// Vector dot product function
//--------------------------------------------------------------------
float (vector from, vector onto) Dot = 
{
	return from_x * onto_x + from_y * onto_y + from_z * onto_z;
};


//--------------------------------------------------------------------
// Removes hook and detaches player
//--------------------------------------------------------------------
void () DropHook =
{
      local entity linkptr;
      local entity nextptr;

	// remove all hook flags
	self.owner.hook = 0;

	// removes hook and chain
	linkptr = self;
	while (linkptr != world)
	{
		nextptr = linkptr.goalentity;
		remove (linkptr);
		linkptr = nextptr;
	}

	sound (self.owner, CHAN_AUTO, "hook/retract.wav", 1, ATTN_NORM);	
};


//--------------------------------------------------------------------
// Spawn and removes and refreshes chain links
//--------------------------------------------------------------------
void () MaintainLinks =
{
	local vector chainvec;		// vector of the chain 
	local vector chainunit; 	// vector of chain with distance of 1
	local float chainlen;		// length of chain
	local entity newlink;		// pointer to chain link being added
	local entity currlink;		// pointer to current link being traversed
	local entity nextlink;		// pointer to next link after current link
	local float currpos;		// numeric position of currlink
	local float linknum;		// number of links that should exist
	local float linkstart;		// length from hook at which currlink starts

      // get info about chain
	chainvec = ((self.owner.origin + '0 0 16') - self.origin);
	chainunit = normalize(chainvec);
	chainvec = chainvec - chainunit * 18;
	chainlen = vlen(chainvec);

	currlink = self;
	currpos = 0;

	// generate and refresh links
	linknum = ceil(chainlen / CHAIN_LINK_LEN);    
	while (currpos < linknum)
	{
		// add entities if chain's length grows
		if (currlink.goalentity == world)
		{
			newlink = spawn();
			newlink.movetype = MOVETYPE_NOCLIP;
			newlink.solid = SOLID_NOT;
			setmodel (newlink, "progs/chain.mdl");
			setsize (newlink, '0 0 0', '0 0 0');
			newlink.goalentity = world;
			currlink.goalentity = newlink;
		}
		currlink = currlink.goalentity;
		currpos = currpos + 1;
		
		// set angles 
		currlink.angles = vectoangles(chainvec);
		if ((currlink.angles_y > 0) && (currlink.angles_y < 180))
			currlink.angles = currlink.angles + '0 1 0';
		currlink.angles_z = currlink.angles_z + crandom() * 30;

		// set position and frames 
		linkstart = (currpos - 1) * CHAIN_LINK_LEN;
		if (currpos < linknum)
		{
			setorigin (currlink, self.origin + chainunit * (linkstart + CHAIN_LINK_LEN / 2 ));
			currlink.frame = 9;
		}
		else
		{
			setorigin (currlink, self.origin + chainunit * (linkstart + (chainlen - linkstart) / 2 ));
			currlink.frame = floor((chainlen - linkstart) / CHAIN_LINK_LEN * 10);
		}
	}
	
	// remove remaining entities if chain's length shrinks
	nextlink = currlink.goalentity;
	currlink.goalentity = world;
	currlink = nextlink;
	while (currlink != world)
	{
		nextlink = currlink.goalentity;
		remove (currlink);
		currlink = nextlink;
	}
};


//--------------------------------------------------------------------
// Hook behavior function
//--------------------------------------------------------------------
void () HookBehavior =
{
      local vector spray;		// for blood	
      local vector chainvec;		// chain vector
	local vector velpart;		// player velocity moving away from hook
	local float chainlen;		// length of extended chain
	local float framestep;		// grow or shrink step per frame
	local float f1;			// antigravity force
	local float i1, i2;		// intermediate values
		      
	// decide when to disconnect hook
	if (  ((self.owner.hook & HOOK_ON) != HOOK_ON) ||
		(self.owner.teleport_time > time) || 	// if player goes through teleport
		(self.owner.deadflag) ||			// if player dies
		(self.enemy.solid == SOLID_NOT)	)	// if target dies
	{
		DropHook();
		return;
	}
	
	// do only every 0.1 of a second
	if (time - self.wait >= 0.1)
	{
		// give some damage to entities that take damage
		if (self.enemy.takedamage)
			T_Damage (self.enemy, self, self.owner, 3);

		// when hook hits player add blood and sounds
		if (self.enemy.solid == SOLID_SLIDEBOX)
		{
			sound (self, CHAN_AUTO, "blob/land1.wav", 0.8, ATTN_NORM);
			spray_x = 100 * crandom();
 			spray_y = 100 * crandom();
			spray_z = 100 * crandom() + 50;
			SpawnBlood (self.origin, spray, 20);
			setorigin (self, self.enemy.origin + self.enemy.mins + self.enemy.size * 0.5);
		}
	
		self.wait = time;
	}

	self.velocity = self.enemy.velocity;

// chain physics

	// grow the length of the chain
	if ((self.owner.hook & GROW_ON) && (self.armorvalue < MAX_CHAIN_LEN))
	{
		if (time - self.ltime > 0.1) self.ltime = time - 0.1;
		framestep = 10 * (time - self.ltime) * GROW_SHRINK_RATE;
		self.armorvalue = self.armorvalue + framestep;
		self.ltime = time;

		// trigger climb sound
		if (time - self.delay >= 0.1)
		{
			sound (self.owner, CHAN_AUTO, "hook/chain1.wav", 0.8, ATTN_NORM);
			self.delay = time;
		}
	}

	// shrink the length of the chain
 	if ((self.owner.hook & SHRINK_ON) && (self.armorvalue > MIN_CHAIN_LEN))
	{
		if (time - self.ltime > 0.1) self.ltime = time - 0.1;
		framestep = 10 * (time - self.ltime) * GROW_SHRINK_RATE;
		self.armorvalue = self.armorvalue - framestep;
		self.ltime = time;

		// trigger slide sound
		if (time - self.delay >= 0.1)		
		{
			sound (self.owner, CHAN_AUTO, "hook/chain2.wav", 0.8, ATTN_NORM);
			self.delay = time;
		}
	}

      chainvec = self.origin - (self.owner.origin + '0 0 16');
	chainlen = vlen (chainvec);

	// if player's location is beyond the chain's reach
	if (chainlen > self.armorvalue)	
	{	 
		// derives force towards hook
	   	f1 = (chainlen - self.armorvalue) * 3;

		// determine player's velocity component moving away from hook
		i1 = Dot(self.owner.velocity,chainvec);
		i2 = Dot(chainvec,chainvec);
		velpart = chainvec * (i1 / i2);

		// get magnitude(tug on chain) of velpart
		i1 = vlen (velpart);
		
		// if hard tug on the chain add a bounce
		if (i1 > 450) velpart = velpart * 1.35;

		// apply chain restrainment 
		self.owner.velocity = self.owner.velocity - velpart;  

		// if harder tug on chain, then inflict pain
		if (i1 > 900)
		{
			T_Damage (self.owner, world, world, 5);
			sound (self.owner, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM);
		}
	}
	else
		f1 = 0;

      // applys forces to players velocity 
	self.owner.velocity = self.owner.velocity + normalize(chainvec) * f1;

	MaintainLinks ();

	self.nextthink = time + 0.01;
};


//--------------------------------------------------------------------
// Hook's touch function
//--------------------------------------------------------------------
void() HookTouch =
{
	if (!(self.owner.hook & HOOK_ON)) 
	{		
		DropHook();
		return;
	}

	if (other.takedamage)
		T_Damage (other, self, self.owner, 10 );

 	if (other.solid == SOLID_SLIDEBOX)
	{
		sound (self, CHAN_AUTO, "shambler/smack.wav", 1, ATTN_NORM);	
		SpawnBlood (self.origin, self.velocity, 10);
		setorigin (self, other.origin + other.mins + other.size * 0.5);
	}
	else
	{
		sound (self, CHAN_AUTO, "player/axhit2.wav", 1, ATTN_NORM);
		self.avelocity = '0 0 0';
	}

	self.velocity = other.velocity;

	self.enemy = other;
	self.think = HookBehavior;
	self.nextthink = time + 0.01;
	self.touch = SUB_Null;
};


//--------------------------------------------------------------------
// Limit hook length during launch 
//--------------------------------------------------------------------
void() LaunchHook =
{
      local vector chainvec;			// chain vector
	local float chainlen;			// length of extended chain
	
	chainvec = self.origin - (self.owner.origin + '0 0 16');
	chainlen = vlen (chainvec);

	// armorvalue is used to hold current length of chain
	self.armorvalue = chainlen;

	if (chainlen > MAX_CHAIN_LEN)
	{
		DropHook();
		return;
	}

	MaintainLinks ();	

	self.nextthink = time + 0.01; 
};


//--------------------------------------------------------------------
// Initiates the hook
//--------------------------------------------------------------------
void(entity myself) InitiateHook =
{
	local entity newhook;

	newhook = spawn ();
	newhook.owner = myself;
	newhook.movetype = MOVETYPE_FLY;
	newhook.solid = SOLID_BBOX;
	setmodel (newhook, "progs/hook.mdl");
	setsize (newhook, '0 0 0', '0 0 0');     
	makevectors (myself.v_angle);
	setorigin (newhook, myself.origin + (v_forward*16) + '0 0 16' );
	newhook.velocity = v_forward*1000;
	newhook.angles = vectoangles(v_forward);
	newhook.avelocity = '0 0 600';
	sound (myself, CHAN_AUTO, "weapons/ax1.wav", 1, ATTN_NORM);
	
	// used as pointer to first chain link
	newhook.goalentity = world;

	// smoothens clib and slide rates; tracks time between frames 
	newhook.ltime = time;  

	// keeps track of how frequent chain noise should occur 
	newhook.delay = time;   

	// keeps track of how frequent damage by hook should occur
	newhook.wait = time;
	
	newhook.touch = HookTouch;
	newhook.nextthink = time + 0.01 ;     
	newhook.think = LaunchHook;
};


//--------------------------------------------------------------------
// Checks impulse
//--------------------------------------------------------------------
void() CheckGrapHook = 
{
	if ((!(self.hook & HOOK_OUT)) && (self.impulse == I_HOOK))
	{
		// flags that one instance of hook is spawned
		self.hook = self.hook | HOOK_OUT;
	
		// flags last activated hook impulse as being ON 
		self.hook = self.hook | HOOK_ON;   

		InitiateHook (self);
		return;
	}

	if (self.hook & HOOK_ON) 
	{ 
		// release hook	
		if (self.impulse == I_HOOK)
		{
			self.hook = self.hook - (self.hook & HOOK_ON);
			return;
		}

		// hop of chain and release hook
		if (	(self.button2) && 
			(!(self.flags & FL_ONGROUND)) &&
			(self.flags & FL_JUMPRELEASED) &&
			(!(self.flags & FL_INWATER))	)
		{
			self.hook = self.hook - (self.hook & HOOK_ON);
			self.velocity_z = self.velocity_z + 200;
			sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
			return;
		}

		// deactivate chain growth or shrink
		if (self.impulse == I_STOP)
		{
			self.hook = self.hook - (self.hook & GROW_ON);
			self.hook = self.hook - (self.hook & SHRINK_ON);
			return;
		}

		// activate chain growth
		if (self.impulse == I_GROW) 
		{
			self.hook = self.hook | GROW_ON;
			self.hook = self.hook - (self.hook & SHRINK_ON);
			return;
		}

		// activate chain shrinking
		if (self.impulse == I_SHRINK)
		{
			self.hook = self.hook | SHRINK_ON;		
			self.hook = self.hook - (self.hook & GROW_ON);
		}
	}
};