/*Please, for the love of whatever deity or power you respect, do not attempt to replicate the coding stunts in this actor. 
I am a professional (read: trained moron) and it is my job to make hacks like this for enterprise-level software.
I do not condone the following code, in fact I hate it, but it works, so I'm keeping it.
  */
  
class BabelRevenant : ChainEnabledMonster Replaces Revenant
{
	default
	{
		Health 300;
		Radius 20;
		Height 56;
		Mass 500;
		Speed 10;
		PainChance 100;
		Monster;
		MeleeThreshold 196;
		+MISSILEMORE 
		+FLOORCLIP
		SeeSound "skeleton/sight";
		PainSound "skeleton/pain";
		DeathSound "skeleton/death";
		ActiveSound "skeleton/active";
		MeleeSound "skeleton/melee";
		HitObituary "$OB_UNDEADHIT";
		Obituary "$OB_UNDEAD";
		Species "AAAAA";
		PainChance "PlayerPistol", 150;
		DamageType "Monster";
		Tag "Revenant (Babel)";
		
		BabelMonster.FearEnabled		true;
		BabelMonster.FearThreshold		450.0;
		BabelMonster.FearMaxValue		550.0;
		BabelMonster.FearFallOff		2.0;
		BabelMonster.FearRecovery		3.5;
		BabelMonster.FearResistChance	22.0;
		BabelMonster.SyncChainCheckLOS	true;
		BabelMonster.AlternatePainSound	"skeleton/pain2";
	}
	
	action void A_RevenantTracerStraightShotAutoLevel(double spawnheight, double spawnofs_xy = 0, bool skipRedirLogic = false)
	{
		let t = invoker.A_SpawnProjectile("RevenantTracerStraight", spawnheight, spawnofs_xy);
		if (!t)
		{
			return;
		}
		if(!skipRedirLogic)
		{
			if(!invoker.target)
			{
				RevenantTracerStraight(t).atTargetVSpeed = true;
				return;
			}
			Vector3 myPosAdj = (invoker.pos.x, invoker.pos.y, invoker.pos.z+40.0); //40 is default spawn height
			Vector3 tarPosAdj = (invoker.target.pos.x, invoker.target.pos.y, invoker.target.pos.z+32.0);
			
			Vector3 vsVec = tarPosAdj - myPosAdj;
			vsVec = vsVec.Unit();
			double targetVSpeed = vsVec.z * t.speed;
			RevenantTracerStraight(t).targetHeight = invoker.target.pos.z + (32.0);
			RevenantTracerStraight(t).targetVSpeed = targetVSpeed;
		}
		else
		{
			RevenantTracerStraight(t).atTargetVSpeed = true;
			return;
		}
	}
	
	bool CheckHighLOS()
	{
		if(!target)
		{
			return false;
		}
		FLineTraceData f;
		double highHeight = 64.0 + 8.0 + 4.0; //Height + projectile height + 4 (padding)
		double lowHeight = 40.0;
		double forward = 10.0+(11.0*3.0) + 1.0;//10 for radius shift forward plus 11*3 to get outer edge or something + 1 because fuck you
		double dist2D = Distance2D(target);
		double range = Distance3D(target) * 1.5;
		double pitchHigh = atan((pos.z + highHeight - target.pos.z - (target.height/2))/dist2D);
		double pitchLow = atan((pos.z + lowHeight - target.pos.z - (target.height/2))/dist2D);
		bool hit = LineTrace(AngleTo(target), forward, 0, 0, highHeight, 0, 0, f);
		if(hit)
		{
			return false;
		}
		//invoker.angle fails with partial invis, leading to weird behaviors
		hit = LineTrace(AngleTo(target), range, pitchHigh, 0, highHeight, forward, 0, f); 
		if(hit)
		{
			if(f.HitActor == target)
			{
				return true;
			}
			else
			{
				//Waist check
				if(LineTrace(AngleTo(target), range, pitchLow, 0, lowHeight, 0, 0, f))
				{
					if(f.HitActor == target)
					{
						return false;
					}
				}
				return true;
			}
		}
		else
		{
			double vecLen = (f.HitLocation-pos).Length() - range; 
			if(vecLen < 16.0 && veclen > -16.0) //Are we at least close enough, maybe? This shouldn't even happen
			{
				return true;
			}
		}
		return false;
	}
	
	action void A_SkelDoubleStraight()
	{
		invoker.A_FaceTarget();
		if(CVar.FindCVar("egr_default_projectile_origins").GetInt() == 1)
		{
			invoker.A_RevenantTracerStraightShotAutoLevel(40, 0, true);
			invoker.A_RevenantTracerStraightShotAutoLevel(40, 0, true);
		}
		else
		{
			if(invoker.ceilingz - invoker.pos.z >= 76)
			{
				if(invoker.CheckHighLOS())
				{
					invoker.A_RevenantTracerStraightShotAutoLevel(64, -14);
					invoker.A_RevenantTracerStraightShotAutoLevel(64, 14);
				}
				else
				{
					invoker.A_RevenantTracerStraightShotAutoLevel(40, -14, true);
					invoker.A_RevenantTracerStraightShotAutoLevel(40, 14, true);
				}
			}
			else
			{
				invoker.A_RevenantTracerStraightShotAutoLevel(40, -14, true);
				invoker.A_RevenantTracerStraightShotAutoLevel(40, 14, true);
			}
		}
	}
	
	action void A_SkelMixedShot1()
	{
		invoker.A_FaceTarget();
		if(CVar.FindCVar("egr_default_projectile_origins").GetInt() == 1)
		{
			invoker.A_RevenantTracerStraightShotAutoLevel(40, 0, true);
			invoker.A_SpawnProjectile("RevenantTracerHoming", 40, 0);
		}
		else
		{
			if(invoker.ceilingz - invoker.pos.z >= 76)
			{
				if(invoker.CheckHighLOS())
				{
					invoker.A_RevenantTracerStraightShotAutoLevel(64, -14);
					invoker.A_SpawnProjectile("RevenantTracerHoming", 64, 14, -25);
				}
				else
				{
					invoker.A_RevenantTracerStraightShotAutoLevel(40, -14, true);
					invoker.A_SpawnProjectile("RevenantTracerHoming", 40, 14, -25);
				}
			}
			else
			{
				invoker.A_RevenantTracerStraightShotAutoLevel(40, -14, true);
				invoker.A_SpawnProjectile("RevenantTracerHoming", 40, 14, -25);
			}
		}
	}
	
	action void A_SkelMixedShot2()
	{
		invoker.A_FaceTarget();
		if(CVar.FindCVar("egr_default_projectile_origins").GetInt() == 1)
		{
			A_RevenantTracerStraightShotAutoLevel(40, 0, true);
			A_SpawnProjectile("RevenantTracerHoming", 40, 0);
		}
		else
		{
			if(invoker.ceilingz - invoker.pos.z >= 76)
			{
				if(invoker.CheckHighLOS())
				{
					invoker.A_SpawnProjectile("RevenantTracerHoming", 64, -14, 25);
					invoker.A_RevenantTracerStraightShotAutoLevel(64, 14);
				}
				else
				{
					invoker.A_SpawnProjectile("RevenantTracerHoming", 40, -14, 25);
					invoker.A_RevenantTracerStraightShotAutoLevel(40, 14, true);
				}
			}
			else
			{
				invoker.A_SpawnProjectile("RevenantTracerHoming", 40, -14, 25);
				invoker.A_RevenantTracerStraightShotAutoLevel(40, 14, true);
			}
		}
	}
	
	States
	{
	Spawn:
		TNT1 A 0 Nodelay 
		{
			return ResolveState("Look");
		}
	Wander:
		SKEL AABBCCDDEEFF 2 Fast 
		{
			A_Wander();
			A_Look();
		}
		Loop;
	Look:
		SKEL AB 10 A_Look();
		Loop;
	
	See:
		TNT1 A 0 
		{
			ReleaseState();
			return JumpIfAfraid("Fear");
		}
		SKEL AABBCCDDEEFF 2 Fast
		{
			A_Chase();
		}
		Goto See;
	Fear:
		SKEL AABBCCDDEEFF 2 Fast
		{
			bFRIGHTENED = true;
			isAfraid = true;
			A_SetSpeed(13);
			A_Chase("Melee", null);
		}
		TNT1 A 0 JumpIfHasFear("Fear");
		
		
		TNT1 A 0 
		{
			currentFear = 0;
			isAfraid = false;
			bFRIGHTENED = false;
			ResetGroupFear();
			A_SetSpeed(10);
		}
		Goto See;
		
	Melee:
		SKEL G 6
		{
			HoldState();
			A_FaceTarget();
			A_SkelWhoosh();
		}
		SKEL H 6 A_FaceTarget();
		SKEL I 6 A_SkelFist;
		Goto See;
	Missile:
		TNT1 A 0 JumpIfCannotSee("See");
		TNT1 A 0 HoldState();
		TNT1 A 0 A_JumpIfCloser(384, "StraightMissile");
		TNT1 A 0 A_Jump(128, "StraightMissile");
		TNT1 A 0 A_Jump(256, "DoubleType1", "DoubleType2");
	StraightMissile:
		SKEL J 10 Bright A_FaceTarget();
		SKEL K 6 A_SkelDoubleStraight();
		SKEL K 6 A_FaceTarget();
		TNT1 A 0 A_Jump(32, "Missile");
		TNT1 A 0 
		{
			TriggerChain(self.GetClassName(), 25.0);
		}
		Goto See;
	DoubleType1:
		SKEL J 10 Bright A_FaceTarget();
		SKEL K 6 A_SkelMixedShot1();
		SKEL K 6 
		{
			A_FaceTarget();
			TriggerChain(self.GetClassName(), 25.0);
		}
		Goto See;
	DoubleType2:
		SKEL J 10 Bright A_FaceTarget();
		SKEL K 6 A_SkelMixedShot2();
		SKEL K 6
		{
			A_FaceTarget();
			TriggerChain(self.GetClassName(), 25.0);
		}
		Goto See;
	Pain:
		SKEL L 5 Fast;
		SKEL L 5 Fast A_BabelPain();
		TNT1 A 0 AddFear(random(0,45));
		TNT1 A 0 JumpIfAfraid("Fear");
		Goto See;
	Death:
		TNT1 A 0 A_Jump(160, "Death.Monster");
		TNT1 A 0 
		{
			TriggerSync("BabelZombieMan", 20.0);
			TriggerSync("BabelChaingunGuy", 30.0);
			TriggerSync("BabelShotgunGuy", 25.0);
			TriggerSync("BabelNazi", 75.0);
			TriggerSync("BabelDemon", 45.0);
			TriggerSync("BabelImp", 35.0);
			TriggerSync("BabelCacodemon", 45.0);

			TriggerChain(self.GetClassName(), 25.0);
		}
	Death.Monster:
	Death.Massacre:
		SKEL L 7;
		SKEL M 7;
		SKEL N 7 A_Scream();
		SKEL O 7 A_NoBlocking;
		SKEL P 7;
		SKEL Q -1;
		Stop;
	Raise:
		TNT1 A 0 
		{
			A_SetSpeed(10);
		}
		SKEL Q 5;
		SKEL PONML 5;
		Goto See;
	}
}
/*
	Straight Shot Variant
*/
class RevenantTracerStraight : BabelFastProjectile
{
	double startHeight;
	double targetHeight;
	double maxVSpeed;
	double targetVSpeed;
	bool atTargetVSpeed;
	bool dirCheck;
	default
	{
		Radius 11;
		Height 8;
		Speed 20;
		Damage 6;
		+STRIFEDAMAGE;
		Projectile;
		+RANDOMIZE;
		SeeSound "skeleton/attack";
		DeathSound "skeleton/tracex";
		decal "RevenantScorch";
		RenderStyle "Add";
		DamageType "Monster";
		
		BabelFastProjectile.HeightCorrection 4.0;
		BabelFastProjectile.LerpOffset 0.4;
		BabelFastProjectile.TrailDetail 1;
		BabelFastProjectile.ParticleColors 0xff0000, 0xff6600, 0xcc0000;
		BabelFastProjectile.ParticleSizeRange 2, 4;
		BabelFastProjectile.ParticleLifeTime 15, 25;
		BabelFastProjectile.ParticleCubeOffsets 2, 2;
		BabelFastProjectile.ParticleCubeAccelerations 0.1, 0.1;
		BabelFastProjectile.StartingAlpha 0.7;
	}
	
	override void BeginPlay()
	{
		super.BeginPlay();
		
		startHeight = self.pos.z;
		targetHeight = self.floorz;
		maxVSpeed = 0;
		atTargetVSpeed = false;
		targetVSpeed = 0.0;
		dirCheck = false;
	}
	
	override void PostBeginPlay()
	{
		maxVSpeed = self.vel.z;
		if(self.vel.z == targetVSpeed)
		{
			atTargetVSpeed = true;
		}
	}
	
	int sign(double num)
	{
		if(num >= 0)
		{
			return 1;
		}
		return -1;
	}
	
	override void Tick()
	{
		super.Tick();
		if(!atTargetVSpeed)
		{
			if(!dirCheck)
			{
				//Just in case we're really going in the wrong direction
				//This shouldn't happen, but it's only one check so no big deal
				if((self.pos.z <= targetHeight && sign(maxVSpeed) == -1) || 
				   (self.pos.z >= targetHeight && sign(maxVSpeed) ==  1))
				{
					atTargetVSpeed = true; //don't bother
					return;
				}
				dirCheck = true;
			}
			
			if((targetHeight > startHeight && sign(maxVSpeed) ==  1) || 
			   (targetHeight < startHeight && sign(maxVSpeed) == -1))
			{
				double diff = targetHeight - self.pos.z;
				diff = diff * sign(diff);
				if(diff <= 1)
				{
					A_ChangeVelocity(self.vel.x, self.vel.y, targetVSpeed, CVF_REPLACE);
					atTargetVSpeed = true;
				}
			}
		}
		
	}
	
	States
	{
	Spawn:
		FATB AB 2 Bright; //A_Tracer
		Loop;
	Death:
		TNT1 A 0 
		{
			trailSpawn = false;
		}
		FBXP A 8 Bright;
		FBXP B 6 Bright;
		FBXP C 4 Bright;
		Stop;
	}
}

//For addon support
class RevenantTracerStraightNoLevel : RevenantTracerStraight
{
	override void BeginPlay()
	{
		super.BeginPlay();
		atTargetVSpeed = true;
	}
}

class RevenantTracerHoming : BabelFastProjectile
{
	int skipTics;
	default
	{
		Radius 11;
		Height 8;
		Speed 15;
		Damage 8;
		Projectile;
		+RANDOMIZE;
		+STRIFEDAMAGE;
		+SEEKERMISSILE;
		SeeSound "skeleton/attack";
		DeathSound "skeleton/tracex";
		decal "RevenantScorch";
		RenderStyle "Add";
		DamageType "Monster";
		
		BabelFastProjectile.HeightCorrection 4.0;
		BabelFastProjectile.LerpOffset 0.7;
		BabelFastProjectile.TrailDetail 2;
		BabelFastProjectile.ParticleColors 0xaaaaaa, 0xaa2200, 0xaa6600;
		BabelFastProjectile.ParticleSizeRange 2, 4;
		BabelFastProjectile.ParticleLifeTime 25, 35;
		BabelFastProjectile.ParticleCubeOffsets 2, 2;
		BabelFastProjectile.ParticleCubeVelocities 2, 2;
		BabelFastProjectile.ParticleCubeAccelerations 0.2, 0.2;
		BabelFastProjectile.StartingAlpha 0.7;
		BabelFastProjectile.VariableDirection true;
	}
	
	override void BeginPlay()
	{
		super.BeginPlay();
		skipTics = 0;
	}
	
	override void Tick()
	{
		super.Tick();
		if(!doParticles) //spawn revvie smoke
		{
			if(skipTics == 2)
			{
				skipTics = 0;
				A_SpawnItemEx("RevenantTracerSmoke", 0,0,4);
			}
			else
			{
				skipTics += 1;
			}
			
		}
	}
	
	States
	{
	Spawn:
		FATB AB 2 Bright A_SeekerMissile(0, 10, SMF_PRECISE|SMF_CURSPEED);
		Loop;
	Death:
		TNT1 A 0 
		{
			trailSpawn = false;
		}
		FBXP A 8 Bright;
		FBXP B 6 Bright;
		FBXP C 4 Bright;
		Stop;
	}
}

class BabelRevenantTracerSmoke : RevenantTracerSmoke Replaces RevenantTracerSmoke //Why does this not work right?
{
	default
	{
		+ZDOOMTRANS;
		RenderStyle "Translucent";
		Alpha 0.5;
	}
}