class BabelMonster : Actor
{
	bool isHardest; //switch for stupidly hard difficulty
	int mo_fearAccumulation;

	bool hasFear; //can the monster become afraid? This cuts off various functions early to optimize

	bool isAfraid; //Is the monster currently afraid? Used to loop the "Fear" state
	double currentFear; //Current amount of fear as a numerical value
	double fearThreshold; //Amount of fear that can be accumulated before possibly breaking and running
	double maxFear; //Maximum amount of fear, this is larger than threshold since monsters can have more fear in their meter than is necessary to scare them
	double fearFallOff; //How fast fear reduces in combat
	double fearRecovery; //How fast fear recovers once scared
	double resistChance; //Chance to, once past the threshold, resist going into the Fear state and lose some accumulated fear

	bool holdingState; //switch used to avoid cancelling certain states to sync or chain

	bool checksLOS; //switch used to perform a check when syncing or chaining to ensure Line of Sight is clear
	bool checksLOF; //switch used to perform a check when syncing or chaining to ensure Line of Fire is clear

	string customPainSound; //Defaults to the default, but I added a bunch more pain sounds for some monsters and this is how it works

	//I apologize to anyone who tries to comprehend this without a keen grasp of how gzdoom executes code per-frame
	int groupFearFrameDelay[2]; //Used to stagger group fear reductions so that groups don't cause massive stutter

	//So basically, this is all to make ambushes less instantly deadly
	bool foundFirstTarget; //Have we woken up and acquired a target?
	int wakeUpTics; //How many tics since we woke up?
	int wakeUpDelay; //How long do we want to wait before enabling sync and chain?
	bool wokenUpFully; //Have we fully woken up? This is a very, very slight optimization for super large maps since checking a bool is faster than checking an int. Also stops Tick() from mashing some ifs.

	//Holy god why didn't I do these sooner
	property FearEnabled: hasFear;
	property FearThreshold: fearThreshold;
	property FearMaxValue: maxFear;
	property FearFallOff: fearFallOff;
	property FearRecovery: fearRecovery;
	property FearResistChance: resistChance;

	property SyncChainCheckLOS: checksLOS;
	property SyncChainCheckLOF: checksLOF;

	property AlternatePainSound: customPainSound;

	property SyncChainWakeDelay: wakeUpDelay;

	default
	{
		BabelMonster.FearEnabled		false;
		BabelMonster.FearThreshold		0.0;
		BabelMonster.FearMaxValue		0.0;
		BabelMonster.FearFallOff		0.0;
		BabelMonster.FearRecovery		0.0;
		BabelMonster.FearResistChance	0.0;
		BabelMonster.SyncChainCheckLOS	false;
		BabelMonster.SyncChainCheckLOF	false;
		BabelMonster.AlternatePainSound	"";
		BabelMonster.SyncChainWakeDelay	20;
	}

	//Setup for monster variable defaults
	override void BeginPlay()
	{
		//Check if we're on the hardest difficulty
		if(G_SkillPropertyInt(SKILLP_ACSReturn) == 1)
			isHardest = true;
		else
			isHardest = false;

		//Generally just initialize everything
		isAfraid = false;
		currentFear = 0.0;

		holdingState = true;

		if(customPainSound == "")
		{
			customPainSound = PainSound;
		}

		groupFearFrameDelay[0] = 0;
		foundFirstTarget = false;
		wakeUpTics = 0;
		wokenUpFully = false;

		mo_fearAccumulation = 0;
		//Always make sure to call super unless you're sure you know what you're doing
		super.BeginPlay();
	}

	action void A_EnableFear()
	{
		invoker.hasFear = true;
	}

	action void A_SetupFearParameters(double threshold, double maxFear, double falloff, double recovery, double resistChance)
	{
		invoker.fearThreshold = threshold;
		invoker.maxFear = maxFear;
		invoker.fearFallOff = falloff;
		invoker.fearRecovery = recovery;
		invoker.resistChance = resistChance;
	}

	action void A_SetChecksLOS(bool b)
	{
		invoker.checksLOS = b;
	}

	action void A_SetChecksLOF(bool b)
	{
		invoker.checksLOF = b;
	}

	override int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags, double angle)
	{
		if(!BabelMonster(source))
		{
			mo_fearAccumulation += damage;
		}
		if(flags & DMG_EXPLOSION)
		{
			if(damage > 0 && !self.bNOBLOOD && CVar.FindCVar("egr_explosion_bleed").GetBool())
			{
				float impact_angle = AngleTo(inflictor);
				float xofs = Radius * cos(impact_angle);
				float yofs = Radius * sin(impact_angle);
				SpawnBlood((pos.x + xofs, pos.y + yofs, pos.z + (height * frandom(0.45, 0.65))), impact_angle, damage);
			}
		}
		return super.DamageMobj(inflictor, source, damage, mod, flags, angle);
	}

	override void Tick()
	{
		if(!wokenUpFully)
		{
			if(!target && !foundFirstTarget)
			{
				currentFear = 0.0; //fix for ambush monsters instantly being feared
			}
			if(target && !foundFirstTarget)
			{
				foundFirstTarget = true;
			}
			if(foundFirstTarget && wakeUpTics < wakeUpDelay)
			{
				wakeUpTics++;
			}
			if(wakeUpTics >= wakeUpDelay)
			{
				wokenUpFully = true;
			}
		}

		//Fear Effects
		if(hasFear && health > 0)
		{
			//Don't allow fear meter to exceed max
			if(currentFear >= maxFear)
			{
				currentFear = maxFear;
			}
			//If we're ove the threshold and not already afraid
			if(currentFear >= fearThreshold && !isAfraid)
			{
				//roll a fear check
				if(frandom(0.0, 100.0) > resistChance)
				{
					//become afraid
					isAfraid = true;
					SetState(ResolveState("Fear"));
				}
				else
				{
					//resist and gain some courage back
					currentFear = fearThreshold - (maxFear - fearThreshold) - 1;
					DisplayFearResistEffect();
				}
			}
			if(isAfraid)
			{
				//display the user's selected fear effect
				displayFearEffect();
				//If we're exiting fear and we've swapped palettes, swap back
				if(currentFear <= 0 && CVar.FindCvar("egr_fear_indicators").GetInt() == 3)
				{
					A_SetTranslation("restore");
				}
				//Reduce fear of monsters like us that are nearby
				GroupFearStagger();
			}
			//Reduce our own fear
			if(currentFear <= 0)
			{
				currentFear = 0;
			}
			else
			{
				if(isAfraid)
				{
					currentFear -= frandom(0.0, fearRecovery*3.0); //recover pseudorandomly
				}
				else
				{
					currentFear -= frandom(0.0, fearFallOff*3.0);
				}
			}
		}
		//If we somehow die, make sure we set our fear to 0 and restore our palette
		else if(health <= 0)
		{
			if(currentFear != 0)
			{
				A_SetTranslation("restore");
				currentFear = 0;
			}
			isAfraid = false;
		}
		//Otherwise, just set currentFear to 0 because we don't want to accidentally trip anything
		else
		{
			currentFear = 0;
		}

		//We could do this with an event handler but this actually saves framerate :^
		if(mo_fearAccumulation > 0)
		{
			BabelLib.InflictFear(self, "BabelMonster", mo_fearAccumulation);
			currentFear += mo_fearAccumulation;
			mo_fearAccumulation = 0;
		}
		super.Tick();
	}

	//Handle new pain sounds
	action void A_BabelPain()
	{
		if(CVar.FindCVar("egr_new_painsounds").GetInt() == 1)
		{
			A_PlaySound(invoker.customPainSound, CHAN_VOICE);
			return;
		}
		else
		{
			A_PlaySound(PainSound, CHAN_VOICE);
		}
	}

	action void A_SetCustomPainSound(string s)
	{
		invoker.customPainSound = s;
	}

	//called when exiting fear
	void ResetGroupFear()
	{
		groupFearFrameDelay[0] = 0;
		groupFearFrameDelay[1] = 0;
	}

	void GroupFearStagger()
	{
		//initialize
		if(groupFearFrameDelay[0] == 0)
		{
			groupFearFrameDelay[0] = random(1, 35); //choose a random frame delay
			groupFearFrameDelay[1] = groupFearFrameDelay[0]; //store it
		}
		//decrement counter
		groupFearFrameDelay[1] = groupFearFrameDelay[1] - 1;
		//When we reach the end, reduce fear then re-initialize
		if(groupFearFrameDelay[1] <= 0)
		{
			//Reduce fear by an amount proportional to how long we waited and our falloff
			GroupFearReduction(fearRecovery * groupFearFrameDelay[0], self.GetClassName());
			groupFearFrameDelay[0] = 0;
		}
	}

	void GroupFearReduction(float groupFallOff, string className, int maxDist = 2048)
	{
		//Basic monster iteration
		ThinkerIterator targetFinder = ThinkerIterator.Create(className);
		BabelMonster other;
		while(other = BabelMonster(targetFinder.Next()))
		{
			if(self == other || !other.hasFear)
			{
				continue;
			}

			if(!BabelLib.CoarseDistanceCheck(self, other, maxDist) || other.health <= 0)
			{
				continue;
			}

			if(!self.CheckSight(other))
			{
				continue;
			}

			if(other.isAfraid && other.currentFear > 0)
			{
				other.currentFear -= groupFallOff;
			}
		}
	}
	//using the "state" keyword you can make your own jump functions. This one jumps if the monster is afraid
	state JumpIfAfraid(StateLabel statelabel)
	{
		if(isAfraid)
		{
			return ResolveState(statelabel);
		}
		return ResolveState(null);
	}
	action state A_JumpIfAfraid(StateLabel statelabel)
	{
		return invoker.JumpIfAfraid(statelabel);
	}
	//This jumps if the monster has a fear level above 0
	state JumpIfHasFear(StateLabel statelabel)
	{
		if(currentFear > 0)
		{
			return ResolveState(statelabel);
		}
		return ResolveState(null);
	}
	action state A_JumpIfAnyFear(StateLabel statelabel)
	{
		return invoker.JumpIfHasFear(statelabel);
	}
	//Jumps to the specified state if the monster cannot see its target
	state JumpIfCannotSee(StateLabel statelabel)
	{
		if(!self.CheckIfTargetInLOS())
		{
			return ResolveState(statelabel);
		}
		return ResolveState(null);
	}
	action state A_JumpIfCannotSeeTarget(StateLabel statelabel)
	{
		return invoker.JumpIfCannotSee(statelabel);
	}
	//Jumps to the specified state if the monster cannot draw a clear line to its target
	state JumpIfCannotTarget(StateLabel statelabel)
	{
		if(!self.CheckIfTargetInLOS() || !self.CheckLOF())
		{
			return ResolveState(statelabel);
		}
		return ResolveState(null);
	}
	action state A_JumpIfCannotShootTarget(StateLabel statelabel)
	{
		return invoker.JumpIfCannotTarget(statelabel);
	}
	//Called every frame while afraid to show that we're afraid
	void DisplayFearEffect()
	{
		//Get the user's specified fear display method
		int fearVersion = CVar.FindCvar("egr_fear_indicators").GetInt();
		if(fearVersion == 1) //Particles
		{
			int density = CVar.FindCvar("egr_particle_density").GetInt(); //avoid recomputation
			for(int i = 0; i < density+1; i++)
			{
				Color purple = Color(255, 230+random(-25, 25), 48+random(0, 80), 230+random(-25, 25)); //generate a random shade of purple
				A_SpawnParticle (purple, SPF_FULLBRIGHT, random(20, 50), random(1,4), frandom(0,360), Radius*cos(random(0,360)),Radius*sin(random(0,360)), frandom(0, 40.0), 0,0,frandom(1.0,2.0), frandom(-0.05, 0.05),frandom(-0.05, 0.05),0, 0.98, -1);
			}
		}
		else if(fearVersion == 2) //Faces
		{
			if(random(0, 1) == 0)
			{
				A_SpawnItemEx("fearAuraFace", Radius*cos(random(0,360)),Radius*sin(random(0,360)), frandom(0, Height));
			}
		}
		else if(fearVersion == 3) //Colorize
		{
			A_SetTranslation("afraid");
		}
		else if(fearVersion == 4) //Icon
		{
			A_SpawnItemEx("fearIcon", 0,0,Height+8,0,0,0,0, SXF_SETMASTER);
		}
		return;
	}

	void DisplayFearResistEffect()
	{
		//STUBBED: Frankly this just isn't intelligible in combat
		//Get the user's specified fear display method
		/*
		int fearVersion = CVar.FindCvar("egr_fear_indicators").GetInt();
		if(fearVersion == 1) //Particles
		{
			FearResistParticles();
		}
		else if(fearVersion == 2) //Faces
		{
			//if(random(0, 1) == 0)
				//A_SpawnItemEx("fearAuraFace", Radius*cos(random(0,360)),Radius*sin(random(0,360)), frandom(0, Height));
		}
		else if(fearVersion == 3) //Colorize
		{
			//A_SetTranslation("afraid");
		}
		else if(fearVersion == 4) //Icon
		{
			//A_SpawnItemEx("fearIcon", 0,0,Height+8,0,0,0,0, SXF_SETMASTER);
		}*/
		return;
	}

	//Similar to DisplayFearEffect() but for combo indicators
	void DisplayComboEffect()
	{
		if(CVar.FindCVar("egr_combo_indicators").GetInt() == 1)
		{
			ComboParticles();
		}
		else if(CVar.FindCVar("egr_combo_indicators").GetInt() == 2)
		{
			ComboIcon();
		}
	}

	//Shoots out some teal particles in a ring around a monster
	void ComboParticles()
	{
		int density = CVar.FindCvar("egr_particle_density").GetInt();//avoid recomputation
		for(double i = 0; i < 360.0; i+=(40/(density+1)))
		{
			int lifetime = random(20, 50);
			double speed = frandom(1.0, 3.0);
			Color particleColor = Color(255, 66+random(-10, 10), 244+random(-10, 10), 176+random(-10, 10));
			A_SpawnParticle (
				particleColor, 			   //color
				SPF_FULLBRIGHT, 		   //flags
				lifetime, 				   //lifetime
				random(1,4),    		   //size
				frandom(0,360),			   //angle

				Radius*cos(i),			   //xpos
				Radius*sin(i),			   //ypos
				frandom(0, 10.0),          //zpos

				speed*cos(i), 	 	       //xvel
				speed*sin(i), 			   //yvel
				frandom(1.0,2.0),          //zvel

				frandom(-0.05, 0.05),      //xacc
				frandom(-0.05, 0.05),      //yacc
				0, 						   //zacc

				0.98, 					   //startalpha
				-1);					   //fadespeed
		}
	}

	//Shoots out some purple particles in a ring around a monster
	void FearResistParticles()
	{
		int density = CVar.FindCvar("egr_particle_density").GetInt();//avoid recomputation
		for(double i = 0; i < 360.0; i+=(40/(density+1)))
		{
			int lifetime = random(20, 50);
			double speed = frandom(1.0, 3.0);
			Color particleColor = Color(255, 230+random(-25, 25), 48+random(0, 80), 230+random(-25, 25));
			A_SpawnParticle (
				particleColor, 			   //color
				SPF_FULLBRIGHT, 		   //flags
				lifetime, 				   //lifetime
				random(1,4),    		   //size
				frandom(0,360),			   //angle

				Radius*cos(i),			   //xpos
				Radius*sin(i),			   //ypos
				frandom(0, 10.0),          //zpos

				speed*cos(i), 	 	       //xvel
				speed*sin(i), 			   //yvel
				frandom(1.0,2.0),          //zvel

				frandom(-0.05, 0.05),      //xacc
				frandom(-0.05, 0.05),      //yacc
				0, 						   //zacc

				0.98, 					   //startalpha
				-1);					   //fadespeed
		}
	}

	//Displays an icon over a monster's head
	void ComboIcon()
	{
		let icon = Spawn("syncIcon", (pos.x, pos.y, pos.z+height+8));
		icon.master = self;
	}

	//To save space in monsters
	//Gives monsters of the specified type the specified amount of fear
	void ScareOthers(class<BabelMonster> targetType, int amount, int maxDist = 2048)
	{
		BabelLib.InflictFear(self, targetType, amount, maxDist);
	}

	action void A_InflictFear(class<BabelMonster> targetType, int amount, int maxDist = 2048)
	{
		invoker.ScareOthers(targetType, amount, maxDist);
	}

	//Sees if self can see a source of fear, and if it can it adds the specified amount of fear
	void TryFear(Actor other, int amount, int maxDist = 2048)
	{
		if(self == other)
		{
			return;
		}
		if(!self.CheckSight(other) || !BabelLib.CoarseDistanceCheck(self, other, maxDist))
		{
			return;
		}
		self.AddFear(amount);
	}

	//Adds fear if the monster is in a state where that is appropriate
	void AddFear(int amount)
	{
		if(hasFear && health > 0)
		{
			currentFear += amount;
		}
	}

	action void A_AddFear(int amount)
	{
		invoker.AddFear(amount);
	}

	//Attempts to trigger all monsters of the specified type within the specified distance to Sync fire
	void TriggerSync(class<SyncEnabledMonster> targetType, double chance, int maxDist = 2048)
	{
		if(!wokenUpFully)
		{
			return;
		}
		ThinkerIterator targetFinder = ThinkerIterator.Create(targetType);
		SyncEnabledMonster other;
		while(other = SyncEnabledMonster(targetFinder.Next()))
		{
			if(self == other)
			{
				continue;
			}
			if(!self.CheckSight(other) || !BabelLib.CoarseDistanceCheck(self, other, maxDist))
			{
				continue;
			}
			other.SyncFire(self.target, chance);
		}
	}

	action void A_TriggerSync(class<SyncEnabledMonster> targetType, double chance, int maxDist = 2048)
	{
		invoker.TriggerSync(targetType, chance, maxDist);
	}

	//Attempts to trigger all monsters of the specified type within the specified distance to Chain fire
	void TriggerChain(class<ChainEnabledMonster> targetType, double chance, int maxDist = 2048)
	{
		if(!wokenUpFully)
		{
			return;
		}
		ThinkerIterator targetFinder = ThinkerIterator.Create(targetType);
		ChainEnabledMonster other;
		while(other = ChainEnabledMonster(targetFinder.Next()))
		{
			if(self == other)
			{
				continue;
			}
			if(!self.CheckSight(other) || !BabelLib.CoarseDistanceCheck(self, other, maxDist))
			{
				continue;
			}
			other.ChainFire(self.target, chance);
		}
	}

	action void A_TriggerChain(class<ChainEnabledMonster> targetType, double chance, int maxDist = 2048)
	{
		invoker.TriggerChain(targetType, chance, maxDist);
	}

	//Used exclusively for Cacodemons, triggers their dodge state
	void TriggerDodge(int maxDist = 2048)
	{
		ThinkerIterator targetFinder = ThinkerIterator.Create("BabelCacodemon");
		BabelCacodemon other;
		while(other = BabelCacodemon(targetFinder.Next()))
		{
			if(self == other)
			{
				continue;
			}
			if(!self.CheckSight(other) || !BabelLib.CoarseDistanceCheck(self, other, maxDist))
			{
				continue;
			}
			other.Dodge();
		}
	}

	action void A_TriggerCacoDodge(int maxDist = 2048)
	{
		invoker.TriggerDodge(maxDist);
	}

	//These are just nice function calls for one-line changes to bools to save space and improve readability
	void HoldState()
	{
		holdingState = true;
	}

	action void A_HoldState()
	{
		invoker.HoldState();
	}

	void ReleaseState()
	{
		holdingState = false;
	}

	action void A_ReleaseState()
	{
		invoker.ReleaseState();
	}

	//Oh god don't even get me started on these. Don't use these, seriously.
	double PredictPlayerMovement(double shotspeed)
	{
		if(self.target && self.target.Player)
		{
			return PredictMovementAim(self.target, shotspeed);
		}
		return 0;
	}

	double PredictPlayerMovementRough(double shotspeed)
	{
		if(self.target && self.target.Player)
		{
			return PredictMovementAimRough(self.target, shotspeed);
		}
		return 0;
	}

	int GetAngleQuadrant(double angle)
	{
		angle = angle % 360;
		if(angle >= 0 && angle <= 90)
		{
			return 1;
		}
		if(angle > 90 && angle <= 180)
		{
			return 2;
		}
		if(angle > 180 && angle <= 270)
		{
			return 3;
		}
		if(angle > 270 && angle <= 360)
		{
			return 4;
		}
		return 0;
	}

	int Sign(double f)
	{
		if(f >= 0) //0 has no sign but FUUUCK YOU
		{
			return 1;
		}
		else
		{
			return -1;
		}
	}

	//This is a formula to predict how to lead a shot of a given speed to hit a moving player.
	//Except I never use it because the function below it works just fine for smaller (read: Standard Doom) ranges
	//If you want to learn how this works, google about it a bit
	double PredictMovementAim(Actor other, double myShotSpeed)
	{
		if(other.vel.x != 0.0 && other.vel.y != 0.0)
		{
			Vector2 totarget =  self.Vec2To(other);
			Vector2 myPos = (self.pos.x, self.pos.y);
			Vector2 otherVel = (other.vel.x, other.vel.y);
			Vector2 otherPos = (other.pos.x, other.pos.y);

			double a = (otherVel dot otherVel) - (myShotSpeed * myShotSpeed);
			double b = 2 * (otherVel dot totarget);
			double c = (totarget dot totarget);

			double p = -b / (2 * a);
			double q = sqrt((b * b) - 4 * a * c) / (2 * a);

			double t1 = p - q;
			double t2 = p + q;
			double t;

			if (t1 > t2 && t2 > 0)
			{
				t = t2;
			}
			else
			{
				t = t1;
			}

			Vector2 aimSpot = otherPos + (otherVel * t);
			Vector2 bulletPath = aimSpot - myPos;

			double otherAngle = BabelLib.atan2(bulletPath);//.y/bulletPath.x);
			return otherAngle-self.angle;
		}
		return 0;
	}

	/*
	Ok, so this is *DEFINITELY* not the proper formula for this but it's a really good approximation for faster projectiles
	*/
	double PredictMovementAimRough(Actor other, double myShotSpeed)
	{
		//Determine if we even need to adjust our aim
		if(other.vel.x != 0.0 && other.vel.y != 0.0)
		{
			double myAngle = self.angle % 360.0;
			double otherSpeed = other.vel.Length();
			double vx, vy;
			vx = other.vel.x;
			vy = other.vel.y;

			//get a (-90, 90) angle
			double otherAngle = atan(vy/vx);

			//correct it to (0, 360) using quadrants
			if(otherAngle >= 0 && vy <= 0) //3
			{
				otherAngle = (otherAngle+180)%360;
			}
			else if(otherAngle <= 0 && vy >= 0) //2
			{
				otherAngle = (otherAngle+180)%360;
			}
			else if(otherAngle <= 0 && vy <= 0) //4
			{
				otherAngle = (360+otherAngle)%360;
			}

			double component = otherSpeed*cos(otherAngle-(myAngle+90)); //get the perpendicular component
			double aimAngle = acos(myShotSpeed/sqrt((myShotSpeed*myShotSpeed)+(component*component))); //determine angle offset to make an intercept shot
			if(component < 0) //use the sign of the component on the aim angle
			{
				aimAngle = -aimAngle;
			}
			return aimAngle;
		}
		return 0;
	}
}

//This class can perform a state switch using Sync Firing. Should contain a MissileNoSignal state which does not send a sync signal anywhere down its progression.
class SyncEnabledMonster : BabelMonster
{
	//Triggers the monster's MissileNoSignal state if it isn't afraid or holding its state, and succeeds a roll
	void SyncFire(Actor newTarget, double chance = 0)
	{
		if(health > 0)
		{
			if(!holdingState && !isAfraid)
			{
				if(frandom(0.0, 100.0) <= chance)
				{
					if(!wokenUpFully)
					{
						//Console.Printf("Tried to sync too early!");
						return;
					}
					if(newTarget != null && newTarget.GetSpecies() != self.GetSpecies())
					{
						self.target = newTarget;
					}
					else
					{
						return;
					}
					if(checksLOF && (!self.CheckIfTargetInLOS() || !self.CheckLOF()))
					{
						return;
					}
					if(checksLOS && !self.CheckIfTargetInLOS())
					{
						return;
					}
					DisplayComboEffect();
					SetState(ResolveState("MissileNoSignal"));
				}
			}
		}
	}
}

//This class can perform a chain attack. Its missile state should send a signal once it terminates.
class ChainEnabledMonster : BabelMonster
{
	//Triggers the monster's Missile state if it isn't afraid or holding its state, and succeeds a roll
	void ChainFire(Actor newTarget, double chance = 0)
	{
		if(health > 0)
		{
			if(!holdingState && !isAfraid)
			{
				if(frandom(0.0, 100.0) <= chance)
				{
					if(!wokenUpFully)
					{
						//Console.Printf("Tried to chain too early!");
						return;
					}
					if(newTarget != null && newTarget.GetSpecies() != self.GetSpecies() && newTarget.health > 0)
					{
						self.target = newTarget;
					}
					else
					{
						return;
					}
					if(checksLOF && (!self.CheckIfTargetInLOS() || !self.CheckLOF()))
					{
						return;
					}
					if(checksLOS && !self.CheckIfTargetInLOS())
					{
						return;
					}
					DisplayComboEffect();
					SetState(ResolveState("Missile"));
				}
			}
		}
	}
}