// this script is a work in progress, but is fully functional
// Fireteam members will follow a human fireteam leader by default
// Leaders can give bot commands via fireteam chat or the fireteam voice chat
/*
	camp <slot number> - the bot at the given slot number will camp at leaders current position / facing
	camp - same as above but all bots will do it ( not recommended )
	formation <front | random> - bots that get close enough while following will offset to whatever the current formation is
	release <slot number> - sets the goal priority to 0 for the given bot
	release - same as above but for all bots
	num - each bot will say their slot number since the ET overlay is retarded
	follow <slot number> the given bot will be set to the default state of following the leader
	follow - same as above but for all bots
*/

// NOTE: this goal relies on an omni-bot.cfg stored var in order to run. /bot ft_controls 1 to enable 0 to disable

this.Name = "FireTeam";
this.Parent = "HighLevel";
this.AutoAdd = true; // set this to true to use the goal
this.AlwaysRecieveEvents = this.AutoAdd;
this.Debug = false;

this.FireTeamEnabled = ConfigGet("FireTeam","enabled", 0);

this.States =
{
	FT_NONE 	= 0,
	FT_FOLLOW 	= 1,
	FT_RELEASED = 2,
	FT_CAMP 	= 3,
	
	// class related states
	FT_MG42 	= 4, // Camp
	FT_MORTAR_TARGET 	= 5, // use mortar ?
	
	FT_DYNAMITE = 6,
	FT_REPAIR 	= 7,
	FT_LANDMINE = 8, // plant mine 
	
	FT_HEALTH 	= 9, // deliver health packs
	
	FT_AMMO 	= 10, // deliver ammo packs
	FT_AIRSTRIKE= 11, 
	FT_ARTY 	= 12,
	
	FT_SMOKE 	= 13,
	FT_SATCHEL 	= 14,
	FT_OPEN_TEAM_DOOR= 15,
	
	// all classes
	FT_SPEC_MINES= 16,
	FT_GRENADE	 = 17,
	FT_ESCORT	 = 18,
};

this.Initialize = function()
{
	// populate with ft info
	this.Bot.FireTeam = {};

	// initialize vars
	this.DefaultPriority = 0.7;
	this.CommandPriority = this.DefaultPriority ;
	this.TargetPosition = Vector3(0,0,0);

	this.Bot.FireTeamState = this.States.FT_NONE;
	this.Bot.FireTeamFormation = "random";
};

this.GetPriority = function()
{
	while(1)
	{
		if ( !this.FireTeamEnabled ) {
			this.Priority = 0;
		}
		else
		{
			// this is needed to catch when leader leaves. add an event?
			if ( this.Bot.IsInFireTeam() )
			{
				this.UpdateFireTeamInfo();
			}

			// TODO: fixme. this will break when GetPriority thread is killed when update activates
			if ( !this.IsActive() )
			{
				if( this.Bot.IsInFireTeam() )
				{
					this.Priority = this.CommandPriority;
				}
				else
				{
					this.Priority = 0;
				}
			}

			// if a bot becomes leader, just disband the team for now
			if ( this.Bot.FireTeamNum && this.Bot.FireTeam[this.Bot.FireTeamNum].Leader == this.Bot.GetGameEntity() )
			{
				this.Bot.FireteamDisband();
				this.Priority = 0;
			}
		}

		sleep(1);
	}
};

this.Enter = function()
{
	this.OnTarget = false;

	if ( this.Bot.FireTeamState == this.States.FT_FOLLOW )
	{
		if ( this.DynamicGotoThread )
		{
			this.KillThread(this.DynamicGotoThread);
		}
		this.DynamicGotoThread = this.ForkThread(this.DynamicGoto);
	}

	if ( this.Bot.FireTeamState != this.States.FT_CAMP )
	{
		this.Bot.camp_pos = null;
		this.Bot.camp_facing = null;
	}

	// Set up some finish criteria
	this.AddFinishCriteria(this.TargetEntity,"hasentflag",ENTFLAG.LIMBO);
};

this.Update = function()
{
	if ( this.Bot.FireTeamState == this.States.FT_FOLLOW )
	{
		this.Follow();
	}
	else if ( this.Bot.FireTeamState == this.States.FT_CAMP )
	{
		this.Camp();
	}
	else if ( this.Bot.FireTeamState == this.States.FT_RELEASED )
	{
		// shouldn't get here
	}
	else
	{
		if ( this.Debug )
		{
			this.Bot.Say("^1INVALID STATE IN FIRETEAM GOAL!");
		}

		// reset to default behavior so the bot won't get stuck in an infinite update loop
		this.Bot.FireTeamState = this.States.FT_FOLLOW;
		this.Finished();
	}
};

this.Exit = function()
{
	if ( this.DynamicGotoThread )
	{
		this.KillThread(this.DynamicGotoThread);
	}

	this.TargetPosition = Vector3(0,0,0);
};

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

this.UpdateFireTeamInfo = function()
{
	this.Bot.ftinfo = this.Bot.FireTeamGetInfo();
	if ( this.Bot.ftinfo )
	{
		this.Bot.FireTeam[ this.Bot.ftinfo.FireTeamNum ] = this.Bot.ftinfo;
		this.Bot.FireTeamNum = this.Bot.ftinfo.FireTeamNum;
	}
	else
	{
		this.Finished();
	}
};

this.GetSlotNumber = function()
{
	slot = 0;

	foreach ( id and ent in this.Bot.FireTeam[this.Bot.FireTeamNum].Members )
	{
		if ( this.Bot.GetGameEntity() == ent )
		{
			slot = id;
		}
	}

	return slot;
};

// from DrEvil's meatshield goal
this.FormationFront = function()
{
	offset = 150;
	interval = 64;

	numbots = tableCount(this.Bot.FireTeam[this.Bot.FireTeamNum].Members) - 1;
	slotnum = this.GetSlotNumber();

	lp = GetEntPosition(this.Bot.FireTeam[this.Bot.FireTeamNum].Leader);
	lf = GetEntFacing(this.Bot.FireTeam[this.Bot.FireTeamNum].Leader);
	side = CrossProduct(lf,Vector3(0,0,1));

	dest = lp + lf * offset;
	dest += (side * (slotnum * interval));
	dest -= (side * 0.5 * numbots * interval);

	if(this.Bot.DistanceTo(dest) > 1024)
	{
		this.Goto(dest);
	}
	else
	{
		this.Bot.MoveTowards(dest);
	}

	this.AddAimRequest(Priority.Low, "facing", GetEntFacing(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader));
	yield();
};

this.FormationRandom = function()
{
	offsets =
	{
		Vector3(128,128,0),
		Vector3(-128,-128,0),
		Vector3(-128,128,0),
		Vector3(128,-128,0),
		Vector3(64,64,0),
		Vector3(-64,-64,0),
		Vector3(-64,64,0),
		Vector3(64,-64,0),
	};

	//choose a random Offset
	i = RandInt(0, tableCount(offsets) - 1);

	// stand at an offset and face the direction the leader is if leader is not moving
	if( !GetEntVelocity(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader).IsZero() && this.Bot.HasLineOfSightTo(this.TargetPosition + offsets[i]))
	{
		this.ReleaseAimRequest();
		this.Goto(this.TargetPosition + offsets[i]);
	}
	else
	{
		this.AddAimRequest(Priority.Low, "facing", GetEntFacing(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader));
		this.Goto(this.Bot.GetPosition());
	}
};

this.DynamicGoto = function()
{
	this.OnTarget = false;

	if(this.Goto(this.TargetPosition, 128.f) == EVENT.PATH_SUCCESS)
	{
		this.OnTarget = true;
	}
	else
	{
		if ( this.Debug )
		{
			this.Bot.SayTeam("No PATH!");
		}
		this.Finished();
	}
};

this.Follow = function()
{
	entPos = GetEntPosition(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader);

	if( this.TargetPosition.IsZero() )
	{
		this.TargetPosition = entPos;
	}

	if ( entPos.IsZero() )
	{
		this.Bot.SayFireTeam("Entity position is  zero!");
		this.Finished();
	}

	d = DistanceBetween(entPos,this.TargetPosition);

	if(d > 128 && d < 1000)
	{
		this.OnTarget = false;
		if ( this.DynamicGotoThread )
			{ this.KillThread(this.DynamicGotoThread); }
		this.TargetPosition = entPos;
		this.DynamicGotoThread = this.ForkThread(this.DynamicGoto);

		if ( d >= 500 )
		{
			this.Bot.HoldButton(BTN.SPRINT, 5);
		}
	}

	if( this.OnTarget )
	{
		if ( this.Bot.FireTeamFormation == "front" )
		{
			this.FormationFront();
		}
		else
		{
			this.FormationRandom();
		}
	}
};

this.Camp = function()
{
	if ( !this.Bot.camp_pos )
	{
		this.Bot.camp_pos = GetEntPosition(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader);
		this.Bot.camp_facing = GetEntFacing(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader);
	}

	if(this.Goto(this.Bot.camp_pos, 64.f) == EVENT.PATH_SUCCESS)
	{
		this.AddAimRequest( Priority.Low, "facing", this.Bot.camp_facing );
		block(0);
	}
	else
	{
		this.Bot.FireTeamState = this.States.FT_FOLLOW;
	}
};

this.ProcessChatEvents = function(Who, Msg) 
{

	tokens = Msg.Tokenize(" ");

	// only let the leader use commands for now
	if ( Who != this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader )
	{
		return;
	}

	//check if there is a slot num specified
	if( tokens[1] )
	{
		num = ToInt(tokens[1]);
		if ( typeId(num) == 1 )
		{
			// just make sure it's updated for now
			this.Bot.ftinfo = this.Bot.FireTeamGetInfo();
			this.Bot.FireTeam[ this.Bot.ftinfo.FireTeamNum ] = this.Bot.ftinfo;

			// skip all but the slot number
			if ( this.Bot.GetGameEntity() != this.Bot.FireTeam[ this.Bot.FireTeamNum ].Members[ num ] )
			{
				return;
			}
		}
	}

	if(tokens[0]=="come" || tokens[0]=="follow" || tokens[0]=="Follow")
	{
		this.CommandPriority = this.DefaultPriority;
		this.Bot.FireTeamState = this.States.FT_FOLLOW;
		this.Finished(); // so they can exit any existing state
	}
	else if(tokens[0]=="go" || tokens[0]=="release" || tokens[0]=="Move!") 
	{
		this.CommandPriority = 0;
		this.Bot.FireTeamState = this.States.FT_RELEASED;
		this.Finished(); // so they can exit any existing state
	}
	else if(tokens[0]=="camp")
	{
		this.CommandPriority = this.DefaultPriority;
		this.Bot.FireTeamState = this.States.FT_CAMP;
		this.Bot.camp_pos = null;
		this.Bot.camp_facing = null;
		this.Finished(); // so they can exit any existing state
	}
	else if(tokens[0]=="ftl")
	{
		this.Bot.SayTeam("Team leader is ", GetEntName(this.Bot.FireTeam[ this.Bot.FireTeamNum ].Leader));
	}
	else if(tokens[0]=="num" || tokens[0]=="slot")
	{
		// slot number isn't matching the display in menu ...
		slot = this.GetSlotNumber();
		this.Bot.SayFireTeam( "my slot number is ", slot );
	}
	else if(tokens[0]=="formation") // Cover me!
	{
		if(tokens[1])
		{
			this.Bot.FireTeamFormation = tokens[1]; // defaults to random if invalid
		}
		else
		{
			this.Bot.SayFireTeam("my formation is ", this.Bot.FireTeamFormation);
		}
	}
};

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

this.Events[EVENT.PRIV_CHAT_MSG] = function(Who, Msg)
{
	this.ProcessChatEvents(Who, Msg);
	
	if (this.Debug)
	{
		print("Private Chat: ", GetEntName(Who), " said ", Msg);
	}
	
};

this.Events[EVENT.FIRETEAM_CHAT_MSG] = function(Who, Msg)
{
	this.ProcessChatEvents(Who, Msg);

	if (this.Debug)
	{
		print("Fire Team Chat: ", GetEntName(Who), " said ", Msg);
	}
};

this.Events[EVENT.PRIVATE_VOICE] = function(Who, id)
{
	
	switch(id)
	{
		case VOICE.COVER_ME:
		{
			
		}
		case FOLLOW_ME:
		{
			
		}
		case VOICE.MOVE:
		{
			
		}
		case VOICE.NEED_BACKUP:
		{
			
		}
		case VOICE.CLEAR_MINES:
		{
			
		}
		
		// NoQuarter specific
		case VOICE.P_ATTACK		:{}
		case VOICE.P_CALLAIRSTRIKE	:{}
		case VOICE.P_CALLARTILLERY	:{}
		case VOICE.P_CHECKFORLANDMINES	:{}
		case VOICE.P_CONSTRUCTOBJECTIVE	:{}
		case VOICE.P_DEPLOYLANDMINES	:{}
		case VOICE.P_DEPLOYMORTAR	:{}
		case VOICE.P_DESTROYOBJECTIVE	:{}
		case VOICE.P_DISARMLANDMINES	:{}
		case VOICE.P_EXPLOREAREA	:{}
		case VOICE.P_FALLBACK		:{}
		case VOICE.P_GOUNDERCOVER	:{}
		case VOICE.P_HEALME		:{}
		case VOICE.P_HEALSQUAD		:{}
		case VOICE.P_INFILTRATE		:{}
		case VOICE.P_PROVIDESNIPERCOVER	:{}
		case VOICE.P_REPAIROBJECTIVE	:{}
		case VOICE.P_RESUPPLYME		:{}
		case VOICE.P_RESUPPLYSQUAD	:{}
		case VOICE.P_REVIVEME		:{}
		case VOICE.P_REVIVETEAMMATE	:{}
		case VOICE.P_SATCHELOBJECTIVE	:{}
		// end NoQuarter specific
		
		default:
		{
			if (this.Debug) {
				print("goal_fireteam: unhandled voice macro:", Util.GetTableKey(VOICE,id), "in EVENT.PRIVATE_VOICE" );
			}
		}
	}


	if (this.Debug)
	{
		print("Private voice:", Util.GetTableKey(VOICE,id), "from :", GetEntName(Who));
	}
	
};

this.Events[EVENT.FIRETEAM_CREATED] = function(FireTeamNum)
{
	if ( this.Debug )
	{
		print("Fire Team Created:", FireTeamNum);
	}
};

this.Events[EVENT.FIRETEAM_DISBANDED] = function()
{
	if ( this.Debug )
	{
		this.Bot.SayTeam("Fire Team disbanded.");
	}

	this.Finished();
};

this.Events[EVENT.FIRETEAM_JOINED] = function(TeamLeader)
{
	if ( this.Debug )
	{
		this.Bot.SayTeam("Fire Team joined:", GetEntName(TeamLeader));
	}

	this.Bot.FireTeamState = this.States.FT_FOLLOW;
	this.UpdateFireTeamInfo();
};

this.Events[EVENT.FIRETEAM_LEFT] = function()
{
	if ( this.Debug )
	{
		this.Bot.SayTeam("Fire Team left.");
	}

	this.UpdateFireTeamInfo();
	this.Finished();
};

this.Events[EVENT.FIRETEAM_INVITED] = function(TeamLeader)
{
	if( this.Debug )
	{
		print("Fire Team Invite from:", GetEntName(TeamLeader));
	}

	this.Bot.VoteYes(); // accept
};

this.Events[EVENT.FIRETEAM_PROPOSAL] = function(Invitee)
{
	if ( this.Debug )
	{
		this.Bot.Say("Fire Team Proposition:", GetEntName(Invitee));
	}

	this.Bot.VoteYes(); // accept
};

this.Events[EVENT.FIRETEAM_WARNED] = function(WarnedBy)
{
	if ( this.Debug )
	{
		print("Fire Team Warned:", GetEntName(WarnedBy));
	}
};

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

this.Commands["ft_controls"] =
{
	Func = function(_params)
	{
		if ( typeId(_params[0]) != 1 )
		{
			ft = ConfigGet("FireTeam","enabled", ft);
			print( "fireteam controls set to", ft );
			return;
		}

		if ( _params[0] < 1 )
		{
			ConfigSet("FireTeam","enabled", 0 );
			this.FireTeamEnabled = false;
			EchoToScreen(3, "^1FireTeam Controls disabled");
		}
		else
		{
			ConfigSet("FireTeam","enabled", 1 );
			this.FireTeamEnabled = true;
		}
	},

	Help =
	{
		"Fireteam Controls, enable or disable",
	},
};

