

class BigSerpStatue : Actor {
	// OH. BEEG.
	
	default {
		Scale 3.0;
		+SOLID;
		+DONTTHRUST;
		Radius 64;
	}
	
	states {
		Spawn:
			RSRP A -1;
	}
}

class DanceMarine : Actor {
	// HEHEENH.
	states {
		Spawn:
			DDAN C 4 A_StartSound("music/girlhell",1,CHANF_LOOPING,0.5);
			DDAN D 3;
			DDAN B 1;
			DDAN AF 3;
			DDAN E 4;
			DDAN F 3;
			DDAN B 1;
			DDAN AD 3;
			Loop;
	}
}

class Bottle : Actor {
	// A bottle that randomly picks its appearance.
	double flighttime;
	double bumpcooldown;
	default {
		+SHOOTABLE;
		+SOLID;
		+THRUSPECIES;
		+ROLLSPRITE;
		BloodType "GlassJunk";
		+NOGRAVITY;
		BounceFactor 0.7;
		+USEBOUNCESTATE;
		+BOUNCEONWALLS; +BOUNCEONFLOORS; +BOUNCEAUTOOFF;
		+SPECIAL;
		Health 1;
		Radius 8;
		Height 16;
		Mass 5;
		BounceSound "misc/clink";
		BloodColor "72 2F 37";
	}
	
	void FirstPush() {
		bNOGRAVITY = false;
// 		int res = RadiusAttack(self,1,32,flags: RADF_NODAMAGE|RADF_OLDRADIUSDAMAGE|RADF_NOIMPACTDAMAGE|RADF_THRUSTZ,fulldamagedistance:32);
// 		console.printf("Pushed %d others!",res);
		double velfac = 0.7;
		ThinkerIterator it = ThinkerIterator.Create();
		Actor mo;
		while (mo = Actor(it.next())) {
			if (mo is "Bottle" && mo.bNOGRAVITY) {
				if (Vec2To(mo).length() < 32) {
					if (mo.vel.length() < (vel.length() * velfac)) {
						mo.vel = vel.unit() * velfac;
						mo.vel.xy = RotateVector(mo.vel.xy,frandom(-3,3));
					}
					mo.vel.z += 1;
				}
			}
		}
	}
	
	override void Tick() {
		Super.Tick();
		if (bNOGRAVITY && vel.length() > 0) {
			FirstPush();
		}
		bMISSILE = (vel.length() > 8);
		flighttime += vel.length() * 1./35.;
		flighttime -= 1./70.;
		flighttime = max(flighttime,0);
		bumpcooldown = max(bumpcooldown - 1./35.,0);
		if (bumpcooldown == 0) {
			target = null;
		}
	}
	
	override void Touch(Actor toucher) {
		if (bumpcooldown > 0) return;
		vector3 to = Vec3To(toucher);
		target = toucher;
		to.xy = RotateVector(to.xy,frandom(170,190));
		to.z += 8;
		to = to.unit() * toucher.vel.length() * 1.2;
		vel += to;
		bumpcooldown = 0.25;
	}
	
	action state BottleBounce() {
// 		console.printf("Bounce!");
// 		console.printf("%f",invoker.flighttime);
		if (frandom(0,invoker.flighttime) > 4) {
			return ResolveState("Death");
		}
		return ResolveState(null);
	}
	
	states {
		Spawn:
			BOTL A 0;
			BOTL A 0 A_Jump(128,"Green");
		Brown:
			BOTL A 0;
			Goto Idle;
		Green:
			BOTL B 0;
			Goto Idle;
		
		Idle:
			"####" "#" 1 {roll = 0; }
			Loop;
		
		Idle1:
			"####" "#" 1 { roll = 90; }
			Loop;
		Idle2:
			"####" "#" 1 { roll = -90; }
			Loop;
		
		Bounce:
			"####" "#" 1 BottleBounce();
			"####" "#" 0 A_Jump(64,"Idle1");
			"####" "#" 0 A_Jump(64,"Idle2");
			Goto Idle;
		
		Death:
			TNT1 A 0 FirstPush();
			TNT1 A 0 A_StartSound("misc/shatter",-1);
			TNT1 AAAA 0 SpawnBlood(pos,VectorAngle(-vel.x,-vel.y),10);
			Stop;
	}
}

class BottleSpawner : Actor {
	// Spawns 4-8 bottles in a small radius.
	override void PostBeginPlay() {
		double rad = 16;
		super.PostBeginPlay();
		int count = random(4,8);
		for (int i = 0; i < count; i++) {
			vector2 offs = (frandom(0,rad),0);
			offs = RotateVector(offs,frandom(0,360));
			Spawn("Bottle",Vec3Offset(offs.x,offs.y,0));
		}
		Die(self,self);
	}
}

class QuakeShotgun : Shotgun {
	// A faster-firing shotgun that doesn't need to pump.
	
	default {
		Weapon.SlotNumber 2;
		inventory.PickupMessage "Got a Quake-brand shotgun!";
	}
	
	action void QShotgun() {
		A_FireBullets(4,4,7,5);
		A_StartSound("weapons/sshotf",pitch:1.1);
		A_GunFlash();
	}
	
	states {
		Select:
			SHTG C 1 A_Raise(18);
			Loop;
		DeSelect:
			SHTG C 1 A_Lower(18);
			Loop;
	
		Fire:
			SHTG A 0 QShotgun();
			SHTG A 4 A_WeaponOffset(0,16,WOF_ADD);
			SHTG AAAAAAAA 2 A_WeaponOffset(0,-2,WOF_ADD);
			Goto Ready;
	}
}

class RailRifle : Weapon {
	default {
		Weapon.SlotNumber 4;
		Weapon.AmmoType1 "Clip";
		Weapon.AmmoUse1 5;
		Weapon.AmmoGive1 50;
		Inventory.PickupMessage "Got the Rail Rifle! Anybody order some nine-inch nails?";
	}
	
	states {
		Spawn:
			PLZM E -1;
		
		Select:
			PLZM D 1 A_Raise(18);
			Loop;
		DeSelect:
			PLZM D 1 A_Lower(18);
			Loop;
			
		Ready:
			PLZM A 1 A_WeaponReady();
			Loop;
		
		Fire:
			PLZM B 0 A_RailAttack(80,color1:"b2b2b2",color2:"c22711");
			PLZM BC 3;
		Recharge:
			PLZM D 6;
			PLZM DADADA 3;
			PLZM A 0 A_StartSound("misc/w_pkup");
			Goto Ready;
	}
}

class Lifesaver : Chaingun {
	// Fires a projectile that does a little less damage than a bullet and falls after a while.
	// However, every third hit on the same target makes a health bonus!
	// In addition, it creates a health bonus if you kill a target with it, regardless of whether there's a full stack.
	
	default {
		Weapon.SlotNumber 4;
		Weapon.AmmoType1 "Clip";
		Weapon.AmmoUse1 1;
		Weapon.AmmoGive1 30;
		
		Inventory.PickupMessage "Got the Lifesaver! Oops, that was not medicine...";
	}
	
	action void FireSaver() {
		A_FireProjectile("LifeNeedle",frandom(-1,1));
		A_StartSound("weapons/shotgf"); // Now it sounds like the chaingunner's chaingun.
		A_GunFlash();
	}
	
	states {
		Select:
			CHGG A 1 A_Raise(18);
			Loop;
		
		DeSelect:
			CHGG A 1 A_Lower(18);
			Loop;
	
		Fire:
			CHGG A 2 FireSaver();
			CHGG B 2;
			Goto Ready;
	}
}

class LifeNeedle : Actor {
	// Gives the target a LifeStack.
	default {
		PROJECTILE;
		Gravity 0.2;
		Speed 60;
		Height 4;
		Radius 8;
		DamageFunction (random(1,4) * 5);
	}
	
	override int DoSpecialDamage(Actor tgt,int dmg, Name mod) {
		tgt.GiveInventory("LifeStack",1);
		return super.DoSpecialDamage(tgt,dmg,mod);
	}
	
	override void Tick() {
		Super.Tick();
		if (GetAge() > 15) {
			bNOGRAVITY = false; // Starts falling after a bit.
		}
	}
	
	states {
		Spawn:
			NLPJ A -1;
		
		Death:
			NLPJ A 0 A_StartSound("weapons/nailhit");
			NLPJ BCDEFG 2;
			Stop;
		
		XDeath:
		Crash:
			NLPJ A 0 A_StartSound("weapons/nailx");
			BLUD ABC 3;
			Stop;
	}
}

class LifeStack : Inventory {
	// Three of these makes a health bonus.
	// Alternatively, gives a bonus on owner death.
	default {
		Inventory.Amount 1;
		Inventory.MaxAmount 999; // Just in case a bunch of stacks apply at once.
	}
	
	void TossDrop() {
		string it = "HealthBonus";
		if (random(0,1) == 1) {
			it = "ArmorBonus";
		}
		owner.A_SpawnItemEX(it,zofs: owner.height / 2,xvel: frandom(-4,4),yvel:frandom(-4,4),zvel:4);
	}
	
	override void DoEffect() {
		if (amount >= 3) {
			TossDrop();
			amount -= 3;
		}
	}
	
	override void OwnerDied() {
		if (amount >= 1) {
			TossDrop();
		}
	}
}

class TNTProj : Actor {
	default {
		PROJECTILE;
		+BRIGHT;
		-NOGRAVITY;
		Speed 50;
		BounceType "Doom";
		Damage 20;
		Radius 8;
		Height 8;
		Scale 0.6;
	}
	
	action void TNTBlast(int dmg, double rad) {
		ThinkerIterator it = ThinkerIterator.Create();
		Actor mo;
		while (mo = Actor(it.next())) {
			double dist = Vec3To(mo).length();
			if ((mo.bSHOOTABLE || mo.bCORPSE) && dist <= rad) {
				int realdmg = ceil(dmg * (dist / rad));
				if (mo == target) { realdmg *= 0.5; } // Owner has 50% blast resist.
				if (!mo.bDONTTHRUST) {
					if (mo.vel.z <= 4) { mo.vel += (0,0,4); }
					if (mo is "PlayerPawn") { mo.vel += (0,0,3); } // 75% more rocketjump vel.
					mo.vel += (((Vec3To(mo) + (0,0,mo.height)).unit() * realdmg)) / max(1,mo.mass * 0.2);
				}
				mo.DamageMobj(self,target,realdmg,"Explosion",DMG_EXPLOSION);
			}
		}
	}
	
	override void Tick() {
		super.Tick();
		
		if (!bNOGRAVITY && GetAge() > (35 * 2)) {
			bNOGRAVITY = true;
			SetState(ResolveState("XDeath"));
		}
	}
	
	states {
		Spawn:
			DYNF ABCDEFGH 4;
			Loop;
		Bounce:
			DYNA A 1;
			Goto Spawn;
		
		Death:
			DYNA ABC 3;
			Loop;
		
		XDeath:
		Crash:
			MISL B 0 { invoker.vel.z = 4; }
			MISL B 0 A_StartSound("weapons/rocklx");
			MISL BBCCDD 2 TNTBlast(32,128);
			Stop;
	}
}

class TNTProj2 : TNTProj {
	// But what if it went off on impact?
	default {
		+Bright;
		Radius 8;
		Height 8;
		Speed 60;
		BounceType "None";
		Scale 0.4;
	}
	
	states {
		Spawn:
			DYNF ABCDEFGH 3;
			Loop;
		Death:
			MISL B 0 A_StartSound("weapons/rocklx");
			MISL BBCCDD 2 TNTBlast(32,128);
			Stop;
	}
}

class TNTGun : Weapon {
	// Sort of a grenade launcher, really.
	default {
		weapon.SlotNumber 5;
		weapon.AmmoType1 "RocketAmmo";
		weapon.AmmoUse1 1;
		weapon.AmmoType2 "RocketAmmo";
		weapon.AmmoUse2 1;
		weapon.AmmoGive1 5;
		inventory.PickupMessage "Got the TNT Launcher!";
	}
	
	states {
		Spawn:
			LAUN A -1;
		
		Select:
			MISG A 1 A_Raise(18);
			Loop;
		DeSelect:
			MISG A 1 A_Lower(18);
			Loop;
		
		Ready:
			MISG A 1 A_WeaponReady();
			Loop;
		
		Fire:
			MISG B 0 A_StartSound("weapons/rocklf");
			MISG B 8 A_FireProjectile("TNTProj",pitch:-5);
			MISG B 8;
			Goto Ready;
			
		AltFire:
			MISG B 0 A_StartSound("weapons/rocklf");
			MISG B 8 A_FireProjectile("TNTProj2",pitch:-5);
			MISG B 8;
			Goto Ready;
		
		Flash:
			MISF AB 3 Bright A_Light1();
			MISF CD 3 Bright A_Light2();
			Goto LightDone;
	}
}

class MotherFlakker : Weapon {
	default {
		Weapon.SlotNumber 3;
		Weapon.AmmoType1 "Shell";
		Weapon.AmmoUse1 2;
		Weapon.AmmoGive1 15;
		Inventory.PickupMessage "Got the Mother Flakker!";
	}
	
	action void FireFlak() {
		A_StartSound("weapons/flakf");
		A_FireBullets(0,0,1,5); // Takes the ammo. Also ensures you always do *something* when you have your crosshair on a target.
		for (int i = 0; i < 7; i++) {
			A_FireBullets(frandom(-10,0),frandom(-1.5,1.5),-1,5,flags:FBF_EXPLICITANGLE);
			A_FireBullets(frandom(-5,5),frandom(-1.5,1.5),-1,5,flags:FBF_EXPLICITANGLE);
			A_FireBullets(frandom(0,10),frandom(-1.5,1.5),-1,5,flags:FBF_EXPLICITANGLE);
		}
		A_Light1();
	}
	
	states {
		Spawn:
			NLSG D -1;
		
		Select:
			NLSG A 1 A_Raise(18);
			Loop;
		
		
		DeSelect:
			NLSG A 1 A_Lower(18);
			Loop;
			
		Ready:
			NLSG A 1 A_WeaponReady();
			Loop;
		
		Fire:
			NLSG B 3 FireFlak();
			NLSG C 3;
			NLSG AAA 1 A_WeaponOffset(0,8,WOF_ADD);
			NLSG A 10;
			NLSG AAAAAA 3 A_WeaponOffset(0,-4,WOF_ADD);
			Goto Ready;
	}
}

class ProtonGun : PlasmaRifle {
	// Mostly normal, but the projectiles it fires are all kinds of colorful, and they home in slightly! Also the cooldown is less obnoxious.
	default {
		Weapon.SlotNumber 6;
		Inventory.PickupMessage "Got the Proton Gun! Who ya gonna call?";
	}
	
	action void FireProton() {
		A_StartSound("weapons/plasmaf");
		A_FireProjectile("ProtonShot");
		A_GunFlash();
	}
	
	states {
		Select:
			PLSG B 1 A_Raise(18);
			Loop;
		DeSelect:
			PLSG B 1 A_Lower(18);
			Loop;
		
		Fire:
			PLSG A 3 FireProton();
			PLSG B 20 A_WeaponReady(WRF_NOSWITCH);
			Goto Ready;
	}
}

class ProtonShot : PlasmaBall {
	default {
		+BRIGHT;
		Damage 4;
	}
	
	override void Tick() {
		Super.Tick();
		ThinkerIterator it = ThinkerIterator.Create();
		Actor mo;
		double shortest = -1;
		while (mo = Actor(it.next())) {
			if (!mo.bISMONSTER || !mo.bSHOOTABLE) return;
			double dist = Vec3To(mo).length();
			if (dist > 512) return;
			if (shortest == -1 || dist < shortest) {
				shortest = dist;
				tracer = mo;
			}
		}
		
		if (tracer) {
			vector3 dir = (Vec3To(tracer) + (0,0,tracer.height / 2)).unit();
			vel += dir * 3 * max(0,1 - vel.unit() dot dir);
		}
	}
	
	states {
		Spawn:
			APLS AB 3;
			PLSS AB 3;
			PLS2 AB 3;
			Loop;
			
		Death:
			PLSE A 3;
			APBX B 3;
			BAL1 C 3;
			PLSE D 3;
			APBX E 3;
			Stop;
	}
}

class QuakeZombie : ShotgunGuy {
	
	default {
		//$Color 4
		//$Colour #FF0000
		DropItem "QuakeShotgun";
	}
	
	states {
		Dummy:
			SPOS E 0;
			Goto Spawn;
	}
}

class Lifeguard : ChaingunGuy {
	default {
		//$Colour #FF0000
		DropItem "Lifesaver";
	}
	
	states {
		Dummy:
			CPOS E 0;
			Goto Spawn;
	}
}

class Cultist : Actor {
	default {
		//$Colour #FF0000
		MONSTER;
		Health 60;
		DropItem "Clip";
		DropItem "Clip";
		DropItem "Clip", 128;
		DropItem "RocketAmmo",128;
		Scale 0.5;
		Speed 12;
		Radius 20;
		Height 48; // Cultists are manlets.
		PainChance 160;
		SeeSound "cultist/see";
		ActiveSound "cultist/taunt";
		PainSound "cultist/pain";
		DeathSound "cultist/death";
		Obituary "%o was deemed heretical by a cultist.";
	}
	
	action void FireTommy() {
		A_CustomBulletAttack(30,3,1,1);
		A_StartSound("weapons/tommyfire");
	}
	
	action state TommyRefire() {
		int tn = 224;
		if (invoker.CheckLOF()) { tn = 128; }
		int roll = random(0,256);
		if (roll < tn) {
			return ResolveState("See");
		} else {
			return ResolveState(null);
		}
	}
	
	states {
		Spawn:
			MCLT A 1 A_Look();
			Loop;
		
		See:
			MCLT ABCDEF 3 A_Chase();
			Loop;
		
		Missile:
			MCLT G 5 A_StartSound("cultist/taunt");
		MissileRefire:
			MCLT H 2 Bright FireTommy();
			MCLT G 1;
			MCLT H 2 Bright FireTommy();
			MCLT G 1;
			MCLT H 2 Bright FireTommy();
			MCLT G 1;
			MCLT G 0 A_Jump(128,"Pause");
			Loop;
		Pause:
			MCLT G 5;
			MCLT G 5 TommyRefire();
			Goto MissileRefire;
		
		Pain:
			MCLT I 8 A_Pain();
			Goto See;
		
		Death:
			MCLT I 6 A_ScreamAndUnblock();
			MCLT JKLMNOPQ 5;
			MCLT R -1;
	}
}

class AxeZombie : Actor {
	// Faster, weaker, feigns death on pain.
	default {
		//$Colour #FF0000
		MONSTER;
		Speed 12;
		Health 90;
		DropItem "LifeHeart";
		Radius 20;
		Height 64;
		Scale 0.6;
		PainChance 200;
		Obituary "%o was axe-murdered.";
		MeleeRange 64;
		SeeSound "zombie/see";
		ActiveSound "zombie/idle";
		PainSound "zombie/pain";
		DeathSound "zombie/death";
	}
	
	action state AxeSwing() {
		invoker.Thrust(8,invoker.angle);
		if (invoker.CheckLOF(0,64)) {
			A_CustomMeleeAttack(30,"axe/hit","axe/whiff");
			return ResolveState("FinishSwing");
		} else {
			return ResolveState(null);
		}
	}
	
	states {
		Spawn:
			ZOMB A 1 A_Look();
			Loop;
		
		See:
			ZOMB ABCDEF 3 A_Chase();
			Loop;
		
		Missile:
			ZOMA ABC 3 A_FaceTarget();
			ZOMA C 6 A_Pain();
			ZOMA DDDEEE 2 AxeSwing();
		FinishSwing:
			ZOMA FG 8;
			Goto See;
			
		Pain:
			ZOMB G 0 { invoker.bNOPAIN = true; }
			ZOMB G 4 A_Pain();
			ZOMB HIJK 4;
			ZOMB L Random(35,70) A_NoBlocking(false);
			ZRIS ABC 4;
			ZRIS D 4 A_SetSolid();
			ZRIS EF 4;
			ZOMB G 0 { invoker.bNOPAIN = false; }
			Goto See;
		
		Death:
			ZOMX A 4 A_ScreamAndUnblock();
			ZOMX BCDEFGH 4;
			ZOMX H -1;
	}
}

class LifeHeart : Health {
	default {
		inventory.Amount 15;
		inventory.PickupMessage "Life essence absorbed";
		Scale 0.5;
	}
	
	states {
		Spawn:
			HHRT ABCDCB 5 Bright;
			Loop;
	}
}

class DeadBaron : BaronOfHell {
	default {
// 		SKIP_SUPER;
		//$Colour #FF0000
		-SOLID;
	}
	states {
		Spawn:
			BOSS O 8;
			BOSS NML 8;
			BOSS K 8 A_SetSolid();
			BOSS JI 8;
			Goto See;
	}
}

class MirrorPinky : Demon {
	default {
		//$Colour #FF0000
		+ONLYVISIBLEINMIRRORS;
	}
}
class MirrorImp : DoomImp {
	default {
		//$Colour #FF0000
		+ONLYVISIBLEINMIRRORS;
	}
}

// Now the big one.

class SnakeMonster : Actor {
	// This thing can be either a head, or a tail segment.
	SnakeMonster forward;
	SnakeMonster back;
	// If forward exists, this thing is a tail segment. Otherwise, it's a head.
	
	int segment; // Not guaranteed to be filled. Used to figure out when we're done spawning segments.
	
	default {
		+BRIGHT;
		MONSTER;
		Health 400;
		Radius 32;
		Height 32;
		Speed 16;
		+FLOAT;
		+NOGRAVITY;
		+THRUSPECIES;
		+AVOIDMELEE;
	}
	
	void SpawnTail(int seg) {
		if (seg >= 10) { return; }
		vector3 targetpos = Vec3Angle(2 * radius,angle+180);
		if (targetpos != targetpos) console.printf("Whoops!");
		let tail = SnakeMonster(Spawn("SnakeMonster",targetpos));
		if (tail) {
			tail.segment = seg;
			tail.forward = self;
			back = tail;
			back.ChangeTID(TID);
// 			back.SetState(back.ResolveState("Tail"));
// 			back.SpawnTail(seg+1);
		}
	}
	
	override int TakeSpecialDamage(Actor inf, Actor src, int dmg, Name mod) {
		int transfer = dmg / 4; // 1/4th of damage moves to neighbors.
		if (back && mod != "Forward") { // Forward damage doesn't go back...
			back.DamageMobj(inf,src,transfer,"Back");
		}
		if (forward && mod != "Back") { // ...and Back damage doesn't go forward
			forward.DamageMobj(inf,src,transfer,"Forward");
		}
		if (mod != "Forward" && mod != "Back") { return dmg / 2; } else { return dmg; }
		// Initial hit is halved. In other words, 50% of damage dealt to a segment is instead split to neighbors.
	}
	
	action void Split() {
		if (invoker.forward) {
			invoker.forward.back = null;
			invoker.forward = null;
		}
		
		if (invoker.back) {
			invoker.back.forward = null;
			invoker.back = null;
			// SnakeTail() is responsible for jumping to the See state once its Forward is null.
		}
	}
	
	action state SnakeTail() {
		if (invoker.forward) {
			A_Face(invoker.forward); // Follow the leader...
// 			vector3 targetpos = invoker.forward.Vec3Angle(radius * 2,invoker.forward.angle+180);
// 			vector3 targetpos = Vec3To(invoker.forward);
// 			if (targetpos != targetpos) console.printf("Whoops! Snake tail got a bad targetpos");
// 			vector3 targetvel = (targetpos - pos).unit();
// 			vel = targetvel * speed;
			double spd = Vec3To(invoker.forward).length();
			spd = max(0,spd - (radius * 0.5));
			VelIntercept(invoker.forward, spd * 1./35., true);
			return ResolveState(null);
		} else {
			// We no longer have a forward to follow. Become a new head!
			return ResolveState("See");
		}
	}
	
	action void SnakeShoot() {
		if (invoker.back) {
			invoker.back.SetState(invoker.back.ResolveState("TailMissile"));
		}
	}
	
	action void SnakeProj() {		
		A_SpawnProjectile("SnakeBlast",angle:90,flags:CMF_AIMDIRECTION);
		A_SpawnProjectile("SnakeBlast",angle:-90,flags:CMF_AIMDIRECTION);
	}
	
	override void PostBeginPlay() {
		Super.PostBeginPlay();
		if (segment > 0) {
			SetState(ResolveState("Tail"));
		}
		SpawnTail(segment+1);
	}
	
	override void Tick() {
		Super.Tick();
		if (segment == 0) {
			if (pos.z - floorz <= 32) {
				console.printf("Height is %d",pos.z - floorz);
				vel.z = 1; // Always try to float upward.
			} else {
				vel.z = sin(GetAge());
			}
		}
		
		if (back) {
			back.target = target;
		}
	}
	
	states {
		Spawn:
			SKUL AB 8 A_Look();
			Loop;
		
		See:
			SKUL AB 5 A_Chase(flags:CHF_FASTCHASE|CHF_RESURRECT);
			Loop;
			
		Heal:
			SKUL C 5 A_SpawnItemEX("SnakeRez",flags:SXF_SETMASTER);
			Goto See;
			
		Missile:
			SKUL C 0 A_Jump(64,"Heal");
			SKUL C 5 SnakeShoot();
			SKUL D 5;
			Goto See;
		
		Tail:
			MANF AB 4 SnakeTail();
			Loop;
			
		TailMissile:
			MANF A 0 SnakeProj();
			MANF ABABABAB 4 SnakeTail();
			MANF B 0 SnakeShoot();
			Goto Tail;
		
		Death:
			MISL B 5 A_ScreamAndUnblock();
			MISL CD 5 A_GiveToChildren("RezEnd");
			MISL D 0 Split();
			Stop;
	}
	
}

class RezEnd : Inventory {
	// Exists solely to tell SnakeRez to stop existing.
}

class SnakeBlast : Actor {
	default {
		PROJECTILE;
		-NOGRAVITY;
		+MTHRUSPECIES;
		+BRIGHT;
		Damage 4;
		Speed 20;
	}
	
	states {
		Spawn:
			MANF AB 4;
			Loop;
		
		Death:
			MISL BC 5;
			MISL D 5 A_Explode(64,flags:0);
			Stop;
	}

}

class SnakeRez : Actor {
	default {
		Speed 32;
		Health 1;
		+BRIGHT;
	}
	
	action void RezWander() {
		if (CountInv("RezEnd") > 0) {
			A_Die();
		}
		
		if (!A_CheckForResurrection(ResolveState("Heal"))) {
			A_Wander();
		}
	}
	
	states {
		Spawn:
			FIRE A 2 RezWander();
			FIRE BAB 2 RezWander();
			FIRE CBC 2 RezWander();
			FIRE DCD 2 RezWander();
			FIRE EDE 2 RezWander();
			FIRE FEF 2 RezWander();
			FIRE GFG 2 RezWander();
			FIRE HGH 2 RezWander();
			Loop;
		
		Heal:
			FIRE ABCDEFGH 2;
			Stop;
	}
}

class BabySpider : Arachnotron {
	// Smaller, slightly less dangerous, and it drops cells! What's not to love?
	
	default {
		Radius 48;
		Height 48;
		Scale 0.75;
		Health 400;
		DropItem "Cell";
		DropItem "Cell",128;
	}
	
	states {
		Dummy:
			BSPI G 0;
			Goto Spawn;
	}
}

class DynMusicHandler : EventHandler {
	double pitchmod;
	const channel = 7; // There's no way anybody else will use this.
	const slide = 1./35.; // How fast pitch slides up and down.
	
	string music;
	
	override void OnRegister() {
		music = "music/dosage";
	}
	
	
	override void WorldTick() {
		// Find the consoleplayer. Only one player should be playing music at any moment.
		// God only knows if this'll even come up, or if it'll cause problems;
		// AFAIK the RAMP mapsets are all SP only.
		let plr = players[consoleplayer].mo;
		bool combat = false;
		
		if (plr) {
			// Next, get a ThinkerIterator and look for things that have a target.
			ThinkerIterator it = ThinkerIterator.Create();
			Actor mo;	
			while (mo = Actor(it.next())) {
				if (mo.bISMONSTER && mo.health > 0 && mo.target && mo.target == plr && mo.CheckLOF()) {
					// Player is being targeted!
// 					console.printf("Active monster!");
					combat = true;
					break;
				}
			}	
		}
		
		if (combat) {
			pitchmod = min(1.0,pitchmod + slide);
		} else {
			if (plr.health > 0) {
				pitchmod = max(0.0,pitchmod - slide);
			} else {
				pitchmod = max(-1.0,pitchmod - slide);
			}
		}
		
		// Finally...
		if (plr.FindInventory("ChangeMusicToken")) {
			// Change music here once I've picked a final boss track. Probly gonna be Girl Hell 1999.
			music = "music/girlhell";
		}
		
		if (plr.FindInventory("MusicToken")) {
			plr.A_StartSound(music,channel,CHANF_LOOPING);
			plr.A_SoundPitch(channel,1.0 + (0.2 * pitchmod));
			plr.A_SoundVolume(channel,0.75 + abs(0.5 * pitchmod));
		} else {
			plr.A_StopSound(channel);
		}
	}
}

class MusicToken : Inventory {
	// Give to start music, take to stop.
	default {
		inventory.Amount 1;
		inventory.MaxAmount 1;
	}
}

class ChangeMusicToken : Inventory {
	// Give to change the music. It gets taken automatically.
	default {
		inventory.Amount 1;
		inventory.MaxAmount 1;
	}	
}

class MushroomCloud : Actor {
	// Basically just does A_Mushroom and makes a big explosion noise.
	default {
		ReactionTime 6;
	}
	states {
		Spawn:
			TNT1 A 0;
			TNT1 AAAAAA 5 A_SpawnItemEX("MushroomExplosion",frandom(-32,32),frandom(-32,32),frandom(16,64));
			TNT1 A 0 A_Mushroom(numspawns:8,vrange:8.0,hrange:0.25);
			TNT1 A 0 A_CountDown();
			Loop;
			
		Death:
			TNT1 A 0 A_Mushroom(numspawns:16);
			TNT1 A 0 A_StartSound("weapons/rocklx",attenuation:0.1);
			Stop;
	}
}

class MushroomExplosion : Actor {
	default {
		+NOGRAVITY;
	}

	states {
		Spawn:
			TNT1 A 0;
			TNT1 A 0 A_StartSound("weapons/rocklx",attenuation:frandom(0.1,0.3));
			MISL BCD Random(8,16) Bright;
			Stop;
	}
}