// shouldn't be placed in the world (it wasn't in UT99)
Class WarheadAmmo : Ammo
{
	Default
	{
		Tag "Redeemer Missile";
		Inventory.PickupMessage "You picked up a Redeemer Missile.";
		Inventory.Amount 1;
		Inventory.MaxAmount 2;
		Ammo.BackpackAmount 0;
		Ammo.BackpackMaxAmount 2;
		Ammo.DropAmount 1;
		Inventory.RespawnTics 2100;
	}
	States
	{
	Spawn:
		WMIS A -1;
		Stop;
	}
}

Class ShockWave : Actor
{
	double shocksize, olddmgradius;
	double lifespan;
	int icount;
	ThinkerIterator t;
	Default
	{
		Obituary "%o was vaporized by %k's Redeemer!!";
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		Scale 1.0;
		ReactionTime 60;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
	}
	override void PostBeginPlay()
	{
		lifespan = ReactionTime;
		A_PlaySound("warhead/explode",CHAN_VOICE,attenuation:ATTN_NONE);
		A_QuakeEx(9,9,9,100,0,12000,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.5);
		t = ThinkerIterator.Create("Actor");
	}
	override void Tick()
	{
		Super.Tick();
		if ( globalfreeze || level.frozen ) return;
		if ( alpha <= 0 ) return;
		icount++;
		if ( icount == 4 ) Spawn("WarheadSubExplosion",pos);
		lifespan--;
		alpha -= 1./ReactionTime;
		shocksize = 13*(ReactionTime-lifespan)+3.5/(lifespan/ReactionTime+0.05);
		A_SetScale(shocksize*0.25);
		double dmgradius = shocksize*1.5;
		Actor a;
		t.Reinit();
		while ( a = Actor(t.Next()) )
		{
			if ( !a.bShootable || !CheckSight(a) || (Distance3D(a) > dmgradius) ) continue;
			Vector3 dir = Vec3To(a);
			double dist = max(1,dir.length());
			dir = dir/dist+(0,0,0.3);
			double moscale = max(0,1100-0.22*dist);
			if ( (dist > olddmgradius) || (dir dot a.vel < 0) )
			{
				a.vel += dir*(moscale/a.mass+20);
				a.DamageMobj(self,target,moscale,'RedeemerDeath');
			}
		}
		olddmgradius = dmgradius;
	}
	States
	{
	Spawn:
		RWAV A 100 Bright;
		Stop;
	}
}

Class WarheadSubExplosion : Actor
{
	Default
	{
		Renderstyle "Add";
		Scale 2.8;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		Spawn("WarheadExplodLight",pos);
	}
	States
	{
	Spawn:
		WE__ ABCDEFGHIJKLMNOPR 3 Bright;
		Stop;
	}
}

Class WarheadHitbox : Actor
{
	Default
	{
		Radius 8;
		Height 8;
		+SHOOTABLE;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+NOBLOOD;
	}
	override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
	{
		if ( (damage > 5) && target )
		{
			target.bAMBUSH = true;
			target.ExplodeMissile(null,inflictor);
		}
		return 0;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos-(0,0,height*0.5),true);
	}
	States
	{
	Spawn:
		TNT1 A 10 A_AlertMonsters(0,AMF_TARGETEMITTER);
		Wait;
	}
}

Class WarheadExplodLight : DynamicLight
{
	double lifetime;
	Default
	{
		DynamicLight.Type "Point";
		ReactionTime 50;
		Args 255,192,128,300;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		lifetime = 1.0;
	}
	override void Tick()
	{
		Super.Tick();
		if ( globalfreeze || level.frozen ) return;
		args[LIGHT_RED] = 255*lifetime;
		args[LIGHT_GREEN] = 192*lifetime;
		args[LIGHT_BLUE] = 128*lifetime;
		lifetime -= 1./ReactionTime;
		if ( lifetime <= 0 ) Destroy();
	}
}

Class WarheadLight : DynamicLight
{
	Default
	{
		DynamicLight.Type "Point";
		Args 255,224,192,70;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target )
		{
			Destroy();
			return;
		}
		Vector3 taildir = -(cos(target.angle)*cos(target.pitch),sin(target.angle)*cos(target.pitch),-sin(target.pitch));
		SetOrigin(target.Vec3Offset(taildir.x*20,taildir.y*20,taildir.z*20),true);
		args[LIGHT_INTENSITY] = Random[Warhead](6,8)*10;
	}
}

Class WarheadTrail : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+ROLLSPRITE;
		+ROLLCENTER;
		Scale 0.2;
	}

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		double ang, pt;
		scale *= FRandom[Puff](0.5,1.5);
		alpha *= FRandom[Puff](0.5,1.5);
		ang = FRandom[Puff](0,360);
		pt = FRandom[Puff](-90,90);
		vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
		roll = FRandom[Puff](0,360);
	}
	override void Tick()
	{
		Super.Tick();
		if ( level.frozen || globalfreeze ) return;
		vel *= 0.99;
		A_FadeOut(0.1);
	}
	States
	{
	Spawn:
		RTRL A -1 Bright;
		Stop;
	}
}

Class WarShell : Actor
{
	double destangle, destpitch;
	Actor l, b;

	Default
	{
		Obituary "%o was vaporized by %k's Redeemer!!";
		Radius 4;
		Height 4;
		Speed 2;
		DamageType 'RedeemerDeath';
		PROJECTILE;
		+FORCEXYBILLBOARD;
		+SKYEXPLODE;
		+FORCERADIUSDMG;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		l = Spawn("Warheadlight",pos);
		l.target = self;
		b = Spawn("WarheadHitbox",pos);
		b.target = self;
		A_PlaySound("warhead/fly",CHAN_VOICE,1.0,true);
		destangle = angle;
		destpitch = pitch;
	}
	override int SpecialMissileHit( Actor victim )
	{
		if ( victim == b ) return 1;
		return -1;
	}
	override void Tick()
	{
		Super.Tick();
		if ( globalfreeze || level.frozen ) return;
		if ( !bMISSILE ) return;
		if ( vel.length() > 0 )
		{
			if ( waterlevel > 0 )
			{
				vel *= 0.98;
				if ( vel.length() < 5 ) vel += vel.unit()*0.5;
			}
			else if ( vel.length() < 10 ) vel += vel.unit()*0.5;
		}
	}
	action void A_Trail()
	{
		Vector3 taildir = -(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
		if ( waterlevel > 0 )
		{
			for ( int i=0; i<4; i++ )
			{
				let s = Spawn("UTBubble",pos+taildir*32+(FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5)));
				s.vel = taildir*2;
			}
			return;
		}
		for ( int i=0; i<4; i++ )
		{
			let s = Spawn("UTSmoke",pos+taildir*32+(FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5)));
			s.vel = taildir*2;
			s.SetShade("404040");
		}
		for ( int i=0; i<4; i++ )
		{
			let s = Spawn("WarheadTrail",pos+taildir*35+(FRandom[Warhead](-1,1),FRandom[Warhead](-1,1),FRandom[Warhead](-1,1)));
			s.vel = taildir*4;
		}
	}
	action void A_Vaporize()
	{
		if ( invoker.l ) invoker.l.Destroy();
		if ( invoker.b ) invoker.b.Destroy();
		A_SetScale(2.0);
		A_Explode(1000,300);
		A_SprayDecal("BigBlast");
		A_QuakeEx(8,8,8,20,0,300,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.35);
		A_PlaySound("shock/hit",CHAN_VOICE,attenuation:0.5);
		A_AlertMonsters();
		A_SetRenderStyle(1.0,STYLE_Add);
		Spawn("WarheadExplodLight",pos);
		let s = Spawn("ShockWave",pos);
		s.target = target;
	}
	action void A_Intercepted()
	{
		if ( invoker.l ) invoker.l.Destroy();
		if ( invoker.b ) invoker.b.Destroy();
		A_Explode(1000,350);
		A_SprayDecal("BigBlast");
		A_QuakeEx(8,8,8,20,0,300,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.35);
		A_PlaySound("shock/hit",CHAN_VOICE,attenuation:0.5);
		A_AlertMonsters();
		A_SetRenderStyle(1.0,STYLE_Add);
		Spawn("WarheadExplodLight",pos);
	}
	States
	{
	Spawn:
		WMIS A 1 A_Trail();
		Wait;
	Death:
		TNT1 A 0 A_JumpIf(bAMBUSH,"Death.Intercept");
		TNT1 A 0 A_Vaporize();
		NE__ ABCDEFGHIJKLMNOPR 3 Bright;
		Stop;
	Death.Intercept:
		TNT1 A 0 A_Intercepted();
		WE__ ABCDEFGHIJKLMNOPR 3 Bright;
		Stop;
	}
}

Class GuidedWarShell : WarShell
{
	double lagangle, lagpitch, lagangle2, lagpitch2;
	double guideangle, guidepitch, lastguideroll;

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		if ( target && target.player ) target.player.camera = self;
		guideangle = angle;
		guidepitch = pitch;
	}
	override void Tick()
	{
		Actor.Tick();
		if ( globalfreeze || level.frozen ) return;
		if ( !bMISSILE ) return;
		if ( !target || !target.player || (target.Health <= 0) )
		{
			bAMBUSH = true;
			ExplodeMissile();
			return;
		}
		if ( target.player.cmd.buttons&BT_ATTACK )
		{
			ExplodeMissile();
			return;
		}
		if ( vel.length() > 0 )
		{
			lagangle = target.player.cmd.yaw/128.;
			lagpitch = -target.player.cmd.pitch/128.;
			guideangle += lagangle2*0.95+lagangle*0.05;
			guidepitch += lagpitch2*0.95+lagpitch*0.05;
			guidepitch = Clamp(guidepitch,-89,89);
			double guideroll = -lagangle2*15;
			Vector3 dir = (cos(guideangle)*cos(guidepitch),sin(guideangle)*cos(guidepitch),-sin(guidepitch));
			destangle = atan2(dir.y,dir.x);
			destpitch = asin(-dir.z);
			double destroll = lastguideroll*0.9+guideroll*0.1;
			A_SetAngle(destangle,SPF_INTERPOLATE);
			A_SetPitch(destpitch,SPF_INTERPOLATE);
			A_SetRoll(destroll,SPF_INTERPOLATE);
			vel = vel.length()*(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
			if ( waterlevel > 0 )
			{
				vel *= 0.98;
				if ( vel.length() < 5 ) vel += vel.unit()*0.5;
			}
			else if ( vel.length() < 10 ) vel += vel.unit()*0.5;
		}
		lagangle2 = lagangle2*0.95+lagangle*0.05;
		lagpitch2 = lagpitch2*0.95+lagpitch*0.05;
		lastguideroll = roll*0.98;
	}
	States
	{
	Death:
		TNT1 A 0
		{
			if ( WarheadLauncher(master) )
				WarheadLauncher(master).Guided = null;
			if ( target && target.player && target.player.camera == self ) target.player.camera = target;
		}
		Goto Super::Death;
	}
}

Class MidTracer : LineTracer
{
	override ETraceStatus TraceCallback()
	{
		if ( Results.HitType == TRACE_HitActor ) return TRACE_Skip;
		else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
		{
			if ( !Results.HitLine.sidedef[1] ) return TRACE_Stop;
			return TRACE_Skip;
		}
		return TRACE_Stop;
	}
}

Class TargetActor
{
	Vector2 vpos;
	String diststr;
}

Class RedeemerHUD : HUDMessageBase
{
	Actor Camera;
	Vector3 ViewPos;
	double ViewAngle, ViewPitch, ViewRoll;
	TextureID reticle, mark, readout;
	Font whfont;
	ThinkerIterator t;
	MidTracer tr;
	Array<TargetActor> ta;

	RedeemerHUD Init()
	{
		reticle = TexMan.CheckForTexture("GuidedX",TexMan.Type_Any);
		mark = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any);
		readout = TexMan.CheckForTexture("Readout",TexMan.Type_Any);
		whfont = Font.GetFont('WHFONT');
		t = ThinkerIterator.Create("Actor");
		tr = new("MidTracer");
		return self;
	}
	override bool Tick()
	{
		// shootable targetting
		if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() )
		{
			t.Reinit();
			ta.Clear();
			Actor a;
			Vector3 vdir = (cos(ViewAngle)*cos(ViewPitch),sin(ViewAngle)*cos(ViewPitch),-sin(ViewPitch));
			while ( a = Actor(t.Next()) )
			{
				Vector3 tdir = Level.Vec3Diff(ViewPos,a.Pos+(0,0,a.Height*0.5));
				if ( !a.bSHOOTABLE || (a.Health <= 0) || ((Camera is 'GuidedWarShell') && (a == GuidedWarShell(Camera).b)) || (tdir.length() > 2000) || (acos(tdir.unit() dot vdir) > players[consoleplayer].FOV) || tr.Trace(ViewPos,Camera.CurSector,tdir.unit(),tdir.length(),0) ) continue;
				Vector3 wpos = ViewPos+tdir;
				Vector3 spos = mkCoordUtil.WorldToScreen(wpos,ViewPos,ViewPitch,ViewAngle,ViewRoll,players[consoleplayer].FOV);
				if ( spos.z > 1.0 ) continue;
				TargetActor te = new("TargetActor");
				te.vpos = mkCoordUtil.ToViewport(spos);
				te.diststr = String.Format("%f",tdir.length());
				te.diststr.Replace(".","");
				ta.Push(te);
			}
		}
		return !Camera;
	}
	override void Draw( int bottom, int visibility )
	{
		if ( visibility != StatusBar.HUDMSGLayer_UnderHUD ) return;
		Screen.Dim("Red",0.5,0,0,Screen.GetWidth(),Screen.GetHeight());
		// shootable targetting
		if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() )
		{
			for ( int i=0; i<ta.Size(); i++ )
			{
				Screen.DrawTexture(mark,false,ta[i].vpos.x,ta[i].vpos.y);
				Screen.DrawText(whfont,Font.CR_UNTRANSLATED,(ta[i].vpos.x-whfont.StringWidth(ta[i].diststr)/2)-12,ta[i].vpos.y+8,ta[i].diststr);
			}
		}
		// other stuff
		Screen.DrawTexture(reticle,false,320,240,DTA_VirtualWidth,640,DTA_VirtualHeight,480);
		int numreadouts = Screen.GetHeight()/128+2;
		for ( int i=0; i<numreadouts; i++ )
		{
			int scroll = (gametic*5)%128;
			Screen.DrawTexture(readout,false,0,i*128-scroll);
		}
	}
}

Class RedeemerHUDStatic : HUDMessageBase
{
	int tickcnt;
	TextureID tx;

	RedeemerHUDStatic Init()
	{
		tx = TexMan.CheckForTexture("static1",TexMan.Type_Any);
		tickcnt = 0;
		return self;
	}
	override bool Tick()
	{
		return (++tickcnt>15);
	}
	override void Draw( int bottom, int visibility )
	{
		if ( visibility != StatusBar.HUDMSGLayer_UnderHUD ) return;
		double sw, sh;
		sw = 256.;
		sh = sw*(Screen.GetHeight()/double(Screen.GetWidth()));
		Screen.DrawTexture(tx,true,0,0,DTA_VirtualWidthF,sw,DTA_VirtualHeightF,sh,DTA_KeepRatio,true);
	}
}

Class RedeemerHUDHandler : EventHandler
{
	ui RedeemerHUD rhud;

	override void RenderOverlay( RenderEvent e )
	{
		if ( e.Camera is 'GuidedWarShell' )
		{
			if ( !rhud )
			{
				rhud = new("RedeemerHUD").Init();
				StatusBar.AttachMessage(rhud,0,StatusBar.HUDMSGLayer_UnderHUD);
			}
			rhud.Camera = e.Camera;
			rhud.ViewPos = e.ViewPos;
			rhud.ViewAngle = e.ViewAngle;
			rhud.ViewPitch = e.ViewPitch;
			rhud.ViewRoll = e.ViewRoll;
		}
		else if ( rhud )
		{
			StatusBar.DetachMessage(rhud);
			rhud.Destroy();
			StatusBar.AttachMessage(new("RedeemerHUDStatic").Init(),0,StatusBar.HUDMSGLayer_UnderHUD);
		}
	}
}

Class WarheadLauncher : UTWeapon replaces BFG9000
{
	Actor guided;
	action void A_WarheadFire()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( weap.Ammo1.Amount <= 0 ) return;
		if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
		A_PlaySound("warhead/fire",CHAN_WEAPON);
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(128,255,128,128),1);
		A_AlertMonsters();
		A_QuakeEx(6,6,6,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.2);
		Vector3 x, y, z;
		[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
		vel -= x*10;
		Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
		Actor p = Spawn("WarShell",origin);
		p.angle = angle;
		p.pitch = BulletSlope();
		p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
		p.target = self;
	}
	action void A_WarheadSmoke()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		Vector3 x, y, z;
		[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
		vel -= x*0.2;
		Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
		int numpt = Random[Warhead](10,20);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (x+(FRandom[Warhead](-.8,.8),FRandom[Warhead](-.8,.8),FRandom[Warhead](-.8,.8))).unit()*FRandom[Warhead](1,2);
			let s = Spawn("UTSmoke",origin);
			s.vel = pvel;
			s.SetShade(Color(1,1,1)*Random[Warhead](32,128));
		}
	}
	action void A_WarheadAlt()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( weap.Ammo1.Amount <= 0 ) return;
		if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
		A_PlaySound("warhead/fire",CHAN_WEAPON);
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(128,255,128,128),1);
		A_AlertMonsters();
		A_QuakeEx(6,6,6,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.2);
		Vector3 x, y, z;
		[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
		vel -= x*10;
		Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
		Actor p = Spawn("GuidedWarShell",origin);
		p.angle = angle;
		p.pitch = BulletSlope();
		p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
		p.target = self;
		p.master = invoker;
		invoker.guided = p;
	}

	// disallow dropping while guiding
	override Inventory CreateTossable( int amt )
	{
		if ( guided ) return null;
		return Super.CreateTossable();
	}

	Default
	{
		Tag "Redeemer";
		Inventory.PickupMessage "You got the Redeemer.";
		Weapon.UpSound "warhead/select";
		Weapon.SlotNumber 0;
		Weapon.SelectionOrder 0;
		Weapon.AmmoType "WarheadAmmo";
		Weapon.AmmoUse 1;
		Weapon.AmmoType2 "WarheadAmmo";
		Weapon.AmmoUse2 1;
		Weapon.AmmoGive 1;
		Inventory.RespawnTics 2100;
		+INVENTORY.ALWAYSPICKUP;
		+WEAPON.NOAUTOFIRE;
	}
	States
	{
	Spawn:
		RDMP A -1;
		Stop;
		RDMP B -1;
		Stop;
	Ready:
		WARS ABCDEFGHIJKLMNO 1;
	Idle:
		WARI A 1
		{
			A_CheckReload();
			A_WeaponReady();
		}
		Wait;
	Fire:
		WARF A 4 A_WarheadFire();
		WARF BCDEFG 4 A_WarheadSmoke();
		WARI A 5;
		Goto Idle;
	AltFire:
		WARF A 4 A_WarheadAlt();
		WARF BCDEFG 4 A_WarheadSmoke();
		WARI A 1 A_JumpIf(!invoker.Guided,1);
		Wait;
		WARI A 30;
		Goto Idle;
	Select:
		WARS A 1 A_Raise(int.max);
		Wait;
	Deselect:
		WARD ABCDEFG 2;
		WARD G 1 A_Lower(int.max);
		Wait;
	}
}
