Class UBioAmmo : Ammo
{
	Default
	{
		Tag "$T_SLUDGE";
		Inventory.Icon "I_Sludge";
		Inventory.PickupMessage "";
		Inventory.Amount 25;
		Inventory.MaxAmount 100;
		Ammo.BackpackAmount 15;
		Ammo.BackpackMaxAmount 200;
		Ammo.DropAmount 10;
	}
	override String PickupMessage()
	{
		if ( PickupMsg.Length() > 0 ) return Super.PickupMessage();
		return String.Format("%s%d%s",StringTable.Localize("$I_SLUDGEL"),Amount,StringTable.Localize("$I_SLUDGER"));
	}
	States
	{
	Spawn:
		BIOA ABCDEFGHIJKLMNOPQRSTUVWYZ 1;
		BIA2 ABCD 1;
		Loop;
	}
}
Class UBioAmmo2 : UBioAmmo
{
	Default
	{
		Tag "$T_SLUDGE2";
		Inventory.Amount 10;
		Ammo.DropAmount 5;
	}
	States
	{
	Spawn:
		BIOA ABCDEFGHIJKL 2;
		Loop;
	}
}

Class UBioGel : Actor
{
	Actor l, b;
	enum EHitType
	{
		HIT_NONE,
		HIT_WALL,
		HIT_CEILING,
		HIT_FLOOR
	};
	int hittype;
	int deadtimer, dttimer;
	Line atline;
	int atside;
	int atpart;
	int atplane;
	Sector atsector;
	double atz;
	double rollvel, pitchvel, yawvel;
	Vector3	normal;
	Actor atbridge;
	bool onbridge;
	Vector3 atbridgeofs;

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		vel.z += 3;
		deadtimer = 105;
		l = Spawn("BioLight",pos);
		l.target = self;
		rollvel = FRandom[GES](10,30)*RandomPick[GES](-1,1);
		pitchvel = FRandom[GES](10,30)*RandomPick[GES](-1,1);
		yawvel = FRandom[GES](10,30)*RandomPick[GES](-1,1);
	}
	override int SpecialMissileHit( Actor victim )
	{
		if ( victim == b ) return 1;
		if ( (victim is 'BioHitbox') && ((victim.target == master) || (victim.target.master == master)) ) return 1;
		return -1;
	}
	override void FallAndSink( double grav, double oldfloorz )
	{
		if ( bNOGRAVITY || (waterlevel < 1) )
		{
			Super.FallAndSink(grav,oldfloorz);
			return;
		}
		vel *= .99;
		if ( pos.z > floorz ) vel.z += grav*.01;	// floats in water
	}
	override void Tick()
	{
		Super.Tick();
		if ( isFrozen() ) return;
		if ( !bNOGRAVITY )
		{
			roll += rollvel;
			pitch += pitchvel;
			pitch += yawvel;
			if ( waterlevel > 0 )
			{
				rollvel *= 0.98;
				pitchvel *= 0.98;
				yawvel *= 0.98;
			}
		}
		if ( deadtimer > -2 )
		{
			if ( onbridge )	// attempt to follow the movement of the bridge (if it's moving)
			{
				if ( atbridge )
				{
					if ( !Warp(atbridge,atbridgeofs.x,atbridgeofs.y,atbridgeofs.z,0,WARPF_ABSOLUTEOFFSET|WARPF_USECALLERANGLE|WARPF_COPYINTERPOLATION) )
						deadtimer = min(deadtimer,0);
				}
				else deadtimer = min(deadtimer,0);
			}
			if ( atline )	// attempt to follow the movement of the line
			{
				if ( atpart == 1 )
				{
					if ( atline.flags&Line.ML_DONTPEGTOP ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true);
					else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1)),true);
				}
				else if ( atpart == -1 )
				{
					if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true);
					else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0)),true);
				}
				else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true);
				else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true);
				if ( (pos.z > ceilingz) || (pos.z < floorz) ) deadtimer = min(deadtimer,0);
			}
			else if ( atsector )	// attempt to follow the movement of the plane
			{
				SetOrigin(Vec2OffsetZ(0,0,atz+atsector.GetPlaneTexZ(atplane)),true);
				if ( ceilingz-floorz <= 2 ) deadtimer = min(deadtimer,0);
			}
		}
		if ( !InStateSequence(CurState,FindState("XDeath")) )
		{
			int numpt;
			if ( (!bNOGRAVITY && !Random[GES](0,2)) || !Random[GES](0,10) )
			{
				numpt = Min(20,int(Scale.x*2))+Random[GES](-1,1);
				for ( int i=0; i<numpt; i++ )
				{
					Vector3 pvel = (FRandom[GES](-1,1),FRandom[GES](-1,1),FRandom[GES](-1,1)).unit()*FRandom[GES](1,3);
					Vector3 spos = pos+normal*2*scale.x;
					let s = Spawn("BioSpark",spos);
					s.vel = pvel;
					if ( normal != (0,0,0) ) s.vel += normal*2;
				}
			}
			numpt = Min(3,int(Scale.x*4))+Random[GES](-2,2);
			for ( int i=0; i<numpt; i++ )
			{
				Vector3 pvel = (FRandom[GES](-1,1),FRandom[GES](-1,1),FRandom[GES](-1,1)).unit()*FRandom[GES](0.2,0.4);
				let s = Spawn("UTSmoke",pos+normal);
				s.vel = pvel+vel*0.25+normal*0.5;
				s.scale *= scale.x;
				s.A_SetRenderStyle(0.75,STYLE_AddShaded);
				if ( Random[GES](0,1) ) s.SetShade("061004");
				else s.SetShade("061006");
			}
		}
		if ( deadtimer <= -1 ) return;
		let bi = BlockThingsIterator.Create(self,4*Scale.x);
		while ( bi.Next() )
		{
			if ( !bi.Thing || !bi.Thing.bSHOOTABLE || (bi.Thing == b) || ((bi.Thing is 'BioHitbox') && ((bi.Thing.target.master == self) || (bi.Thing.target == master))) || (bi.Thing == target && !bHITOWNER) ) continue;
			if ( (Distance2D(bi.Thing)-bi.Thing.radius <= 4*Scale.x) && ((bi.Thing.pos.z <= pos.z+4*Scale.x) && (bi.Thing.pos.z+bi.Thing.height >= pos.z-4*Scale.x)) ) deadtimer = 0;
		}
		if ( deadtimer-- <= 0 )
		{
			deadtimer = -1;
			SetStateLabel("XDeath");
		}
	}
	// align self to what surface was hit
	// TODO handle plane collision within the very border between two
	// sectors (most noticeable with moving 3d floors)
	virtual void AlignSelf()
	{
		F3DFloor ff;
		bINTERPOLATEANGLES = false;
		bHITOWNER = true;
		A_NoGravity();
		A_Stop();
		if ( bAMBUSH )
		{
			SetStateLabel("XDeath");
			return;
		}
		A_SetSize(0.1,0);
		if ( tracer && tracer.bACTLIKEBRIDGE )
		{
			atbridge = tracer;
			onbridge = true;
			if ( (pos.x+radius) <= (atbridge.pos.x-atbridge.radius) )
			{
				// west side
				normal = (-1,0,0);
				SetOrigin((atbridge.pos.x-atbridge.radius,pos.y,pos.z),false);
				atbridgeofs = pos-atbridge.pos;
				angle = 180;
				pitch = 0;
				roll = 180;	// otherwise it slides upwards (UT changes roll like this too)
				if ( waterlevel > 0 ) hittype = HIT_FLOOR;
				else hittype = HIT_WALL;
			}
			else if ( (pos.x-radius) >= (atbridge.pos.x+atbridge.radius) )
			{
				// east side
				normal = (1,0,0);
				SetOrigin((atbridge.pos.x+atbridge.radius,pos.y,pos.z),false);
				atbridgeofs = pos-atbridge.pos;
				angle = 0;
				pitch = 0;
				roll = 180;	// otherwise it slides upwards (UT changes roll like this too)
				if ( waterlevel > 0 ) hittype = HIT_FLOOR;
				else hittype = HIT_WALL;
			}
			else if ( (pos.y+radius) <= (atbridge.pos.y-atbridge.radius) )
			{
				// north side
				normal = (0,-1,0);
				SetOrigin((pos.x,atbridge.pos.y-atbridge.radius,pos.z),false);
				atbridgeofs = pos-atbridge.pos;
				angle = 270;
				pitch = 0;
				roll = 180;	// otherwise it slides upwards (UT changes roll like this too)
				if ( waterlevel > 0 ) hittype = HIT_FLOOR;
				else hittype = HIT_WALL;
			}
			else if ( (pos.y-radius) >= (atbridge.pos.y+atbridge.radius) )
			{
				// south side
				normal = (0,1,0);
				SetOrigin((pos.x,atbridge.pos.y+atbridge.radius,pos.z),false);
				atbridgeofs = pos-atbridge.pos;
				angle = 90;
				pitch = 0;
				roll = 180;	// otherwise it slides upwards (UT changes roll like this too)
				if ( waterlevel > 0 ) hittype = HIT_FLOOR;
				else hittype = HIT_WALL;
			}
			else if ( pos.z >= (atbridge.pos.z+atbridge.height) )
			{
				// top of actor
				normal = (0,0,1);
				SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false);
				atbridgeofs = pos-atbridge.pos;
				pitch = -90;
				angle = 0;
				roll = FRandom[GES](0,360);
				hittype = HIT_FLOOR;
			}
			else if ( (pos.z+height) <= atbridge.pos.z )
			{
				// bottom of actor
				normal = (0,0,-1);
				SetOrigin((pos.x,pos.y,atbridge.pos.z),false);
				pitch = 90;
				angle = 0;
				roll = FRandom[GES](0,360);
				if ( waterlevel > 0 ) hittype = HIT_FLOOR;
				else hittype = HIT_CEILING;
			}
			else
			{
				// inside of actor, just shove to the top or bottom based on our Z velocity
				if ( vel.z <= 0 )
				{
					normal = (0,0,1);
					SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false);
					atbridgeofs = pos-atbridge.pos;
					pitch = -90;
					angle = 0;
					roll = FRandom[GES](0,360);
					hittype = HIT_FLOOR;
				}
				else
				{
					normal = (0,0,-1);
					SetOrigin((pos.x,pos.y,atbridge.pos.z),false);
					pitch = 90;
					angle = 0;
					roll = FRandom[GES](0,360);
					if ( waterlevel > 0 ) hittype = HIT_FLOOR;
					else hittype = HIT_CEILING;
				}
			}
		}
		else if ( BlockingFloor )
		{
			// find closest 3d floor for its normal
			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 )
			{
				normal = -ff.top.Normal;
				atsector = ff.model;
				atplane = 1;
			}
			else
			{
				normal = BlockingFloor.floorplane.Normal;
				atsector = BlockingFloor;
				atplane = 0;
			}
			pitch = asin(-normal.z);
			angle = atan2(normal.y,normal.x);
			roll = FRandom[GES](0,360);
			SetOrigin((pos.x,pos.y,floorz)+normal*0.5,false);
			atz = pos.z-atsector.GetPlaneTexZ(atplane);
			hittype = HIT_FLOOR;
		}
		else if ( BlockingCeiling )
		{
			// find closest 3d floor for its normal
			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 )
			{
				normal = -ff.bottom.Normal;
				atsector = ff.model;
				atplane = 0;
			}
			else
			{
				normal = BlockingCeiling.ceilingplane.Normal;
				atsector = BlockingCeiling;
				atplane = 1;
			}
			pitch = asin(-normal.z);
			angle = atan2(normal.y,normal.x);
			roll = FRandom[GES](0,360);
			SetOrigin((pos.x,pos.y,ceilingz)+normal*0.5,false);
			atz = pos.z-atsector.GetPlaneTexZ(atplane);
			if ( waterlevel > 0 ) hittype = HIT_FLOOR;
			else if ( normal dot (0,0,-1) > 0.7 )
				hittype = HIT_CEILING;
			else hittype = HIT_FLOOR;
		}
		else if ( BlockingLine )
		{
			atline = BlockingLine;
			normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
			atside = 1;
			if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
			{
				atside = 0;
				normal *= -1;
			}
			Vector3 orig = (BlockingLine.v1.p.x,BlockingLine.v1.p.y,0);
			Vector3 onwall = pos-(normal dot (pos-orig))*normal;
			SetOrigin(onwall+normal*0.5,false);
			// attempt to guess line part (upper/mid/lower)
			if ( !atline.sidedef[1] ) atpart = 0;	// mid
			else if ( atline.sidedef[atside?0:1].sector.ceilingplane.ZAtPoint(pos.xy) < pos.z ) atpart = 1;	// upper
			else if ( atline.sidedef[atside?0:1].sector.floorplane.ZAtPoint(pos.xy) > (pos.z+height) ) atpart = -1;	// lower
			else
			{
				atpart = 0;
				// check if we're touching a 3d floor line
				Sector backsector = atline.sidedef[atside?0:1].sector;
				for ( int i=0; i<backsector.Get3DFloorCount(); i++ )
				{
					if ( backsector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) > (pos.z+height) ) continue;
					if ( backsector.Get3DFloor(i).top.ZAtPoint(pos.xy) < pos.z ) continue;
					ff = backsector.Get3DFloor(i);
					break;
				}
				// attach to it
				if ( ff )
				{
					atline = ff.master;
					atside = 0;
				}
			}
			if ( atpart == 1 )
			{
				if ( atline.flags&Line.ML_DONTPEGTOP ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1);
				else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1);
			}
			else if ( atpart == -1 )
			{
				if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0);
				else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0);
			}
			else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0);
			else atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1);
			angle = atan2(normal.y,normal.x);
			pitch = 0;
			roll = 180;	// otherwise it slides upwards (UT changes roll like this too)
			if ( waterlevel > 0 ) hittype = HIT_FLOOR;
			else hittype = HIT_WALL;
		}
		else
		{
			SetStateLabel("XDeath");
			return;
		}
		b = Spawn("BioHitbox",pos);
		b.target = self;
		A_StartSound("ges/hit");
		A_SprayDecal("BioSplat",-172);
		int numpt = Min(100,int(Scale.x*10))+Random[GES](-5,5);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (normal+(FRandom[GES](-.8,.8),FRandom[GES](-.8,.8),FRandom[GES](-.8,.8))).unit()*FRandom[GES](3,6);
			let s = Spawn("BioSpark",pos);
			s.vel = pvel;
		}
	}
	action void A_DropDrip()
	{
		let d = Spawn("UBioSplash",pos+invoker.normal*2*scale.x);
		d.target = target;
		d.angle = angle;
		d.pitch = pitch;
		d.roll = roll;
		d.master = self;
		d.scale = scale*0.5;
		d.vel.z -= 6;
	}
	action void A_GelExplode()
	{
		A_NoGravity();
		A_Stop();
		A_SetRenderStyle(1.,STYLE_Add);
		if ( invoker.b ) invoker.b.Destroy();
		let s = Spawn("BioXLight",pos);
		s.args[3] = int(s.args[3]*Scale.x);
		invoker.deadtimer = -2;
		if ( invoker.atline ) invoker.atline.RemoteActivate(target,invoker.atside,SPAC_Impact,pos);
		UTMainHandler.DoBlast(self,int(Scale.x*100),40000*Scale.x);
		A_Explode(int(Args[0]*Scale.x),int(Scale.x*100));
		A_StartSound("ges/explode",CHAN_VOICE);
		int numpt = Min(300,int(Scale.x*30))+Random[GES](-10,10);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[GES](-1,1),FRandom[GES](-1,1),FRandom[GES](-1,1)).unit()*FRandom[GES](3,12);
			let s = Spawn("BioSpark",pos);
			s.vel = pvel;
		}
		numpt = Min(100,int(Scale.x*10))+Random[GES](-4,4);
		for ( int i=0; i<numpt; i++ )
		{
			Vector3 pvel = (FRandom[GES](-1,1),FRandom[GES](-1,1),FRandom[GES](-1,1)).unit()*FRandom[GES](1.2,2.4);
			let s = Spawn("UTSmoke",pos+invoker.normal*4);
			s.vel = pvel;
			s.scale *= 2;
			s.A_SetRenderStyle(0.5,STYLE_AddShaded);
			if ( Random[GES](0,1) ) s.SetShade("40FF60");
			else s.SetShade("60FF40");
		}
	}
	Default
	{
		Obituary "$O_BIORIFLE";
		DamageType 'Slime';
		Args 40;
		Radius 3;
		Height 3;
		Speed 11;
		Gravity 0.25;
		PROJECTILE;
		-NOGRAVITY;
		+SKYEXPLODE;
		+EXPLODEONWATER;
		+FORCERADIUSDMG;
		+FORCEXYBILLBOARD;
		+MOVEWITHSECTOR;
		+NODAMAGETHRUST;
		+HITTRACER;
		+INTERPOLATEANGLES;
		+NOFRICTION;
	}
	States
	{
	Spawn:
		GELF ABCDEFGHIJKLM 1;
		Loop;
	Death:
		GELH A 1 AlignSelf();
		GELH BCDEFGHIJ 1;
		GELH J 1 A_SetTics(Random[GES](10,30));
		GELH J -1
		{
			invoker.deadtimer = Random[GES](250,300);
			if ( invoker.hittype == HIT_WALL ) return ResolveState("Slide");
			else if ( invoker.hittype == HIT_CEILING ) return ResolveState("Drip");
			return ResolveState(null);
		}
		Stop;
	Drip:
		GELD ABCDEFGH 4;
		GELD I 4 A_DropDrip();
		GELD JKLM 4;
		GELH J -1;
		Stop;
	Slide:
		GELS ABCDEF 3;
		GELS G -1;
		Stop;
	Crash:
	XDeath:
		TNT1 A 0 A_JumpIf(invoker.HitType==HIT_NONE,"Explode");
		TNT1 A 0 A_JumpIf(invoker.HitType==HIT_WALL,2);
		GELH J 1 A_SetTics(Random[GES](3,10));
		Goto Explode;
		GELS G 1 A_SetTics(Random[GES](3,10));
		Goto Explode;
	Explode:
		TNT1 A 0 A_Jump(255,"Explode1","Explode2","Explode3");
	Explode1:
		GEX1 A 1 Bright A_GelExplode();
		GEX1 BCDEFGHIJK 3 Bright;
		Stop;
	Explode2:
		GEX2 A 1 Bright A_GelExplode();
		GEX2 BCDEFGHIJK 3 Bright;
		Stop;
	Explode3:
		GEX3 A 1 Bright A_GelExplode();
		GEX3 BCDEFGHIJK 3 Bright;
		Stop;
	Shrivel:
		GELX ABCDEFGHIJKL 1 Bright;	// UNUSED
		Stop;
	}
}

Class UBioGlob : UBioGel
{
	int numsplash;

	Default
	{
		Args 75;
	}

	void SpawnSplash()
	{
		Vector3 ofs = dt_Utility.Vec3FromAngle(angle,pitch);
		for ( int i=0; i<2; i++ )
		{
			if ( numsplash-- <= 0 ) return;
			Vector3 dir = (ofs+(FRandom[GES](-.8,.8),FRandom[GES](-.8,.8),FRandom[GES](-.8,.8))).unit();
			A_SetScale(scale.x-0.15);
			let d = Spawn("UBioSplash",pos+ofs*4);
			d.target = target;
			d.master = self;
			d.scale *= FRandom[GES](0.5,0.7);
			d.angle = atan2(dir.y,dir.x);
			d.pitch = -asin(dir.z);
			d.vel = dt_Utility.Vec3FromAngle(d.angle,d.pitch)*d.speed*FRandom[GES](0.4,0.6);
			d.vel.z -= 2;
		}
	}
	override void AlignSelf()
	{
		Super.AlignSelf();
		if ( !bAMBUSH && (Scale.x > 1) ) numsplash = int(4*Scale.x)-1;
		if ( sting_biosplash ) SpawnSplash();
		else Scale *= 1.4;
	}
	override void Tick()
	{
		Super.Tick();
		if ( isFrozen() || !sting_biosplash ) return;
		SpawnSplash();
	}
}

Class UBioSplash : UBioGel
{
	override void AlignSelf()
	{
		Super.AlignSelf();
		if ( hittype == HIT_CEILING ) hittype = HIT_FLOOR;
	}
}

Class UBioRifle : UnrealWeapon
{
	double chargesize, count;
	bool bCharging;
	int OldSkin;

	override void Tick()
	{
		Super.Tick();
		switch ( sting_oldbrifle )
		{
		case 1:
			if ( OldSkin != 1 )
			{
				A_ChangeModel("",1,"","",1,"models/OldSkins","JBRifle1_.png");
				A_ChangeModel("",0,"","",0,"models/OldSkins","JBrifle1.png",CMDL_USESURFACESKIN);
			}
			OldSkin = 1;
			break;
		case 2:
			if ( OldSkin != 2 )
			{
				A_ChangeModel("",1,"","",1,"models/OldSkins","Jnapalm1_.png");
				A_ChangeModel("",0,"","",0,"models/OldSkins","Jnapalm1.png",CMDL_USESURFACESKIN);
			}
			OldSkin = 2;
			break;
		case 0:
			if ( OldSkin != 0 )
			{
				A_ChangeModel("",1,"","",1,"models","JBRifle1_.png");
				A_ChangeModel("",0,"","",0,"models","JBrifle1.png",CMDL_USESURFACESKIN);
			}
			OldSkin = 0;
			break;
		}
	}

	override int, int, bool, bool GetClipAmount()
	{
		return bCharging?min(5,int(chargesize+0.1)):-1, -1, false, false;
	}
	action void A_BioFire( bool bAlt = false )
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( !bAlt )
		{
			if ( weap.Ammo1.Amount <= 0 ) return;
			if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
		}
		invoker.bCharging = false;
		invoker.chargesize = min(invoker.chargesize,4.9);
		A_StopSound(CHAN_WEAPONMISC);
		if ( bAlt ) A_StartSound("ges/fire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.17:1.,pitch:max(.5,1.35-invoker.chargesize/8.));
		else A_StartSound("ges/fire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.17:1.);
		invoker.FireEffect();
		UTMainHandler.DoFlash(self,Color(48,0,255,0),1);
		if ( !Dampener.Active(self) ) A_AlertMonsters();
		if ( bAlt ) 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,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05+0.01*invoker.chargesize);
		else A_QuakeEx(1,1,1,5,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05);
		Vector3 x, y, z;
		double a, s;
		[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
		Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-4*y-5*z);
		Actor p;
		if ( bAlt )
		{
			p = Spawn("UBioGlob",origin);
			p.A_SetScale(0.5+invoker.chargesize/3.5);
		}
		else p = Spawn("UBioGel",origin);
		p.angle = angle;
		p.pitch = BulletSlope();
		p.vel = dt_Utility.Vec3FromAngle(p.angle,p.pitch)*p.speed;
		p.target = self;
		for ( int i=0; i<12; i++ )
		{
			let s = Spawn("UTViewSmoke",origin);
			UTViewSmoke(s).ofs = (10,-4,-5);
			s.scale *= 2.0;
			s.target = self;
			if ( Random[GES](0,1) ) s.SetShade("40FF60");
			else s.SetShade("60FF40");
			s.A_SetRenderStyle(0.5,STYLE_AddShaded);
			UTViewSmoke(s).vvel += (FRandom[GES](0.8,1.6),FRandom[GES](-0.5,0.5),FRandom[GES](-0.5,0.5));
		}
	}
	action void A_ChargeUp()
	{
		if ( Health <= 0 )
		{
			invoker.bCharging = false;
			player.SetPSprite(-9999,ResolveState("Null"));
			return;
		}
		A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.13:1.);
		let weap = Weapon(invoker);
		if ( (invoker.chargesize < 4.9) && (weap.Ammo1.Amount > 0) )
		{
			invoker.chargesize += 1./TICRATE;
			invoker.count += 1./TICRATE;
			if ( invoker.count > 1. )
			{
				weap.DepleteAmmo(weap.bAltFire,true,1);
				invoker.count = 0;
			}
		}
		if ( !(player.cmd.buttons&BT_ALTATTACK) )
			player.SetPSPrite(PSP_WEAPON,invoker.FindState("AltRelease"));
	}
	action void A_BeginCharge()
	{
		let weap = Weapon(invoker);
		invoker.bCharging = true;
		weap.DepleteAmmo(weap.bAltFire,true,1);
		invoker.count = invoker.chargesize = 0;
		A_StartSound("ges/load",CHAN_WEAPONMISC,CHANF_LOOPING,Dampener.Active(self)?.13:1.);
		A_Overlay(-9999,"Dummy2");
	}
	override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount )
	{
		if ( bCharging ) return true;
		return Super.CheckAmmo(fireMode,autoSwitch,requireAmmo,ammocount);
	}
	Default
	{
		Tag "$T_BIORIFLE";
		Inventory.PickupMessage "$I_BIORIFLE";
		Weapon.UpSound "ges/select";
		Weapon.SlotNumber 8;
		Weapon.SelectionOrder 1300;
		Weapon.SlotPriority 1;
		Weapon.AmmoType "UBioAmmo";
		Weapon.AmmoUse 1;
		Weapon.AmmoType2 "UBioAmmo";
		Weapon.AmmoUse2 1;
		Weapon.AmmoGive 25;
		UTWeapon.DropAmmo 5;
	}
	States
	{
	Spawn:
		BIOP A -1;
		Stop;
		BIOP B -1;
		Stop;
	Select:
		BIOS A 1 A_Raise(int.max);
		Wait;
	Ready:
		BIOS ABCDEFGHIJKLMNOPQRST 1 A_WeaponReady(WRF_NOFIRE);
		Goto Idle;
	Dummy:
		TNT1 A 1
		{
			A_CheckReload();
			A_WeaponReady();
			// that's a long-ass if
			if ( player.FindPSprite(PSP_WEAPON).CurState.InStateSequence(invoker.FindState("Idle")) && (player.cmd.forwardmove || player.cmd.sidemove) && (player.vel.length() > 0.5) )
				player.SetPSPrite(PSP_WEAPON,invoker.FindState("Sway"));
		}
		Wait;
	Idle:
		#### # 4 A_Overlay(-9999,"Dummy");	// little hackeroo to make this more responsive
		BIOI A 1 A_Jump(80,"Drip");
		BIOI A -1;
		Stop;
	Sway:
		#### # 3;
		BIOW ABCDEFGHIJKLMNOPQRS 3 A_JumpIf((!player.cmd.forwardmove && !player.cmd.sidemove) || (player.vel.length() < 0.5),"Idle");
		Goto Sway+1;
	Drip:
		#### # 4;
		BIOT ABC 6;
		BIOT D 6 A_StartSound("ges/drip",CHAN_WEAPONMISC,volume:Dampener.Active(self)?.05:.5);
		BIOT EFG 6;
		BIOI A 4;
		Goto Idle;
	Fire:
		BIOF A 0
		{
			A_Overlay(-9999,"Null");
			A_BioFire();
		}
		BIOF ABCDEFGHIJ 1;
		Goto Idle;
	Dummy2:
		TNT1 A 1 A_ChargeUp();
		Wait;
	AltFire:
		#### # 2 A_BeginCharge();
		BIOA ABCDEFGHIJKLMNOPQRSTUVWXYZ 6;
		BIA2 ABC 6;
		BIA2 D 0 A_JumpIf(sting_bhold,1);
		Goto AltRelease;
		BIA2 D 3;
		BIA2 CBA 6;
		BIOA ZYXWVUT 6;
		BIOA S 0;
		Goto AltRelease;
	AltRelease:
		#### # 1
		{
			A_Overlay(-9999,"Null");
			if ( self is 'UTPlayer' )
				UTPlayer(self).PlayAttacking3();
			A_BioFire(true);
		}
		BIOF ABCDEFGHIJ 2;
		Goto Idle;
	Deselect:
		BIOD A 0 A_Overlay(-9999,"Null");
		BIOD ABCDEF 1;
		BIOD G 1 A_Lower(int.max);
		Wait;
	}
}
