// This script contains functionality to allow bots to satchel or dynamite goals
this.Name = "PLANT";
this.Parent = "HighLevel";
this.SkipGetPriorityWhenActive = true;
this.LimitToClass(CLASS.COVERTOPS, CLASS.ENGINEER);
this.DisableRifleNade = true; // should engineers conserve their charge?


// define some goal states
this.LAY_EXPLOSIVE = 0;
this.ARM_EXPLOSIVE = 1;
this.RUNAWAY = 2;
this.DETONATE_EXPLOSIVE = 3;
this.COVER_DYNAMITE = 4;
this.LOOK_FOR_DYNO = 5;
this.GOTO = 6;
this.GoalStateNames = { "LAY_EXPLOSIVE", "ARM_EXPLOSIVE", "RUNAWAY", "DETONATE_EXPLOSIVE", "COVER_DYNAMITE", "LOOK_FOR_DYNO", "GOTO" };

this.Initialize = function()
{
	this.WatchForEntityCategory({
		Radius = 350,
		Category = CAT.PROJECTILE,
	});

	// check for entity status so we can finish if another player destroys it
	this.LimitTo(this.CheckDestroyableState, 1, true);
};

this.CheckDestroyableState = function()
{
	return !this.GoalEntity || this.Bot.GetDestroyableState(this.GoalEntity) > 0;
};

this.GetPriority = function()
{
	if (this.Bot.GetClass() == CLASS.ENGINEER)
	{
		query = { 0xbbcae592 /* PLANT */, 0xb9e32716 /* DYNAMITE */};
		weapon = WEAPON.DYNAMITE;
	} else {
		query = 0xbbcae592 /* PLANT */;
		weapon = WEAPON.SATCHEL;
	}

	Util.GetPriority(this,query,weapon,0, function(goal, bot, _this)
	{
		if (bot.GetClass() == CLASS.COVERTOPS)
		{
			if (goal.ExplosiveType && !(goal.ExplosiveType & goal.ExplosiveTypeSatchel)) {
				return;
			}
		}
		else //bot.GetClass() == CLASS.ENGINEER
		{
			if (goal.ExplosiveType && !(goal.ExplosiveType & goal.ExplosiveTypeDynamite)) {
				return;
			}
		}

		if (!goal.IgnoreEntity)
		{
			if (goal.TraceEntity)
			{
				goalEnt = goal.TraceEntity;
				if (!EntityIsValid(goalEnt)) {
					return;
				}
			}
			else
			{
				goalEnt = goal.GetEntity();
				if (!goalEnt) {
					return;
				}
			}
			if (bot.GetDestroyableState(goalEnt) < 1) {
				return;
			}
		}

		if (_this.ExplosiveEntity)
		{
			if (!EntityIsValid(_this.ExplosiveEntity)) {
				// dynamite was defused or exploded
				_this.ExplosiveEntity = null;
			}
			else if (DistanceBetween(goal, _this.ExplosiveEntity) < 400) {
				// there is ticking dynamite at goal
				return;
			}
		}

		return true;
	});
};

this.Enter = function()
{
	this.MarkInProgress();

	this.ExplosiveEntity = null;

	this.Goal_Name = this.MapGoal.GetName();

	if (!this.MapGoal.IgnoreEntity)
	{
		if (this.MapGoal.TraceEntity)
		{
			this.GoalEntity = this.MapGoal.TraceEntity;
		}
		else
		{
			this.GoalEntity = this.MapGoal.GetEntity();
		}
		if (!this.GoalEntity)
		{
			Util.InvalidGoal(this, "entered with no entity!");
		}
	}

	// disable the rifle nade while the goal is active
	if (this.DisableRifleNade) {
		ETUtil.DisableRifleNade(this.Bot, true);
	}

	this.GoalState = this.GOTO;
	this.UpdateDebugString();
};

this.Update = function()
{
	Util.RouteTo(this, 64);

	switch(this.Bot.GetClass())
	{
		case CLASS.ENGINEER:
		{
			this.GoalState = this.LOOK_FOR_DYNO;
			this.UpdateDynamite();
		}
		case CLASS.COVERTOPS:
		{
			this.GoalState = this.LAY_EXPLOSIVE;
			this.UpdateSatchel();
		}
	}

	this.Finished();
};

this.Exit = function()
{
	this.Bot.ReleaseButton(BTN.PRONE, BTN.CROUCH);
	this.Bot.DoNotKill = null;

	this.GoalEntity = null;

	// clear the occupied flag
	if (this.MapGoal && !IsNull(this.spotIndex)) {
		this.MapGoal.CoverSpots[ this.spotIndex ].occupied = null;
		this.spotIndex = null;
	}

	// re-enable the rifle nade
	ETUtil.DisableRifleNade(this.Bot, false);

	this.MapGoal = null;

	// restore combat movement
	if (this.CombatMovementDisabled) {
		this.Bot.DisableCombatMovement = false;
		this.CombatMovementDisabled = false;
	}

	if (EntityIsValid(this.SatchelEntity) && GetModName() == "nitmod") {
		//if the goal exited during RUNAWAY state then satchel is still on the ground and covertops cannot use another
		sleep(1);
		if (!this.Bot || this.Bot.HasEntityFlag(ENTFLAG.LIMBO)){
			//g_headshot 2
			return;
		}
		this.AddWeaponRequest(WEAPON.SATCHEL_DET);
		sleep(1); //BlockForWeaponChange does not work
		if (this.Bot && this.Bot.GetCurrentWeapon() == WEAPON.SATCHEL_DET) {
			this.Bot.HoldButton(BTN.ATTACK1, 1);
			sleep(1);
		}
		this.ReleaseWeaponRequest();
	}
	this.SatchelEntity = null;
};

this.CoverDisabled = function(position)
{
	vec = this.MapGoal.CoverSplit;
	if(!vec){ return false; }
	return ((this.Bot.GetPosition() - this.MapGoal.GetPosition()).DotProduct(vec) > 0) !=
			 ((position - this.MapGoal.GetPosition()).DotProduct(vec) > 0);
};

this.UpdateDynamite = function()
{
	hasCoverSpot = false;
	aimVectors = null;
	coverTimeout = 25; // note: if it takes >= 5 seconds to get to the cover spot, this will not be reached.
	goalPriority = this.Priority;

	for(;;)
	{
		switch(this.GoalState)
		{
			case this.LOOK_FOR_DYNO:
			{
				ent = null;
				activeDyno = 0;
				goalPos = this.MapGoal.GetPosition();
				for(;;) {
					ent = GetEntityInSphere(this.Bot.GetPosition(), 300, CLASS.DYNAMITE_ENT, ent);
					if (!ent) { break; }
					if (DistanceBetween(goalPos, ent) < 200 && this.Bot.IsAllied(ent))
					{
						state = this.Bot.GetExplosiveState(ent);
						if (state == 1) { break; }
						if (state == 0) {
							++activeDyno;
							if (!this.ExplosiveEntity) {
								this.ExplosiveEntity = ent;
							}
						}
					}
				}

				if (ent) {
					this.ExplosiveEntity = ent;
					this.GoalState = this.ARM_EXPLOSIVE;
				}
				else if (this.Bot.IsWeaponCharged(WEAPON.DYNAMITE) && activeDyno < this.MapGoal.MaxUsers_InUse()) {
					this.GoalState = this.LAY_EXPLOSIVE;
				}
				else if (this.MapGoal.CoverSpots && tableCount(this.MapGoal.CoverSpots) > 0) {
					this.GoalState = this.COVER_DYNAMITE;
					this.UpdateDebugString();
				}
				else {
					this.Finished();
				}
			}
			case this.LAY_EXPLOSIVE:
			{
				if (!this.Bot.IsWeaponCharged(WEAPON.DYNAMITE))
				{
					this.Finished();
				}
				this.UpdateDebugString();

				this.Bot.HoldButton(BTN.CROUCH, 99);
				this.Priority = 2.0;
				this.Bot.DoNotKill = true;

				// for voice chat 'cover me'
				signal(this.Bot.Name + "_plant");

				goalPos = this.MapGoal.GetPosition();
				this.AddAimRequest(Priority.High, "position", Vec3(goalPos.x, goalPos.y, this.Bot.GetPosition().z - 30));
				this.AddWeaponRequest(WEAPON.DYNAMITE);
				this.BlockForWeaponChange(WEAPON.DYNAMITE);
				sleep(0.2);
				this.Bot.PressButton(BTN.ATTACK1);
				sleep(0.3);
			}
			case this.ARM_EXPLOSIVE:
			{
				this.UpdateDebugString();

				if (!EntityIsValid(this.ExplosiveEntity))
				{
					this.Finished();
				}

				// crouch to arm others dyno too.
				if (!this.Bot.HasEntityFlag(ENTFLAG.CROUCHED)) {
					this.Bot.HoldButton(BTN.CROUCH, 99);
				}

				this.AddWeaponRequest(WEAPON.PLIERS);
				this.BlockForWeaponChange(WEAPON.PLIERS);

				while (EntityIsValid(this.ExplosiveEntity) && this.Bot.GetExplosiveState(this.ExplosiveEntity) == 1)
				{
					dynoPosition = GetEntPosition(this.ExplosiveEntity);
					this.AddAimRequest(Priority.High, "position", dynoPosition);
					this.Bot.MoveTowards(dynoPosition);
					this.Bot.PressButton(BTN.ATTACK1);
					yield();
				}

				this.ReleaseWeaponRequest();
				this.ReleaseAimRequest();
				this.Bot.ReleaseButton(BTN.CROUCH);

				// look for other close by dynos to arm
				this.GoalState = this.LOOK_FOR_DYNO;
			}
			case this.COVER_DYNAMITE:
			{
				if (!EntityIsValid(this.ExplosiveEntity) || this.Bot.GetExplosiveState(this.ExplosiveEntity) != 0)
				{
					this.Finished();
				}

				// pick a cover spot
				if (!hasCoverSpot) {

					// choose spot which is not occupied
					maxval = -1;
					foreach (id and spot in this.MapGoal.CoverSpots) {
						if (spot.occupied || this.CoverDisabled(spot.position)) {
							continue;
						}
						val = UnitRandom();
						if (val > maxval) {
							maxval = val;
							this.spotIndex = id;
						}
					}

					// no more cover spots available
					if (IsNull(this.spotIndex)) { this.Finished(); }

					spot = this.MapGoal.CoverSpots[ this.spotIndex ];
					spot.occupied = true;

					if (spot.AimVectors && tableCount(spot.AimVectors) > 0) {
						aimVectors = spot.AimVectors;
						if (spot.Timeout) { coverTimeout = spot.Timeout; }
						delay = 31 - coverTimeout;
						hasCoverSpot = true;
					}
					else {
						if (this.Debug) { print(this.Goal_Name + ": Cover Spot has no AimVectors!"); }
						this.Finished();
					}

					this.Priority = goalPriority;
					this.Bot.DoNotKill = null;
					this.Goto(spot.position, 32);

					if (spot.DisableCM > 0 && !this.Bot.DisableCombatMovement)
					{
						this.CombatMovementDisabled = true;
						this.Bot.DisableCombatMovement = true;
					}
				}

				switch (spot.stance)
				{
					case "crouch":
					{
						this.Bot.HoldButton(BTN.CROUCH, 1.1);
					}
					case "prone":
					{
						this.Bot.HoldButton(BTN.PRONE, 1.1);
					}
				}

				this.AddAimRequest(Priority.Low, "facing", Util.GetRandomTableValue(aimVectors));

				sleep(1);

				--coverTimeout;
				if (coverTimeout <= 0) {
					if (delay > 0) {
						this.BlackboardDelay(delay);
					}
					this.Finished();
				}
			}
			default:
			{
				this.Finished();
			}
		}
	}
};

this.UpdateSatchel = function()
{
	for(;;)
	{
		switch(this.GoalState)
		{
			case this.LAY_EXPLOSIVE:
			{
				if (!this.Bot.IsWeaponCharged(WEAPON.SATCHEL))
				{
					this.Finished();
				}
				this.UpdateDebugString();

				this.Priority = 2.0;
				this.Bot.DoNotKill = true;

				if (EntityIsValid(this.SatchelEntity) && GetModName() == "nitmod")
				{
					if (this.Bot.DistanceTo(this.SatchelEntity, true) > 1300){
						this.Goto(GetEntPosition(this.SatchelEntity));
					}
					this.GoalState = this.RUNAWAY;
					continue;
				}

				if (this.MapGoal.SatchelFunction)
				{
					this.MapGoal.SatchelFunction(this);
					this.SatchelEntity = null;
					this.Finished();
				}
				this.AddAimRequest(Priority.High, "position", this.MapGoal.GetPosition());
				this.AddWeaponRequest(WEAPON.SATCHEL);
				this.BlockForWeaponChange(WEAPON.SATCHEL);
				this.Bot.PressButton(BTN.ATTACK1);
			}
			case this.RUNAWAY:
			{
				this.UpdateDebugString();
				this.ReleaseWeaponRequest();
				this.ReleaseAimRequest();

				detSpot = null;
				if (this.MapGoal.DetonateSpots) {
					// choose detonate spot which is not disabled
					maxval = -1;
					foreach (spot in this.MapGoal.DetonateSpots) {
						if (this.CoverDisabled(spot)) {
							continue;
						}
						val = UnitRandom();
						if (val > maxval) {
							maxval = val;
							detSpot = spot;
						}
					}
				}
				if (detSpot)
				{
					if (this.Goto(detSpot) == EVENT.PATH_SUCCESS)
					{
						this.GoalState = this.DETONATE_EXPLOSIVE;
					}
					else
					{
						detSpot = null;
					}
				}
				if (!detSpot)
				{
					if (!this.GotoRandomAsync(32)) {
						return;
					}

					sleep(0.5); // give them a chance to start moving before checking velocity ...
					// todo: need a func that will return random dest vec to evaluate instead
					while (!this.Bot.GetVelocity().IsZero())
					{
						if (!EntityIsValid(this.SatchelEntity))
						{
							return;
						}

						if (this.Bot.DistanceTo(this.SatchelEntity) > 300)
						{
							this.GoalState = this.DETONATE_EXPLOSIVE;
							break;
						}

						sleep(1);
					}
				}
			}
			case this.DETONATE_EXPLOSIVE:
			{
				this.UpdateDebugString();

				if (EntityIsValid(this.SatchelEntity))
				{
					this.AddWeaponRequest(WEAPON.SATCHEL_DET);
					this.BlockForWeaponChange(WEAPON.SATCHEL_DET);
					this.Bot.HoldButton(BTN.ATTACK1, 1.5);

					for (timeout = 20; timeout > 0 && EntityIsValid(this.SatchelEntity); --timeout)
					{
						yield();
					}
				}
				this.SatchelEntity = null;
				this.Finished();
			}
			default:
			{
				return;
			}
		}

		sleep(0.25);
	}
};

this.UpdateDebugString = function()
{
	// for /bot debugbot <bot or all> fpinfo prints
	this.DebugString = this.Goal_Name + " : " + this.GoalStateNames[this.GoalState];
};

this.Events[EVENT.WEAPON_FIRE] = function(weapon, ent)
{
	switch(weapon)
	{
		case WEAPON.SATCHEL:
		{
			this.SatchelEntity = ent;
			this.GoalState = this.RUNAWAY;
		}
		case WEAPON.DYNAMITE:
		{
			this.ExplosiveEntity = ent;
			this.GoalState = this.ARM_EXPLOSIVE;
		}
	}
};

this.Events[EVENT.ENT_ENTER_RADIUS] = function(ent)
{
	if (ent && GetEntClass(ent) == CLASS.SATCHEL_ENT && this.Bot.GetClass() == CLASS.ENGINEER
		&& this.Bot.IsAllied(ent) && this.GoalState != this.COVER_DYNAMITE)
	{
		// abort this goal only if engineer has same goal as covertops
		pos = GetEntPosition(ent);
		if (pos && DistanceBetween(pos, this.MapGoal.GetPosition()) < 250)
		{
			this.BlackboardDelay(10);
			this.Finished();
		}
	}
};
