/*
	RMQ FBX CTF AI - try saying that three times fast
	
	2009-10-12 Supa initial skeleton + initial team brain
	2009-10-13 Supa more work on team brains, starting work on event logic
	2009-10-14 Supa rearranged and renamed most events, added event hooks to CTF
	2009-10-15 Supa finished 1v1 logic, first actual implementation, bot_ctf.qc is now bigger than the actual CTF module by about 50% kb..
	2009-10-16~17 Supa made a few adjustments based on testing, flags now directly tell bots what to do now, adjusted item weighting, bots actually cap now although flag returns are completely broken
	2009-10-19 Supa fixed flag returns, fixed bots losing targets and general role issues
	2009-10-20 Supa de-broke target drops, bots should drop the right targets when their role changes now, this should be considered a beta now
	
	The general way this works is each bot is assigned a role by the team brain every so often, and events can change up those roles too.
	Basically roles will influence what the bot will pay attention to in priority_for_thing, each bot will (hopefully) ignore things that do not apply to its role.
	Keep in mind that roles can easily switch due to event logic (eg if a bot grabs the enemy flag, all remaining attackers are converted to escorts), so don't count on a bot holding a specific role for long
*/

//
// defs
//

float	IDLE		= 0,	// get a new role ASAP
		ATTACKER	= 1,	// attacker enemy flag
		DEFENDER	= 2,	// defend our flag
		MIDFIELDER	= 3,	// attack hostile attackers
		ESCORT		= 4,	// hang around flag carrier and attack hostile attackers
		CHASER		= 5,	// seek and destroy the EFC, return our flag when dropped
		CARRIER		= 6;	// get back to base ASAP
		
.float	ctf_role,		// stores the bot's current job
		ctf_roletime;	// and how long until we decide to eval the bot's current role

entity	teambrain_red,
		teambrain_blue;

/*entity	botlist_red_head,
		botlist_blue_head;

.entity	botlist_next,
		botlist_last;*/

void(float whichteam) AssignTeam_ReturnOurFlag;
void(float whichteam) AssignTeam_TakeDroppedEnemyFlag;
void(float whichteam) AssignTeam_DefendTheBase;

void(float whichteam) AssignHostiles_ReturnOurFlag;
void(float whichteam) AssignHostiles_AttackTheBase;
void(float whichteam) AssignHostiles_TakeDroppedEnemyFlag;
void(float whichteam) AssignHostiles_EscortFlagCarrier;

void() TeamBrain_Think;

void(float whichteam) Spawn_TeamBrain;


//
// utilities
//		
		
// list add/remove functions, called on botconnect and botdisconnect in CTF / CTFFF
void(float whichteam, entity whom) BotList_Add =
{
	dprint("BotList: Adding ");
	dprint(whom.netname);
	dprint("\n");
	
	if (whichteam == RED)
	{
		if (botlist_red_head)
			botlist_red_head.botlist_last = whom;
			
		whom.botlist_next = botlist_red_head;
	}
	else if (whichteam == BLUE)
	{
		if (botlist_blue_head)
			botlist_blue_head.botlist_last = whom;
			
		whom.botlist_next = botlist_blue_head;
	}

	whom.botlist_last = world;

	if		(whichteam == RED	)	botlist_red_head	= whom;
	else if (whichteam == BLUE	)	botlist_blue_head	= whom;
};


void(float whichteam, entity whom) BotList_Remove =
{
	dprint("BotList: \bRemoving\b ");
	dprint(whom.netname);
	dprint("\n");
	
	// If whom is the head of the list, pass the head status off to the next neighbour

	if		(whichteam			== RED	&&
			 botlist_red_head	== whom	)	botlist_red_head	= whom.botlist_next;
	else if (whichteam			== BLUE	&&
			 botlist_blue_head	== whom	)	botlist_blue_head	= whom.botlist_next;
		
	// Check if whom has any neighbours in the list so we can join any adjacents to plug the gap

	if (whom.botlist_next)
		whom.botlist_next.botlist_last = whom.botlist_last;
	if (whom.botlist_last)
		whom.botlist_last.botlist_next = whom.botlist_next;
};



//
// event triggered logic
//

// tells all defenders/midfielders + up to 1 attacker to become chasers, in order to frag the EFC and/or return our flag when it drops
void(float whichteam) AssignTeam_ReturnOurFlag =
{
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_red_head;
	else if (whichteam == BLUE)	placeholder = botlist_blue_head;

	while (placeholder)		// check for attacker bots
	{
		if (placeholder.ctf_role == MIDFIELDER	||
			placeholder.ctf_role == DEFENDER	)
		{
			placeholder.ctf_role		= CHASER;
			placeholder.ctf_roletime	= time + 9999999;
		}
		
		placeholder = placeholder.botlist_next;
	}
};


// tells all escorts + 1 midfielder to become attackers, in order to retake a dropped enemy flag
void(float whichteam) AssignTeam_TakeDroppedEnemyFlag =
{
	local float nummidfielders;
	
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_red_head;
	else if (whichteam == BLUE)	placeholder = botlist_blue_head;

	while (placeholder)		// check for attacker bots
	{
		if (placeholder.ctf_role == ESCORT								||
			(placeholder.ctf_role == MIDFIELDER	&& nummidfielders == 0)	)
		{
			if (placeholder.ctf_role == MIDFIELDER)
				nummidfielders = 1;	// don't convert any more midfielders

			placeholder.ctf_role		= ATTACKER;
			placeholder.ctf_roletime	= time + 180;
		}
		
		placeholder = placeholder.botlist_next;
	}
};


// triggered when we make a cap, tells all escort, chaser and idle bots to defend for a short period of time, in order to hold off attacks by any chasers
void(float whichteam) AssignTeam_DefendTheBase = 
{
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_red_head;
	else if (whichteam == BLUE)	placeholder = botlist_blue_head;

	while (placeholder)		// check for escort,  chaser and idle bots
	{
		// quick check to see if this bot was a carrier that temporarily went chaser, restore their carrier role if so
		if (red_flag.owner	== placeholder	||
			blue_flag.owner	== placeholder	)
		{
			placeholder.ctf_role		= CARRIER;
			placeholder.ctf_roletime	= time + 9999999;
		}
		else if (placeholder.ctf_role == ESCORT	||
				 placeholder.ctf_role == CHASER	)
		{
			placeholder.ctf_role		= DEFENDER;
			placeholder.ctf_roletime	= time + 15;
		}
		
		placeholder = placeholder.botlist_next;
	}
};


// tells all hostile midfielders to become chasers, in order to help pick up the flag after our FC was fragged
void(float whichteam) AssignHostiles_ReturnOurFlag =
{
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_blue_head;
	else if (whichteam == BLUE)	placeholder = botlist_red_head;

	while (placeholder)		// check for attacker bots
	{
		if (placeholder.ctf_role == ATTACKER	||
			placeholder.ctf_role == MIDFIELDER	)
		{
			placeholder.ctf_role		= CHASER;
			placeholder.ctf_roletime	= time + 9999999;
		}
		
		placeholder = placeholder.botlist_next;
	}
};

// triggered when we make a cap, tells all hostile chasers to become attackers
void(float whichteam) AssignHostiles_AttackTheBase =
{
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_blue_head;
	else if (whichteam == BLUE)	placeholder = botlist_red_head;

	while (placeholder)		// check for hostile chasers and tell them to become attackers
	{
		if (placeholder.ctf_role == CHASER)
		{
			placeholder.ctf_role		= ATTACKER;
			placeholder.ctf_roletime	= time + 180;
		}
		
		placeholder = placeholder.botlist_next;
	}
};


// tells all escorts + 1 midfielder to become attackers, in order to retake a dropped enemy flag
void(float whichteam) AssignHostiles_TakeDroppedEnemyFlag =
{
	local float nummidfielders;
	
	local entity placeholder;

	// this one works in reverse, the red flag will tell blue bots to attack and vice versa
	
	if		(whichteam == RED)	placeholder = botlist_blue_head;
	else if (whichteam == BLUE)	placeholder = botlist_red_head;

	while (placeholder)		// check for attacker bots
	{
		if (placeholder.ctf_role == ESCORT								||
			(placeholder.ctf_role == MIDFIELDER	&& nummidfielders == 0)	)
		{
			if (placeholder.ctf_role == MIDFIELDER)
				nummidfielders = 1;	// don't convert any more midfielders

			placeholder.ctf_role		= ATTACKER;
			placeholder.ctf_roletime	= time + 180;
		}
		
		placeholder = placeholder.botlist_next;
	}
};


// tells all current attackers to escort the carrier + 1 midfielder bot to escort in case no attackers are found
void(float whichteam) AssignHostiles_EscortFlagCarrier =
{
	local float nummidfielders;
	
	local entity placeholder;

	if		(whichteam == RED)	placeholder = botlist_blue_head;
	else if (whichteam == BLUE)	placeholder = botlist_red_head;

	while (placeholder)		// check for attacker bots
	{
		if (placeholder.ctf_role == ATTACKER							||
			(placeholder.ctf_role == MIDFIELDER	&& nummidfielders == 0)	)
		{
			if (placeholder.ctf_role == MIDFIELDER)
				nummidfielders = 1;	// don't convert any more midfielders
		
			placeholder.ctf_role		= ESCORT;
			placeholder.ctf_roletime	= time + 600;
		}
		
		placeholder = placeholder.botlist_next;
	}
};



//
// team brain logic
//

// this is where most of the magic happens
// one brain is spawned for each team on map load, this brain will think every x time
// basically its job is to divvy up idler bots between the different roles, but it also keeps track of how many bots are doing what
// so in the future we could do weird things like flip all attacker and defender roles every once in a while
// a great example of the things we could do with this would be the Tactical Support System featured in the Q3 mod Hunt
// which would monitor things like flag state, player health + positions and so on and tell each player what to do based on that
void() TeamBrain_Think =
{
	local float	idlers,
				attackers,
				defenders,
				midfielders,
				escorts,
				chasers,
				carriers;	// no, really
	
	local float idealattackers,
				idealdefenders,
				idealmidfielders;
	
	local float dist_to_enemybase,
				dist_to_homebase,
				dist_to_efc;
				
	local float totalbots;
	
	local float have_human;
	
	local entity placeholder;
	
	local entity human, enemyflag, ourflag;

	if		(self.assigned_team == RED)		placeholder = botlist_red_head;
	else if (self.assigned_team == BLUE)	placeholder = botlist_blue_head;
	else									{bprint ("TeamBrain_Think: Something just broke.\nPlease inform Supa of how to reproduce this bug ASAP.\n"); return;}	// sanest thing we can do in this situation is become a vegtable

	while (placeholder)		// check for idle bots + run counters on which bots are doing what
	{
		totalbots++;

		if (placeholder.ctf_roletime < time)	// has your role expired yet?
			placeholder.ctf_role = IDLE;
	
		switch(placeholder.ctf_role)
		{
			case ATTACKER:		attackers++;	break;
			case DEFENDER:		defenders++;	break;
			case MIDFIELDER:	midfielders++;	break;
			case ESCORT:		escorts++;		break;
			case CHASER:		chasers++;		break;
			case CARRIER:		carriers++;		break;
			default:	// idle?
			{
				placeholder.ctf_role = IDLE;	// just to make 100% sure..
			
				idlers++;
			}
			break;
		}
		
		placeholder = placeholder.botlist_next;
	}
	
	if		(self.assigned_team == RED)		placeholder = botlist_red_head;
	else if (self.assigned_team == BLUE)	placeholder = botlist_blue_head;
	
	if (idlers)	// only shuffle bots if we have idlers
	{
		// compare counters: try to keep 2/1/1 ratio of attackers/defenders/midfielders and assign idle bots to best free spaces if needed
		// note that midfielder and defender roles should expire quicker than normal, so the defenders and midfielders are constantly being shuffled
		
		if (totalbots == 2)	// special handling for 2v2 and 1v1
		{
			idealdefenders = 1 - defenders;
			idealattackers = 1 - attackers;
		}
		else if (totalbots == 1)	// 2v2 or 1v1? check if we have a teammate
		{
			// if we have a human teammate, check who's closest to the enemy flag - defend if the human is closest to the enemy base
			
			human = find(world, classname, "player");
			
			while (human)
			{
				if (human.assigned_team == self.assigned_team)
				if (human.ishuman)
				{
					have_human = TRUE;
				
					if		(self.assigned_team == RED)		{enemyflag = blue_flagbase;	ourflag = red_flagbase;	}
					else if (self.assigned_team == BLUE)	{enemyflag = red_flagbase;	ourflag = blue_flagbase;}
					
					dist_to_enemybase	= vlen(human.origin - enemyflag.origin	);
					dist_to_homebase	= vlen(human.origin - ourflag.origin	);
				}
			
				human = find(human, classname, "player");
			}
			
			if (have_human)	// check if the human is closer to enemybase than to ourbase, attack if they're closer to our base and vice versa
			{
				if (dist_to_enemybase < dist_to_homebase)	idealdefenders = 1;		// the human's closer to their base, let's defend
				else										idealattackers = 1;		// okay, so we're closer to the enemy base, let's attack
			}
			else
			{
				// if no human, check game state - if the flag is safe, let's attack
				if		(self.assigned_team == RED)		{enemyflag = blue_flagbase;	ourflag = red_flagbase;	}
				else if (self.assigned_team == BLUE)	{enemyflag = red_flagbase;	ourflag = blue_flagbase;}
				
				if (!ourflag.owner)	// does the enemy have our flag? get it back if so, otherwise retsuattacku~
				{
					if (enemyflag.owner == placeholder)	// do we have the flag? cap if so
					{
						placeholder.ctf_role		= CARRIER;
						placeholder.ctf_roletime	= time + 9999999;
						
						idlers = 0;
					}
					else idealattackers = 1;
				}
				else
				{
					// if our flag is missing and we don't have the enemy flag, we'll chase the EFC if they're closer than the enemy flag, else we'll attack
					// however, we'll always chase in a crosscap situation, regardless of distance
					
					dist_to_enemybase	= vlen(placeholder.origin - enemyflag.origin	);
					dist_to_efc			= vlen(placeholder.origin - ourflag.origin		);
				
					if ((dist_to_efc < dist_to_enemybase)	||
						enemyflag.owner == placeholder		)
					{
						placeholder.ctf_role		= CHASER;		// ought to add idealchasers sometime..
						placeholder.ctf_roletime	= time + 9999999;
						
						idlers = 0;
					}
					else idealattackers = 1;
				}
			}
		}
		else	// remember to keep a 2:1:1 ratio of attackers/midfielders/defenders
		{
			idealdefenders		= ceil((totalbots / 4) - defenders);
			idealmidfielders	= ceil((totalbots / 4) - midfielders);
			idealattackers		= idlers - idealdefenders - idealmidfielders;
		}

		while	(idlers			&&
				 placeholder	)
		{
			if (placeholder.ctf_role == IDLE)	// don't overwrite non-idles
			{
				if		(idealdefenders		)	{placeholder.ctf_role = DEFENDER;	placeholder.ctf_roletime = time + 20 + 10*crandom();	idlers--;	idealdefenders--;	}
				else if (idealmidfielders	)	{placeholder.ctf_role = MIDFIELDER;	placeholder.ctf_roletime = time + 20 + 10*crandom();	idlers--;	idealmidfielders--;	}
				else if (idealattackers		)	{placeholder.ctf_role = ATTACKER;	placeholder.ctf_roletime = time + 180;					idlers--;	idealattackers--;	}
			}

			placeholder = placeholder.botlist_next;
		}
	}
	
	self.nextthink = time + 5 + 1*crandom();	// check again later
};

void(float whichteam) Spawn_TeamBrain =
{
	local entity brain;
	
	brain = spawn();

	brain.assigned_team = whichteam;
	
	brain.think = TeamBrain_Think;
	brain.nextthink = time + 5;
};

// gj Supa - gb ;-)

