//base class for *most* bullets
class BabelBullet : FastProjectile
{
	int type;
	double px,py,pz;
	double baseScale;
	double trailDetail;
	double trailLength;
	double heightCorrection;
	string trailSprite;
	Vector3 originalVel;
	
	//predefine these on creation to speed up calculation
	double i;
	bool interpolate;
	
	property BaseScale: baseScale;
	property HeightCorrection: heightCorrection;
	property TrailLength: trailLength;
	property TrailDetail: trailDetail;
	property TrailSprite: trailSprite;
	property InterpolateMovement: interpolate;
	
	default
	{
		Projectile;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+NOTELEPORT;
		//+NOINTERACTION;
		Damage 0;
		Radius 1;
		Height 2;
		Speed 80;
		RenderStyle "Add";
		
		BabelBullet.BaseScale 1.0;
		BabelBullet.HeightCorrection 0.0;
		BabelBullet.TrailLength 0.6;
		BabelBullet.TrailDetail 10;
		BabelBullet.TrailSprite "SpriteTrail";
		BabelBullet.InterpolateMovement false;
	}
	
	override void BeginPlay()
	{
		super.BeginPlay();
		i = 0;
		type = CVar.FindCVar("egr_player_tracer_type").GetInt();
	}
	
	override void PostBeginPlay()
	{
		scale = (baseScale, baseScale);
		originalVel = vel;
		
		super.PostBeginPlay();
	}
	override void Tick()
	{
		super.Tick();
		//if (level.frozen || globalfreeze) return; deprecated sine 3.8
		if(level.isFrozen() > 0) return; //don't redraw
		SpawnTracer();
	}
	
	void SpawnTracer()
	{
		if(type == 2)
		{
			//get our new position
			px = pos.x;
			py = pos.y;
			pz = pos.z+heightCorrection;
			for (i = 0; i < trailLength; i+=trailLength/trailDetail)
			{
				double scaleTerm = baseScale * (1.0-(i/trailLength));
				let piece = Spawn(trailSprite, 
					(BabelLib.Lerp(px, px-originalVel.x, i), 
					 BabelLib.Lerp(py, py-originalVel.y, i), 
					 BabelLib.Lerp(pz, pz-originalVel.z, i))
				);
				piece.Scale = (scaleTerm, scaleTerm);
				if(interpolate)
					piece.A_ChangeVelocity(vel.x, vel.y, vel.z);
			}
		}
	}
	
	void SpawnFinalTracer()
	{
		return;
		if(type == 2)
		{
			//get our new position
			px = pos.x;
			py = pos.y;
			pz = pos.z+heightCorrection;
			for (i = 0; i < trailLength; i+=trailLength/trailDetail)
			{
				double scaleTerm = baseScale * (1.0-(i/trailLength));
				let piece = Spawn(trailSprite, 
					(BabelLib.Lerp(px, px-originalVel.x, i), 
					 BabelLib.Lerp(py, py-originalVel.y, i), 
					 BabelLib.Lerp(pz, pz-originalVel.z, i))
				);
				piece.Scale = (scaleTerm, scaleTerm);
			}
		}
	}
	
	States
	{
		Spawn:
			TNT1 A 0 NoDelay
			{
				switch(type)
				{
					case 0:
						return ResolveState("Invisible");
					case 1:
						return ResolveState("Model");
					case 2:
						return ResolveState("Sprite");
				}
				return ResolveState("Invisible");
			}
		Invisible:
			TNT1 A 0;
			Goto Fly;
		Model:
			TRAC A 0;
			Goto Fly;
		Sprite:
			TNT1 A 0;
			Goto Fly;
		Fly:
			"####" "#" 1 Bright;
			Loop;
		Crash:
		Death:
			TNT1 A 0
			{
				if(type == 2)
					SpawnFinalTracer();
			}
			Stop;
		XDeath:
			TNT1 A 0
			{
				if(type == 2)
					SpawnFinalTracer();
			}
			Stop;
	}
}

class SpriteTrail : Actor
{
	default
	{
		//+NOINTERACTION;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+FORCEXYBILLBOARD;
		+DONTSPLASH;
		+NOTELEPORT;
		Damage 0;
		Radius 1;
		Height 2;
		Speed 0;
		RenderStyle "Add";
	}
	States
	{
		Spawn:
			CHIP C 2 NoDelay Bright;
			stop;
	}
}

class TracerMover : Actor
{
	default
	{
		Radius 1;
		Height 1;
		Speed 0;
		+NOINTERACTION;
		+NOGRAVITY;
		+DONTSPLASH;
	}
	Array<Actor> parts;
	Vector3 StartPosition, EndPosition;
	Vector3 SegmentsSize;
	Vector3 SpeedVector;
	Vector3 forward;
	int segNum;
	double traildef;
	bool die;
	double length;
	double moveSpeed;
	double heightCorrection;
	string renderObj;
	
	double partScale, partStartRatio, partEndRatio;
	
	double Lerp(double val1, double val2, double ratio)
	{
		return (1 - ratio) * val1 + ratio * val2;
	}
	double InvLerp(double val1, double val2, double lerp)
	{
		if(val2 == val1)
		{
			return -1.0; //This is a hack so I don't have to write extra code. Not programatically correct
		}
		return (lerp-val1)/(val2-val1);
	}
	override void BeginPlay()
	{
		super.BeginPlay();
		segNum = 0;				//Internal variable used for movement.
		die = false;			//another internal variable
		
		renderObj = "NewTracerBeam";	//the object to use for trail rendering
		heightCorrection = 0.0;			//For rendering, this helps to correct the height of the tracer. Usually 0.0 is fine, but some sprites may need minor adjustments
		moveSpeed = 200;				//The "speed" at which the tracer moves, in map units
		length = 210.0;					//The length of the tracer, in map units
		traildef = 45.0;					//How many pieces the tracer has
		
		partScale = 0.3;
		partStartRatio = 0.0;
		partEndRatio = 1.0;
	}
	override void PostBeginPlay()
	{
		super.PostBeginPlay();
		StartPosition = pos + (CVar.FindCVar("egr_sprite_tracer_forward_adjust").GetFloat()*forward);
		Vector3 c = EndPosition - StartPosition;
		Vector3 cUnit = (c/c.Length());
		SegmentsSize = cUnit * length;
		SpeedVector = SegmentsSize * moveSpeed/length;
		
		//Render the first section of the tracer exactly 1 movespeed step behind its firing position
		StartPosition = StartPosition - SpeedVector;//why the fuck can't it just render on frame 1? Graf I swear to god I'm coming to Germany to find you and spank you.
		Vector3 e = StartPosition + SegmentsSize;
		for(double i = (1/(traildef)); i <= 1.0; i += (1/(traildef)))
		{
			Vector3 TargetPosition = (Lerp(StartPosition.x, e.x, i), Lerp(StartPosition.y, e.y, i), Lerp(StartPosition.z, e.z, i)+heightCorrection);
			let p = Spawn(renderObj, TargetPosition);
			NewTracerBeam(p).s = i;
			//Why the FUCK wouldn't I set these here when these are all essentially constant?
			//Save 3*definition lookups PER NewTracerBeam
			//God damn I am a moron for not doing it this way to begin with
			NewTracerBeam(p).bs = partScale;
			NewTracerBeam(p).startRatio = partStartRatio;
			NewTracerBeam(p).endRatio = partEndRatio;
			parts.Push(p);
		}
	}
	//This could probably just return a state label to be more logical in terms of flow, maybe
	void MoveTracer()
	{
		if(level.isFrozen() > 0) return;
		if(die) 
		{
			return;
		}
		//render next segment
		Vector3 adjust = SpeedVector * segNum++;
		Vector3 s = StartPosition + adjust;
		Vector3 e = s + SegmentsSize;
		int partIndex = 0;
		for(double i = (1/(traildef)); i <= 1.0; i += (1.0/(traildef)))
		{
			Actor p = parts[partIndex++];
			if(p)
			{
				Vector3 TargetPosition = (Lerp(s.x, e.x, i), Lerp(s.y, e.y, i), Lerp(s.z, e.z, i));
				if(InvLerp(StartPosition.x, EndPosition.x, TargetPosition.x) > 1.0 || InvLerp(StartPosition.y, EndPosition.y, TargetPosition.y) > 1.0 || InvLerp(StartPosition.z, EndPosition.z, TargetPosition.z) > 1.0)
				{
					p.SetOrigin(EndPosition, false);
					p.Destroy();
					if(partIndex == 1) //parts are stored in reverse order, durr
					{
						die = true;
						break;
					}
					else
					{
						continue;
					}
				}
				p.SetOrigin(TargetPosition, false);
				NewTracerBeam(p).s = i;
			}
		}
	}
	States
	{
		Spawn:
			TNT1 A 1 NoDelay
			{
				MoveTracer();
				if(!die)
				{
					return ResolveState(null);
				}
				return ResolveState("Die");
			}
			loop;
		Die:
			TNT1 A 1
			{
				for(int i = parts.Size()-1; i >= 0; i--)
				{
					let p = parts[i];
					if(p)
					{
						p.Destroy();
					}
				}
			}
			stop;
	}
}

//The actors that make up the beam. NOINTERACTION speeds them up a lot
class NewTracerBeam : Actor
{
	default
	{
		+NOINTERACTION;
		+NOGRAVITY;
		+FORCEXYBILLBOARD;
		+DONTSPLASH;
		+NOTELEPORT;
		Damage 0;
		Height 1;
		Radius 1;
		Speed 0;
		RenderStyle "Add";
		Scale 0.4;
	}
	double s, bs;
	double startRatio, endRatio;
	double Lerp(double val1, double val2, double ratio)
	{
		return (1 - ratio) * val1 + ratio * val2;
	}
	override void BeginPlay()
	{
		super.BeginPlay();
		bs = 0.4;
		startRatio = 0.0;
		endRatio = 1.0;
	}
	States
	{
		Spawn:
			TNT1 A 1 NoDelay
			{
				A_SetScale(bs*Lerp(startRatio, endRatio, s));
			}
		Stay:
			CHIP C 1 Bright;
			loop;
	}
}

class DEBUGMARKER : Actor
{
	default
	{
		+NOINTERACTION;
		+NOGRAVITY;
		+FORCEXYBILLBOARD;
		+DONTSPLASH;
		+NOTELEPORT;
		Damage 0;
		Height 1;
		Radius 1;
		Speed 0;
		RenderStyle "Add";
		Scale 0.4;
	}
	States
	{
		Spawn:
			APLS A 1 NoDelay;
			stop;
	}
}