//==================================================================================================
//	This script goal is a commented example of a simple bot behavior for instructional purposes.
//	The intention is to provide a brief introduction of the script goal system in simple terms.
//	This is NOT a technical document.
//
//  More technical information can be found for:
//
//  - Script goal system - http://www.omni-bot.com/wiki/index.php?title=ScriptGoal
//  - Bot library and entity functions - http://www.omni-bot.com/wiki/index.php?title=Bot_Library
//  - Bot functions - http://www.omni-bot.com/wiki/index.php?title=Bot
//  - Available event list - http://www.omni-bot.com/wiki/index.php?title=EVENT
//  - Classes - http://www.omni-bot.com/wiki/index.php?title=CLASS
//  - Teams - http://www.omni-bot.com/wiki/index.php?title=TEAM
//
//	Furthermore, none of the actual scripting will be documented in detail as the scope of this
//	document is to provide a general understanding of the script goal system.
//==================================================================================================

// ----------------------------------------- PARAMETERS ----------------------------------------- //

/*
	this.Name is the name of the script goal. All script goals need a unique name as it is an
	identifier (state name). Best practice is to make them somewhat descriptive in terms of what
	the goal is going to be doing.
 */
this.Name = "Demo";

/*
	this.Parent sets the state in which the script goal will run. The three valid states are
	HighLevel, LowLevel, and Limbo. Only one HighLevel script goal can be active at a time,
	while numerous LowLevel and Limbo script goals can be active at the same time. For the
	purpose of simplicity, the general rule is that if you want to have to bots travel to
	specific locations using pathfinding, then setting this.Parent to HighLevel is required. For
	simple tasks like responding to voice chats LowLevel should be used. And anything you may
	want a bot to do in limbo; like choose a new weapon, class, etc should be set up with the
	Limbo parent.

	This goal is going to have the bots moving to specific locations so it needs
	to be HighLevel.
 */
this.Parent = "HighLevel";

/*
	this.AutoAdd specifies whether or not the goal will be loaded automatically by the bot.
	Typically this would be set to true (default setting), but there may be some instances where
	you only want certain bots to use the script. bot.AddScriptGoal(<script goal name>) would be
	used to limit them to specific bots in conjunction with this.AutoAdd set to false. This
	setting also provides an easy method to disable a script goal entirely. Since we don't want
	this particular goal active all the time, it's set to false.

	Change this to true if running the demo.
 */
this.AutoAdd = false;

/*
	this.SkipGetPriorityWhenActive keeps the GetPriority function from being called when the
	goal is already active. A HighLevel goal becomes active when it's priority is the highest
	available for the bot. Skipping GetPriority can help ensure that unnecessary processing of
	complex scripting is avoided. It is also a good way to ensure that the current goal cannot
	be interrupted for goals of the same type. This setting defaults to false.

	We don't want this goal interrupted for the same type.
*/
this.SkipPriorityWhenActive = true;

/*
	this.AlwaysRecieveEvents sets whether or not the goal will receive events when it is not
	active. When a goal might be activated based on an event, this must be set to true as it
	defaults to false.

	This goal won't be using events.
*/
this.AlwaysRecieveEvents = false;

/*
	this.LimitToClass is one of many functions that limit a script goals execution to various
	evaluators being true. Reference the Wiki for a complete list of LimitTo functions. While
	the goal is still enabled for each bot, none of the main script functions will be called
	unless this evaluator is true.

	This goal is going to be limited to Medics
*/
this.LimitToClass(CLASS.MEDIC);

/*
	this.Debug is an optional setting that is useful for providing additional information about
	the goal in debug mode only. This setting can be toggled on with the console command:

	bot debugbot all statename 1|0
	bot debugbot all Demo 1 in this case

	Debug modes typically should be disabled by default
*/
this.Debug = false;

// ----------------------------------------- FUNCTIONS ------------------------------------------ //

/*
	this.Initialize is a function that is called only once; and that is when the bot 'loads' the
	scriptgoal. Typically this happens every map start if the goal is set up to be auto-added.

	It it mostly useful for initializing variables that the script goal will use. Since this
	function is only called once, setting up variables and / or tables that are going to be used
	heavily in the script is a good idea. Doing so will ensure that memory isn't re-allocated
	unnecessarily by initializing them in functions that are called often.

	This goal is just going to provide a console print to let the user know the goal was loaded
	as well as define a couple static variables that are used.
*/
this.Initialize = function()
{
	// some variables for 'states'
	this.FOLLOW = 1;
	this.GO_TO_COVER = 2;
	this.COVER = 3;

	this.GoalsInRadius = {};

	// print the initialized message IF Map Debug prints are enabled
	Util.ScriptDebugPrint(this.Name, "initialized");
};

/*
	this.GetPriority provides a means for the script goal to set a priority for the goal.
	Without a priority, none of the other script goal functions will run (other than the
	initialize function).

	This function is called every frame that it is not currently running or delayed. The
	frequency it is called can be throttled by setting this.GetPriorityDelay. It can also be
	throttled by adding sleeps, yields, or blocks inside this function.

	An important note is that this is not the only place a priority can be set. In fact, this
	function is completely optional. Priority can be set in events for example.

	This goal is going to check and see if there is a close by engineer for the medic to follow.
*/
this.GetPriority = function()
{
	// wrap it in a while loop so a new thread isn't created repeatedly
	for(;;)
	{
		// search for close by engineers that this medic will follow
		engineer = this.Bot.GetNearestAlly(CAT.PLAYER, CLASS.ENGINEER);

		// if we found one, check distance before setting a priority
		if (engineer)
		{
			if (this.Bot.DistanceTo(engineer) <= 400)
			{
				// store the entity in a scope that other functions can have
				// access to it
				this.TargetEntity = engineer;

				// we could check if the target bot has a build or plant goal
				// before setting a priority here, but for the purposes of this
				// demo we will skip that step

				// keep priority lower than Revive goals, but higher than most
				// other goals
				this.Priority = 0.9;
			}
		}

		// don't check every frame
		sleep(3);
	}
};

/*
	this.Enter is called ONLY once every time the goal has been activated. In order for a
	HighLevel goal to be activated, it must be the highest priority goal available for the bot.

	Because it's only called once, it's a good place to put functions that only need to be
	called a single time after the goal has been activated. Similarly it's a good place to
	set variables used only while the goal is active.

	For this goal, we'll have the bot team chat who it is following and also set the goal up
	to finish if the engineer it is following leaves or goes into Limbo
*/
this.Enter = function()
{
	// automatically finish the goal if the target goes to limbo
	this.AddFinishCriteria(this.TargetEntity,"hasentflag",ENTFLAG.LIMBO);

	// announce in team chat who the bot is following
	this.Bot.SayTeam("I'm going to follow " + GetEntName(this.TargetEntity));

	// default state is to follow
	this.State = this.FOLLOW;

	// start looking for camp goals around the target entity we can set up as a smart defense point
	this.WatchForMapGoalsInRadius(this.TargetEntity,512,"CAMP.*");
	tableClear(this.GoalsInRadius);
};

/*
	this.Update is called AFTER this.Enter and is repeatedly called while the goal is active and
	the function thread is not active. Similar to GetPriority, it's a good idea to wrap it in
	a while loop if the goal doesn't always finish the first time through. This helps to avoid
	unnecessary creation and destruction of script goal threads.

	This function is typically used to handle all the real work of the script goal like
	instructing the bot to go somewhere or perform specific actions for the entire lifetime of
	the goal.

	For this goal, we'll have the medic follow the engineer around until the medic finds
	something better to do (like revive), or it finishes for other reasons like the
	target going into limbo.
*/
this.Update = function()
{
	// wrap it in a while loop so a new thread isn't created repeatedly
	for(;;)
	{
		// call the appropriate functions based on the current state
		switch(this.State)
		{
			case this.FOLLOW:
			{
				this.UpdateFollow();
			}
			case this.GO_TO_COVER:
			{
				this.UpdateGotoCover();
			}
			case this.COVER:
			{
				this.UpdateCover();
			}
		}

		yield();
	}
};

/*
	this.Exit is called ONLY once and AFTER the goal finishes. It's useful for cleaning up any
	variables, releasing buttons, etc
*/
this.Exit = function()
{
	this.Bot.ReleaseButton(BTN.CROUCH);
	this.TargetEntity = null;
};

/*
	Optionally, scripters can create their own functions that are called from the default ones.
	This is a matter of preference and not required.

	For this goal, moving some of the behavior scripting to separate functions makes it a bit
	easier to follow / read.
*/
this.UpdateFollow = function()
{
	// if the target has stopped, cover them
	if (this.TargetEntity && GetEntVelocity(this.TargetEntity).IsZero())
	{
		this.State = this.GO_TO_COVER;
		return;
	}

	// target is moving, so follow them. finish if they are unreachable
	pos = GetEntPosition(this.TargetEntity);
	if (!pos || this.GotoAsync(pos, 128) == EVENT.PATH_FAILED)
	{
		this.Finished();
	}
};

this.UpdateGotoCover = function()
{
	pos = GetEntPosition(this.TargetEntity);
	foreach(ind and offset in this.CoverOffsets)
	{
		// todo: check line of sight reachability
		if (this.Goto(pos+offset) == EVENT.PATH_SUCCESS)
		{
			this.State = this.COVER;
			return;
		}
	}

	// couldn't get to a cover offset, just finish it
	this.Finished();
};

this.UpdateCover = function()
{
	// if the target has started to move again, follow
	dist = this.Bot.DistanceTo(this.TargetEntity);
	if (this.TargetEntity && !GetEntVelocity(this.TargetEntity).IsZero() || dist > 300)
	{
		this.State = this.FOLLOW;
		this.ReleaseAimRequest();
		return;
	}

	// just face the opposite direction
	tFacing = GetEntFacing(this.TargetEntity);
	this.AddAimRequest(Priority.Medium, "facing", Vec3(tFacing.x * -1, tFacing.y * -1, 0));

	// crouch if the engineer is
	if (GetEntFlags(this.TargetEntity, ENTFLAG.CROUCHED))
	{
		this.Bot.HoldButton(BTN.CROUCH, 0.25);
	}

	// stay still
	this.Goto(this.Bot.GetPosition());
};

this.CoverOffsets =
{
	Vec3(128,128,0),
	Vec3(-128,-128,0),
	Vec3(-128,128,0),
	Vec3(128,-128,0),
	Vec3(64,64,0),
	Vec3(-64,-64,0),
	Vec3(-64,64,0),
	Vec3(64,-64,0),
};

this.Events[EVENT.MG_ENTER_RADIUS] = function(mg)
{
	print(mg.GetName(), "entered");
	this.GoalsInRadius[mg] = 1;
};

this.Events[EVENT.MG_LEAVE_RADIUS] = function(mg)
{
	print(mg.GetName(), "left");
	this.GoalsInRadius[mg] = null;
};




