Class UFireLight : PaletteLight
{
	Default
	{
		Tag "FlamG";
		Args 0,0,0,60;
	}
}

Class UFireTrail : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+NOTELEPORT;
		Scale 0.5;
		Alpha 0.2;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target || !target.bMISSILE )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
	}
	States
	{
	Spawn:
		FGFL A -1 Bright;
		Stop;
	}
}

Class UFireball : Actor
{
	Vector3 Acceleration;
	double spreadf, maxspeed;
	int ns, nsp;

	property SpreadFactor : spreadf;
	property NumSmokes : ns;
	property NumSparks : nsp;

	Default
	{
		DamageType 'Fire';
		DamageFunction 20;
		Radius 4;
		Height 4;
		Speed 15;
		PROJECTILE;
		+SKYEXPLODE;
		+EXPLODEONWATER;
		+FORCERADIUSDMG;
		+NODAMAGETHRUST;
		+FORCEXYBILLBOARD;
		+DONTSPLASH;
		+NOFRICTION;
		UFireball.SpreadFactor 0.2;
		UFireball.NumSmokes 1;
		UFireball.NumSparks 2;
		DeathSound "flamegun/exp";
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		maxspeed = 30.;
		if ( bAMBUSH ) return;
		let l = Spawn("UFireTrail",pos);
		l.target = self;
		Acceleration = vel.unit()*50;
		A_StartSound("eightball/fly",CHAN_VOICE,CHANF_LOOPING,.6,3.,1.5);
	}
	action void A_Spread()
	{
		vel += invoker.Acceleration/TICRATE;
		vel += (FRandom[EWizFX](-1,1),FRandom[EWizFX](-1,1),FRandom[EWizFX](-1,1))*FRandom[EWizFX](.5,1.)*invoker.spreadf;
		Vector3 dir = vel.unit();
		if ( vel.length() > invoker.maxspeed ) vel = dir*invoker.maxspeed;
		angle = atan2(dir.y,dir.x);
		pitch = asin(-dir.z);
		if ( waterlevel > 0 )
		{
			int numpt = Random[ExploS](10,15);
			if ( bAMBUSH ) numpt /= 3;
			for ( int i=0; i<numpt; i++ )
			{
				Vector3 pvel = (dir*3+(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1))).unit()*FRandom[ExploS](1,4);
				let s = Spawn("UTSmoke",pos);
				s.vel = pvel;
				s.alpha *= 0.6;
				s.scale *= FRandom[ExploS](0.9,2.0);
				s.SetShade(Color(1,1,1)*Random[ExploS](64,128));
			}
			Destroy();
			return;
		}
		for ( int i=0; i<invoker.ns; i++ )
		{
			if ( Random[FlameGun](0,2) ) continue;
			let s = Spawn("UTSmoke",pos);
			s.vel = (FRandom[FlameGun](-0.2,0.2),FRandom[FlameGun](-0.2,0.2),FRandom[FlameGun](-0.2,0.2));
			s.vel += vel*0.2;
			s.scale *= 0.5;
		}
		for ( int i=0; i<invoker.nsp; i++ )
		{
			if ( Random[FlameGun](0,6) ) continue;
			let s = Spawn("UTSpark",pos);
			s.vel = (FRandom[FlameGun](-0.2,0.2),FRandom[FlameGun](-0.2,0.2),FRandom[FlameGun](-0.2,0.2));
			s.ClearBounce();
			s.gravity *= 0.1;
			s.A_SetTranslation('FlamGSpark');
		}
	}
	action void A_FireballExplo()
	{
		A_SetRenderStyle(1.,STYLE_Add);
		A_NoGravity();
		if ( bAMBUSH )
		{
			A_Explode(GetMissileDamage(0,0),20);
			UTMainHandler.DoBlast(self,20,3000);
		}
		else
		{
			Spawn("UFireLight",pos);
			A_Explode(GetMissileDamage(0,0),60);
			UTMainHandler.DoBlast(self,60,9000);
			A_StartSound(DeathSound,CHAN_VOICE,attenuation:1.5,pitch:FRandom[ExploS](0.8,1.2));
		}
		A_SprayDecal("SmallRocketBlast");
		Scale *= FRandom[ExploS](0.6,0.9);
		Vector3 dir = dt_Utility.Vec3FromAngle(angle,pitch);
		int numpt = Random[ExploS](10,15);
		if ( bAMBUSH ) numpt /= 3;
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (dir*3+(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1))).unit()*FRandom[ExploS](2,8);
			let s = Spawn("UTSmoke",pos);
			s.vel = pvel;
			s.scale *= FRandom[ExploS](0.9,1.5);
			s.SetShade(Color(1,1,1)*Random[ExploS](64,128));
		}
		numpt = Random[ExploS](6,12);
		if ( bAMBUSH ) numpt /= 3;
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
			let s = Spawn("UTSpark",pos);
			s.vel = pvel;
		}
		numpt = Random[ExploS](10,20);
		if ( bAMBUSH ) numpt /= 3;
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
			let s = Spawn("UTChip",pos);
			s.vel = pvel;
			s.scale *= FRandom[ExploS](0.9,1.3);
		}
	}
	override void Tick()
	{
		Super.Tick();
		if ( !isFrozen() && InStateSequence(CurState,FindState("Spawn")) )
			A_Spread();
	}
	States
	{
	Spawn:
		FIRB ABCDEFGHIJK 2 Bright;
		Loop;
	Death:
		TNT1 A 0 A_FireballExplo();
		FGEX ABCDEFGH 2 Bright;
		Stop;
	}
}

Class UFireball2 : UFireball
{
	Default
	{
		Radius 8;
		Height 8;
		DamageFunction 50;
		Speed 3;
		Scale 1.5;
		UFireball.SpreadFactor 0.35;
		UFireball.NumSmokes 6;
		UFireball.NumSparks 12;
		DeathSound "flamegun/exp2";
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		Acceleration = vel.unit()*150;
		maxspeed = 50.;
		A_StartSound("eightball/fly",CHAN_VOICE,CHANF_LOOPING,1.,1.5,.85);
	}
	action void A_FireballExplo2()
	{
		scale *= 2.;
		A_Explode(GetMissileDamage(0,0),90);
		UTMainHandler.DoBlast(self,90,12000);
		Vector3 dir = dt_Utility.Vec3FromAngle(angle,pitch);
		int numpt = Random[ExploS](10,15);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (dir*3+(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1))).unit()*FRandom[ExploS](4,9);
			let s = Spawn("UTSmoke",pos);
			s.vel = pvel;
			s.scale *= FRandom[ExploS](1.9,2.5);
			s.SetShade(Color(1,1,1)*Random[ExploS](32,64));
		}
		Vector3 HitNormal = dir;
		if ( BlockingLine )
		{
			HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
			if ( CurSector == BlockingLine.frontsector ) HitNormal *= -1;
		}
		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;
		}
		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;
		}
		for ( int i=0; i<20; i++ )
		{
			dir = (HitNormal*1.1+(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1))).unit();
			let s = Spawn("UFireballEmber",level.Vec3Offset(pos,HitNormal*2));
			s.vel = dir*s.speed*FRandom[ExploS](0.6,1.8);
			s.angle = atan2(dir.y,dir.x);
			s.pitch = asin(-dir.z);
			s.target = target;
			s.bHITOWNER = true;
		}
	}
	States
	{
	Death:
		TNT1 A 0 A_FireballExplo2();
		Goto Super::Death;
	}
}

Class UFireballEmber : UFireball
{
	Default
	{
		DamageFunction 5;
		+AMBUSH;
		-NOGRAVITY;
		Gravity 0.3;
		Speed 8;
		UFireball.SpreadFactor 0.5;
		UFireball.NumSmokes 1;
		UFireball.NumSparks 1;
		DeathSound "";
	}
	States
	{
	Death:
		TNT1 A 0
		{
			Scale *= 0.4;
		}
		Goto Super::Death;
	}
}

Class FlameGun : UnrealWeapon
{
	bool bCharging;

	Default
	{
		Tag "$T_FLAMEGUN";
		Inventory.PickupMessage "$I_FLAMEGUN";
		Weapon.UpSound "flamegun/select";
		Weapon.SlotNumber 5;
		Weapon.SelectionOrder 1200;
		Weapon.SlotPriority 0.9;
		Weapon.AmmoType "FlameAmmo";
		Weapon.AmmoUse 10;
		Weapon.AmmoType2 "FlameAmmo";
		Weapon.AmmoUse2 20;
		Weapon.AmmoGive 100;
		UTWeapon.DropAmmo 50;
	}
	override bool TryPickup( in out Actor toucher )
	{
		if ( !sting_proto ) return false;	// not allowed
		return Super.TryPickup(toucher);
	}
	override void Tick()
	{
		Super.Tick();
		if ( sting_proto ) return;
		if ( !Owner )
		{
			let r = Spawn("RocketLauncher",pos,ALLOW_REPLACE);
			r.spawnangle = spawnangle;
			r.spawnpoint = spawnpoint;
			r.angle = angle;
			r.pitch = pitch;
			r.roll = roll;
			r.special = special;
			r.args[0] = args[0];
			r.args[1] = args[1];
			r.args[2] = args[2];
			r.args[3] = args[3];
			r.args[4] = args[4];
			r.ChangeTid(tid);
			r.SpawnFlags = SpawnFlags&~MTF_SECRET;
			r.HandleSpawnFlags();
			r.SpawnFlags = SpawnFlags;
			r.bCountSecret = SpawnFlags&MTF_SECRET;
			r.vel = vel;
			r.master = master;
			r.target = target;
			r.tracer = tracer;
			r.bDropped = bDropped;
			r.bNeverRespawn = bNeverRespawn;
		}
		else Owner.RemoveInventory(self);
		Destroy();
	}
	action void A_FlameGunFire( bool bAlt = false )
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
		A_StartSound(bAlt?"flamegun/alt":"flamegun/fire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.2:1.);
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(32,96,255,0),1);
		if ( bAlt ) A_QuakeEx(2,2,2,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1);
		else A_QuakeEx(1,1,1,2,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1);
		if ( !Dampener.Active(self) ) A_AlertMonsters();
		Vector3 x, y, z, x2, y2, z2, dir;
		double a, s;
		[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-3*y-z);
		[x2, y2, z2] = dt_Utility.GetAxes(angle,BulletSlope(),roll);
		for ( int i=0; i<(bAlt?1:3); i++ )
		{
			a = FRandom[FlameGun](0,360);
			s = FRandom[FlameGun](0,bAlt?0.:.06);
			dir = dt_Utility.ConeSpread(x2,y2,z2,a,s);
			let p = Spawn(bAlt?"UFireball2":"UFireball",origin);
			p.angle = atan2(dir.y,dir.x);
			p.pitch = asin(-dir.z);
			p.vel = dir*p.speed;
			p.target = self;
		}
		int numpt = Random[FlameGun](10,15);
		for ( int i=0; i<numpt; i++ )
		{
			let s = Spawn("UTViewSpark",origin);
			UTViewSpark(s).ofs = (10,-3,-1);
			UTViewSpark(s).vvel = (FRandom[FlameGun](3,12),FRandom[FlameGun](-2,2),FRandom[FFlameGun](-2,2));
			s.target = self;
			s.A_SetTranslation('FlamGSpark');
		}
		for ( int i=0; i<8; i++ )
		{
			let s = Spawn("UTViewSmoke",origin);
			UTViewSmoke(s).ofs = (10,-3,-1);
			UTViewSmoke(s).vvel = (FRandom[FlameGun](0,1.2),FRandom[FlameGun](-.3,.3),FRandom[FlameGun](-.3,.3));
			s.target = self;
			s.scale *= 1.6;
			s.alpha *= 0.5;
		}
	}
	States
	{
	Spawn:
		FGNP A -1;
		Stop;
		FGNP B -1;
		Stop;
	Select:
		FGNS A 1 A_Raise(int.max);
		Wait;
	Ready:
		FGNS ABCDEFGHIJ 2 A_WeaponReady(WRF_NOFIRE);
		Goto Idle;
	Dummy:
		TNT1 A 1
		{
			A_CheckReload();
			int flags = (waterlevel>=2)?WRF_NOFIRE:0;
			if ( invoker.Ammo1.Amount < invoker.AmmoUse2 )
				flags |= WRF_NOSECONDARY;
			A_WeaponReady(flags);
		}
		Wait;
	Idle:
		FGNI A 0 A_Overlay(-9999,"Dummy");
		FGNI ABCDEFG 12;
		FGNI A 0 A_Jump(40,"Twiddle");
		Goto Idle+1;
	Twiddle:
		#### # 2 A_StartSound("flamegun/idle",CHAN_WEAPONMISC,CHANF_DEFAULT,Dampener.Active(self)?.1:1.);
		FGNT ABCDEFGHIJKLM 3;
		Goto Idle+1;
	Fire:
		#### # 1 A_Overlay(-9999,"Null");
		FGNF A 2
		{
			invoker.special1 = 0;
			A_StartSound("flamegun/start",CHAN_WEAPONMISC,CHANF_DEFAULT,Dampener.Active(self)?.1:1.);
			if ( !Dampener.Active(self) ) A_AlertMonsters();
		}
	Hold:
		FGNF B 1 A_FlameGunFire();
		FGNF CDEFG 1;
		FGNF H 0
		{
			invoker.special1++;
			if ( invoker.CheckAmmo(0,false,true) && (waterlevel < 2) && (invoker.special1 < 3) )
				A_Refire("Refire");
		}
		FGNF H 0 A_ClearRefire();
		FGNF HIJ 2;
		FGNT A 0
		{
			if ( invoker.CheckAmmo(0,false,true) && (waterlevel < 2) && (invoker.special1 < 3)  )
				A_Refire("Fire");
		}
		FGNT A 0
		{
			invoker.special1 = 0;
			A_StartSound("flamegun/end",CHAN_WEAPONMISC,CHANF_OVERLAP,Dampener.Active(self)?.1:1.);
			A_ClearRefire();
		}
		FGNT ABCDEFGHIJKLM 2;
		Goto Idle;
	Refire:
		FGNF H 2 A_StartSound("flamegun/start",CHAN_WEAPONMISC,CHANF_DEFAULT,Dampener.Active(self)?.1:1.);
		Goto Hold;
	AltFire:
		#### # 1 A_Overlay(-9999,"Null");
		FGNF A 1
		{
			invoker.special1 = 0;
			A_StartSound("flamegun/charge",CHAN_WEAPONMISC,CHANF_DEFAULT,Dampener.Active(self)?.1:1.);
			if ( !Dampener.Active(self) ) A_AlertMonsters();
		}
		FGNF A 1
		{
			invoker.special1++;
			A_WeaponOffset(FRandom[FlameGun](-.1,.1)*invoker.special1,32+FRandom[FlameGun](-.1,.1)*invoker.special1);
			return A_JumpIf(invoker.special1>40,"AltRelease");
		}
		Wait;
	AltRelease:
		FGNF B 2
		{
			invoker.special1 = 0;
			A_WeaponOffset(0,32);
			A_FlameGunFire(true);
		}
		FGNF CDEFGHIJ 2;
		FGNT ABCDEFGHIJKLM 2;
		Goto Idle;
	Deselect:
		FGND A 1 A_Overlay(-9999,"Null");
		FGND BCDEFGHIJ 1;
		FGND J 1 A_Lower(int.max);
		Wait;
	}
}
