class Minotaur : Actor
{
	const MAULATORTICS = 25 * TICRATE;
	const MNTR_CHARGE_SPEED =13.;
	const MINOTAUR_LOOK_DIST = 16*54.;
	
	Default
	{
		Health 3000;
		Radius 28;
		Height 100;
		Mass 800;
		Speed 16;
		Damage 7;
		Painchance 25;
		Monster;
		+DROPOFF
		+FLOORCLIP
		+BOSS
		+NORADIUSDMG
		+DONTMORPH
		+NOTARGET
		+BOSSDEATH
		SeeSound "minotaur/sight";
		AttackSound "minotaur/attack1";
		PainSound "minotaur/pain";
		DeathSound "minotaur/death";
		ActiveSound "minotaur/active";
		DropItem "ArtiSuperHealth", 51;
		DropItem "PhoenixRodAmmo", 84, 10;
		Obituary "$OB_MINOTAUR";
		HitObituary "$OB_MINOTAURHIT";
	}

	States
	{
	Spawn:
		MNTR AB 10 A_MinotaurLook;
		Loop;
	Roam:
		MNTR ABCD 5 A_MinotaurRoam;
		Loop;
	See:
		MNTR ABCD 5 A_MinotaurChase;
		Loop;
	Melee:
		MNTR V 10 A_FaceTarget;
		MNTR W 7 A_FaceTarget;
		MNTR X 12 A_MinotaurAtk1;
		Goto See;
	Missile:
		MNTR V 10 A_MinotaurDecide;
		MNTR Y 4 A_FaceTarget;
		MNTR Z 9 A_MinotaurAtk2;
		Goto See;
	Hammer:
		MNTR V 10 A_FaceTarget;
		MNTR W 7 A_FaceTarget;
		MNTR X 12 A_MinotaurAtk3;
		Goto See;
	HammerLoop:
		MNTR X 12;
		Goto Hammer;
	Charge:
		MNTR U 2 A_MinotaurCharge;
		Loop;
	Pain:
		MNTR E 3;
		MNTR E 6 A_Pain;
		Goto See;
	Death:
		MNTR F 6 A_MinotaurDeath;
		MNTR G 5;
		MNTR H 6 A_Scream;
		MNTR I 5;
		MNTR J 6;
		MNTR K 5;
		MNTR L 6;
		MNTR M 5 A_NoBlocking;
		MNTR N 6;
		MNTR O 5;
		MNTR P 6;
		MNTR Q 5;
		MNTR R 6;
		MNTR S 5;
		MNTR T -1 A_BossDeath;
		Stop;
	FadeOut:
		MNTR E 6;
		MNTR E 2 A_Scream;
		MNTR E 5 A_SpawnItemEx("MinotaurSmokeExit");
		MNTR E 5;
		MNTR E 5 A_NoBlocking;
		MNTR E 5;
		MNTR E 5 A_SetTranslucent(0.66, 0);
		MNTR E 5 A_SetTranslucent(0.33, 0);
		MNTR E 10 A_BossDeath;
		Stop;
	}
	
	//---------------------------------------------------------------------------
	//
	// FUNC P_MinotaurSlam
	//
	//---------------------------------------------------------------------------

	void MinotaurSlam (Actor target)
	{
		double ang = AngleTo(target);
		double thrust = 16 + random[MinotaurSlam]() / 64.;
		target.VelFromAngle(ang, thrust);
		int damage = random[MinotaurSlam](1, 8) * (bSummonedMonster? 4 : 6);
		int newdam = target.DamageMobj (null, null, damage, 'Melee');
		target.TraceBleedAngle (newdam > 0 ? newdam : damage, ang, 0.);
		if (target.player)
		{
			target.reactiontime = random[MinotaurSlam](14, 21);
		}
	}

	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	override void Tick ()
	{
		Super.Tick ();
		
		// The unfriendly Minotaur (Heretic's) is invulnerable while charging
		if (!bSummonedMonster)
		{
			bInvulnerable = bSkullFly;
		}
	}

	override bool Slam (Actor thing)
	{
		// Slamming minotaurs shouldn't move non-creatures
		if (!thing.bIsMonster && !thing.player)
		{
			return false;
		}
		return Super.Slam (thing);
	}

	override int DoSpecialDamage (Actor target, int damage, Name damagetype)
	{
		damage = Super.DoSpecialDamage (target, damage, damagetype);
		if (damage != -1 && bSkullFly)
		{ // Slam only when in charge mode
			MinotaurSlam (target);
			return -1;
		}
		return damage;
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurAtk1
	//
	// Melee attack.
	//
	//----------------------------------------------------------------------------

	void A_MinotaurAtk1()
	{
		let targ = target;
		if (!targ)
		{
			return;
		}
		A_PlaySound ("minotaur/melee", CHAN_WEAPON);
		if (CheckMeleeRange())
		{
			PlayerInfo player = targ.player;
			int damage = random[MinotaurAtk1](1, 8) * 4;
			int newdam = targ.DamageMobj (self, self, damage, 'Melee');
			targ.TraceBleed (newdam > 0 ? newdam : damage, self);
			if (player != null && player.mo == targ)
			{ // Squish the player
				player.deltaviewheight = -16;
			}
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurDecide
	//
	// Choose a missile attack.
	//
	//----------------------------------------------------------------------------

	void A_MinotaurDecide()
	{
		bool friendly = bSummonedMonster;

		if (!target)
		{
			return;
		}
		if (!friendly)
		{
			A_PlaySound ("minotaur/sight", CHAN_WEAPON);
		}
		double dist = Distance2D(target);
		if (target.pos.z + target.height > pos.z
			&& target.pos.z + target.height < pos.z + height
			&& dist < (friendly ? 16*64. : 8*64.)
			&& dist > 1*64.
			&& random[MinotaurDecide]() < 150)
		{ // Charge attack
			// Don't call the state function right away
			SetStateLabel("Charge", true);
			bSkullFly = true;
			if (!friendly)
			{ // Heretic's Minotaur is invulnerable during charge attack
				bInvulnerable = true;
			}
			A_FaceTarget ();
			VelFromAngle(MNTR_CHARGE_SPEED);
			special1 = TICRATE/2; // Charge duration
		}
		else if (target.pos.z == target.floorz
			&& dist < 9*64.
			&& random[MinotaurDecide]() < (friendly ? 100 : 220))
		{ // Floor fire attack
			SetStateLabel("Hammer");
			special2 = 0;
		}
		else
		{ // Swing attack
			A_FaceTarget ();
			// Don't need to call P_SetMobjState because the current state
			// falls through to the swing attack
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurCharge
	//
	//----------------------------------------------------------------------------

	void A_MinotaurCharge()
	{
		if (target == null)
		{
			return;
		}
		if (special1 > 0)
		{
			Class<Actor> type;

			if (gameinfo.gametype == GAME_Heretic)
			{
				type = "PhoenixPuff";
			}
			else
			{
				type = "PunchPuff";
			}
			Actor puff = Spawn (type, Pos, ALLOW_REPLACE);
			if (puff != null) puff.Vel.Z = 2;
			special1--;
		}
		else
		{
			bSkullFly = false;
			bInvulnerable = false;
			SetState (SeeState);
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurAtk2
	//
	// Swing attack.
	//
	//----------------------------------------------------------------------------

	void A_MinotaurAtk2()
	{
		bool friendly = bSummonedMonster;

		let targ = target;
		if (targ == null)
		{
			return;
		}
		A_PlaySound ("minotaur/attack2", CHAN_WEAPON);
		if (CheckMeleeRange())
		{
			int damage = random[MinotaurAtk2](1, 8) * (friendly ? 3 : 5);
			int newdam = targ.DamageMobj (self, self, damage, 'Melee');
			targ.TraceBleed (newdam > 0 ? newdam : damage, self);
			return;
		}
		double z = pos.z + 40;
		Class<Actor> fx = "MinotaurFX1";
		Actor mo = SpawnMissileZ (z, targ, fx);
		if (mo != null)
		{
//			S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
			double vz = mo.Vel.Z;
			double ang = mo.angle;
			SpawnMissileAngleZ (z, fx, ang-(45./8), vz);
			SpawnMissileAngleZ (z, fx, ang+(45./8), vz);
			SpawnMissileAngleZ (z, fx, ang-(45./16), vz);
			SpawnMissileAngleZ (z, fx, ang+(45./16), vz);
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurAtk3
	//
	// Floor fire attack.
	//
	//----------------------------------------------------------------------------

	void A_MinotaurAtk3()
	{
		bool friendly = bSummonedMonster;

		let targ = target;
		if (!targ)
		{
			return;
		}
		A_PlaySound ("minotaur/attack3", CHAN_VOICE);
		if (CheckMeleeRange())
		{
			PlayerInfo player = targ.player;
			int damage = random[MinotaurAtk3](1, 8) * (friendly ? 3 : 5);
			int newdam = targ.DamageMobj (self, self, damage, 'Melee');
			targ.TraceBleed (newdam > 0 ? newdam : damage, self);
			if (player != null && player.mo == targ)
			{ // Squish the player
				player.deltaviewheight = -16;
			}
		}
		else
		{
			if (Floorclip > 0 && compat_minotaur)
			{
				// only play the sound. 
				A_PlaySound ("minotaur/fx2hit", CHAN_WEAPON);
			}
			else
			{
				Actor mo = SpawnMissile (target, "MinotaurFX2");
				if (mo != null)
				{
					mo.A_PlaySound ("minotaur/attack1", CHAN_WEAPON);
				}
			}
		}
		if (random[MinotaurAtk3]() < 192 && special2 == 0)
		{
			SetStateLabel ("HammerLoop");
			special2 = 1;
		}
	}

	//----------------------------------------------------------------------------
	//
	// PROC A_MinotaurDeath
	//
	//----------------------------------------------------------------------------

	void A_MinotaurDeath()
	{
		if (Wads.CheckNumForName ("MNTRF1", Wads.ns_sprites) < 0 &&
			Wads.CheckNumForName ("MNTRF0", Wads.ns_sprites) < 0)
			SetStateLabel("FadeOut");
	}


	//----------------------------------------------------------------------------
	//
	// A_MinotaurRoam
	//
	//----------------------------------------------------------------------------

	void A_MinotaurRoam()
	{
		// In case pain caused him to skip his fade in.
		A_SetRenderStyle(1, STYLE_Normal);

		let mf = MinotaurFriend(self);
		if (mf)
		{
			if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
			{
				DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
				return;
			}
		}

		if (random[MinotaurRoam]() < 30)
			A_MinotaurLook();		// adjust to closest target

		if (random[MinotaurRoam]() < 6)
		{
			//Choose new direction
			movedir = random[MinotaurRoam]() % 8;
			FaceMovementDirection ();
		}
		if (!MonsterMove())
		{
			// Turn
			if (random[MinotaurRoam]() & 1)
				movedir = (movedir + 1) % 8;
			else
				movedir = (movedir + 7) % 8;
			FaceMovementDirection ();
		}
	}


	//----------------------------------------------------------------------------
	//
	//	PROC A_MinotaurLook
	//
	// Look for enemy of player
	//----------------------------------------------------------------------------

	void A_MinotaurLook()
	{
		if (!(self is "MinotaurFriend"))
		{
			A_Look();
			return;
		}

		Actor mo = null;
		PlayerInfo player;
		double dist;
		Actor master = tracer;

		target = null;
		if (deathmatch)					// Quick search for players
		{
			for (int i = 0; i < MAXPLAYERS; i++)
			{
				if (!playeringame[i]) continue;
				player = players[i];
				mo = player.mo;
				if (mo == master) continue;
				if (mo.health <= 0) continue;
				dist = Distance2D(mo);
				if (dist > MINOTAUR_LOOK_DIST) continue;
				target = mo;
				break;
			}
		}

		if (!target)				// Near player monster search
		{
			if (master && (master.health > 0) && (master.player))
				mo = master.RoughMonsterSearch(20);
			else
				mo = RoughMonsterSearch(20);
			target = mo;
		}

		if (!target)				// Normal monster search
		{
			ThinkerIterator it = ThinkerIterator.Create("Actor");

			while ((mo = Actor(it.Next())) != null)
			{
				if (!mo.bIsMonster) continue;
				if (mo.health <= 0) continue;
				if (!mo.bShootable) continue;
				dist = Distance2D(mo);
				if (dist > MINOTAUR_LOOK_DIST) continue;
				if (mo == master || mo == self) continue;
				if (mo.bSummonedMonster && mo.tracer == master) continue;
				target = mo;
				break;			// Found actor to attack
			}
		}

		if (target)
		{
			SetState (SeeState, true);
		}
		else
		{
			SetStateLabel ("Roam", true);
		}
	}

	//----------------------------------------------------------------------------
	//
	//	PROC A_MinotaurChase
	//
	//----------------------------------------------------------------------------

	void A_MinotaurChase()
	{
		let mf = MinotaurFriend(self);
		if (!mf)
		{
			A_Chase();
			return;
		}


		// In case pain caused him to skip his fade in.
		A_SetRenderStyle(1, STYLE_Normal);

		if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
		{
			DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
			return;
		}

		if (random[MinotaurChase]() < 30)
			A_MinotaurLook();		// adjust to closest target

		if (!target || (target.health <= 0) || !target.bShootable)
		{ // look for a new target
			SetIdle();
			return;
		}

		FaceMovementDirection ();
		reactiontime = 0;

		// Melee attack
		if (MeleeState && CheckMeleeRange ())
		{
			if (AttackSound)
			{
				A_PlaySound (AttackSound, CHAN_WEAPON);
			}
			SetState (MeleeState);
			return;
		}

		// Missile attack
		if (MissileState && CheckMissileRange())
		{
			SetState (MissileState);
			return;
		}

		// chase towards target
		if (!MonsterMove ())
		{
			NewChaseDir ();
			FaceMovementDirection ();
		}

		// Active sound
		if (random[MinotaurChase]() < 6)
		{
			PlayActiveSound ();
		}
	}
}

class MinotaurFriend : Minotaur
{
	int StartTime;
	
	Default
	{
		Health 2500;
		-DROPOFF
		-BOSS
		-DONTMORPH
		+FRIENDLY
		+NOTARGETSWITCH
		+STAYMORPHED
		+TELESTOMP
		+SUMMONEDMONSTER
		RenderStyle "Translucent";
		Alpha 0.3333;
		DropItem "None";
	}
	States
	{
	Spawn:
		MNTR A 15;
		MNTR A 15 A_SetTranslucent(0.66, 0);
		MNTR A 3 A_SetTranslucent(1, 0);
		Goto Super::Spawn;
	Idle:
		Goto Super::Spawn;
	Death:
		Goto FadeOut;
	}
	
	//----------------------------------------------------------------------------
	//
	// 
	//
	//----------------------------------------------------------------------------

	override void BeginPlay ()
	{
		Super.BeginPlay ();
		StartTime = -1;
	}

	override void Die (Actor source, Actor inflictor, int dmgflags)
	{
		Super.Die (source, inflictor, dmgflags);

		if (tracer && tracer.health > 0 && tracer.player)
		{
			// Search thinker list for minotaur
			ThinkerIterator it = ThinkerIterator.Create("MinotaurFriend");
			MinotaurFriend mo;

			while ((mo = MinotaurFriend(it.Next())) != null)
			{
				if (mo.health <= 0) continue;
				// [RH] Minotaurs can't be morphed, so this isn't needed
				//if (!(mo.flags&MF_COUNTKILL)) continue;		// for morphed minotaurs
				if (mo.bCorpse) continue;
				if (mo.StartTime >= 0 && (level.maptime - StartTime) >= MAULATORTICS) continue;
				if (mo.tracer != null && mo.tracer.player == tracer.player) break;
			}

			if (mo == null)
			{
				Inventory power = tracer.FindInventory("PowerMinotaur");
				if (power != null)
				{
					power.Destroy ();
				}
			}
		}
	}

	
}

// Minotaur FX 1 ------------------------------------------------------------

class MinotaurFX1 : Actor
{
	Default
	{
		Radius 10;
		Height 6;
		Speed 20;
		FastSpeed 26;
		Damage 3;
		DamageType "Fire";
		Projectile;
		-ACTIVATEIMPACT
		-ACTIVATEPCROSS
		+ZDOOMTRANS
		RenderStyle "Add";
	}
	States
	{
	Spawn:
		FX12 AB 6 Bright;
		Loop;
	Death:
		FX12 CDEFGH 5 Bright;
		Stop;
	}
}


// Minotaur FX 2 ------------------------------------------------------------

class MinotaurFX2 : MinotaurFX1
{
	Default
	{
		Radius 5;
		Height 12;
		Speed 14;
		FastSpeed 20;
		Damage 4;
		+FLOORHUGGER
		ExplosionDamage 24;
		DeathSound "minotaur/fx2hit";
	}
	
	states
	{
	Spawn:
		FX13 A 2 Bright A_MntrFloorFire;
		Loop;
	Death:
		FX13 I 4 Bright A_Explode;
		FX13 JKLM 4 Bright;
		Stop;
	}
	
	//----------------------------------------------------------------------------
	//
	// PROC A_MntrFloorFire
	//
	//----------------------------------------------------------------------------

	void A_MntrFloorFire()
	{
		SetZ(floorz);
		double x = Random2[MntrFloorFire]() / 64.;
		double y = Random2[MntrFloorFire]() / 64.;
		
		Actor mo = Spawn("MinotaurFX3", Vec2OffsetZ(x, y, floorz), ALLOW_REPLACE);
		if (mo != null)
		{
			mo.target = target;
			mo.Vel.X = MinVel; // Force block checking
			mo.CheckMissileSpawn (radius);
		}
	}
}

// Minotaur FX 3 ------------------------------------------------------------

class MinotaurFX3 : MinotaurFX2
{
	Default
	{
		Radius 8;
		Height 16;
		Speed 0;
		DeathSound "minotaur/fx3hit";
		ExplosionDamage 128;
	}
	States
	{
	Spawn:
		FX13 DC 4 Bright;
		FX13 BCDE 5 Bright;
		FX13 FGH 4 Bright;
		Stop;
	}
}

// Minotaur Smoke Exit ------------------------------------------------------

class MinotaurSmokeExit : Actor
{
	Default
	{
		+NOBLOCKMAP
		+NOTELEPORT
		RenderStyle "Translucent";
		Alpha 0.4;
	}
	States
	{
	Spawn:
		MNSM ABCDEFGHIJIHGFEDCBA 3;
		Stop;
	}
}

extend class Actor
{
	enum dirtype_t
	{
		DI_EAST,
		DI_NORTHEAST,
		DI_NORTH,
		DI_NORTHWEST,
		DI_WEST,
		DI_SOUTHWEST,
		DI_SOUTH,
		DI_SOUTHEAST,
		DI_NODIR,
		NUMDIRS
	};

	void FaceMovementDirection()
	{
		switch (movedir)
		{
		case DI_EAST:
			angle = 0.;
			break;
		case DI_NORTHEAST:
			angle = 45.;
			break;
		case DI_NORTH:
			angle = 90.;
			break;
		case DI_NORTHWEST:
			angle = 135.;
			break;
		case DI_WEST:
			angle = 180.;
			break;
		case DI_SOUTHWEST:
			angle = 225.;
			break;
		case DI_SOUTH:
			angle = 270.;
			break;
		case DI_SOUTHEAST:
			angle = 315.;
			break;
		}
	}
}