class GHAGKeyEvents : EventHandler
{   
	override bool InputProcess (InputEvent e)
	{
		if (e.Type == InputEvent.Type_KeyDown)
			SendNetworkEvent("KinesisActivate", e.KeyScan); 
		if (e.Type == InputEvent.Type_KeyUp)
			SendNetworkEvent("KinesisDeactivate", e.KeyScan); 
		return false;
   }

   override void NetworkProcess(ConsoleEvent e)
   {               
		if (e.Name == "KinesisActivate")  
		{
			let gardie = GHAGGardevoir(players[consoleplayer].mo);
		
			int key1, key2;
			[key1, key2] = Bindings.GetKeysForCommand("kinesis");
			if ((key1 && key1 == e.Args[0]) || (key2 && key2 == e.Args[0]))
			{
				let blast = GHAGKinesisBlast(Actor.spawn('GHAGKinesisBlast', (0, 0, 0)));
				Array<Actor> list;
				FLineTraceData shot;
				
				//Prioritize walls and switches first
				gardie.LineTrace(gardie.angle, 2048, gardie.pitch, TRF_THRUACTORS | TRF_THRUHITSCAN | TRF_THRUBLOCK | TRF_NOSKY, gardie.height - 12, 0, 0, shot);
				
				blast.setOrigin(shot.hitlocation, false);
				
				if (shot.HitLine)
				{
					if (shot.hitline.special != 243 && shot.hitline.special != 244)
					{
						int orgSpeed = shot.HitLine.args[1];
						if (shot.HitLine.args[0] == 0 && shot.HitLine.args[1] != 0) shot.HitLine.args[1] += int(shot.HitLine.args[1] * gardie.getRageModifier() * 2);
						bool activate = shot.HitLine.Activate(Gardie, shot.LineSide, SPAC_Use);
						if (activate)
						{
							GHAGKinesisPing.spawn('GHAGKinesisPing', shot.HitLocation);
							return;
						} else {
							shot.HitLine.args[1] = orgSpeed;
						}
					}
				}
				
				else if (shot.HitActor)
				{
					if (shot.hitActor is "ExplosiveBarrel" || shot.hitActor is "pod")
					{
						blast.list.clear();
						blast.list.push(shot.HitActor);
						blast.startPulling = true;
						
						return;
					}
				}
				
				//Perform item pulls and barrel tossing
				int kinesis_distance = 32;
				int iterations = 0;
				
				while (kinesis_distance < 2048)
				{
					gardie.LineTrace(gardie.angle, kinesis_distance, gardie.pitch, TRF_THRUACTORS | TRF_THRUHITSCAN, gardie.height - 12, 0, 0, shot);
					
					blast.setOrigin(shot.Hitlocation, false);
					
					blast.getRadialActors();
					list.copy(blast.list);
						
					if (list.size() != 0)
					{
						blast.startpulling = true;
						break;
					}
					
					kinesis_distance += 32;
					++iterations;
				}
			}
		}
	}
}

Class GHAGKinesisBlast : Actor
{

	Array<Actor> list;
	bool startPulling;
	bool slapProjectile;

	void getRadialActors()
	{
		list.clear();
		
		int radial = 64;
		
		let gardie = GHAGGardevoir(players[consoleplayer].mo);
		ThinkerIterator it = ThinkerIterator.Create("Actor");
		Actor mo;
		while ( (mo = Actor(it.Next ())) )
		{
			if (mo.bIsMonster && mo.bIceCorpse && distance3D(mo) <= radial)
			{
				mo.spawn("GHAGKinesisPing", mo.pos);
				mo.A_DamageSelf(1);
				continue;
			}
		
			if (!Inventory(mo) && !ExplosiveBarrel(mo) && !Pod(mo)) continue;
			
			if (Inventory(mo)) {
				if (Inventory(mo) && Inventory(mo).owner is "PlayerPawn") continue;
			}
			
			if ((mo is "ExplosiveBarrel" || mo is "pod") && distance3D(mo) > 32) continue;
			
			if (Gardie.distance2D(mo) <= 64) continue; //ignore items within single step distance
			
			if (distance3D(mo) <= radial && isVisible(mo, true) && Gardie.isVisible(mo, false)) {
			
				if (radial == 64) 
				{	
					it.Reinit();
					list.clear();
					radial = 256;
				}
			
				list.push(mo);
				GHAGKinesisPing(Actor.spawn('GHAGKinesisPing', mo.pos));
			}
		}
	}
	
	void ReturnToSender()
	{
		let gardie = GHAGGardevoir(players[consoleplayer].mo);
		
		for (int i = 0; i < list.size(); ++i)
		{
			if (!list[i]) {
				list.delete(i--);
				list.shrinkToFit();
				continue;
			}
			else
			{
				Actor victim = list[i];
				
				if (victim is "ExplosiveBarrel" || victim is "pod")
				{
				
					if (!victim.InStateSequence(victim.CurState, victim.ResolveState("Death")))
					{
						if (!victim.tracer)
						{
							victim.target = gardie;
							victim.bMissile = true;
							victim.bNoExplodeFloor = true;
							victim.bSeekerMissile = true;
							victim.bRollSprite = true;
							victim.bRollCenter = true;
							for (int i = 0; i < 10; ++ i) victim.A_SeekerMissile(360, 90, SMF_LOOK | SMF_PRECISE);
							GHAGKinesisPing(Actor.spawn('GHAGKinesisPing', victim.pos));
						}
						if (victim.tracer)
						{
							double spring = 0.8;
							double dampening = 0.8;
					
							double velx = DampedSpring(victim.pos.x, victim.tracer.pos.x, victim.vel.x, spring, dampening);
							double vely = DampedSpring(victim.pos.y, victim.tracer.pos.y, victim.vel.y, spring, dampening);
							double velz = DampedSpring(victim.pos.z, victim.tracer.pos.z, victim.vel.z, spring, dampening);
							
							victim.vel = (velx, vely, velz);
							
							victim.A_SetRoll(victim.roll + 25, SPF_INTERPOLATE);
							
							double speed = sqrt((victim.vel.x * victim.vel.x) + (victim.vel.y * victim.vel.y) + (victim.vel.z * victim.vel.z));
							if (speed <= 12)
							{
								victim.bMissile = false;
								victim.damageMobj(victim, victim, 9999999, '');
								list.delete(i--);
								list.shrinkToFit();
							}
						} else {
							victim.bMissile = false;
							victim.damageMobj(victim, victim, 9999999, '');
							list.delete(i--);
							list.shrinkToFit();
						}
					}
					else
					{
						list.delete(i--);
						list.shrinkToFit();
					}
					continue;
				}
				
				if (Inventory(victim))
				{
					Inventory(victim).bNoCLip = true;
				}
	
				if (gametic % 2 == 0)
				{
					double angle = gardie.AngleTo(victim);
					Vector2 move = gardie.AngleToVector(angle, -32);
					
					double spring = 0.2;
					double dampening = 0.2;
					
					double velx = DampedSpring(victim.pos.x, gardie.pos.x, victim.vel.x, spring, dampening);
					double vely = DampedSpring(victim.pos.y, gardie.pos.y, victim.vel.y, spring, dampening);
					double velz = DampedSpring(victim.pos.z, gardie.pos.z, victim.vel.z, spring, dampening);
					
					victim.vel = (velx, vely, velz);
					
					if (gardie.distance3D(victim) <= 64)
					{
						if (Inventory(victim))
						{
							if (GHAGRageEssence(Victim))
							{
								int rage = gardie.GetRageValue();
								if (rage < gardie.rageconstant) Inventory(victim).touch(gardie);
							} 
							else 
							{
								if (Inventory(victim) && !victim.bDestroyed) 
								{
									gardie.touch(victim);
								}
							}
							if (victim) {
								victim.bNoClip = false;
								victim.vel = (0, 0, 0);
							}
						}
						list.delete(i);
						list.ShrinkToFit();
					}
				}
			}
		}
	}
	
	//Thank you Cali
	double DampedSpring(double p, double r, double v, double k, double d) {
		return -(d * v) - (k * (p - r));
	}
	
	default
	{
		+NoGravity;
	}
	
	states
	{
		Spawn:
			TNT1 A 1 Bright
			{
				if (invoker.startpulling) self.returnToSender();
				if (invoker.list.size() == 0) self.destroy();
			}
			loop;
	}
}

Class GHAGKinesisPing : Actor
{
	default
	{
		+NoGravity;
		Gravity 0.0;
	}
	States
	{
		Spawn :
			TNT1 A 4 A_SetTics(Random(1, 3));
			TNT1 A 0 A_StartSound("psychic/Ping");
			PING ABCDEFGHIJKLMN 1 Bright;
			Stop;
	}
}