// This script contains useful game specific utility functions.

///////////////////////////////////////////////////////////////////////////////
// Set up some core utility module with game specific stuff.
Util.PlayerClassTable =
{
	CLASS.SOLDIER,
	CLASS.MEDIC,
	CLASS.ENGINEER,
	CLASS.FIELDOPS,
	CLASS.COVERTOPS,
};
if (GetModName() == "etblight")
{
	Util.PlayerClassTable[5] = CLASS.SCIENTIST;
	Util.PlayerClassTable[6] = CLASS.SUPER_SOLDIER;
}

Util.SniperClass = CLASS.COVERTOPS;

// offset for converted waypoint goals
Util.WaypointGoalOffset = Vec3(0,0,-24);

Util.AllTeams = (1 << TEAM.AXIS) | (1 << TEAM.ALLIES);

Util.PrimaryRouteGoalQuery = { 0xc39bf2a3 /* BUILD */, 0xbbcae592 /* PLANT */, 0xbdeaa8d7 /* FLAG */, 0xf7e4a57f /* CHECKPOINT */ };

// debug string color
Util.DebugColorString = "^b";


///////////////////////////////////////////////////////////////////////////////
// these values are for makegm.gm

Util.FlagCarrier = "CLASS.ENGINEER";
Util.Sniper = "CLASS.COVERTOPS";

Util.HaveTeam = { false, true, true, false, false };

Util.TeamNames =
{
	{ "", "", "" },
	{ "AXIS", "Axis", "axis" },
	{ "ALLIES", "Allies", "allies" },
};

///////////////////////////////////////////////////////////////////////////////
// these values are for testmap.gm

Util.NoTimeLimit = "timelimit 0";
Util.TimeLimit1 = "timelimit 1";

Util.BotNames =
{
	{
		"", "", "", "",
		"", "",
	},
	{
		"", "axis_soldier", "axis_medic", "axis_engineer",
		"axis_fieldops", "axis_covertops",
	},
	{
		"", "allies_soldier", "allies_medic", "allies_engineer",
		"allies_fieldops", "allies_covertops",
	},
};

///////////////////////////////////////////////////////////////////////////////
// store time at start of map since GetTime() is server uptime. set in PostMapLoad
member MapStartTime = 0.0;

member GetTimeElapsed = function()
{
	t = GetTime();
	return t - ETUtil.MapStartTime;
};

///////////////////////////////////////////////////////////////////////////////
//
member CheckVehiclePath = function( _params )
{
	if (this.CheckVehiclePathFlag)
	{
		this.CheckVehiclePathFlag = null;
		return;
	}

	if ( !Util.CheatsEnabled() ) {
		return;
	}

	goalname = _params[ 0 ];
	goal = Util.GetGoal( goalname, "ETUtil.CheckVehiclePath" );
	if ( !goal )
	{
		return;
	}
	if (goal.GoalType != "BUILD")
	{
		print( "CheckVehiclePath: goal type is not BUILD" );
		return;
	}

	goalname2 = goal.Vehicle;
	if (!goalname2)
	{
		print( "CheckVehiclePath: Goal", goalname, "has no vehicle property (MOVER goal)" );
		return;
	}
	goal2 = Util.GetGoal( goalname2, "ETUtil.CheckVehiclePath" );
	if ( !goal2 )
	{
		return;
	}

	if ( goal.IsAvailable( TEAM.ALLIES ) )
	{
		print("Allies objective");
		team = TEAM.ALLIES;
		botname = "allies_engineer";
	}
	else if ( goal.IsAvailable( TEAM.AXIS ) )
	{
		print("Axis objective");
		team = TEAM.AXIS;
		botname = "axis_engineer";
	}
	else
	{
		print( "CheckVehiclePath: Goal:", goalname, "not available" );
		return;
	}

	this.CheckVehiclePathFlag = true;
	AddBot( team, CLASS.ENGINEER, botname );

	entity = goal2.GetEntity();

	sleeptime = _params[ 1 ];
	if ( !sleeptime )
		{ sleeptime = 2; }

	print( "Started checking vehicle path for:", goalname2 );

	while ( this.CheckVehiclePathFlag )
	{
		EntityKill( entity );

		yield();
		SetAvailableMapGoals( team, false, ".*" );
		SetAvailableMapGoals( team, true, { goalname, goalname2 } );

		while ( Util.GetEntHealth(entity) <= 0 && this.CheckVehiclePathFlag )
		{
			yield();
		}

		if ( !this.CheckVehiclePathFlag )
			{ break; }

		sleep( sleeptime );
	}

	print( "Finished checking vehicle path for:", goalname2 );
};

///////////////////////////////////////////////////////////////////////////////
//
member BuildVehiclePath = function( _params )
{
	if (this.BuildVehiclePathFlag)
	{
		this.BuildVehiclePathFlag = null;
		return;
	}

	if ( !Util.CheatsEnabled() )
	{
		return;
	}

	goalname = _params[ 0 ];
	goal = Util.GetGoal( goalname, "ETUtil.BuildVehiclePath" );
	if ( !goal ) {
		return;
	}
	if (goal.GoalType != "MOVER")
	{
		print( "BuildVehiclePath: goal type is not MOVER" );
		return;
	}

	entity = goal.GetEntity();

	this.BuildVehiclePathFlag = true;

	sleeptime = _params[ 1 ];
	if ( !sleeptime )
		{ sleeptime = 2; }

	print( "Started build vehicle path for:", goalname );

	while ( this.BuildVehiclePathFlag )
	{
		EntityKill( entity );

		while ( Util.GetEntHealth(entity) <= 0 && this.BuildVehiclePathFlag )
		{
			yield();
		}

		if ( !this.BuildVehiclePathFlag )
			{ break; }

		sleep( sleeptime );
	}

	print( "Finished buidling vehicle path for:", goalname );
};

///////////////////////////////////////////////////////////////////////////////
//
member CountClass = function( team, class )
{
	count = 0;
	foreach ( bot in BotTable )
	{
		if ( bot.GetTeam() == team && bot.GetClass() == class )
			{ ++count; }
	}

	return count;
};

///////////////////////////////////////////////////////////////////////////////
//
member NoSnipeWeapons =
{
	WEAPON.STEN,
	WEAPON.FG42,
};

///////////////////////////////////////////////////////////////////////////////
//
member SetPrimaryGoals = function(priority)
{
	SetGoalPriority( "CAPPOINT_.*", priority );
	SetGoalPriority( "PLANT_.*", priority - 0.08 );
	SetGoalPriority( "CHECKPOINT_.*", priority - 0.1 );
	SetGoalPriority( "FLAG_.*", priority - 0.15, 0, 0, true );
	SetGoalPriority( "FLAGRETURN_.*", priority - 0.05, 0, 0, true  );
};

///////////////////////////////////////////////////////////////////////////////
member SelectWeapon = function(bot, w)
{
	if (!Map.DontSelectWeapons) {
		// goal_selectweapons changes weapon every spawn
		Util.MapDeprecateMessage( "ETUtil.SelectWeapon", "WeaponTable.SetWeaponAvailability" );
		return;
	}
	tbl = WeaponTable.GetPrimaryWeapons( bot.GetTeam(), bot.GetClass() );
	if (!tbl || !IsNull(tbl[w]))
	{
		bot.ChangePrimaryWeapon( w );
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member SwitchWeapon = function( w, team )
{
	if (!Map.DontSelectWeapons) {
		Util.MapDeprecateMessage( "ETUtil.SwitchWeapon", "WeaponTable.SetWeaponAvailability" );
		return;
	}
	foreach ( bot in BotTable )
	{
		if ( team && bot.GetTeam() != team )
		{
			continue;
		}

		this.SelectWeapon( bot, w );
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member SetCabinets = function()
{
	Util.MapDebugPrint( "ETUtil.SetCabinets is deprecated", 1 );
};

///////////////////////////////////////////////////////////////////////////////
//
member SetDualObjective = function( bot )
{
	team = bot.GetTeam();

	if ( !Map.Defend )
		{ Map.Defend = {}; }

	// counters for number of users
	if ( !Map.Defend.AlliesDefenders )
		{ Map.Defend.AlliesDefenders = 0; }

	if ( !Map.Defend.AxisDefenders )
		{ Map.Defend.AxisDefenders = 0; }

	// some default values
	if ( !Map.Defend.MaxBots )
		{ Map.Defend.MaxBots = 4; }

	if ( team == TEAM.ALLIES  )
	{
		if (IsNull(this.numallies)) { this.numallies = 0; }
		++this.numallies;

		if ( Map.Defend.AlliesDefenders < Map.Defend.MaxBots ||  Map.Defend.AlliesDefenders < Map.Defend.MaxAlliesDefenders )
		{
			if ( this.numallies % 2 == 0 )
			{
				bot.SetRoles(ROLE.DEFENDER);

				//first bot defending will switch to crucial class
				if ( Map.Defend.AlliesDefenders == 0 && Map.Defend.CrucialClass )
					{ bot.ChangeClass(Map.Defend.CrucialClass); }

				if ( Map.Defend.AlliesSpawnPt )
					{ bot.ChangeSpawnPoint( Map.Defend.AlliesSpawnPt ); }

				++Map.Defend.AlliesDefenders;
			}
		}
	}

	if ( team == TEAM.AXIS  )
	{
		if (IsNull(this.numaxis)) { this.numaxis = 0; }
		++this.numaxis;

		if ( Map.Defend.AlliesDefenders < Map.Defend.MaxBots ||  Map.Defend.AlliesDefenders < Map.Defend.MaxAxisDefenders )
		{
			if ( this.numaxis % 2 == 0 )
			{
				bot.SetRoles(ROLE.DEFENDER);

				//first bot defending will switch to crucial class
				if ( Map.Defend.AxisDefenders == 0 && Map.Defend.CrucialClass )
					{ bot.ChangeClass(Map.Defend.CrucialClass); }

				if ( Map.Defend.AxisSpawnPt )
					{ bot.ChangeSpawnPoint( Map.Defend.AxisSpawnPt ); }

				++Map.Defend.AxisDefenders;
			}
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member TraceArty = function(usePlayer)
{
	radius = 200;
	playerEnt = GetLocalEntity();
	playerPos = GetEntPosition(playerEnt);
	foundGoal = null;
	sourceOffset = Vec3(0,0,60); // 60 is what the goal uses

	if ( !usePlayer )
	{
		dist = radius;
		foreach ( mapgoal in Util.GoalTable("CALLART.*") )
		{
			evalDist = DistanceBetween(mapgoal, playerPos);
			if ( evalDist < dist )
			{
				dist = evalDist;
				foundGoal = mapgoal;
			}
		}
	}

	if (foundGoal || usePlayer)
	{
		if ( !usePlayer )
		{
			print("ETUtil.TraceArty: Selected", foundGoal.GetName());
			sourcePos = foundGoal.GetPosition();
		}
		else
		{
			print("ETUtil.TraceArty: Tracing from", GetEntName(playerEnt), "^2");
			sourcePos = playerPos - Vec3(0,0,24);
		}

		targets = Util.GoalTable("ARTILLERY.*");
		if ( tableCount(targets) > 0 )
		{
			foreach ( targetgoal in targets )
			{
				targetPos = targetgoal.GetPosition();
				targetName = targetgoal.GetName();

				tr = TraceLine(sourcePos + sourceOffset, targetPos, null, TRACE.SHOT, playerEnt, false);
				if ( tr.fraction < 1 )
				{
					print("ETUtil.TraceArty: ^1No line of sight to", targetName, "^2");
					DrawDebugLine(sourcePos + sourceOffset, tr.end, COLOR.PINK, 30);
					DrawDebugLine(tr.end, targetPos, COLOR.RED, 30);
				}
				else
				{
					print("ETUtil.TraceArty: ^2Has line of sight to", targetName);
					DrawDebugLine(sourcePos + sourceOffset, targetPos, COLOR.GREEN, 30);
				}
			}
		}
		else
		{
			Error("ETUtil.TraceArty: No Arty Targets");
		}
	}
	else
	{
		Error("ETUtil.TraceArty: No Arty Goal Close Enough");
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member TraceAllArty = function()
{
	targets = Util.GoalTable("ARTILLERY.*");
	sourceOffset = Vec3(0,0,64);

	foreach( mapgoal in Util.GoalTable("CALLART.*") )
	{
		sourcePos = mapgoal.GetPosition();
		found = "";
		foreach (targetgoal in targets)
		{
			targetPos = targetgoal.GetPosition();
			tr = TraceLine(sourcePos + sourceOffset, targetPos, null, TRACE.SHOT, GetLocalEntity(), false);
			if ( tr.fraction == 1 )
			{
				found += targetgoal.GetName() + " ";
			}
		}

		if ( found == "" )
		{
			Error( mapgoal.GetName(), "has zero visible targets!, try running /bot fgp" );
		}
		else
		{
			print( mapgoal.GetName(), "has visible targets", found );
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member IsTeamDead = function(team, minbots, class)
{
	numalive = 0;
	// loop through the reserved player ents since there is no team table stored
	for ( i = 0; i < 64; ++i )
	{
		if (EntityIsValid(i) && GetEntTeam(i) == team
			&& !GetEntFlags(i, ENTFLAG.DEAD, ENTFLAG.LIMBO) && (!class || GetEntClass(i)==class))
		{
			++numalive;
			if (!minbots)
			{
				return false;
			}
		}
	}

	if ( (minbots && numalive < minbots) || numalive == 0 )
	{
		return true;
	}
	else
	{
		return false;
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member IsTeamClassDead = function(team, class)
{
	return this.IsTeamDead(team, 0, class);
};

///////////////////////////////////////////////////////////////////////////////
// used for setting start indexes for goals that are pathing to offsets
member SetStartIndex = function( goalName, startIndex )
{
	startIndex = ToInt(startIndex, -1);
	g = GetGoal(goalName);
	if (g && g.Offsets) {
		offsetCount = tableCount(g.Offsets);
		if ( startIndex > offsetCount -1 ) {
			startIndex = -1; // random
		}

		g.StartIndex = startIndex;
	}
	else {
		Util.MapDebugPrint("ETUtil.SetStartIndex: Invalid Goal: " + goalName, 2);
	}
};

///////////////////////////////////////////////////////////////////////////////
// used for excluding indexes for goals that are pathing to offsets
// use null or an empty table to re-enable all
member SetExcludeIndexes = function( goalName, excludeTable )
{
	g = GetGoal(goalName);
	if (g && g.Offsets) {
		// passing null or an empty table will enable all
		if ( IsNull(excludeTable) ) {
			excludeTable = {};
		}

		if ( IsTable(excludeTable) ) {
			g.ExcludeIndexes = excludeTable;
		}
		else {
			Util.MapDebugPrint("ETUtil.ExcludeIndexes: Expecting table, got " + typeName(excludeTable), 2);
		}
	}
	else {
		Util.MapDebugPrint("ETUtil.ExcludeIndexes: Invalid Goal: " + goalName, 2);
	}
};

///////////////////////////////////////////////////////////////////////////////
// find a usable corpse for the bot to steal uniform
member FindUsableCorpse = function(bot, range)
{
	pos = bot.GetPosition();
	ent = null;
	dowhile (ent && bot.GetTeam() == GetEntTeam(ent)) {
		ent = GetEntityInSphere(pos, range, CLASS.CORPSE, ent);
	}
	return ent;
};

///////////////////////////////////////////////////////////////////////////////
//
member SetAimModeForMount = function(goalName, aimType)
{
	goal = Util.GetGoal(goalName, "ETUtil.SetAimModeForMount");
	if ( goal ) {
		if ( aimType == "opposite"
			|| IsVec3(aimType)
			|| IsTable(aimType)
			|| aimType == "random"
			|| aimType == "velocity" )
		{
			goal.AimMode = aimType;
			goal.ExtraDebugText = null; // update the render string
		}
		else {
			Util.MapDebugPrint("ETUtil.SetAimModeForMount: invalid aimType: " + aimType, 2 );
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member SuicideSpawn = function( team, spawn, numbots, class )
{
	c = 0;
	b = 0;

	foreach ( bot in BotTable )
	{
		if ( bot.GetTeam() == team )
		{
			++b;

			// every other bot to avoid low bot count numbers spawning all at a point
			if ( numbots && numbots != -1 && (b % 2 == 0) ) {
				continue;
			}

			// is it class limited?
			if ( class && bot.GetClass() != class ) {
				continue;
			}

			++c;

			bot.ChangeSpawnPoint( spawn );
			thread(ETUtil.SuicideNextSpawn, bot);

			if ( numbots && numbots != -1 && c >= numbots )
				{ return; }
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member GetBotSideArm = function(bot)
{
	team = Util.TeamName(bot.GetTeam());

	if ( !Util.TeamSideArms[ team ] ) {
		return 0;
	}

	foreach ( id and weap in Util.TeamSideArms[ team ] ) {
		if ( bot.HasWeapon( weap ) ) {
			return weap;
		}
	}

	// shouldn't get here
	return 0;
};

///////////////////////////////////////////////////////////////////////////////
//
member GetBotPrimaryWeapon = function(bot, params)
{
	if (!params){
		params = { CheckCharged=false, CheckAmmo=false };
	}
	tbl = WeaponTable.GetPrimaryWeapons(bot.GetTeam(), bot.GetClass());
	if (tbl) {
		 weap = bot.HasAnyWeapon(tbl, params);
		 if (!weap) {
			 weap = bot.HasAnyWeapon(Util.IndexedSniperWeapons, params);
		 }
		 return weap;
	}
	return 0;
};

///////////////////////////////////////////////////////////////////////////////
//
member GetReinforceTime = function(team)
{
	foreach (bot in BotTable)
	{
		if (bot.GetTeam() == team)
		{
			return bot.GetReinforceTime();
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member WaitForSpawn = function(team)
{
	t = this.GetReinforceTime(team) - 0.15;
	if (t < 0) {
		sleep(0.15);
		t = this.GetReinforceTime(team) - 0.15;
	}
	if (t > 0) {
		sleep(t);
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member KillTeam = function(team, maxTime)
{
	if (maxTime)
	{
		t = this.GetReinforceTime(team);
		if (t > maxTime || t < 0.15) {
			return;
		}
	}
	thread( function(team)
	{
		ETUtil.WaitForSpawn(team);
		Util.KillTeam(team);
	}, team);
};

///////////////////////////////////////////////////////////////////////////////
// note: this should be called via thread
member SuicideNextSpawn = function(bot)
{
	ETUtil.WaitForSpawn(bot.GetTeam());
	if (bot.Name) {
		bot.ExecCommand("kill");
	}
};

///////////////////////////////////////////////////////////////////////////////
// some maps are set up for panzer wars
member PanzerMap = function( )
{
	this.IsPanzereMap = true;

	// reset the min class counts
	Util.InitializeClassTables();

	foreach ( name and teamId in TEAM )
	{
		if ( name != "SPECTATOR" )
		{
			Server.MinClassCount[teamId][CLASS.SOLDIER] = Server.MaxPlayers;
		}
	}

	foreach ( bot in BotTable )
	{
		this.ChangeToPanzer(bot); //also add to OnBotJoin
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member ChangeToPanzer = function( bot )
{
	if ( bot.GetClass() != CLASS.SOLDIER ) {
		bot.ChangeClass(CLASS.SOLDIER);
		yield();
	}
	if ( bot.GetTeam() == TEAM.ALLIES ) {
		bot.ChangePrimaryWeapon(WeaponTable.AlliedPanzer);
	} else {
		bot.ChangePrimaryWeapon(WEAPON.PANZERFAUST);
	}
};

///////////////////////////////////////////////////////////////////////////////
//
member DisableRifleNade = function( bot, disable )
{
	if (!bot ) { return; }

	foreach( weapon in { WEAPON.M7, WEAPON.GPG40 })
	{
		// get the weapon object
		w = bot.GetWeapon(weapon);
		if (!w) { Util.MapDebugPrint("ETUtil.DisableRifleNade: bot does not have rifle nade weapon", 2); continue; }

		// note: this will do nothing if weapon script sets default to 0
		// this is to avoid scripts like the plant goal stomping on user
		// defined settings.
		if ( disable ) {
			if ( w.PrimaryFire.DefaultDesirability > 0.1 ) {
				w.PrimaryFire.Bias = 0.0;
				w.PrimaryFire.SetDesirabilityRange(0, 2600, 0.0);
			}
		}
		else {
			if ( w.PrimaryFire.Bias < 0.1 ) {
				w.PrimaryFire.Bias = 1.0;
				w.PrimaryFire.SetDesirabilityRange(0, 2600, 0.8);
			}
		}
	}
};

///////////////////////////////////////////////////////////////////////////////
// randomly global chat some victory message
member WinningChat = function( team )
{
	Util.RandomChat(team, { VOICE.G_CHEER, VOICE.G_ENEMY_WEAK, VOICE.G_AFFIRMATIVE, VOICE.G_GOODGAME, VOICE.G_GREATSHOT });
};

// randomly global chat some displeasure about losing
member LosingChat = function( team )
{
	Util.RandomChat(team, { VOICE.G_NEGATIVE, VOICE.G_OOPS, VOICE.G_GOODGAME, VOICE.G_HOLD_FIRE });
};

///////////////////////////////////////////////////////////////////////////////
//
member CheckTripminePosition = function()
{
	// must be within 64 units of wall with some tolerance
	tr = Util.TraceFromPlayer(58, TRACE.SHOT);
	if ( tr.fraction >= 1.0 ) {
		return false;
	}

	// wire distance is max of 512 units
	tr2 = Util.TraceFromPlane(tr.end, tr.normal, 512);
	if ( tr2.fraction >= 1.0 ) {
		return false;
	}

	// original trace position is used for the goals aim position
	return tr.end;
};

///////////////////////////////////////////////////////////////////////////////

member CountTeam = function( team )
{
	Util.MapDeprecateMessage( "ETUtil.CountTeam", "Server.Team[ teamId ].NumBots" );
	return Server.Team[team].NumBots;
};

member ChangeClass = function( team, originalclass, newclass, revert, maxbots )
{
	Util.MapDeprecateMessage( "ETUtil.ChangeClass", "Server.MinClassCount" );
};
