Class URocketAmmo : Ammo
{
	Default
	{
		Tag "$T_ROCKETCAN";
		Inventory.Icon "I_Rocket";
		Inventory.PickupMessage "";
		Inventory.Amount 12;
		Inventory.MaxAmount 48;
		Ammo.BackpackAmount 6;
		Ammo.BackpackMaxAmount 96;
		Ammo.DropAmount 6;
		+INVENTORY.IGNORESKILL;
	}
	override String PickupMessage()
	{
		if ( PickupMsg.Length() > 0 ) return Super.PickupMessage();
		return String.Format("%s%d%s",StringTable.Localize("$I_ROCKETCANL"),Amount,StringTable.Localize("$I_ROCKETCANR"));
	}
	States
	{
	Spawn:
		RPAK A 8 A_CheckProximity(1,"PlayerPawn",80,1,CPXF_ANCESTOR|CPXF_CHECKSIGHT);
		Wait;
		RPAK AABCDEFGHIJ 8;
		RPAK J 8 A_CheckProximity(1,"PlayerPawn",80,0,CPXF_ANCESTOR|CPXF_CHECKSIGHT|CPXF_EXACT);
		Goto Spawn+12;
		RPAK JJIHGFEDCBA 8;
		Goto Spawn;
	}
}
Class URocketAmmo2 : URocketAmmo
{
	Default
	{
		Tag "$T_ROCKETCAN2";
		Inventory.Amount 2;
		Ammo.DropAmount 2;
		+INVENTORY.IGNORESKILL;
	}
	States
	{
	Spawn:
		RCKT A 8 A_CheckProximity(1,"PlayerPawn",80,1,CPXF_ANCESTOR|CPXF_CHECKSIGHT);
		Wait;
		RCKT AA 8;
		RCKT B 8 A_CheckProximity(1,"PlayerPawn",80,0,CPXF_ANCESTOR|CPXF_CHECKSIGHT|CPXF_EXACT);
		Goto Spawn+3;
		RCKT BB 8;
		Goto Spawn;
	}
}

Class URocketTrail : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+NOTELEPORT;
		Scale 0.4;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		let l = Spawn("RocketLight",pos);
		l.target = self;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target || !target.bMISSILE )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
	}
	States
	{
	Spawn:
		MFLA A -1 Bright;
		Stop;
	}
}

Class URocket : Actor
{
	Mixin UMissileFix;

	Vector3 InitialDir, Acceleration;
	int ticcnt;
	bool ringex;
	Default
	{
		Obituary "$O_EIGHTBALL";
		DamageType 'RocketDeath';
		Radius 2;
		Height 2;
		Speed 15;
		PROJECTILE;
		+SKYEXPLODE;
		+EXPLODEONWATER;
		+SEEKERMISSILE;
		+FORCERADIUSDMG;
		+NODAMAGETHRUST;
		+INTERPOLATEANGLES;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		let l = Spawn("URocketTrail",pos);
		l.target = self;
		A_StartSound("eightball/fire",CHAN_WEAPON,attenuation:.6);
		A_StartSound("eightball/fly",CHAN_VOICE,CHANF_LOOPING,1.,2.5);
		if ( tracer ) vel *= 0.9;
		Acceleration = vel.unit()*50;
	}
	action void A_RocketExplode( int dmg, int rad )
	{
		bFORCEXYBILLBOARD = true;
		A_SetRenderStyle(1.0,STYLE_Add);
		A_SprayDecal("RocketBlast",50);
		A_NoGravity();
		A_SetScale(FRandomPick[ExploS](-2.5,2.5),FRandomPick[ExploS](-2.5,2.5));
		UTMainHandler.DoBlast(self,rad,80000);
		A_Explode(dmg,rad);
		A_QuakeEx(3,3,3,8,0,rad+50,"",QF_RELATIVE|QF_SCALEDOWN,falloff:rad,rollIntensity:0.2);
		A_StartSound("eightball/explode",CHAN_VOICE);
		A_AlertMonsters();
		Spawn("RocketExplLight",pos);
		int numpt = Random[Eightball](15,30);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Eightball](-1,1),FRandom[Eightball](-1,1),FRandom[Eightball](-1,1)).unit()*FRandom[Eightball](1,3);
			let s = Spawn("UTSmoke",pos);
			s.vel = pvel;
		}
		numpt = Random[Eightball](10,20);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Eightball](-1,1),FRandom[Eightball](-1,1),FRandom[Eightball](-1,1)).unit()*FRandom[Eightball](2,6);
			let s = Spawn("UTSpark",pos);
			s.vel = pvel;
		}
		numpt = Random[Eightball](35,70);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Eightball](-1,1),FRandom[Eightball](-1,1),FRandom[Eightball](-1,1)).unit()*FRandom[Eightball](2,12);
			let s = Spawn("UTChip",pos);
			s.vel = pvel;
			s.scale *= FRandom[Eightball](0.9,2.7);
		}
		if ( !invoker.ringex ) return;
		Actor r = Spawn("ASMDSBeamRing",pos);
		Vector3 HitNormal = dt_Utility.Vec3FromAngle(angle,pitch);
		if ( BlockingLine ) HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
		else if ( BlockingFloor )
		{
			// find closest 3d floor for its normal
			F3DFloor ff = null;
			for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
			{
				if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
				ff = BlockingFloor.Get3DFloor(i);
				break;
			}
			if ( ff ) HitNormal = -ff.top.Normal;
			else HitNormal = BlockingFloor.floorplane.Normal;
			r.SetOrigin(r.Vec3Offset(0,0,2),false);
		}
		else if ( BlockingCeiling )
		{
			// find closest 3d floor for its normal
			F3DFloor ff = null;
			for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
			{
				if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
				ff = BlockingCeiling.Get3DFloor(i);
				break;
			}
			if ( ff ) HitNormal = -ff.bottom.Normal;
			else HitNormal = BlockingCeiling.ceilingplane.Normal;
		}
		r.angle = atan2(HitNormal.y,HitNormal.x);
		r.pitch = asin(-HitNormal.z);
	}
	action void A_RocketSeek()
	{
		if ( invoker.InitialDir.length() < double.epsilon ) invoker.InitialDir = vel.unit();
		if ( tracer && (tracer != target) )
		{
			Vector3 SeekingDir = level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2)).unit();
			if ( SeekingDir dot invoker.InitialDir > 0 )
			{
				double MagnitudeVel = Vel.length();
				SeekingDir = (SeekingDir*0.5*MagnitudeVel+Vel).unit();
				Vel = MagnitudeVel * SeekingDir;
				invoker.Acceleration = 25 * SeekingDir;
			}
		}
	}
	States
	{
	Spawn:
		RCKT ABC 1
		{
			roll += 30;
			if ( invoker.ticcnt++ > 3 )
			{
				invoker.ticcnt = 0;
				A_RocketSeek();
			}
			vel += invoker.Acceleration/TICRATE;
			if ( vel.length() > 30. ) vel = Vel.unit()*30.;
			Vector3 dir = vel.unit();
			if ( waterlevel <= 0 ) vel = dir*min(vel.length()+1,24);
			angle = atan2(dir.y,dir.x);
			pitch = asin(-dir.z);
			for ( int i=0; i<3; i++ )
			{
				let s = Spawn("UTSmoke",pos);
				s.vel = (FRandom[Eightball](-0.2,0.2),FRandom[Eightball](-0.2,0.2),FRandom[Eightball](-0.2,0.2));
				s.vel += vel*0.1;
			}
		}
		Wait;
	Death:
		TNT1 A 0
		{
			A_RocketExplode(100,120);
			return A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5");
		}
	Explo1:
		EXP1 ABCDEFGH 3 Bright;
		Stop;
	Explo2:
		EXP2 ABCDEFGH 3 Bright;
		Stop;
	Explo3:
		EXP3 ABCDEFGH 3 Bright;
		Stop;
	Explo4:
		EXP4 ABCDEFGH 3 Bright;
		Stop;
	Explo5:
		EXP5 ABCDEFGH 3 Bright;
		Stop;
	}
}

Class UGrenade : URocket
{
	double rollvel, pitchvel, anglevel;

	Default
	{
		DamageType 'GrenadeDeath';
		-NOGRAVITY;
		+USEBOUNCESTATE;
		-EXPLODEONWATER;
		+CANBOUNCEWATER;
		+NOEXPLODEFLOOR;
		+DONTBOUNCEONSHOOTABLES;
		BounceType "Hexen";
		WallBounceFactor 0.75;
		BounceFactor 0.75;
		ReactionTime 85;
		Speed 15;
		Gravity 0.35;
	}
	override void PostBeginPlay()
	{
		Actor.PostBeginPlay();
		rollvel = FRandom[Eightball](-8,8);
		pitchvel = FRandom[Eightball](-8,8);
		anglevel = FRandom[Eightball](-8,8);
		ReactionTime += Random[Eightball](0,20);
	}
	States
	{
	Spawn:
		RCKT A 1
		{
			if ( !bNOGRAVITY )
			{
				angle += anglevel;
				pitch += pitchvel;
				roll += rollvel;
			}
			let s = Spawn("UTSmoke",pos);
			s.scale *= 2.0;
			s.alpha *= 0.6;
			s.vel = (FRandom[Eightball](-0.1,0.1),FRandom[Eightball](-0.1,0.1),FRandom[Eightball](-0.1,0.3));
			s.vel += vel*0.05;
			s.SetShade("000000");
			A_Countdown();
		}
		Wait;
	Bounce:
		RCKT A 0
		{
			bHITOWNER = true;
			A_StartSound("utrl/bounce");
			rollvel = FRandom[Eightball](-16,16);
			pitchvel = FRandom[Eightball](-16,16);
			anglevel = FRandom[Eightball](-16,16);
			if ( vel.z > 10 ) vel.z = 0.5*(10+vel.z);
			else if ( BlockingFloor && (vel.xy.length() < 0.5) )
			{
				vel *= 0;
				bNOGRAVITY = true;
				bMOVEWITHSECTOR = true;
				ClearBounce();
			}
		}
		Goto Spawn;
	Death:
		TNT1 A 0 A_RocketExplode(120,140);
		Goto Super::Death+1;
	}
}

Class Eightball : UnrealWeapon
{
	bool LockedOn;
	Actor LockedTarget;
	TextureID lockontex;
	int locktics;
	bool bSingleRocket;

	override int, int, bool, bool GetClipAmount()
	{
		return special1?special1:-1, -1, false, false;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		lockontex = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any);
	}

	override void PreRender( double lbottom )
	{
		if ( LockedTarget ) Screen.DrawTexture(lockontex,false,Screen.GetWidth()*0.5,Screen.GetHeight()*0.5);
	}

	override void Tick()
	{
		Super.Tick();
		if ( !Owner ) return;
		if ( Owner.Health <= 0 )
		{
			LockedTarget = null;
			LockedOn = false;
			crosshair = 0;
			return;
		}
		if ( LockedOn && (!LockedTarget || (LockedTarget.Health <= 0) || !LockedTarget.bIsMonster || LockedTarget.bKilled || LockedTarget.bCorpse || !LockedTarget.bShootable || (Owner.player.ReadyWeapon != self)) )
		{
			LockedTarget = null;
			LockedOn = false;
			if ( Owner.player.ReadyWeapon == self )
			{
				Owner.A_StartSound("eightball/seeklost",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(Owner)?.1:1.);
				if ( !Dampener.Active(Owner) ) Owner.A_AlertMonsters();
			}
		}
		if ( LockedTarget ) crosshair = 99;
		else crosshair = 0;
	}

	// consumes 1 ammo
	action void A_LoadRocket( bool checktarget = true )
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( weap.Ammo1.Amount <= 0 ) return;
		if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
		if ( weap.bAltFire )
		{
			invoker.LockedTarget = null;
			invoker.LockedOn = false;
		}
		if ( checktarget && !weap.bAltFire ) A_CheckTarget();
		invoker.special1++;
	}

	// refire that is ignored if there's no ammo
	action void A_LoadedRefire( statelabel flash = null )
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( weap.Ammo1.Amount <= 0 ) return;
		A_Refire(flash);
	}

	// fire all the rockets (or grenades)
	action void A_FireRockets( int num )
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		A_Overlay(-9999,"Null");
		invoker.special1 = 0;
		if ( weap.bAltFire )
		{
			A_StartSound("eightball/altfire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.3:3.);
			if ( !Dampener.Active(self) ) A_AlertMonsters();
		}
		else A_AlertMonsters();
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(64,255,0,0),1);
		if ( self is 'UTPlayer' )
			UTPlayer(self).PlayAttacking3();
		A_QuakeEx(2+num,2+num,2+num,6+num,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1+num*0.05);
		Vector3 x, y, z, x2, y2, z2;
		double a, s;
		[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-2*z);
		[x2, y2, z2] = dt_Utility.GetAxes(angle,BulletSlope(),roll);
		Actor p;
		if ( weap.bAltFire )
		{
			// grenades
			for ( int i=0; i<num; i++ )
			{
				a = FRandom[Eightball](0,360);
				s = FRandom[Eightball](0,(num>1)?12:0);
				Vector3 dir = dt_Utility.ConeSpread(x2,y2,z2,a,s*0.004);
				p = Spawn("UGrenade",level.Vec3Offset(origin,dt_Utility.CircleOffset(y,z,a,s)));
				p.vel = x*(vel dot x)*0.4 + dir*p.speed*FRandom[Eightball](1.0,1.2);
				p.vel.z += 3.5;
				p.target = self;
			}
		}
		else if ( num <= 1 )
		{
			// single rocket
			p = Spawn("URocket",level.Vec3Offset(origin,dt_Utility.CircleOffset(y,z,a,s)));
			p.vel = x2*p.speed;
			p.target = self;
			p.tracer = invoker.LockedTarget;
		}
		else if ( player.cmd.buttons&BT_ALTATTACK )
		{
			// rockets ("tight wad" as UT calls it)
			double step = 360/num;
			a = 90;
			s = (num>1)?6:0;
			for ( int i=0; i<num; i++ )
			{
				p = Spawn("URocket",level.Vec3Offset(origin,dt_Utility.CircleOffset(y,z,a,s)));
				p.vel = x2*p.speed;
				p.target = self;
				p.tracer = invoker.LockedTarget;
				a += step;
				if ( !invoker.LockedTarget && (i==0) )
					URocket(p).ringex = true;
			}
		}
		else
		{
			// rockets (wide spread)
			double range = 3.6*(num-1);
			double step = range/(num-1);
			s = -range*0.5;
			for ( int i=0; i<num; i++ )
			{
				p = Spawn("URocket",level.Vec3Offset(origin,sin(s)*y));
				p.vel = (x2+sin(s)*y2).unit()*p.speed;
				p.target = self;
				p.tracer = invoker.LockedTarget;
				s += step;
			}
		}
		// lose lock-on
		if ( invoker.LockedOn ) A_StartSound("eightball/seeklost",CHAN_WEAPONMISC,CHANF_OVERLAP);
		invoker.LockedTarget = null;
		invoker.LockedOn = false;
		for ( int i=0; i<12; i++ )
		{
			let s = Spawn("UTViewSmoke",origin);
			UTViewSmoke(s).ofs = (10,0,-2);
			UTViewSmoke(s).vvel = (FRandom[Eightball](0,1.2),FRandom[Eightball](-.4,.4),FRandom[Eightball](-.4,.4));
			s.target = self;
			s.scale *= 1.8;
			s.alpha *= 0.8;
		}
	}

	// lock-on check
	action void A_CheckTarget()
	{
		let t = ThinkerIterator.Create("Actor");
		Actor a;
		double closest = double.max;
		invoker.LockedTarget = null;
		while ( a = Actor(t.Next()) )
		{
			if ( !a.bSHOOTABLE || (a.Health <= 0) || a.bKilled || !a.bIsMonster || a.bCorpse || (a == self) || isTeammate(a) || !CheckSight(a) ) continue;
			Vector3 viewdir = dt_Utility.Vec3FromAngle(angle,pitch);
			Vector3 reldir = level.Vec3Diff(Vec2OffsetZ(0,0,player.viewz),a.Vec2OffsetZ(0,0,a.pos.z+a.height*0.5));
			double reldist = reldir.length();
			if ( reldist > 2000 ) continue;
			if ( reldir.unit() dot viewdir < 0.99 ) continue;
			if ( reldist < closest )
			{
				closest = reldist;
				invoker.LockedTarget = a;
			}
		}
		if ( invoker.LockedTarget )
		{
			A_StartSound("eightball/seeklock",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.1:1.);
			if ( !Dampener.Active(self) ) A_AlertMonsters();
		}
		else if ( invoker.LockedOn )
		{
			A_StartSound("eightball/seeklost",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.1:1.);
			if ( !Dampener.Active(self) ) A_AlertMonsters();
		}
		if ( invoker.LockedTarget ) invoker.LockedOn = true;
	}

	Default
	{
		Tag "$T_EIGHTBALL";
		Inventory.PickupMessage "$I_EIGHTBALL";
		Weapon.UpSound "eightball/select";
		Weapon.SlotNumber 5;
		Weapon.SelectionOrder 1000;
		Weapon.SlotPriority 1;
		Weapon.AmmoType "URocketAmmo";
		Weapon.AmmoUse 1;
		Weapon.AmmoType2 "URocketAmmo";
		Weapon.AmmoUse2 1;
		Weapon.AmmoGive 6;
		UTWeapon.DropAmmo 3;
	}
	States
	{
	Spawn:
		EBLP A -1;
		Stop;
		EBLP B -1;
		Stop;
	Select:
		EBLS A 1 A_Raise(int.max);
		Wait;
	Ready:
		EBLS ABCDEFGHIJKLMNO 1 A_WeaponReady(WRF_NOFIRE);
		EBLS O 8 A_Overlay(-9999,"Dummy");
		Goto Idle+1;
	Ready2:
		EBLS O 0 A_CheckReload();
		EBLL A 0 A_StartSound("eightball/load",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.1:1.);
		EBLL ABCDEFGHIJK 1 A_WeaponReady(WRF_NOFIRE);
		EBLL L 0 A_StartSound("eightball/rotate",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.01:.1);
		EBLL LMNOPQRSTUVWXYZ[ 1 A_WeaponReady(WRF_NOFIRE);
		EBLL Z 0;	// force no tweening
		EBLI A 0;
		Goto Idle;
	Idle:
		#### # 0 A_Overlay(-9999,"Dummy");
		EBLI AB 50;
		Goto Idle+1;
	Dummy:
		TNT1 A 1
		{
			invoker.locktics = 0;
			invoker.special1 = 0;
			A_CheckReload();
			A_WeaponReady(WRF_ALLOWRELOAD);
		}
		TNT1 A 1
		{
			A_CheckReload();
			A_WeaponReady(WRF_ALLOWRELOAD);
			invoker.locktics++;
			if ( invoker.locktics > 42 )
			{
				invoker.locktics = 0;
				A_CheckTarget();
			}
		}
		Wait;
	Reload:
		#### # 5
		{
			A_Overlay(-9999,"Null");
			A_StartSound("eightball/load",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.03:.3);
			if ( invoker.bSingleRocket = !invoker.bSingleRocket )
				A_Print(StringTable.Localize("$M_SINGLEROCKETON"));
			else A_Print(StringTable.Localize("$M_SINGLEROCKETOFF"));
		}
		Goto Idle;
	Dummy2:
		TNT1 A 1
		{
			if ( !(player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK)) )
				player.SetPSprite(PSP_WEAPON,ResolveState("Release"));
		}
		Wait;
	Fire:
	AltFire:
		// one is loaded already
		#### # 1
		{
			A_Overlay(-9999,"Dummy2");
			A_LoadRocket(false);
		}
		#### # 0 A_JumpIf(!invoker.bAltFire&&invoker.bSingleRocket,"Release");
		#### # 0 A_LoadedRefire(1);
		Goto Release;
	Loading:
		EBLI A 0;
		EBLL A 0 A_StartSound("eightball/load",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.1:1.);
		EBLL ABCDEFGHIJK 1;
		EBLL L 0 A_LoadRocket();
		EBLL L 0 A_StartSound("eightball/rotate",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.01:.1);
		EBLL LMNOPQRSTUVWXYZ[ 1;
		EBLL Z 0;
		EBLI A 0;
		EBLI A 1
		{
			if ( invoker.special1 >= 6 )
			{
				if ( sting_ehold )
				{
					invoker.special2 = 70;
					return ResolveState("LoadHold");
				}
				else return ResolveState("Release");
			}
			return ResolveState(null);
		}
		EBLI A 0 A_LoadedRefire("Loading");
		Goto Release;
	LoadHold:
		EBLI A 2;
	LoadHoldL:
		EBLI C 1 A_JumpIf(--invoker.special2<0,"Release");
		EBLI C 0 A_LoadedRefire("LoadHoldL");
		Goto Release;
	Release:
		EBLF A 0 A_FireRockets(invoker.special1);
		EBLF ABCDEFGH 3;
		EBLS S 0;
		Goto Ready2;
	Deselect:
		EBLD A 1 A_Overlay(-9999,"Null");
		EBLD BCDEFGHIJKL 1;
		EBLD L 1 A_Lower(int.max);
		Wait;
	}
}
