Class StunnerAmmo : Ammo
{
	double rechargephase, rechargespeed;

	Default
	{
		Inventory.Icon "I_Stun";
		Inventory.Amount 10;
		Inventory.MaxAmount 50;
		Ammo.BackpackAmount 0;
		Ammo.BackpackMaxAmount 50;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( !Owner.player || !(Owner.player.ReadyWeapon is 'Stunner') ) return;	// don't recharge if we don't have a stunner out
		if ( rechargespeed <= 0. ) rechargespeed = 2.;
		rechargephase += 1./rechargespeed;
		if ( rechargephase < 7 ) return;
		rechargespeed = max(2.,.2*Amount);
		rechargephase = 0;
		if ( Amount >= MaxAmount ) return;
		let sting = Owner.FindInventory("StingerAmmo");
		if ( sting && (sting.Amount > 0) )
		{
			if ( !sv_infiniteammo && !Owner.FindInventory('PowerInfiniteAmmo',true) )
				sting.Amount--;
			Amount++;
		}
	}
	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 ) Owner.RemoveInventory(self);
		Destroy();
	}
}

Class StunTrail : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 0.1;
		Height 0;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
		+NOTELEPORT;
		+FORCEXYBILLBOARD;
	}
	override void Tick()
	{
		Super.Tick();
		if ( isFrozen() ) return;
		A_FadeOut(1./11.,0);
	}
	States
	{
	Spawn:
		FATR ABCDE 3 Bright;
		Stop;
	}
}

Class StunLight : PaletteLight
{
	Default
	{
		Tag "DYellow";
	}
}

Class StunTracer : LineTracer
{
	Actor owner, ignore;
	Array<Line> ShootThroughList;

	override ETraceStatus TraceCallback()
	{
		if ( Results.HitType == TRACE_HitActor )
		{
			if ( (Results.HitActor == owner) || (Results.HitActor == ignore) ) return TRACE_Skip;
			if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
			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 StunProj : Actor
{
	StunTracer t;
	Vector3 tracedir;
	bool moving;
	double totaldist;

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		t = new("StunTracer");
		t.owner = target;
		t.ignore = self;
		moving = true;
	}
	override void Tick()
	{
		Super.Tick();
		if ( isFrozen() ) return;
		if ( !moving )
		{
			A_FadeOut(1/19.,0);
			return;
		}
		// step trace
		tracedir = dt_Utility.Vec3FromAngle(angle,pitch);
		t.ShootThroughList.Clear();
		t.Trace(pos,cursector,tracedir,500,0);
		for ( int i=0; i<t.ShootThroughList.Size(); i++ )
			t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
		totaldist += t.Results.Distance;
		// spawn particles
		for ( int i=10; i<t.Results.Distance; i+=80 )
			Spawn("StunLight",Vec3Offset(tracedir.x*i,tracedir.y*i,tracedir.z*i));
		for ( int i=0; i<t.Results.Distance; i+=4 )
		{
			Vector3 pofs = tracedir*FRandom[Stunner](0,2)+(FRandom[Stunner](-.5,.5),FRandom[Stunner](-.5,.5),FRandom[Stunner](-.5,.5));
			let s = Spawn("UTSpark",Vec3Offset(tracedir.x*i+pofs.x,tracedir.y*i+pofs.y,tracedir.z*i+pofs.z));
			s.scale *= 0.2;
			s.bNOGRAVITY = true;
			s.vel = (FRandom[Stunner](-.3,.3),FRandom[Stunner](-.3,.3),FRandom[Stunner](-.3,.3));
		}
		for ( int i=10; i<t.Results.Distance; i+=20 )
		{
			let s = Spawn("StunTrail",Vec3Offset(tracedir.x*i,tracedir.y*i,tracedir.z*i));
			s.angle = angle;
			s.pitch = pitch;
		}
		if ( totaldist >= 10000.0 )
		{
			// reposition and explode on air
			SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false);
			StunExplode();
		}
		else if ( t.Results.HitType == TRACE_HitNone )
		{
			// reposition
			SetOrigin(t.Results.HitPos+t.Results.HitVector,false);
			angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x);
			pitch = asin(-t.Results.HitVector.z);
		}
		else if ( t.Results.HitType == TRACE_HitActor )
		{
			// reposition and explode on actor
			SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false);
			StunExplode();
			Actor a = t.Results.HitActor;
			a.DamageMobj(self,target,max(1,int(10*specialf1)),'jolted',DMG_USEANGLE,atan2(t.Results.HitVector.y,t.Results.HitVector.x));
			if ( !a.bDONTTHRUST )
			{
				UTMainHandler.DoKnockback(a,t.Results.HitVector,(bAMBUSH?-22000:26000)*specialf1);
				a.vel.z += 2.*specialf1;
			}
			angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x);
			pitch = asin(-t.Results.HitVector.z);
		}
		else
		{
			// reposition and explode on wall
			SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false);
			A_SprayDecal("ShockMark",16);
			Vector3 HitNormal = t.Results.HitVector;
			if ( t.Results.HitType == TRACE_HitWall )
			{
				t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,pos);
				// calculate normal
				HitNormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
				if ( t.Results.Side == 0 ) HitNormal *= -1;
			}
			else if ( t.Results.HitType == TRACE_HitFloor )
			{
				if ( t.Results.ffloor ) HitNormal = -t.Results.ffloor.top.Normal;
				else HitNormal = t.Results.HitSector.floorplane.Normal;
			}
			else if ( t.Results.HitType == TRACE_HitCeiling )
			{
				if ( t.Results.ffloor ) HitNormal = -t.Results.ffloor.bottom.Normal;
				else HitNormal = t.Results.HitSector.ceilingplane.Normal;
			}
			StunExplode();
			angle = atan2(HitNormal.y,HitNormal.x);
			pitch = asin(-HitNormal.z);
		}
	}
	void StunExplode()
	{
		moving = false;
		SetStateLabel("Death");
		A_QuakeEx(1,1,1,3,0,250,"",QF_RELATIVE|QF_SCALEDOWN,falloff:120,rollintensity:0.2);
		A_StartSound("stun/hit",CHAN_VOICE,pitch:FRandom[Stunner](1.5,1.9)-0.08*specialf1);
		A_AlertMonsters(gameinfo.gametype&GAME_Strife?100:0);
		Vector3 dir = dt_Utility.Vec3FromAngle(angle,pitch);
		int numpt = Random[ExploS](10,15);
		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.scale *= 0.2;
			s.bNOGRAVITY = true;
			s.vel = pvel;
		}
	}
	override int DoSpecialDamage( Actor target, int damage, Name damagetype )
	{
		if ( gameinfo.gametype&GAME_Strife )
			target.DaggerAlert(self.target);
		return damage;
	}
	Default
	{
		Obituary "$O_STUNNER";
		RenderStyle "Add";
		DamageType 'jolted';
		Radius 0;
		Height 0.1;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+FORCERADIUSDMG;
		+NODAMAGETHRUST;
		+NOTELEPORT;
		+FORCEXYBILLBOARD;
		+NOEXTREMEDEATH;
	}
	States
	{
	Spawn:
		TNT1 A -1;
		Stop;
	Death:
		FATR ABCDEFGHIJK 2 Bright;
		Stop;
	}
}

Class StunnerImpact : Actor
{
	Default
	{
		Radius 0.1;
		Height 0;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+NOTELEPORT;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		A_SprayDecal("WallCrack",20);
		int numpt = Random[Stunner](10,20);
		Vector3 x = dt_Utility.Vec3FromAngle(angle,pitch);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (-x+(FRandom[Stunner](-.8,.8),FRandom[Stunner](-.8,.8),FRandom[Stunner](-.8,.8))).unit()*FRandom[Stunner](0.1,1.2);
			let s = Spawn("UTSmoke",pos);
			s.vel = pvel;
			s.SetShade(Color(1,1,1)*Random[Stunner](128,192));
		}
		numpt = Random[Stunner](4,10);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Stunner](-1,1),FRandom[Stunner](-1,1),FRandom[Stunner](-1,1)).unit()*FRandom[Stunner](2,8);
			let s = Spawn("UTSpark",pos);
			s.vel = pvel;
		}
		numpt = Random[Stunner](4,12);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[Stunner](-1,1),FRandom[Stunner](-1,1),FRandom[Stunner](-1,1)).unit()*FRandom[Stunner](2,8);
			let s = Spawn("UTChip",pos);
			s.vel = pvel;
		}
		Destroy();
	}
}

Class Stunner : UnrealWeapon
{
	double chargesize, count;
	bool bCharging;

	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("Chainsaw",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();
	}

	override int, int, bool, bool GetClipAmount()
	{
		return bCharging?min(5,int(chargesize)):-1, -1, false, false;
	}
	action void A_StunnerFire()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		A_WeaponOffset(0,32);
		invoker.bCharging = false;
		if ( self is 'UTPlayer' )
			UTPlayer(self).PlayAttacking3();
		StunnerAmmo(weap.Ammo1).rechargephase = (((weap.Ammo1.Amount>0)||!deathmatch)?0:5);
		StunnerAmmo(weap.Ammo1).rechargespeed = 2.;
		A_StopSound(CHAN_WEAPONMISC);
		A_StartSound("stun/fire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.4:1.,pitch:1.2-invoker.chargesize*0.06);
		double mult = Amplifier.GetMult(self,int(invoker.ChargeSize*50)+50);
		invoker.FireEffect();
		A_QuakeEx(1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),5+int(1.2*invoker.chargesize),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05+0.01*invoker.chargesize);
		A_Overlay(-2,"Null");
		Vector3 x, y, z;
		[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*10-z*5);
		let p = Spawn("StunProj",origin);
		p.angle = angle;
		p.pitch = BulletSlope();
		p.target = self;
		p.specialf1 = invoker.chargesize*mult;
		p.bAMBUSH = weap.bAltFire;
		FLineTraceData d;
		LineTrace(angle,60,BulletSlope(),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
		if ( d.HitType == TRACE_HitActor )
		{
			int dmg = int(12*invoker.chargesize);
			if ( d.HitLocation.z >= (d.HitActor.pos.z+d.HitActor.height*0.81) )
				dmg = d.HitActor.DamageMobj(invoker,self,dmg*2,'Decapitated',DMG_THRUSTLESS);
			else dmg = d.HitActor.DamageMobj(invoker,self,dmg,'impact',DMG_THRUSTLESS);
			UTMainHandler.DoKnockback(d.HitActor,x,4000*invoker.chargesize);
			if ( d.HitActor.bNOBLOOD )
			{
				let p = Spawn("StunnerImpact",d.HitLocation-d.HitDir*4);
				p.angle = atan2(d.HitDir.y,d.HitDir.x);
				p.pitch = asin(-d.HitDir.z);
			}
			else
			{
				d.HitActor.TraceBleed(dmg,invoker);
				d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
			}
		}
		else if ( d.HitType != TRACE_HitNone )
		{
			let p = Spawn("StunnerImpact",d.HitLocation-d.HitDir*4);
			p.angle = atan2(d.HitDir.y,d.HitDir.x);
			p.pitch = asin(-d.HitDir.z);
			if ( d.HitType == TRACE_HitWall ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4);
		}
	}
	action void A_BeginCharge()
	{
		let weap = Weapon(invoker);
		invoker.bCharging = true;
		weap.DepleteAmmo(weap.bAltFire,true,1);
		invoker.count = 0;
		invoker.chargesize = .6;
		A_StartSound("stun/charge",CHAN_WEAPONMISC,volume:Dampener.Active(self)?.15:1.);
		A_Overlay(-2,"Sparks");
		A_OverlayFlags(-2,PSPF_RenderStyle,true);
		A_OverlayRenderStyle(-2,STYLE_Add);
		StunnerAmmo(weap.Ammo1).rechargephase = 0;
		if ( !Dampener.Active(self) && !(gameinfo.gametype&GAME_Strife) ) A_AlertMonsters();
	}
	action State A_ChargeUp()
	{
		if ( !(player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK)) )
			return ResolveState("Release");
		Weapon weap = Weapon(invoker);
		if ( !weap ) return ResolveState(null);
		StunnerAmmo(weap.Ammo1).rechargephase = 0;
		A_WeaponOffset(FRandom[Stunner](-1,1)*1.2*invoker.chargesize,32+FRandom[Stunner](-1,1)*1.2*invoker.chargesize);
		A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.15:1.);
		if ( !Dampener.Active(self) && !(gameinfo.gametype&GAME_Strife) ) A_AlertMonsters();
		if ( invoker.chargesize >= 5. )
		{
			invoker.count += 1./35.;
			if ( invoker.count > 1.5 ) return ResolveState("Release");
			return ResolveState(null);
		}
		invoker.chargesize += 2.4/35.;
		invoker.count += 1.2/35.;
		if ( invoker.count < 0.24 ) return ResolveState(null);
		invoker.count = 0;
		if ( !(sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) )
		{
			if ( weap.Ammo1.Amount < 1 )
				return ResolveState("Release");
			weap.Ammo1.Amount--;
		}
		return ResolveState(null);
	}

	Default
	{
		Tag "$T_STUNNER";
		Inventory.PickupMessage "$I_STUNNER";
		Weapon.UpSound "stun/select";
		Weapon.SlotNumber 4;
		Weapon.SelectionOrder 8000;
		Weapon.SlotPriority 0.9;
		Weapon.AmmoType "StunnerAmmo";
		Weapon.AmmoUse 1;
		Weapon.AmmoType2 "StunnerAmmo";
		Weapon.AmmoUse2 1;
		Weapon.AmmoGive 50;
		UTWeapon.DropAmmo 50;
		+WEAPON.WIMPY_WEAPON;
		+NOEXTREMEDEATH;
	}
	States
	{
	Spawn:
		STNP A -1;
		Stop;
		STNP B -1;
		Stop;
	Select:
		STNS A 1 A_Raise(int.max);
		Wait;
	Ready:
		STNS ABCDEFGHIJKLMNOPQRSTUVWX 2 A_WeaponReady(WRF_NOFIRE);
		Goto Idle;
	Idle:
		STNI A 1
		{
			A_CheckReload();
			let weap = Weapon(invoker);
			if ( weap && weap.Ammo1.Amount > 0 ) A_WeaponReady();
			else A_WeaponReady(WRF_NOFIRE);
		}
		Wait;
	Fire:
	AltFire:
		#### # 2 A_BeginCharge();
		STNF ABCDEFGHIJKLMNOPQRST 2 A_ChargeUp();
		Goto Hold;
	Hold:
		STNH R 1 A_ChargeUp();
		Wait;
	Release:
		#### # 2;
		STNR A 0 A_StunnerFire();
		STNR ABC 1;
		STR2 ABCDE 2;
		STNF TRPNLJHFDB 1 A_WeaponReady(WRF_NOSWITCH|WRF_DISABLESWITCH);
		Goto Idle;
	Deselect:
		STND ABCDEFGHIJKL 1;
		STND L 1 A_Lower(int.max);
		Wait;
	Sparks:
		TNT1 A 8;
		STFF ABCDEFGHIJKLMNOPQRS 2 Bright;
		STFF J 0 A_JumpIf(invoker.chargesize>=5.,1);
		Goto Sparks+10;
		STFF JIHGFEDCBA 2 Bright;
		Stop;
	}
}
