//Literally just a collection of useful functions
class BabelLib play
{
	//basic function to inflict fear, should normally always target BabelMonster except for special effects
	//Only static because some non-BabelMonsters use it, like weapons
	static void InflictFear(Actor me, class<BabelMonster> targetType, double amount, int maxDist = 2048)
	{
		if(amount == 0)
			return;
		amount = amount * 0.45 * CVar.FindCVar("egr_mo_fearscale").GetFloat(); //0.45 is a magic number determined from earlier versions
		//Console.Printf("Inflicting %f fear.", amount);
		ThinkerIterator targetFinder = ThinkerIterator.Create(targetType);
		BabelMonster other;
		//int time = MSTime();
		//Console.Printf("Inflictor Co-Ords: %f %f %f", me.pos.x, me.pos.y, me.pos.z);
		while(other = BabelMonster(targetFinder.Next()))
		{
			//if(!other.CheckClass(targetType, AAPTR_DEFAULT, true) || other.health <= 0) deprecated since 3.7.0
			if(!(other.GetClass() is targetType) || other.health <= 0)
			{
				continue;
			}

			//Console.Printf("Monster Co-Ords: %f %f %f", other.pos.x, other.pos.y, other.pos.z);
			other.TryFear(me, int(amount), int(maxDist));
		}
		//Console.Printf("Scaring monsters took %i ms", MSTime()-time);
	}

	static bool CoarseDistanceCheck(Actor source, Actor destination, double distance)
	{
		//coarse square check to reduce all monsters outside
		if(abs(source.pos.x - destination.pos.x) > distance || abs(source.pos.y - destination.pos.y) > distance)
		{
			return false;
		}
		//coarse square check to return true for monsters inside
		//1/sqrt(2) ~= 0.707 but we'll use 0.7 to speed stuff up
		if(abs(source.pos.x - destination.pos.x) < (distance*0.7) && abs(source.pos.y - destination.pos.y) < (distance*0.7))
		{
			return true;
		}
		//then do the real check. This case is slightly slower to reach than just doing it outright but considering how many cases will be handled by coarse checks it's ok
		return (source.Vec2To(destination).Length() <= distance);
	}

	static double Lerp(double val1, double val2, double ratio)
	{
		return (1 - ratio) * val1 + ratio * val2;
	}

	static bool FloorIsLiquid(Actor checker)
	{
		static const String liquidTextures[] =
		{
			"NUKAGE1", "NUKAGE2", "NUKAGE3", "BLOOD1",
			"BLOOD2", "BLOOD3", "LAVA1", "LAVA2",
			"LAVA3", "LAVA4", "SLIME01", "SLIME02",
			"SLIME03", "SLIME04", "SLIME05", "SLIME06",
			"SLIME07", "SLIME08", "FWATER1", "FWATER2",
			"FWATER3", "FWATER4"
		};

		for (int i = 0; i <= 21; i++)
		{
			TextureID tx = TexMan.CheckForTexture(liquidTextures[i], TexMan.Type_Flat);
			//Console.Printf("%s", liquidTextures[i]);
			if (tx && checker.floorpic == tx)
			{
				return true;
			}
		}
		return false;
	}

	static int LiquidFloorType(Actor checker)
	{
		static const String liquidTextures[] =
		{
			"NUKAGE1", "NUKAGE2", "NUKAGE3", "BLOOD1",
			"BLOOD2", "BLOOD3", "LAVA1", "LAVA2",
			"LAVA3", "LAVA4", "SLIME01", "SLIME02",
			"SLIME03", "SLIME04", "SLIME05", "SLIME06",
			"SLIME07", "SLIME08", "FWATER1", "FWATER2",
			"FWATER3", "FWATER4"
		};

		for (int i = 0; i <= 21; i++)
		{
			TextureID tx = TexMan.CheckForTexture(liquidTextures[i], TexMan.Type_Flat);
			if (tx && checker.floorpic == tx)
			{
				if(i >= 0 && i < 3)
				{
					return 0;
				}
				else if (i >= 3 && i < 6)
				{
					return 1;
				}
				else if (i >= 6 && i < 10)
				{
					return 2;
				}
				else if (i >= 10 && i < 18)
				{
					return 3;
				}
				else if (i >= 18 && i < 22)
				{
					return 4;
				}
			}
		}
		return -1;
	}

	static Color GetFloorTypeColor(int floorType)
	{
		Color c;
		if(floorType == 0) //nukage
		{
			float brightness = frandom(0.0, 1.0);
			c = Color(255, 11+(int)(75*brightness), 23+(int)(136*brightness), 7+(int)(56*brightness));
		}
		else if(floorType == 1) //blood
		{
			c = Color(255, 71+random(0, 68), 0, 0);
		}
		else if(floorType == 2) //lava
		{
			if(random(0,1) == 0) //orange
			{
				float brightness = frandom(0.0, 1.0);
				c = Color(255, 115+(int)(100*brightness), 43+(int)(144*brightness), 0);
			}
			else //red
			{
				c = Color(255, 71+random(0, 24), 0, 0);
			}
		}
		else if(floorType == 3) //slime
		{
			float brightness = frandom(0.0, 1.0);
			c = Color(255, 31+(int)(76*brightness), 23+(int)(48*brightness), 11+(int)(28*brightness));
		}
		else if(floorType == 4) //water
		{
			c = Color(255, 0, 0, 23+random(0, 155));
		}
		return c;
	}

	static String GetFloorTypeSound(int floorType)
	{
		String s = "";
		if(floorType == 0) //nukage
		{
			s = "liquid/burn";
		}
		else if(floorType == 1) //blood
		{
			s = "liquid/splash";
		}
		else if(floorType == 2) //lava
		{
			s = "liquid/sizzle";
		}
		else if(floorType == 3) //slime
		{
			s = "liquid/splash";
		}
		else if(floorType == 4) //water
		{
			s = "liquid/splash";
		}
		return s;
	}

	//Gets the current camera offset due to view bobbing
	static double BobOffset()
	{
		return (players[consoleplayer].mo.pos.z + players[consoleplayer].viewheight - players[consoleplayer].viewz);
	}

	static double VerticalInterpolationOffset(double originalViewHeight)
	{
		return (players[consoleplayer].viewz-players[consoleplayer].mo.pos.z-originalViewHeight);
	}

	static double atan2(Vector2 d)
	{
		return VectorAngle(d.x, d.y);
	}

	static double CorrectFlatAtanAngle(Vector2 direction)
	{
		double angle = atan(direction.y/direction.x);
		//correct it to (0, 360) using quadrants
		if(angle >= 0 && direction.y <= 0) //3
			angle = (angle+180)%360;
		else if(angle <= 0 && direction.y >= 0) //2
			angle = (angle+180)%360;
		else if(angle <= 0 && direction.y <= 0) //4
			angle = (360+angle)%360;

		return angle;
	}

	//Setting player health is surprisingly annoying
	static void SetPlayerHealth(int newAmount)
	{
		//Why does it have to be like this? Fuck you that's why
		let p = players[consoleplayer].mo;
		p.player.health = newAmount;
		p.health = newAmount;
	}
}