Class ImpalerAmmo : Ammo
{
	Default
	{
		Tag "$T_IMPAMMO";
		Inventory.Icon "I_Impale";
		Inventory.PickupMessage "$I_IMPAMMO";
		Inventory.Amount 3;
		Inventory.MaxAmount 15;
		Ammo.BackpackAmount 2;
		Ammo.BackpackMaxAmount 30;
		Ammo.DropAmount 3;
		+INVENTORY.IGNORESKILL;
	}
	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((GetClass()=="ImpalerAmmo")?"CellPack":"Cell",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;
			Destroy();
		}
		else
		{
			Owner.RemoveInventory(self);
			Destroy();
		}
	}
	States
	{
	Spawn:
		IAMO A -1;
		Stop;
	}
}
Class ImpalerAmmo2 : ImpalerAmmo
{
	Default
	{
		Tag "$T_IMPAMMO2";
		Inventory.PickupMessage "$I_IMPAMMO2";
		Inventory.Amount 1;
		Ammo.DropAmount 1;
		+INVENTORY.IGNORESKILL;
	}
}

Class ImpalerSpark : PulseSpark
{
	States
	{
	Spawn:
		ISPK A 1 Bright
		{
			A_FadeOut(FRandom[Pulse](0.,.15));
			vel *= .96;
		}
		Wait;
	}
}

Class ViewImpalerSpark : ViewPulseSpark
{
	States
	{
	Spawn:
		ISPK A 1 Bright A_FadeOut(FRandom[Pulse](0.,.15));
		Wait;
	}
}

Class ImpalerChunk : StingerChunk
{
	override void Tick()
	{
		Super.Tick();
		if ( isFrozen() ) return;
		let c = Spawn("UTSmoke",pos);
		c.vel = vel*.3;
		c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
		c.bBRIGHT = true;
		c.alpha *= .5*alpha;
		c.scale *= .5*scale.x;
	}
}

Class ImpalerBurstLight : PaletteLight
{
	Default
	{
		Tag "ImpExpl";
		Args 0,0,0,50;
		ReactionTime 15;
	}
}

Class ImpalerBoltTracer : LineTracer
{
	Actor ignoreme;
	Array<HitListEntry> hitlist;
	Array<Line> ShootThroughList;

	override ETraceStatus TraceCallback()
	{
		if ( Results.HitType == TRACE_HitActor )
		{
			if ( Results.HitActor == ignoreme ) return TRACE_Skip;
			if ( Results.HitActor.bSHOOTABLE )
			{
				let ent = new("HitListEntry");
				ent.hitactor = Results.HitActor;
				ent.hitlocation = Results.HitPos;
				ent.x = Results.HitVector;
				hitlist.Push(ent);
			}
			return TRACE_Skip;
		}
		else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
		{
			if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
				return TRACE_Stop;
			ShootThroughList.Push(Results.HitLine);
			return TRACE_Skip;
		}
		return TRACE_Stop;
	}
}

Class ImpalerBurstBolt : Actor
{
	Vector3 nextpos, nextdir;

	override void Tick()
	{
		Super.Tick();
		frame++;
		if ( frame >= 5 ) frame = 0;
	}
	action void A_Trace()
	{
		let t = new("ImpalerBoltTracer");
		t.hitlist.Clear();
		Vector3 x, y, z;
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		t.ShootThroughList.Clear();
		t.Trace(pos,CurSector,x,11.125,0);
		for ( int i=0; i<t.ShootThroughList.Size(); i++ )
			t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
		for ( int i=0; i<t.hitlist.Size(); i++ )
		{
			UTMainHandler.DoKnockback(t.hitlist[i].hitactor,t.hitlist[i].x,6000);
			t.hitlist[i].hitactor.DamageMobj(self,target,3,'Impaler',DMG_THRUSTLESS);
		}
		Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector;
		if ( t.Results.HitType == TRACE_HitWall )
		{
			normal = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
			if ( t.Results.Side ) normal *= -1;
			t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
			dir -= 2*normal*(dir dot normal);
		}
		else if ( t.Results.HitType == TRACE_HitFloor )
		{
			if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal;
			else normal = t.Results.HitSector.floorplane.Normal;
			dir -= 2*normal*(dir dot normal);
		}
		else if ( t.Results.HitType == TRACE_HitCeiling )
		{
			if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal;
			else normal = t.Results.HitSector.ceilingplane.Normal;
			dir -= 2*normal*(dir dot normal);
		}
		else
		{
			// why the fuck
			t.Results.HitPos = level.Vec3Offset(pos,x*10.125);
			normal *= 0;
		}
		double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.8);
		invoker.nextpos = level.Vec3Offset(t.Results.HitPos,normal);
		invoker.nextdir = (dir+cos(a)*y*s+sin(a)*z*s).unit();
	}
	action void A_Spread()
	{
		Vector3 tdir = level.Vec3Diff(pos,invoker.nextpos);
		double tdist = tdir.length();
		tdir /= tdist;
		for ( int i=2; i<tdist; i+=4 )
		{
			let s = Spawn("ImpalerSpark",level.Vec3Offset(pos,tdir*i));
			s.vel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](.2,.8);
		}
		if ( special1 > 5 ) return;
		let b = Spawn("ImpalerBurstBolt",invoker.nextpos);
		b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x);
		b.pitch = asin(-invoker.nextdir.z);
		b.target = target;
		b.special1 = special1+1;
		b.frame = frame;
	}
	Default
	{
		RenderStyle "Add";
		Obituary "$O_IMPALER1";
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+INTERPOLATEANGLES;
		+NOTELEPORT;
	}
	States
	{
	Spawn:
		PBLT A 1 Bright;
		PBLT A 1 Bright A_Trace();
		PBLT # 1 Bright A_Spread();
		PBLT # 1 Bright A_FadeOut();
		Wait;
	Dummy:
		PBLT ABCDE -1;
		Stop;
	}
}

Class ImpalerBoltHit : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+NOTELEPORT;
		Scale 0.3;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !master ) Destroy();
	}
	States
	{
	Spawn:
		#### ABCD 1 Bright;
		Loop;
	Dummy:
		IHIT ABCD -1;
		ICAP ABCD -1;
		Stop;
	}
}

Class ImpalerBolt : Actor
{
	const beamsize = 20.25;

	ImpalerBoltTracer t;
	ImpalerBolt next;
	StarterImpalerBolt start;
	Vector3 oldx;
	Actor weffect;

	override void OnDestroy()
	{
		Super.OnDestroy();
		if ( next ) next.Destroy();
		if ( weffect ) weffect.Destroy();
		A_StopSound(CHAN_BODY);
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		t = new("ImpalerBoltTracer");
		if ( !(GetClass() is 'StarterImpalerBolt') )
			A_PlaySound("impaler/beam",CHAN_BODY,.2,true,2.,pitch:.75);
	}
	void CheckBeam( Vector3 x )
	{
		t.HitList.Clear();
		if ( bHITOWNER ) t.ignoreme = null;
		else t.ignoreme = target;
		t.ShootThroughList.Clear();
		t.Trace(pos,cursector,x,beamsize,0);
		for ( int i=0; i<t.ShootThroughList.Size(); i++ )
			t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
		for ( int i=0; i<t.HitList.Size(); i++ )
		{
			if ( !(GetAge()%15) )
			{
				UTMainHandler.DoKnockback(t.hitlist[i].hitactor,t.hitlist[i].x,500*specialf1**2);
				t.hitlist[i].hitactor.DamageMobj(self,target,int(max(5,10*specialf1**1.5)),'Impaler',DMG_THRUSTLESS);
			}
			if ( start && (start.Hitlist.Find(t.HitList[i].HitActor) == start.HitList.Size()) )
				start.Hitlist.Push(t.HitList[i].HitActor);
		}
		// seeking
		double closest = max(200,500*specialf1);
		if ( tracer )
		{
			double tdist = Distance3D(tracer);
			if ( (tdist > closest) || (tracer.Health <= 0) )
				tracer = null;
		}
		let bt = BlockThingsIterator.Create(self,closest);
		while ( bt.Next() )
		{
			let a = bt.Thing;
			if ( !a || !a.bSHOOTABLE || !a.bISMONSTER || (a.Health <= 0) || target.IsFriend(a) || !CheckSight(a) || (start && (start.Hitlist.Find(a) < start.HitList.Size())) ) continue;
			Vector3 dirto = level.Vec3Diff(pos,a.Vec3Offset(0,0,a.height/2));
			double dist = dirto.length();
			dirto /= dist;
			if ( dirto dot x < .5 ) continue;
			if ( dist > closest ) continue;
			tracer = a;
			closest = dist;
		}
		if ( tracer ) special1 = 0;
		Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector;
		if ( t.Results.HitType == TRACE_HitWall )
		{
			normal = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
			if ( t.Results.Side ) normal *= -1;
			t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
			dir -= 2*normal*(dir dot normal);
		}
		else if ( t.Results.HitType == TRACE_HitFloor )
		{
			if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal;
			else normal = t.Results.HitSector.floorplane.Normal;
			dir -= 2*normal*(dir dot normal);
		}
		else if ( t.Results.HitType == TRACE_HitCeiling )
		{
			if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal;
			else normal = t.Results.HitSector.ceilingplane.Normal;
			dir -= 2*normal*(dir dot normal);
		}
		else
		{
			// why the fuck
			t.Results.HitPos = level.Vec3Offset(pos,x*beamsize);
			normal *= 0;
		}
		if ( t.Results.HitType != TRACE_HitNone )
		{
			A_SprayDecal("BoltScorch",beamsize+8);
			int numpt = Random[Impaler](10,20)*!Random[Impaler](0,2);
			for ( int i=0; i<numpt; i++ )
			{
				Vector3 pvel = (normal+(FRandom[Impaler](-.6,.6),FRandom[Impaler](-.6,.6),FRandom[Impaler](-.6,.6))).unit()*FRandom[Impaler](2,4);
				let s = Spawn("ImpalerSpark",t.Results.HitPos+normal*4);
				s.vel = pvel;
			}
		}
		Vector3 tdir = level.Vec3Diff(pos,t.Results.HitPos);
		double tdist = tdir.length();
		tdir /= tdist;
		for ( int i=8; i<tdist; i+=16 )
		{
			let s = Spawn("ImpalerSpark",level.Vec3Offset(pos,tdir*i));
			s.alpha *= .5;
			s.scale *= .4;
			s.vel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](.2,.8);
		}
		if ( (special1 < int(max(5,10*specialf1))) && (special2 < int(max(20,40*specialf1**.5))) && (start && (start.hitlist.Size() < int(max(4,4*specialf1)))) && (waterlevel <= 0) )
		{
			if ( !next )
			{
				next = ImpalerBolt(Spawn("ImpalerBolt",t.Results.HitPos+normal));
				next.angle = atan2(dir.y,dir.x);
				next.pitch = asin(-dir.z);
				next.target = target;
				next.start = start;
				next.tracer = tracer;
				next.special1 = special1+1;
				next.special2 = special2+1;
				next.specialf1 = specialf1;
				next.oldx = dir;
				next.bHITOWNER = !sting_impself;
			}
			else
			{
				next.tracer = tracer;
				next.special1 = special1+1;
				next.specialf1 = specialf1;
				next.bHITOWNER = !sting_impself;
				next.UpdateBeam(self,t.Results.HitPos+normal,dir);
			}
			if ( t.Results.HitType != TRACE_HitNone )
			{
				if ( !weffect )
				{
					weffect = Spawn("ImpalerBoltHit",t.Results.HitPos+normal*4);
					weffect.master = self;
				}
				weffect.SetOrigin(t.Results.HitPos+normal*4,true);
				weffect.sprite = weffect.GetSpriteIndex('IHIT');
			}
			else if ( weffect ) weffect.Destroy();
			return;
		}
		if ( !weffect )
		{
			weffect = Spawn("ImpalerBoltHit",t.Results.HitPos+normal*4);
			weffect.master = self;
		}
		if ( t.Results.HitType != TRACE_HitNone ) weffect.sprite = weffect.GetSpriteIndex('IHIT');
		else weffect.sprite = weffect.GetSpriteIndex('ICAP');
		weffect.SetOrigin(t.Results.HitPos+normal*4,true);
		if ( next ) next.Destroy();
	}
	void UpdateBeam( ImpalerBolt parent, Vector3 ofs, Vector3 dir )
	{
		bRELATIVETOFLOOR = parent.bRELATIVETOFLOOR;
		frame = parent.frame;
		SetOrigin(ofs,true);
		angle = atan2(dir.y,dir.x);
		pitch = asin(-dir.z);
		Vector3 x, y, z, dir;
		double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.1);
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		Vector3 dirto = (0,0,0);
		double distto = 0.;
		if ( tracer && (Distance3D(tracer) > 1) )
		{
			dirto = level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2));
			distto = dirto.length();
			dirto /= distto;
			distto = 1.-clamp(distto*0.1,0.,.9);
		}
		dir = (x+y*cos(a)*s+z*sin(a)*s+dirto*distto).unit();
		dir = dir*.5+oldx*.5;
		oldx = dir;
		A_SetAngle(atan2(dir.y,dir.x));
		A_SetPitch(asin(-dir.z));
		CheckBeam(dir);
	}
	Default
	{
		RenderStyle "Add";
		Obituary "$O_IMPALER2";
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+INTERPOLATEANGLES;
		+NOTELEPORT;
	}
	States
	{
	Spawn:
		PBLT # -1 Bright;
		Stop;
	Dummy:
		PBLT ABCDE -1;
		Stop;
	}
}

Class ImpalerFlare : Actor
{
	Default
	{
		RenderStyle "Add";
		Scale 0.02;
		Alpha 0.5;
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+INTERPOLATEANGLES;
		+NOTELEPORT;
		+FORCEXYBILLBOARD;
		+ROLLSPRITE;
	}
	States
	{
	Spawn:
		IFLA A -1 Bright;
		Stop;
	}
}

Class StarterImpalerBolt : ImpalerBolt
{
	Array<Actor> hitlist;
	Actor flares[2];

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		start = self;
		flares[0] = Spawn("ImpalerFlare",pos);
		flares[1] = Spawn("ImpalerFlare",pos);
		oldx = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
	}
	override void OnDestroy()
	{
		Super.OnDestroy();
		if ( flares[0] ) flares[0].Destroy();
		if ( flares[1] ) flares[1].Destroy();
	}
	override void Tick()
	{
		Super.Tick();
		frame++;
		if ( frame > 4 ) frame = 0;
		if ( isFrozen() ) return;
		if ( !target )
		{
			Destroy();
			return;
		}
		Vector3 x, y, z, origin;
		bRELATIVETOFLOOR = (target.pos.z <= target.floorz);	// hack, but kinda works
		if ( target.player )
		{
			[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
			origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),15*x+2*y-2.5*z);
		}
		else origin = target.Vec3Offset(0,0,target.missileheight);
		SetOrigin(origin,true);
		if ( !flares[0] ) flares[0] = Spawn("ImpalerFlare",pos);
		if ( !flares[1] ) flares[1] = Spawn("ImpalerFlare",pos);
		flares[1].bRELATIVETOFLOOR = flares[0].bRELATIVETOFLOOR = bRELATIVETOFLOOR;
		flares[0].SetOrigin(pos,true);
		flares[1].SetOrigin(pos,true);
		flares[0].roll += 15.;
		flares[1].roll += 5.;
		flares[0].A_SetScale(0.01+cos(gametic*8)*0.002);
		flares[1].A_SetScale(0.02+cos(gametic*8)*0.004);
		double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.1);
		[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
		Vector3 dir = (x+cos(a)*y*s+sin(a)*z*s).unit();
		dir = oldx*.5+dir*.5;
		oldx = dir;
		A_SetAngle(atan2(dir.y,dir.x));
		A_SetPitch(asin(-dir.z));
		hitlist.Clear();
		CheckBeam((cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)));
	}
}

Class ImpalerProjectile : Actor
{
	Default
	{
		Obituary "$O_IMPALER1";
		DamageType 'Impaler';
		Speed 30;
		Radius 4;
		Height 4;
		PROJECTILE;
		+SKYEXPLODE;
		+EXPLODEONWATER;
		+FORCERADIUSDMG;
		+NODAMAGETHRUST;
		+INTERPOLATEANGLES;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		A_PlaySound("impaler/fly",CHAN_BODY,1.,true,2.);
	}
	action void A_ImpalerHit()
	{
		A_StopSound(CHAN_BODY);
		bFORCEXYBILLBOARD = true;
		scale *= 2.+special1*0.01;
		A_AlertMonsters();
		A_SetRenderStyle(1.,STYLE_Add);
		A_NoGravity();
		A_Explode(50+special1/2,80+special1/5);
		UTMainHandler.DoBlast(self,80+special1/5,40000);
		A_QuakeEx(2,2,2,5,0,150+special1/5,"",QF_RELATIVE|QF_SCALEDOWN,falloff:80+special1/2,rollintensity:0.2);
		A_PlaySound("impaler/hit",CHAN_VOICE);
		A_SprayDecal("ShockMark",20);
		let l = Spawn("ImpalerBurstLight",pos);
		l.Args[3] += special1/5;
		double ang, pt;
		int numpt = Random[Impaler](4,8);
		for ( int i=0; i<numpt; i++ )
		{
			ang = FRandom[Impaler](0,360);
			pt = FRandom[Impaler](-90,90);
			let c = Spawn("ImpalerChunk",pos);
			c.angle = ang;
			c.pitch = pt;
			c.vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](3,9);
		}
		numpt = Random[Impaler](6,12);
		for ( int i=0; i<numpt; i++ )
		{
			ang = FRandom[Impaler](0,360);
			pt = FRandom[Impaler](-90,90);
			let c = Spawn("UTSmoke",pos);
			c.vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.3,.8);
			c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
			c.bBRIGHT = true;
			c.alpha *= .5;
		}
		numpt = Random[Impaler](8,15);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](1,3);
			let s = Spawn("UTSmoke",pos);
			s.vel = pvel;
		}
		numpt = Random[Impaler](8,16);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](2,6);
			let s = Spawn("UTSpark",pos);
			s.vel = pvel;
		}
		numpt = Random[Impaler](20,30);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](2,12);
			let s = Spawn("UTChip",pos);
			s.vel = pvel;
			s.scale *= FRandom[Impaler](0.9,2.7);
		}
		numpt = 3+min(20,special1/10);
		for ( int i=0; i<numpt; i++ )
		{
			ang = FRandom[Impaler](0,360);
			pt = FRandom[Impaler](-90,90);
			let b = Spawn("ImpalerBurstBolt",Vec3Offset(0,0,height/2));
			b.angle = ang;
			b.pitch = pt;
			b.target = target;
			b.special1 = Random[Impaler](-3,3);
			b.special1 -= special1/30;
		}
	}
	States
	{
	Spawn:
		TPRJ A 1
		{
			if ( waterlevel > 0 )
			{
				let b = Spawn("UTBubble",pos);
				b.vel = vel*0.5;
				special1 = -30;
			}
			roll += 15.;
			double ang, pt;
			int numpt;
			if ( special1 > 80 )
			{
				numpt = special1/90;
				for ( int i=0; i<numpt; i++ )
				{
					ang = FRandom[Impaler](0,360);
					pt = FRandom[Impaler](-90,90);
					let c = Spawn("ImpalerSpark",pos);
					c.vel = vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.2,.8)*(special1/90.);
				}
			}
			if ( special1 > 120 )
			{
				numpt = special1/120;
				for ( int i=0; i<numpt; i++ )
				{
					ang = FRandom[Impaler](0,360);
					pt = FRandom[Impaler](-90,90);
					let c = Spawn("UTSmoke",pos);
					c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
					c.bBRIGHT = true;
					c.alpha *= min(1.,special1/350.);
					c.scale *= min(2.,special1/350.);
					c.vel = vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.2,.8)*(special1/120.);
				}
			}
		}
		Wait;
	Death:
		TNT1 A 0 A_ImpalerHit();
		TNT1 A 0 A_Jump(256,"Explo1","Explo2","Explo3");
	Explo1:
		IEX1 ABCDEFGHIJKLM 1 Bright;
		Stop;
	Explo2:
		IEX2 ABCDEFGHIJKLM 1 Bright;
		Stop;
	Explo3:
		IEX3 ABCDEFGHIJKLM 1 Bright;
		Stop;
	}
}

Class ImpalerLight : EnforcerLight
{
	Default
	{
		args 255,128,224,80;
	}
}

Class Impaler : UnrealWeapon
{
	int ClipCount;
	bool HasGem;
	Actor beam;

	property ClipCount : ClipCount;

	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("PlasmaRifle",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;
			Destroy();
		}
		else
		{
			Owner.RemoveInventory(self);
			Destroy();
		}
	}
	override int, int, bool, bool GetClipAmount()
	{
		return HasGem?ClipCount:-1, -1, (ClipCount<10), false;
	}
	action void A_ImpalerFire()
	{
		A_Overlay(-9999,"Null");
		A_Overlay(-3,"Null");
		A_Overlay(-2,"Null");
		A_PlaySound("impaler/fire",CHAN_WEAPON,Dampener.Active(self)?.1:1.);
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(16,224,64,255),1);
		UTMainHandler.DoSwing(self,(FRandom[Impaler](-0.1,-0.2),FRandom[Impaler](-0.1,0.1)),4,-1.5,2,SWING_Spring,2,2);
		if ( !Dampener.Active(self) ) A_AlertMonsters();
		A_QuakeEx(1,1,1,4,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1);
		Vector3 x, y, z;
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-2.5*z);
		Actor p = Spawn("ImpalerProjectile",origin);
		p.angle = angle;
		p.pitch = BulletSlope();
		p.roll = FRandom[Impaler](0,360);
		p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
		p.target = self;
		double mult = Amplifier.GetMult(self,100);
		p.special1 = int(3*invoker.clipcount*mult+120*(mult-1.));
		int numpt = Random[Impaler](8,16);
		for ( int i=0; i<numpt; i++ )
		{
			let s = Spawn("ViewImpalerSpark",origin);
			ViewImpalerSpark(s).ofs = (10,2,-2.5);
			s.target = self;
			s.scale *= 3.;
			ViewImpalerSpark(s).vvel += (FRandom[Impaler](0.2,0.8),FRandom[Impaler](-0.5,0.5),FRandom[Impaler](-0.5,0.5));
		}
		invoker.HasGem = false;
		invoker.ClipCount = -1;
	}
	action void A_StartBeam()
	{
		Vector3 x, y, z, origin;
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),15*x+2*y-2.5*z);
		invoker.beam = Spawn("StarterImpalerBolt",origin);
		invoker.beam.angle = angle;
		invoker.beam.pitch = BulletSlope();
		invoker.beam.target = self;
		invoker.beam.specialf1 = Amplifier.GetMult(self,50);
	}
	action void A_DrainAmmo()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( !(invoker.special1%15) )
		{
			if ( invoker.ClipCount <= 0 ) return;
			double mul = Amplifier.GetMult(self,10);
			if ( invoker.beam )
			{
				invoker.beam.specialf1 = mul;
				invoker.beam.specialf1 *= .5+clamp(invoker.clipcount/double(invoker.default.clipcount),.0,.5);
			}
			invoker.clipcount = max(0,invoker.clipcount-int(mul));
		}
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(16,255,32,255),3);
		UTMainHandler.DoSwing(self,(FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)),0.05,0.05,8,SWING_Spring);
		A_AlertMonsters();
		Vector3 x, y, z;
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),15*x+2*y-2.5*z);
		int numpt = Random[Impaler](4,7);
		for ( int i=0; i<numpt; i++ )
		{
			let s = Spawn("UTViewSmoke",origin);
			UTViewSmoke(s).ofs = (15,2,-2.5);
			s.scale *= 1.8;
			s.target = self;
			s.SetShade("602060");
			s.A_SetRenderStyle(0.4,STYLE_AddShaded);
			UTViewSmoke(s).vvel += (FRandom[Impaler](0.2,0.6),FRandom[Impaler](-0.2,0.2),FRandom[Impaler](-0.2,0.2));
		}
		numpt = Random[Impaler](8,16);
		for ( int i=0; i<numpt; i++ )
		{
			let s = Spawn("ViewImpalerSpark",origin);
			ViewImpalerSpark(s).ofs = (15,2,-2.5);
			s.target = self;
			s.scale *= 3.;
			ViewImpalerSpark(s).vvel += (FRandom[Impaler](0.2,0.8),FRandom[Impaler](-0.5,0.5),FRandom[Impaler](-0.5,0.5));
		}
	}
	action void A_StopBeam()
	{
		A_StopSound(CHAN_WEAPON);
		if ( invoker.beam ) invoker.beam.Destroy();
	}
	override void OwnerDied()
	{
		if ( beam ) beam.Destroy();
		Super.OwnerDied();
	}
	override void DetachFromOwner()
	{
		if ( beam ) beam.Destroy();
		Super.DetachFromOwner();
	}
	override void OnDestroy()
	{
		if ( beam ) beam.Destroy();
		Super.OnDestroy();
	}
	private action bool TryHit( double angle, int dmg )
	{
		FTranslatedLineTarget t;
		double slope = AimLineAttack(angle,DEFMELEERANGE*1.5,t,0.,ALF_CHECK3D);
		FLineTraceData d;
		Vector3 x, y, z, origin;
		[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
		origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),y*4-z*4);
		LineTrace(angle,DEFMELEERANGE*1.5,slope,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
		if ( d.HitType != TRACE_HitNone )
		{
			if ( d.HitType == TRACE_HitActor )
			{
				if ( d.HitLocation.z >= (d.HitActor.pos.z+d.HitActor.height*0.8) )
					dmg = d.HitActor.DamageMobj(invoker,self,dmg*2,'Decapitated',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
				else dmg = d.HitActor.DamageMobj(invoker,self,dmg,'slashed',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
				UTMainHandler.DoKnockback(d.HitActor,d.HitDir,12000);
				if ( d.HitActor.player ) d.HitActor.A_QuakeEx(2,2,2,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.25);
				if ( !d.HitActor.bNOBLOOD )
				{
					d.HitActor.TraceBleed(dmg,invoker);
					d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
				}
			}
			else if ( d.HitType == TRACE_HitWall )
				d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4);
			A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12);
			if ( !d.HitActor || d.HitActor.bNOBLOOD )
			{
				A_PlaySound("impaler/wall",CHAN_WEAPON);
				let p = Spawn("SawImpact",d.HitLocation-d.HitDir*4);
				p.angle = atan2(d.HitDir.y,d.HitDir.x);
				p.pitch = asin(-d.HitDir.z);
			}
			else A_PlaySound("impaler/flesh",CHAN_WEAPON);
			A_AlertMonsters();
			A_QuakeEx(1,1,1,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06);
			return true;
		}
		return false;
	}
	action void A_Stab()
	{
		invoker.FireEffect();
		UTMainHandler.DoSwing(self,(FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)),0.3,-0.2,2,SWING_Spring,0,2);
		for ( int i=0; i<8; i++ ) if ( TryHit(angle+i*(45./16),15) || TryHit(angle-i*(45./16),15) ) return;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( (Ammo1.Amount <= 0) && (ClipCount <= 0) ) SelectionOrder = 6600;
		else SelectionOrder = default.SelectionOrder;
		if ( Owner.player.ReadyWeapon != self ) return;
		if ( (Owner.waterlevel > 2) && !(level.maptime%5) && HasGem )
			ClipCount = max(0,ClipCount-1);
		let psp = Owner.player.FindPSprite(-2);
		if ( psp ) psp.alpha = clamp(ClipCount/double(default.ClipCount),0.,1.);
	}
	override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount )
	{
		if ( ClipCount > 0 ) return true;
		return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
	}
	Default
	{
		Tag "$T_IMPALER";
		Obituary "$O_IMPALERHIT";
		Inventory.PickupMessage "$I_IMPALER";
		Weapon.UpSound "impaler/select";
		Weapon.SlotNumber 7;
		Weapon.SelectionOrder 700;
		Weapon.SlotPriority 0.9;
		Weapon.AmmoType "ImpalerAmmo";
		Weapon.AmmoUse 1;
		Weapon.AmmoType2 "ImpalerAmmo";
		Weapon.AmmoUse2 1;
		Weapon.AmmoGive 6;
		UTWeapon.DropAmmo 3;
		Impaler.ClipCount 30;
		+WEAPON.AMMO_OPTIONAL;
		+WEAPON.ALT_AMMO_OPTIONAL;
	}
	States
	{
	Spawn:
		IMPP A -1;
		Stop;
		IMPP B -1;
		Stop;
	Select:
		IMPS A 1 A_Raise(int.max);
		Wait;
	Ready:
		IMPS ABCDEF 3 A_WeaponReady(WRF_NOFIRE);
		IMPI A 0
		{
			let weap = Weapon(invoker);
			invoker.HasGem = false;
			if ( (invoker.ClipCount>=0) || (weap.Ammo1.Amount > 0) )
				return ResolveState("Reload");
			return ResolveState("Idle");
		}
		Goto Idle;
	Dummy:
		TNT1 A 1
		{
			let weap = Weapon(invoker);
			int flags = 0;
			if ( (weap.Ammo1.Amount > 0) || (invoker.ClipCount > 0) ) flags |= WRF_ALLOWRELOAD;
			if ( invoker.HasGem && ((invoker.ClipCount <= 0) || (waterlevel >= 2)) ) flags |= WRF_NOSECONDARY;
			A_WeaponReady(flags);
		}
		Wait;
	Idle:
		IMPI A 0 A_Overlay(-9999,"Dummy");
		IMPI ABCDEFGH 10;
		Goto Idle+1;
	Melee:
		IMPM A 2
		{
			A_Overlay(-9999,"Null");
			A_PlaySound("impaler/stab",CHAN_WEAPON);
		}
		IMPM BC 2;
		IMPM D 2 A_Stab();
		IMPM EFGHIJ 2;
		Goto Idle;
	Fire:
		IMPF A 0
		{
			if ( !invoker.HasGem )
				return ResolveState("Melee");
			A_ImpalerFire();
			return ResolveState(null);
		}
		IMPF ABCDEFGHI 2;
		IMPI A 0 A_JumpIfNoAmmo("Idle");
		Goto Reload;
	AltFire:
		IMPA A 0
		{
			if ( !invoker.HasGem )
				return ResolveState("Melee");
			A_Overlay(-9999,"Null");
			player.SetPSprite(-3,invoker.FindState("GemAltFire"));
			player.SetPSprite(-2,invoker.FindState("ZapAltFire"));
			return ResolveState(null);
		}
		IMPA ABCDEFGH 2;
		IMPA I 0
		{
			A_PlaySound("impaler/altfire",CHAN_WEAPON,looping:true);
			A_StartBeam();
			invoker.special1 = 0;
		}
		Goto AltHold;
	AltHold:
		IMPA IJKLMNOP 2
		{
			A_DrainAmmo();
			invoker.special1++;
			return A_JumpIf((invoker.ClipCount<=0)||!(player.cmd.buttons&BT_ALTATTACK)||(waterlevel>=2),"AltRelease");
		}
		Loop;
	AltRelease:
		IMPA Q 0
		{
			player.SetPSprite(-3,invoker.FindState("GemAltRelease"));
			player.SetPSprite(-2,invoker.FindState("ZapAltRelease"));
			A_StopBeam();
			A_PlaySound("impaler/altend",CHAN_WEAPON,looping:true);
		}
		IMPA QRSTUVWX 2;
		Goto Idle;
	Reload:
		IMPG A 0
		{
			A_Overlay(-9999,"Null");
			invoker.HasGem = !invoker.HasGem;
			if ( invoker.HasGem )
			{
				let weap = Weapon(invoker);
				if ( (invoker.ClipCount < 0) && (weap.Ammo1.Amount > 0) )
				{
					weap.DepleteAmmo(false,true,1);
					invoker.ClipCount = invoker.default.ClipCount;
				}
				A_Overlay(-3,"GemUp");
				A_Overlay(-2,"ZapUp");
				A_OverlayFlags(-2,PSPF_RenderStyle|PSPF_ForceStyle|PSPF_Alpha|PSPF_ForceAlpha,true);
				A_OverlayRenderStyle(-2,STYLE_Add);
				A_PlaySound("impaler/gem",CHAN_WEAPON,looping:true);
			}
			else
			{
				player.SetPSprite(-3,invoker.FindState("GemDown"));
				player.SetPSprite(-2,invoker.FindState("ZapDown"));
				A_PlaySound("impaler/gemdown",CHAN_WEAPON);
			}
			if ( self is 'UTPlayer' ) UTPlayer(self).PlayReloading();
		}
		IMPG ABCDE 2;
		Goto Idle;
	Deselect:
		IMPD A 0 A_Overlay(-9999,"Null");
		IMPD A 0 A_JumpIf(!invoker.HasGem,"FullDeselect");
		IMPG A 0
		{
			invoker.HasGem = false;
			player.SetPSprite(-3,invoker.FindState("GemDown"));
			player.SetPSprite(-2,invoker.FindState("ZapDown"));
			A_PlaySound("impaler/gemdown",CHAN_WEAPON);
		}
		IMPG ABCDE 2;
		Goto FullDeselect;
	FullDeselect:
		IMPD ABCDEF 2;
		IMPD F 1 A_Lower(int.max);
		Wait;
	GemUp:
		IMGS ABCDE 2;
		Goto GemIdle;
	GemIdle:
		IMGI ABCDEFGH 10;
		Loop;
	GemDown:
		IMGD ABCDE 2;
		Stop;
	GemAltFire:
		IMGA ABCDEFGH 2;
		Goto GemAltHold;
	GemAltHold:
		IMGA IJKLMNOP 2;
		Loop;
	GemAltRelease:
		IMGA QRSTUVWX 2;
		Goto GemIdle;
	ZapUp:
		IMZS ABCDE 2 Bright;
		Goto ZapIdle;
	ZapIdle:
		IMZI ABCDEFGH 10 Bright;
		Loop;
	ZapDown:
		IMZD ABCDE 2 Bright;
		Stop;
	ZapAltFire:
		IMZA ABCDEFGH 2 Bright;
		Goto ZapAltHold;
	ZapAltHold:
		IMZA IJKLMNOP 2 Bright;
		Loop;
	ZapAltRelease:
		IMZA QRSTUVWX 2 Bright;
		Goto ZapIdle;
	}
}
