// Leaf node definitions for default conditions.
class X2AIBTDefaultConditions extends X2AIBTLeafNode
	native(AI);

enum eConditionValueType
{
	eBTCV_None,
	eBTCV_Stat,
	eBTCV_HitChance,
	eBTCV_PotentialHitChance,
	eBTCV_PopularSupport,
	eBTCV_AlertDataAge,
	eBTCV_AlertDataRadius,
	eBTCV_AlertDistance,
	eBTCV_AlertCount,
	eBTCV_AbilityTargetUnitCount,
	eBTCV_OverwatcherCount,
	eBTCV_SuppressorCount,
	eBTCV_TargetSetCount,
	eBTCV_Difficulty,
	eBTCV_CombatCount,
	eBTCV_TopHitChance, // Check against highest standard shot hit chance against all XCom targets.
	eBTCV_VisibleEnemies, // Checks visible living XCom units only.  Skips Turrets, cosmetic units, and incapacitated units.
	eBTCV_VisibleAllies, // Checks visible living allies only.  Skips Turrets, cosmetic units, and incapacitated units.
	eBTCV_BTVar, // local transient variable generated by behavior tree.
	eBTCV_UnitValue, // Persistent variable saved with the unit state.
	eBTCV_GroupSize,
	eBTCV_ObjectiveDistance, // Distance from objective along axis of play, in meters.
	eBTCV_TargetDistance,    // Distance from 'potential' Target, in meters.
	eBTCV_PriorityObjectAttackCount, // Number of times a priority object has been attacked.
	eBTCV_CiviliansKilled,	 // Number of civilians killed in this mission.  (Intended for Retaliation maps)
};

var delegate<BTConditionDelegate> m_dConditionFn;
var ECharStatType m_eStat;
var int m_iVal;
var bool m_bPercent;
var eConditionValueType m_eValType;
var delegate<StatCompareDelegate> m_dStatCompareFn;
var bool m_bApplyToTarget;

delegate bt_status BTConditionDelegate();       
delegate bool StatCompareDelegate(int A, int B);

native function SetApplyToTarget( );
protected function OnInit( int iObjectID, int iFrame )
{
	local int bIsPercent;
	super.OnInit(iObjectID, iFrame);

	// Fill out parameters based on ParamList strings
	if( FindBTStatConditionDelegate(m_ParamList, m_dConditionFn, m_dStatCompareFn, m_eStat, m_iVal, bIsPercent, m_eValType, SplitNameParam) )
	{
		if (bIsPercent != 0)
		{
			m_bPercent = true;
		}
	}
	else if (!FindBTConditionDelegate(m_strName, m_dConditionFn, SplitNameParam))
	{
		`WARN("X2AIBTDefaultConditions- No delegate condition defined for node"@m_strName);
	}
	// For nodes with ability names, check unit if ability exists and replace with an equivalent ability if needed.
	if( SplitNameParam != '' )
	{
		ResolveAbilityNameWithUnit(SplitNameParam, m_kBehavior);
	}
}

protected function bt_status Update()
{
	local bt_status eStatus;
	// Early exit if this has already been evaluated.
	if (m_eStatus == BTS_SUCCESS || m_eStatus == BTS_FAILURE)
		return m_eStatus;

	X2AIBTBehaviorTree(Outer).ActiveNode = self;

	if( m_dConditionFn != None )
	{
		eStatus = m_dConditionFn();
			return eStatus;
	}
	return BTS_FAILURE;
}

static function int GetIntFromParam( array<Name> ParamList, int iNum )
{
	local string strVal;
	local int nValue;
	if (iNum < ParamList.Length)
	{
		strVal = string(ParamList[iNum]);
		nValue = int(strVal);
	}
	return nValue;
}

static event bool FindBTStatConditionDelegate( array<Name> ParamList, optional out delegate<BTConditionDelegate> dOutFn, optional out delegate<StatCompareDelegate> dCmpFn, optional out ECharStatType eStat, optional out int nValue, optional out int bPercent, optional out eConditionValueType ValueType_out, optional out name NameParam)
{
	local string StatName, Operation, strVal;
	local int iPercentIdx;
	ValueType_out = eBTCV_None;
	if( ParamList.Length == 3 || ParamList.Length == 4)
	{
		StatName = string(ParamList[0]);
		Operation = string(ParamList[1]);
		strVal = string(ParamList[2]);
		if( StatName == "HitChance" )
		{
			ValueType_out = eBTCV_HitChance;
		}
		else if( ParseNameForNameAbilitySplit(Name(StatName), "PotentialHitChance-", NameParam) )
		{
			ValueType_out = eBTCV_PotentialHitChance;
		}
		else if( StatName == "TopHitChance" )
		{
			ValueType_out = eBTCV_TopHitChance;
		}
		else if( StatName == "BTVar" )
		{
			ValueType_out = eBTCV_BTVar;
		}
		else if( StatName == "VisibleEnemyCount" )
		{
			ValueType_out = eBTCV_VisibleEnemies;
		}
		else if( StatName == "VisibleAllyCount" )
		{
			ValueType_out = eBTCV_VisibleAllies;
		}
		else if( StatName == "PopularSupport" )
		{
			ValueType_out = eBTCV_PopularSupport;
		}
		else if (StatName == "AlertDataAge")
		{
			ValueType_out = eBTCV_AlertDataAge;
		}
		else if (StatName == "AlertDataRadius")
		{
			ValueType_out = eBTCV_AlertDataRadius;
		}
		// dkaplan: removed 3/23/15
		//else if (StatName == "AlertDataDistanceAtTimeOfAlert")
		//{
		//	ValueType_out = eBTCV_AlertDataDistanceAtTimeOfAlert;
		//}
		else if( StatName == "AlertDataDistance" )
		{
			ValueType_out = eBTCV_AlertDistance;
		}
		else if( StatName == "AlertDataCount" )
		{
			ValueType_out = eBTCV_AlertCount;
		}
		else if( StatName == "OverwatcherCount" )
		{
			ValueType_out = eBTCV_OverwatcherCount;
		}
		else if( StatName == "SuppressorCount" )
		{
			ValueType_out = eBTCV_SuppressorCount;
		}
		else if( StatName == "TargetSelectedThisTurnCount" )
		{
			ValueType_out = eBTCV_TargetSetCount;
		}
		else if( ParseNameForNameAbilitySplit(Name(StatName), "AbilityTargetUnitCount-", NameParam) )
		{
			ValueType_out = eBTCV_AbilityTargetUnitCount;
		}
		else if( StatName == "Difficulty" )
		{
			ValueType_out = eBTCV_Difficulty;
		}
		else if( StatName == "CombatCount" )
		{
			ValueType_out = eBTCV_CombatCount;
		}
		else if( StatName == "GroupSize" )
		{
			ValueType_out = eBTCV_GroupSize;
		}
		else if( StatName == "ObjectiveDistance" )
		{
			ValueType_out = eBTCV_ObjectiveDistance;
		}
		else if( StatName == "PotentialTargetDistance" )
		{
			ValueType_out = eBTCV_TargetDistance;
		}
		else if( StatName == "PriorityObjectAttackCount" )
		{
			ValueType_out = eBTCV_PriorityObjectAttackCount;
		}
		else if( ParseNameForNameAbilitySplit(Name(StatName), "UnitValue-", NameParam) )
		{
			ValueType_out = eBTCV_UnitValue;
		}
		else
		{
			eStat = FindStatByName(StatName);

			if (eStat <= eStat_Invalid || eStat >= eStat_MAX)
			{
				`RedScreen("Invalid eStat:"$StatName);
				return false;
			}
			else
			{
				ValueType_out = eBTCV_Stat;
			}
		}

		switch (Operation)
		{
			case "=":
			case "==":
				dCmpFn=CheckStatEqual;
			break;
			case "!=":
				dCmpFn=CheckStatNotEqual;
			break;
			case ">":
				dCmpFn=CheckStatGreater;
			break;
			case ">=":
				dCmpFn=CheckStatGreaterOrEqual;
			break;
			case "<":
				dCmpFn=CheckStatLessThan;
			break;
			case "<=":
				dCmpFn=CheckStatLessThanOrEqual;
			break;
			default:
				`RedScreen("Invalid Operator:"$Operation);
				return false;
			break;
		}
		dOutFn = CheckStatCompare;
		iPercentIdx = InStr(strVal, "%");
		if( iPercentIdx != -1 )
		{
			bPercent = 1;
			strVal -= "%";
			nValue = int(strVal);
			if( nValue > 100 || nValue < 0 )
			{
				`RedScreen("Invalid percent value:"$strVal);
				return false;
			}
		}
		else
		{
			nValue = int(strVal);
			// sanity check
			if( nValue > 9999 || nValue < -9999 )
			{
				`RedScreen("Invalid value:"$strVal);
				return false;
			}
		}
		return true;
	}
	return false;
}

function int GetPlayerTurnCount()
{
	local XComGameState_Player ControllingPlayer;
	ControllingPlayer = XComGameState_Player(`XCOMHISTORY.GetGameStateForObjectID(m_kUnitState.ControllingPlayer.ObjectID));
	return ControllingPlayer.PlayerTurnCount;
}

static event bool FindBTConditionDelegate(name strName, optional out delegate<BTConditionDelegate> dOutFn, optional out Name NameParam)
{
	dOutFn = None;
	if (ParseNameForNameAbilitySplit(strName, "IsAbilityAvailable-", NameParam))
	{
		dOutFn = IsAbilityAvailable;
		return true;
	}
	if (ParseNameForNameAbilitySplit(strName, "IsAbilityReady-", NameParam))
	{
		dOutFn = IsAbilityReady;
		return true;
	}
	if( ParseNameForNameAbilitySplit(strName, "TargetIsApplyingEffect-", NameParam) )
	{
		dOutFn = IsTargetApplyingEffect;
		return true;
	}
	if( ParseNameForNameAbilitySplit(strName, "TargetAffectedByEffect-", NameParam) )
	{
		dOutFn = IsTargetAffectedBy;
		return true;
	}
	if( ParseNameForNameAbilitySplit(strName, "AffectedByEffect-", NameParam) )
	{
		dOutFn = IsAffectedBy;
		return true;
	}
	if (ParseNameForNameAbilitySplit(strName, "HasValidTarget-", NameParam))
	{
		dOutFn = HasValidTarget;
		return true;
	}

	if (ParseNameForNameAbilitySplit(strName, "WasLastAbility-", NameParam))
	{
		dOutFn = WasLastAbility;
		return true;
	}

	if (ParseNameForNameAbilitySplit(strName, "IsMissionOfType-", NameParam))
	{
		dOutFn = IsMissionOfType;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsMyJob-", NameParam) )
	{
		dOutFn = IsMyJob;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "AlertDataHasTag-", NameParam) )
	{
		dOutFn = AlertDataHasTag;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "AlertDataIsType-", NameParam) )
	{
		dOutFn = AlertDataIsType;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "HasBTVar-", NameParam) )
	{
		dOutFn = HasBTVar;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsTargetInAttackRange-", NameParam) )
	{
		dOutFn = IsTargetInAttackRange;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsTargetInMovementRange-", NameParam) )
	{
		dOutFn = IsTargetInMovementRange;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "TargetTemplateNameIs-", NameParam) )
	{
		dOutFn = TargetTemplateNameCheck;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "TemplateNameIs-", NameParam) )
	{
		dOutFn = TemplateNameCheck;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsUnitTypeVisible-", NameParam) )
	{
		dOutFn = IsUnitTypeVisible;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "MultipleUnitsVisibleOfType-", NameParam) )
	{
		dOutFn = MultipleUnitsVisibleOfType;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsTargetClosestOfType-", NameParam) )
	{
		dOutFn = IsTargetClosestOfType;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "AreAllRemainingAlliesUnderEffect-", NameParam) )
	{
		dOutFn = AreAllRemainingAlliesUnderEffect;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsActiveTeam-", NameParam) )
	{
		dOutFn = IsActiveTeam;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsTeam-", NameParam) )
	{
		dOutFn = IsTeam;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "HasUnitValue-", NameParam) )
	{
		dOutFn = HasUnitValue;
		return true;
	}

	if( ParseNameForNameAbilitySplit(strName, "IsSelectedAbility-", NameParam) )
	{
		dOutFn = IsSelectedAbility;
		return true;
	}

	switch( strName )
	{
	case 'HasAmmo':
		dOutFn = HasAmmo;
		return true;
	
	case 'IsLastActionPoint':
		dOutFn = IsLastActionPoint;
		return true;

	case 'HasThreeActionPoints':
		dOutFn = HasThreeActionPoints;
		return true;
	
	case 'HasGoodShotTarget':
		dOutFn = HasGoodShotTarget;
		return true;

	case 'IsInDangerousArea':
		dOutFn = IsInDangerousArea;
		return true;

	case 'HasKillShot':
		dOutFn = HasKillShot;
		return true;

	case 'TargetHitChanceLow':
		dOutFn = HasLowHitChanceOnTarget;
		return true;

	case 'TargetHitChanceHigh':
		dOutFn = HasHighHitChanceOnTarget;
		return true;

	case 'BestTargetHitChanceOver50':
		dOutFn = IsBestHitChanceOver50;
		return true;

	case 'TargetIsKillable':
		dOutFn = TargetIsKillable;
		return true;

	case 'TargetIsAdvent':
		dOutFn = TargetIsAdvent;
		return true;

	case 'TargetIsAlien':
		dOutFn = TargetIsAlien;
		return true;

	case 'TargetIsRobotic':
		dOutFn = TargetIsRobotic;
		return true;

	case 'IsFlankingTarget':
		dOutFn= IsFlankingTarget;
		return true;

	case 'HasPriorityTargetUnit':
		dOutFn= HasPriorityTargetUnit;
		return true;

	case 'HasPriorityTargetObject':
		dOutFn= HasPriorityTargetObject;
		return true;

	case 'TargetIsPriorityUnit':
		dOutFn = TargetIsPriorityUnit;
		return true;

	case 'TargetIsPriorityObject':
		dOutFn = TargetIsPriorityObject;
		return true;

	case 'HasEnemyVIP':
		dOutFn = HasEnemyVIP;
		return true;

	case 'TargetIsEnemyVIP':
		dOutFn = TargetIsEnemyVIP;
		return true;

	case 'CanSeeLivingVIPOrCarriedVIP':
		dOutFn = CanSeeLivingVIPOrCarriedVIP;
		return true;

	case 'EvacWithinVisRange':
		dOutFn = EvacWithinVisRange;
		return true;

	case 'IsOrangeAlert':
		dOutFn= IsOrangeAlert;
		return true;

	case 'IsInFirstCombatTurn':
		dOutFn= IsFirstCombatTurn;
		return true;

	case 'DidNotMoveLastTurn':
		dOutFn= DidNotMoveLastTurn;
		return true;

	case 'IsFlanked':
		dOutFn= IsFlanked;
		return true;

	case 'IsVisibleToPlayer':
		dOutFn = IsVisibleToPlayer;
		return true;

	case 'TargetIsVisibleToPlayer': // Used in special cases where an AI is targeting an AI unit or civilian.
		dOutFn = TargetIsVisibleToPlayer;
		return true;

	case 'LowPopSupport':
		dOutFn= IsLowPopularSupport;
		return true;
		break;

	case 'IsCallReinforcementsTriggered':
		dOutFn= IsCallReinforcementsTriggered;
		return true;
		break;

	case 'TargetHasHighestSoldierRank':
		dOutFn= TargetHasHighestSoldierRank;
		return true;
	break;

	case 'TargetHasHighestTeamVisibility':
		dOutFn= TargetHasHighestTeamVisibility;
		return true;
	break;

	case 'TargetHasHighestShotHitChance':
		dOutFn= TargetHasHighestShotHitChance;
		return true;
	break;

	case 'AlertDataIsAbsoluteKnowledge':
		dOutFn = AlertDataIsAbsoluteKnowledge;
		return true;
	break;

	case 'AlertDataWasSoundMade':
		dOutFn = AlertDataWasSoundMade;
		return true;
	break;

	case 'AlertDataWasEnemyThere':
		dOutFn = AlertDataWasEnemyThere;
		return true;
	break;

	case 'AlertDataIsCorpseThere':
		dOutFn = AlertDataIsCorpseThere;
		return true;
	break;

	case 'AlertDataIsAggressive':
		dOutFn = AlertDataIsAggressive;
		return true;
	break;

	case 'AlertDataTileIsVisible':
		dOutFn = AlertDataTileIsVisible;
		return true;

	case 'HasValidAlertDataLocation':
		dOutFn = HasValidAlertDataLocation;
		return true;
	break;

	case 'IsXComInCivilianRadius':
		dOutFn = IsXComInCivilianRadius;
		return true;
		break;

	case 'IsAIInCivilianRadius':
		dOutFn = IsAIInCivilianRadius;
		return true;
		break;

	case 'TargetIsCivilian':
		dOutFn = TargetIsCivilian;
		return true;
	break;

	case 'TargetIsAlly':
		dOutFn = TargetIsAlly;
		return true;
		break;

	case 'TargetIsEnemy':
		dOutFn = TargetIsEnemy;
		return true;
		break;

	case 'TargetIsNotAttackable':
		dOutFn = TargetIsNotAttackable;
		return true;
		break;

	case 'TargetIsClosestValidTarget':
		dOutFn = TargetIsClosestValidTarget;
		return true;
		break;

	case 'HasJob':
		dOutFn = HasJob;
		return true;
		break;

	case 'HasUnengagedJob':
		dOutFn = HasUnengagedJob;
		return true;
		break;

	case 'HasRevealed':
		dOutFn = HasRevealed;
		return true;
	break;

	case 'IsGroupLeader':
		dOutFn = IsGroupLeader;
		return true;
	break;

	case 'HasLivingEnemiesWithoutEffects':
		dOutFn = HasLivingEnemiesWithoutEffects;
		return true;
	break;

	case 'TargetCanBecomeZombie':
		dOutFn = TargetCanBecomeZombie;
		return true;
		break;

	case 'DoesGrenadeCauseDamage':
		dOutFn = DoesGrenadeCauseDamage;
		return true;
		break;

	case 'HasEncounterBandBeenPassed':
		dOutFn = HasEncounterBandBeenPassed;
		return true;
		break;

	case 'IsInMovementRangeOfAxisOfPlay':
		dOutFn = IsInMovementRangeOfAxisOfPlay;
		return true;
		break;

	case 'HasUnconcealedEnemies':
		dOutFn = HasUnconcealedEnemies;
		return true;
		break;

	case 'HasTwoTurnAttackTargets':
		dOutFn = HasTwoTurnAttackTargets;
		return true;
		break;

	case 'IsTargetInTwoTurnAttackArea':
		dOutFn = IsTargetInTwoTurnAttackArea;
		return true;
		break;

	case 'HasXComUnitsCloserToObjective':
		dOutFn = HasXComUnitsCloserToObjective;
		return true;
		break;

	case 'HasVisibleCivilianInMoveRange':
		dOutFn = HasVisibleCivilianInMoveRange;
		return true;
		break;

	case 'IsLastResortTarget':
		dOutFn = IsLastResortTarget;
		return true;
		break;

	case 'IsTargetValidBasedOnLastResortEffects':
		dOutFn = IsTargetValidBasedOnLastResortEffects;
		return true;
		break;
		
	case 'HasNonLastResortEnemies':
		dOutFn = HasNonLastResortEnemies;
		return true;
		break;

	case 'NumKilledCiviliansIsLessThanTurnCount':
		dOutFn = NumKilledCiviliansIsLessThanTurnCount;
		return true;
		break;

	case 'RollForSuppressionPerVisibleAlly':
		dOutFn = RollForSuppressionPerVisibleAlly;
		return true;
		break;

	case 'HasHitAttackLimit':
		dOutFn = HasHitAttackLimit;
		return true;
	break;

	case 'IsTargetScamperInstigator':
		dOutFn = IsTargetScamperInstigator;
		return true;
	break;

	case 'WasTargetPreviouslyConcealed':
		dOutFn = WasTargetPreviouslyConcealed;
		return true;
	break;

	default:
		`WARN("Unresolved behavior tree condition name with no delegate definition:"@strName);
		break;
	}
	return false;
}

function bt_status IsTargetScamperInstigator()
{
	local XComGameState_AIGroup Group;
	Group = m_kUnitState.GetGroupMembership();
	if( Group != None
	   && m_kBehavior.m_kBTCurrTarget.TargetID > 0
	   && m_kBehavior.m_kBTCurrTarget.TargetID == Group.RevealInstigatorUnitObjectID )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status WasTargetPreviouslyConcealed()
{
	local XComGameState_AIGroup Group;
	Group = m_kUnitState.GetGroupMembership();
	if( Group != None
	   && m_kBehavior.m_kBTCurrTarget.TargetID > 0
	   && Group.PreviouslyConcealedUnitObjectIDs.Find(m_kBehavior.m_kBTCurrTarget.TargetID) != INDEX_NONE )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasHitAttackLimit()
{
	local int AttackLimit, Difficulty, NumAttackingUnits;
	local XComGameState_AIPlayerData AIData;
	local XGAIPlayer AIPlayer;
	AIData = m_kBehavior.GetAIPlayerData();
	if( AIData != None )
	{
		Difficulty = `DIFFICULTYSETTING;
		if( AIData.MaxEngagedEnemies.Length > Difficulty )
		{
			AttackLimit = AIData.MaxEngagedEnemies[Difficulty];
			if( AttackLimit > 0 )
			{
				AIPlayer = XGAIPlayer(`BATTLE.GetAIPlayer());
				if( AIPlayer != None)
				{
					NumAttackingUnits = AIPlayer.GetNumAggressiveUnitsThisTurn();
					`LogAIBT(`ShowVar(NumAttackingUnits)$", "@`ShowVar(AttackLimit));
					if( NumAttackingUnits >= AttackLimit )
					{
						return BTS_SUCCESS;
					}
				}
			}
			// Attack limit is negative.  Unlimited attacks!
			return BTS_FAILURE;
		}
		else
		{
			`LogAIBT("Error - MaxEngagedEnemies array length ="@AIData.MaxEngagedEnemies.Length@" - Current Difficulty Setting="@Difficulty);
			return BTS_FAILURE;
		}
	}
	`LogAIBT("Error - No AIPlayerData game state found!");
	return BTS_FAILURE;
}

// Roll - Chance per visible Ally is in Param[0].
function bt_status RollForSuppressionPerVisibleAlly()
{
	local int ChancePerAlly;
	local String ChanceString;
	local int Roll, TotalChance, NumVisibleAllies;

	if( m_ParamList.Length == 1 )
	{
		ChanceString = String(m_ParamList[0]);
		ChancePerAlly = int(ChanceString);
		if( ChancePerAlly < 0 || ChancePerAlly > 100 )
		{
			`LogAIBT("RollForSuppressionPerVisibleAlly failure - Param[0] entered for chance value per visible ally is invalid!"@`ShowVar(ChancePerAlly));
			return BTS_FAILURE;
		}
		NumVisibleAllies = m_kBehavior.BT_GetVisibleAllyCount();
		TotalChance = ChancePerAlly * NumVisibleAllies;
		Roll = `SYNC_RAND(100);
		`LogAIBT("RollForSuppressionPerVisibleAlly rolled a "$Roll$".  TotalChance = ("@NumVisibleAllies@"x"@ChancePerAlly@") ="@TotalChance);
		if( Roll < TotalChance )
		{
			`LogAIBT(" Roll Result: SUCCESS! ");
			return BTS_SUCCESS;
		}
		`LogAIBT(" Roll Result: FAILURE! ");
	}
	else
	{
		`LogAIBT("RollForSuppressionPerVisibleAlly failure - No Param[0] entered for chance value per visible ally!");
	}
	return BTS_FAILURE;
}

function bt_status NumKilledCiviliansIsLessThanTurnCount()
{
	local int NumKilledCivilians, TurnCount;
	NumKilledCivilians = class'Helpers'.static.GetNumCiviliansKilled();
	TurnCount = GetPlayerTurnCount();
	`LogAIBT(`ShowVar(NumKilledCivilians)@`ShowVar(TurnCount));
	if( NumKilledCivilians < TurnCount )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasNonLastResortEnemies()
{
	if( m_kBehavior.m_kPlayer != None )
	{
		if( m_kBehavior.m_kPlayer.HasNonLastResortEnemies() )
		{
			return BTS_SUCCESS;
		}
	}
	else
	{
		`LogAIBT("IsLastResortTarget failure - AIPlayer not found for unit: "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}

function bt_status IsTargetValidBasedOnLastResortEffects()
{
	local XComGameState_Unit CurrTarget;
	if( m_kBehavior.m_kPlayer != None )
	{
		if (m_kBehavior.BT_GetTarget(CurrTarget)) // Get current target or curr alert target.
		{
			if( m_kBehavior.m_kPlayer.IsLastResortTarget(CurrTarget.ObjectID) )
			{
				return BTS_SUCCESS;
			}
		}
		else
		{
			`LogAIBT("IsTargetValidBasedOnLastResortEffects failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
		}
	}
	else
	{
		`LogAIBT("IsTargetValidBasedOnLastResortEffects failure - AIPlayer not found for unit: "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}

function bt_status IsLastResortTarget()
{
	local XComGameState_Unit CurrTarget;
	if( m_kBehavior.m_kPlayer != None )
	{
		if (m_kBehavior.BT_GetTarget(CurrTarget)) // Get current target or curr alert target.
		{
			if( m_kBehavior.m_kPlayer.IsLastResortTarget(CurrTarget.ObjectID) )
			{
				return BTS_SUCCESS;
			}
		}
		else
		{
			`LogAIBT("IsLastResortTarget failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
		}
	}
	else
	{
		`LogAIBT("IsLastResortTarget failure - AIPlayer not found for unit: "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}

function bt_status IsSelectedAbility()
{
	if( m_kBehavior.m_strBTAbilitySelection == SplitNameParam )
	{
		return BTS_SUCCESS;
	}
	`LogAIBT("Selected Ability = "$m_kBehavior.m_strBTAbilitySelection);
	return BTS_FAILURE;
}

// Visible to both players, in move range to unit.
function bt_status HasVisibleCivilianInMoveRange()
{
	local array<StateObjectReference> VisibleCivilians;
	local StateObjectReference CivRef;
	local int EnemyPlayerID;
	local X2GameRulesetVisibilityManager VisibilityMgr;
	local GameRulesCache_VisibilityInfo VisInfo;
	local float MaxDistSq;

	class'X2TacticalVisibilityHelpers'.static.GetAllVisibleUnitsOnTeamForSource(m_kUnitState.ObjectID, eTeam_Neutral, VisibleCivilians);
	if( VisibleCivilians.Length > 0 && m_kBehavior.m_kPlayer != None)
	{
		VisibilityMgr = `TACTICALRULES.VisibilityMgr;
		EnemyPlayerID = `BATTLE.GetEnemyPlayer(m_kBehavior.m_kPlayer).ObjectID;
		MaxDistSq = `METERSTOUNITS(m_kUnitState.GetCurrentStat(eStat_Mobility));
		MaxDistSq = Square(MaxDistSq);
		foreach VisibleCivilians(CivRef)
		{
			// Check visible to other team.
			if( class'X2TacticalVisibilityHelpers'.static.GetTargetIDVisibleForPlayer(CivRef.ObjectID, EnemyPlayerID) )
			{
				// Check if in movement range.
				if( VisibilityMgr.GetVisibilityInfo(m_kUnitState.ObjectID, CivRef.ObjectID, VisInfo) )
				{
					if( VisInfo.DefaultTargetDist <= MaxDistSq )
					{
						return BTS_SUCCESS;
					}
				}
			}
		}
	}
	return BTS_FAILURE;
}

function bt_status HasXComUnitsCloserToObjective()
{
	local float FuzzDist;
	local String FuzzDistString;
	if( m_ParamList.Length == 1 )
	{
		FuzzDistString = String(m_ParamList[0]);
		FuzzDist = float(FuzzDistString);
	}
	if( m_kBehavior.HasXComUnitsCloserToObjective(FuzzDist) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status IsTargetInTwoTurnAttackArea()
{
	if( m_kBehavior.m_kPlayer != None && m_kBehavior.m_kPlayer.TwoTurnAttackTargets.Length > 0 )
	{
		if( m_kBehavior.m_kPlayer.TwoTurnAttackTargets.Find('ObjectID', m_kUnitState.ObjectID) != INDEX_NONE )
		{
			return BTS_SUCCESS;
		}
	}
	return BTS_FAILURE;
}

function bt_status HasTwoTurnAttackTargets()
{
	local StateObjectReference TargetRef;
	local X2GameRulesetVisibilityManager VisibilityMgr;
	local GameRulesCache_VisibilityInfo VisInfo;
	local float MaxDistSq;

	if( m_kBehavior.m_kPlayer != None && m_kBehavior.m_kPlayer.TwoTurnAttackTargets.Length > 0 )
	{
		VisibilityMgr = `TACTICALRULES.VisibilityMgr;
		MaxDistSq = `METERSTOUNITS(m_kUnitState.GetVisibilityRadius());
		MaxDistSq = Square(MaxDistSq);
		// Return success iff targets are within sight range distance. 
		foreach m_kBehavior.m_kPlayer.TwoTurnAttackTargets(TargetRef)
		{
			if( VisibilityMgr.GetVisibilityInfo( m_kUnitState.ObjectID, TargetRef.ObjectID, VisInfo ) )
			{
				if( VisInfo.DefaultTargetDist <= MaxDistSq )
				{
					return BTS_SUCCESS;
				}
			}
		}
	}
	return BTS_FAILURE;
}

function bt_status HasUnitValue()
{
	local UnitValue Val;
	if( m_kUnitState.GetUnitValue(SplitNameParam, Val) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsTeam()
{
	local String TeamName;
	TeamName = String(XGUnit(m_kUnitState.GetVisualizer()).GetTeam()); // Using XGUnit since the UnitState version returns a byte instead of an eTeam.
	if( TeamName ~= String(SplitNameParam) )
	{
		return BTS_SUCCESS;
	}
	`LogAIBT("Failed.  Team="$TeamName@", !="@SplitNameParam);
	return BTS_FAILURE;
}

function bt_status IsActiveTeam()
{
	local String TeamName;
	local XComGameState_Player PlayerState;
	local X2TacticalGameRuleset RuleSet;
	local XComGameStateHistory History;

	RuleSet = `TACTICALRULES;
	History = `XCOMHISTORY;

	PlayerState = XComGameState_Player(History.GetGameStateForObjectID(RuleSet.GetCachedUnitActionPlayerRef().ObjectID));
	
	TeamName = String(PlayerState.GetTeam());
	if( TeamName ~= String(SplitNameParam) )
	{
		return BTS_SUCCESS;
	}
	`LogAIBT("Failed.  Team="$TeamName@", !="@SplitNameParam);
	return BTS_FAILURE;
}

function bt_status HasUnconcealedEnemies()
{
	local XComGameState_Unit Unit;
	local XGPlayer EnemyPlayer;
	local array<XComGameState_Unit> AllEnemies;

	EnemyPlayer = `BATTLE.GetEnemyPlayer(m_kBehavior.m_kPlayer);
	if( EnemyPlayer != None )
	{
		EnemyPlayer.GetUnits(AllEnemies);
		foreach AllEnemies(Unit)
		{
			if( !Unit.IsConcealed() && !Unit.bRemovedFromPlay )
			{
				return BTS_SUCCESS;
			}
		}
	}
	else
	{
		`LogAIBT("Failed to find enemy player!  m_kBehavior.m_kPlayer == None?");
	}
	return BTS_FAILURE;
}

function bt_status IsInMovementRangeOfAxisOfPlay()
{
	local vector ClosestPointOnAxis;
	local TTile ClosestTileToAxis;
	if( m_kBehavior.m_kPlayer != None && m_kBehavior.m_kPlayer.m_kNav != None )
	{
		ClosestPointOnAxis = m_kBehavior.m_kPlayer.m_kNav.GetNearestPointOnAxisOfPlay(m_kBehavior.GetGameStateLocation(), true);
		ClosestTileToAxis = `XWORLD.GetTileCoordinatesFromPosition(ClosestPointOnAxis);
		if( m_kBehavior.IsWithinMovementRange(ClosestTileToAxis) )
		{
			return BTS_SUCCESS;
		}
		else
		{
			`LogAIBT("Failed IsWithinMovementRange check.");
		}
	}
	else
	{
		`LogAIBT("Failed due to no player or nav object defined on unit.");
	}
	return BTS_FAILURE;
}

function bt_status HasEncounterBandBeenPassed()
{
	local XComGameState_AIGroup Group;
	Group = m_kUnitState.GetGroupMembership();
	if( Group != None )
	{
		if( Group.XComSquadMidpointPassedGroup() )
		{
			return BTS_SUCCESS;
		}
	}
	else
	{
		`LogAIBT("Failed check HasEncounterBandBeenPassed due to no group found!");
	}
	return BTS_FAILURE;
}

function bt_status AreAllRemainingAlliesUnderEffect()
{
	local XComGameState_Unit Ally;
	local array<XComGameState_Unit> Allies;
	if( m_kBehavior.m_kPlayer != None )
	{
		m_kBehavior.m_kPlayer.GetPlayableUnits(Allies, true);
		foreach Allies(Ally)
		{
			if( !Ally.IsUnitAffectedByEffectName(SplitNameParam) )
			{
				`LogAIBT("Failed due to Unit#"$Ally.ObjectID@" not under effect:"$SplitNameParam@"\n");
				return BTS_FAILURE;
			}
		}
		`LogAIBT("Passed: all "$Allies.Length@" Allies under effect:"$SplitNameParam@"\n");
		return BTS_SUCCESS;
	}
	else
	{
		`LogAIBT("Failed due to no valid m_kBehavior.m_kPlayer object.");
	}
	return BTS_FAILURE;
}

function bt_status IsTargetClosestOfType()
{
	local array<XComGameState_Unit> VisibleTypeUnits;
	local XComWorldData World;
	local vector MyLocation;
	local XComGameState_Unit CurrTargetState, Enemy;
	local float DistSq, CurrTargetDistSq;
	World = `XWORLD;

	// Get all visible enemies of type.
	HasUnitsVisibleOfType(SplitNameParam, -1, VisibleTypeUnits);
	if( VisibleTypeUnits.Length > 1 )
	{
		MyLocation = m_kBehavior.GetGameStateLocation();
		if (m_kBehavior.BT_GetTarget(CurrTargetState))
		{

			CurrTargetDistSq = VSizeSq(World.GetPositionFromTileCoordinates(CurrTargetState.TileLocation) - MyLocation);
			foreach VisibleTypeUnits(Enemy)
			{
				if( Enemy.ObjectID == CurrTargetState.ObjectID )
				{
					continue;
				}
				DistSq = VSizeSq(World.GetPositionFromTileCoordinates(Enemy.TileLocation) - MyLocation);
				if( DistSq < CurrTargetDistSq )
				{
					`LogAIBT("Found Unit# "@Enemy.ObjectID@"closer to this unit than Unit#"@CurrTargetState.ObjectID@"\n");
					return BTS_FAILURE;
				}
			}
		}
		else
		{
			`LogAIBT("IsTargetClosestOfType failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
		}
	}
	else
	{
		`LogAIBT("Returning SUCCESS only because there are not more than 1 visible enemy of type"@SplitNameParam@"\n");
	}
	return BTS_SUCCESS;
}
function bt_status MultipleUnitsVisibleOfType()
{
	return HasUnitsVisibleOfType(SplitNameParam, 2);
}

function bt_status IsUnitTypeVisible()
{
	return HasUnitsVisibleOfType(SplitNameParam, 1);
}

function bt_status HasUnitsVisibleOfType(Name CharacterType, int MinCount, optional out array<XComGameState_Unit> VisibleTypeUnits, optional array<X2Condition> RequiredConditions=class'X2TacticalVisibilityHelpers'.default.LivingLOSVisibleFilter)
{
	local array<StateObjectReference> VisibleUnits;
	local XComGameState_Unit VisibleUnitState;
	local StateObjectReference UnitRef;
	local XComGameStateHistory History;
	local int Count;
	History = `XCOMHISTORY;
	class'X2TacticalVisibilityHelpers'.static.GetAllVisibleEnemyUnitsForUnit(m_kUnitState.ObjectID, VisibleUnits, RequiredConditions);
	foreach VisibleUnits(UnitRef)
	{
		VisibleUnitState = XComGameState_Unit(History.GetGameStateForObjectID(UnitRef.ObjectID));
		if( VisibleUnitState.GetMyTemplateName() == CharacterType && !VisibleUnitState.bRemovedFromPlay )
		{
			Count++;
			VisibleTypeUnits.AddItem(VisibleUnitState);
			if( MinCount > 0 && Count >= MinCount )
			{
				return BTS_SUCCESS;
			}
		}
	}
	return BTS_FAILURE;
}

// Function used to check if grenade ability can cause damage- and prevent smoke grenade usage for mind-controlled.
function bt_status DoesGrenadeCauseDamage()
{
	local XComGameState_Ability AbilityState;
	local StateObjectReference TargetRef;
	local WeaponDamageValue MinDamage, MaxDamage;
	local int AllowsShields;

	m_kBehavior.FindAbilityByName('ThrowGrenade', AbilityState);
	if( AbilityState != None )
	{
		AbilityState.GetDamagePreview(TargetRef, MinDamage, MaxDamage, AllowsShields);
		if( MinDamage.Damage > 0 )
		{
			return BTS_SUCCESS;
		}
		else
		{
			`LogAIBT("DoesGrenadeCauseDamage MinDamage = "$MinDamage.Damage@" . MaxDamage = "$MaxDamage.Damage);
		}
	}
	else
	{
		`LogAIBT("DoesGrenadeCauseDamage failed due to no ThrowGrenade ability found.");
	}
	return BTS_FAILURE;
}


function bt_status TargetCanBecomeZombie()
{
	if( m_kBehavior.BT_TargetCanBecomeZombie() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasLivingEnemiesWithoutEffects()
{
	if( m_kBehavior.BT_GetLivingEnemiesWithoutEffects(m_ParamList,,true) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsGroupLeader()
{
	if( m_kUnitState.IsGroupLeader() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasRevealed()
{
	if( m_kUnitState.IsUnrevealedAI() ) // If true, this unit has not yet triggered its reveal.
	{
		return BTS_FAILURE;
	}
	return BTS_SUCCESS;
}

function bt_status HasUnengagedJob()
{
	local XComGameState_AIUnitData AIUnitData;
	local Name JobName;
	local X2AIJobManager JobMgr;
	AIUnitData = XComGameState_AIUnitData(`XCOMHISTORY.GetGameStateForObjectID(m_kBehavior.GetAIUnitDataID(m_kUnitState.ObjectID)));
	if( AIUnitData.JobIndex == INDEX_NONE )
	{
		return BTS_FAILURE;
	}
	JobMgr = `AIJOBMGR;
	JobName = JobMgr.GetJobName(AIUnitData.JobIndex);
	if( JobMgr.GetJobListing(JobName).bRequiresEngagement == false )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasJob()
{
	local XComGameState_AIUnitData AIUnitData;
	AIUnitData = XComGameState_AIUnitData(`XCOMHISTORY.GetGameStateForObjectID(m_kBehavior.GetAIUnitDataID(m_kUnitState.ObjectID)));
	if( AIUnitData.JobIndex == INDEX_NONE )
	{
		return BTS_FAILURE;
	}
	return BTS_SUCCESS;
}

function bt_status IsMyJob()
{
	local XComGameState_AIUnitData AIUnitData;
	AIUnitData = XComGameState_AIUnitData(`XCOMHISTORY.GetGameStateForObjectID(m_kBehavior.GetAIUnitDataID(m_kUnitState.ObjectID)));
	if( AIUnitData.JobIndex != INDEX_NONE && `AIJOBMGR.GetJobIndex(SplitNameParam) == AIUnitData.JobIndex )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}


function bt_status HasPriorityTargetUnit()
{
	local XComGameState_AIPlayerData kAIData;
	kAIData = m_kBehavior.GetAIPlayerData();
	if (kAIData != None)
	{
		if (kAIData.HasPriorityTargetUnit())
		{
			return BTS_SUCCESS;
		}
	}
	return BTS_FAILURE;
}
function bt_status HasPriorityTargetObject()
{
	local XComGameState_AIPlayerData kAIData;
	kAIData = m_kBehavior.GetAIPlayerData();
	if (kAIData != None)
	{
		if (kAIData.HasPriorityTargetObject())
		{
			return BTS_SUCCESS;
		}
	}
	return BTS_FAILURE;
}
function bt_status TargetIsPriorityUnit()
{
	local XComGameState_AIPlayerData kAIData;
	local XComGameState_Unit kPriorityTarget;

	if( m_kBehavior.m_kBTCurrTarget.TargetID == m_kBehavior.PriorityTarget.ObjectID )
	{
		return BTS_SUCCESS;
	}

	kAIData = m_kBehavior.GetAIPlayerData();
	if( kAIData != None )
	{
		if( kAIData.HasPriorityTargetUnit(kPriorityTarget) )
		{
			if( kPriorityTarget.ObjectID > 0
			   && m_kBehavior.m_kBTCurrTarget.TargetID == kPriorityTarget.ObjectID )
			{
				return BTS_SUCCESS;
			}
		}
	}
	return BTS_FAILURE;
}

function bt_status TargetIsPriorityObject()
{
	local XComGameState_AIPlayerData kAIData;
	local XComGameState_InteractiveObject kPriorityTarget;

	if( m_kBehavior.m_kBTCurrTarget.TargetID == m_kBehavior.PriorityTarget.ObjectID )
	{
		return BTS_SUCCESS;
	}

	kAIData = m_kBehavior.GetAIPlayerData();
	if( kAIData != None )
	{
		if( kAIData.HasPriorityTargetObject(kPriorityTarget) )
		{
			if( kPriorityTarget.ObjectID > 0
			   && m_kBehavior.m_kBTCurrTarget.TargetID == kPriorityTarget.ObjectID )
			{
				return BTS_SUCCESS;
			}
		}
	}
	return BTS_FAILURE;
}

function bt_status EvacWithinVisRange()
{
	local XComGameState_EvacZone EvacZone;
	local float SightRadiusUnitsSq, DistToEvacSq;
	local vector EvacZoneCenter, UnitLoc;

	EvacZone = class'XComGameState_EvacZone'.static.GetEvacZone(eTeam_XCom);
	if( EvacZone != None )
	{
		EvacZoneCenter = `XWORLD.GetPositionFromTileCoordinates(EvacZone.CenterLocation);
		UnitLoc = `XWORLD.GetPositionFromTileCoordinates(m_kUnitState.TileLocation);
		DistToEvacSq = VSizeSq(UnitLoc - EvacZoneCenter);

		SightRadiusUnitsSq = `METERSTOUNITS(m_kUnitState.GetVisibilityRadius());
		SightRadiusUnitsSq = SightRadiusUnitsSq * SightRadiusUnitsSq;
		if( DistToEvacSq <= SightRadiusUnitsSq )
		{
			return BTS_SUCCESS;
		}
		`LogAIBT(GetFuncName()@"failed - Evac Zone found, but outside of sight radius.");
	}
	else
	{
		`LogAIBT(GetFuncName()@"failed - No Evac Zone found.");
	}
	return BTS_FAILURE;
}

function bt_status HasEnemyVIP()
{
	local XComGameState_AIPlayerData kAIData;
	kAIData = m_kBehavior.GetAIPlayerData();
	if( kAIData != None  && kAIData.HasEnemyVIP() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsEnemyVIP()
{
	local XComGameState_AIPlayerData AIData;
	local XComGameState_Unit VIP;

	AIData = m_kBehavior.GetAIPlayerData();
	if( AIData != None  && AIData.HasEnemyVIP(VIP) && VIP.ObjectID == m_kBehavior.m_kBTCurrTarget.TargetID )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status CanSeeLivingVIPOrCarriedVIP()
{
	local XComGameState_AIPlayerData kAIData;
	local XComGameState_Unit VIP;
	local array<StateObjectReference> EnemyViewers;
	kAIData = m_kBehavior.GetAIPlayerData();
	if( kAIData != None )
	{
		if( kAIData.HasEnemyVIP(VIP) )
		{
			if( VIP.IsAlive() || VIP.IsUnitAffectedByEffectName(class'X2Ability_CarryUnit'.default.CarryUnitEffectName) )
			{
				class'X2TacticalVisibilityHelpers'.static.GetEnemyViewersOfTarget(VIP.ObjectID, EnemyViewers);
				if( (EnemyViewers.Find('ObjectID', m_kUnitState.ObjectID) != INDEX_NONE)
					|| class'X2TacticalVisibilityHelpers'.static.CanUnitSeeLocation(m_kUnitState.ObjectID, VIP.TileLocation) )
				{
					return BTS_SUCCESS;
				}
				`LogAIBT(GetFuncName()@"failure: VIP exists, and is alive or carried, but not visible to this unit.");
			}
			else
			{
				`LogAIBT(GetFuncName()@"failure: VIP exists, but is dead, and not being carried.");
			}
		}
		else
		{
			`LogAIBT(GetFuncName()@"failure: No priority target unit exists.");
		}
	}
	else
	{
		`LogAIBT(GetFuncName()@"failure: m_kBehavior.GetAIPlayerData() returned None.");
	}
	return BTS_FAILURE;
}

function bt_status TargetIsCivilian()
{
	if( m_kBehavior.BT_TargetIsCivilian() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsAlly()
{
	local ETeam TargetTeam;
	TargetTeam = m_kBehavior.BT_GetTargetTeam();
	if( TargetTeam == m_kUnitState.GetTeam() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsEnemy()
{
	local ETeam TargetTeam;
	TargetTeam = m_kBehavior.BT_GetTargetTeam();
	if( m_kUnitState.GetEnemyTeam() == TargetTeam )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsNotAttackable()
{
	local XComGameState_Unit Target;
	if( m_kBehavior.BT_GetTarget(Target) ) // This pulls the alert target if there is no current target stack .
	{
		if( Target != None )
		{
			if( !Target.IsAlive() || Target.IsIncapacitated() || Target.bRemovedFromPlay || Target.GetMyTemplate().bIsCosmetic )
			{
				return BTS_SUCCESS;
			}
		}
	}
	else
	{
		`LogAIBT("TargetIsNotAttackable failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}

function bt_status TargetIsClosestValidTarget()
{
	if( m_kBehavior.BT_TargetIsClosestValidTarget() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetHasHighestSoldierRank()
{
	if (m_kBehavior.BT_TargetHasHighestSoldierRank())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status TargetHasHighestTeamVisibility()
{
	if (m_kBehavior.BT_TargetHasHighestTeamVisibility())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status TargetHasHighestShotHitChance()
{
	if (m_kBehavior.BT_TargetHasHighestShotHitChance())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status WasLastAbility()
{
	if (m_kBehavior.BT_GetLastAbilityName() ~= string(SplitNameParam))
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsCallReinforcementsTriggered()
{
	local XComGameState_AIReinforcementSpawner AISPawnerState;
	local XComGameStateHistory History;

	History = `XCOMHISTORY;

	foreach History.IterateByClassType(class'XComGameState_AIReinforcementSpawner', AISPawnerState)
	{
		break;
	}

	// true if there are any active reinforcement spawners
	if( AISPawnerState != None )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsLowPopularSupport()
{
	if (XGBattle_SP(`BATTLE).IsLowPopularSupport())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsVisibleToPlayer()
{
	if (m_kBehavior.BT_IsVisibleToPlayer(m_kUnitState.ObjectID))
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsVisibleToPlayer()
{
	if( m_kBehavior.BT_IsVisibleToPlayer(m_kBehavior.m_kBTCurrTarget.TargetID) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsFlanked()
{
	if (m_kBehavior.BT_IsFlanked())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status DidNotMoveLastTurn()
{
	if (m_kBehavior.BT_DidNotMoveLastTurn())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status IsFirstCombatTurn()
{
	if (m_kBehavior.BT_IsFirstCombatTurn())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}
function bt_status IsOrangeAlert()
{
	if (m_kBehavior.IsOrangeAlert())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status HasAmmo()
{
	if (m_kBehavior.HasAmmo())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status HasThreeActionPoints()
{
	if( m_kUnitState.NumAllActionPoints() == 3 )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}


function bt_status IsLastActionPoint()
{
	if (m_kUnitState.NumAllActionPoints() == 1 )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status HasGoodShotTarget()
{
	return BTS_FAILURE;
}
function bt_status IsInDangerousArea()
{
	local string DangerText;
	if( m_kBehavior.IsInDangerousArea(DangerText) )
	{
		`LogAIBT( DangerText );
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status HasKillShot()
{
	if (m_kBehavior.HasPotentialKillShot())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status IsAbilityAvailable()
{
	local AvailableAction kAbility;
	local string strError;
	kAbility = m_kBehavior.GetAvailableAbility(string(SplitNameParam),,strError);
	if (kAbility.AbilityObjectRef.ObjectID > 0)
		return BTS_SUCCESS;
	`LogAIBT("IsAbilityAvailable failure code:"$strError);
	return BTS_FAILURE;
}

function bt_status IsAbilityReady()
{
	local XComGameState_Ability AbilityState;
	local array<name> ErrorList;
	local name Error;
	local Name ErrorName;
	m_kBehavior.FindAbilityByName(SplitNameParam, AbilityState);
	if( AbilityState != None )
	{
		ErrorList = AbilityState.GetAvailabilityErrors(m_kUnitState);
		foreach ErrorList(Error)
		{
			// Apparently you can't convert an enum directly to a Name.  But String is valid.
			ErrorName = Name(String(Error));
			// Check Params for additional abilities to ignore.
			if( m_ParamList.Find(ErrorName) != INDEX_NONE )
			{
				continue;
			}
			if( Error != 'AA_NoTargets'
				&& Error != 'AA_NotInRange'
				&& Error != 'AA_Success' )
			{
				`LogAIBT("IsAbilityReady FAILED due to failure code:"$ErrorName);
				return BTS_FAILURE;
			}
		}
		return BTS_SUCCESS;
	}
	else
	{
		`LogAIBT("IsAbilityAvailable failed - Ability Not Found on unit:"$SplitNameParam);
	}
	return BTS_FAILURE;
}

function bt_status IsAffectedBy()
{
	if (m_kUnitState.IsUnitAffectedByEffectName(SplitNameParam))
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status IsMissionOfType()
{
	if (`BATTLE.m_kDesc.MapData.ActiveMission.sType == string(SplitNameParam))
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status IsXComInCivilianRadius()
{
	if( IsTeamInCivilianRadius(eTeam_XCom, m_kUnitState, true) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status IsAIInCivilianRadius()
{
	if( IsTeamInCivilianRadius(eTeam_Alien, m_kUnitState, true) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
static function bool IsTeamInCivilianRadius( ETeam Team, XComGameState_Unit Civilian, bool OriginalTeamOnly )
{
	local XComGameStateHistory History;
	local XComGameState_Unit CurrentUnitState;
	local ETeam CurrTeam;
	local float MaxDist;
	History = `XCOMHISTORY;
	MaxDist = `TILESTOUNITS(class'XGAIBehavior_Civilian'.default.CIVILIAN_NEAR_STANDARD_REACT_RADIUS);
	foreach History.IterateByClassType(class'XComGameState_Unit', CurrentUnitState)
	{
		if( !CurrentUnitState.IsAbleToAct() )
		{
			continue;
		}
		if( OriginalTeamOnly )
		{
			CurrTeam = CurrentUnitState.GetPreviousTeam(); // Defaults to GetTeam if not mind-controlled.
		}
		else
		{
			CurrTeam = CurrentUnitState.GetTeam();
		}
		if( CurrTeam == Team )
		{
			if( class'Helpers'.static.IsUnitInRange(Civilian, CurrentUnitState, 0, MaxDist) )
			{
				return true;
			}
		}
	}
	return false;
}

function bt_status HasValidTarget()
{
	if (m_kBehavior.BT_HasTargetOption(SplitNameParam))
		return BTS_SUCCESS;
	return BTS_FAILURE;
}
function bt_status CheckStatCompare()
{
	local XComGameState_Unit kUnit;
	local int iStatOperandA, iStatOperandB;
	local UnitValue UValue;
	switch (m_eValType)
	{
		case eBTCV_Stat:
			if (m_bApplyToTarget)
			{
				if ( m_kBehavior.m_kBTCurrTarget.TargetID > 0 )
				{
					kUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(m_kBehavior.m_kBTCurrTarget.TargetID));
				}
				else
				{
					`LogAIBT("Behavior Tree Error - target not set on node"@m_strName$". Returning FAILURE.");
					return BTS_FAILURE; 
				}
			}
			else
			{
				kUnit = m_kUnitState;
			}

			iStatOperandA = kUnit.GetCurrentStat(m_eStat);
			if (m_bPercent)
			{
				iStatOperandB = (float(m_iVal)/100.f) * kUnit.GetMaxStat(m_eStat);
			}
			else 
			{
				iStatOperandB = m_iVal;
			}
		break;

		case eBTCV_HitChance:
			iStatOperandA = m_kBehavior.BT_GetHitChanceOnTarget();
			iStatOperandB = m_iVal;
			break;

		case eBTCV_PotentialHitChance:
			iStatOperandA = m_kBehavior.BT_GetHitChanceForPotentialTargetOnAbility(SplitNameParam);
			iStatOperandB = m_iVal;
			break;

		case eBTCV_TopHitChance:
			iStatOperandA = m_kBehavior.BT_GetHighestHitChanceAgainstXCom();
			if( iStatOperandA < 0 )
			{
				`LogAIBT("Error- No valid targets.  Highest hit chance returned -1.");
				return BTS_FAILURE;
			}
			iStatOperandB = m_iVal;
		break;

		case eBTCV_BTVar:
			if( !m_kBehavior.BT_HasBTVar(m_ParamList[3], iStatOperandA) )
			{
				`LogAIBT("Error- BTVar not found:"@m_ParamList[3]);
				return BTS_FAILURE;
			}
			iStatOperandB = m_iVal;
			if( m_bPercent )
			{
				`RedScreen("Error, percent value not supported for stat condition type: BTVar. \nBehaviorTree StatCondition: BTVar \n@acheng");
			}
			break;

		case eBTCV_VisibleEnemies:
			iStatOperandA = m_kBehavior.BT_GetVisibleEnemyXcomOnlyCount();
			iStatOperandB = m_iVal;
			if( m_bPercent )
			{
				`RedScreen("Error, percent value not supported for calculating number of visible enemies. \nBehaviorTree StatCondition: VisibleEnemies \n@acheng");
			}
		break;
		case eBTCV_VisibleAllies:
			iStatOperandA = m_kBehavior.BT_GetVisibleAllyCount();
			iStatOperandB = m_iVal;
			if( m_bPercent )
			{
				`RedScreen("Error, percent value not supported for calculating number of visible Allies. \nBehaviorTree StatCondition: VisibleAllies \n@acheng");
			}
		break;

		case eBTCV_PopularSupport:
			iStatOperandA = XGBattle_SP(`BATTLE).GetPopularSupport();
			if (m_bPercent)
			{
				// TODO - what is the max aim value?
				iStatOperandB = ((float(m_iVal)/100.f) * XGBattle_SP(`BATTLE).GetMaxPopularSupport());
			}
			else
			{
				iStatOperandB = m_iVal;
			}
		break;

		case eBTCV_AlertDataAge:
			iStatOperandA = m_kBehavior.BT_GetAlertDataAge();
			iStatOperandB = m_iVal;
		break;

		case eBTCV_AlertDataRadius:
			iStatOperandA = m_kBehavior.BT_GetAlertDataRadius();
			iStatOperandB = m_iVal;
		break;

		// dkaplan: removed 3/23/15
		//case eBTCV_AlertDataDistanceAtTimeOfAlert:
		//	iStatOperandA = m_kBehavior.BT_GetAlertDataDistanceAtTimeOfAlert();
		//	iStatOperandB = m_iVal;
		//break;
		case eBTCV_AlertDistance:
			iStatOperandA = m_kBehavior.BT_GetAlertDataDistance();
			iStatOperandB = m_iVal;
		break;

		case eBTCV_AlertCount:
			iStatOperandA = m_kBehavior.BT_GetAlertCount();
			iStatOperandB = m_iVal;
		break;

		case eBTCV_AbilityTargetUnitCount:
			iStatOperandA = m_kBehavior.BT_GetAbilityTargetUnitCount(SplitNameParam);
			iStatOperandB = m_iVal;
		break;

		case eBTCV_OverwatcherCount:
			if( m_ParamList.Length > 3 && m_ParamList[3] == '1' )
			{
				iStatOperandA = m_kBehavior.BT_GetOverwatcherCount(true);
			}
			else
			{
				iStatOperandA = m_kBehavior.BT_GetOverwatcherCount();
			}
			iStatOperandB = m_iVal;
			break;

		case eBTCV_SuppressorCount:
			iStatOperandA = m_kBehavior.BT_GetSuppressorCount();
			iStatOperandB = m_iVal;
			break;

		case eBTCV_TargetSetCount:
			iStatOperandA = m_kBehavior.BT_GetTargetSelectedThisTurnCount();
			iStatOperandB = m_iVal;
			break;

		case eBTCV_Difficulty:
			iStatOperandA = `DIFFICULTYSETTING;
			iStatOperandB = m_iVal;
			break;

		case eBTCV_CombatCount:
			iStatOperandA = `XTACTICALSOUNDMGR.NumCombatEvents;
			iStatOperandB = m_iVal;
			break;

		case eBTCV_GroupSize:
			iStatOperandA = m_kBehavior.BT_GetGroupSize();
			iStatOperandB = m_iVal;
			break;

		case eBTCV_ObjectiveDistance:
			iStatOperandA = int(CalcMetersFromObjectiveOnAxis());
			iStatOperandB = m_iVal;
			break;
		
		case eBTCV_TargetDistance:
			iStatOperandA = int(m_kBehavior.BT_GetTargetDistMeters('Potential'));
			iStatOperandB = m_iVal;
			break;

		case eBTCV_PriorityObjectAttackCount:
			if( m_kBehavior.m_kPlayer != None )
			{
				iStatOperandA = PriorityObjectTargetedCount();
				iStatOperandB = m_iVal;
				break;
			}

		case eBTCV_UnitValue:
			if( m_kUnitState.GetUnitValue(SplitNameParam, UValue) )
			{
				iStatOperandA = int(UValue.fValue);
				iStatOperandB = m_iVal;
			}
			else
			{
				`LogAIBT("StatCompare- UnitValue not found:"$SplitNameParam);
				return BTS_FAILURE;
			}
			break;

		case eBTCV_CiviliansKilled:
			iStatOperandA = class'Helpers'.static.GetNumCiviliansKilled();
			iStatOperandB = m_iVal;
			break;

		default:
			return BTS_FAILURE;
		break;
	}
	`LogAIBT("\nStatCompare:"@String(m_eValType)@"value="$iStatOperandA@"\nOperator: "$string(m_ParamList[1])@"\nOperandB="@m_iVal);

	if (m_dStatCompareFn(iStatOperandA, iStatOperandB))
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

//------------------------------------------------------------------------------------------------
// Creating these functions only because I can't seem to set operators as delegates.
function bool CheckStatEqual(int A, int B) { return A == B; }
function bool CheckStatNotEqual( int A, int B ) { return A != B; }
function bool CheckStatGreater( int A, int B ) { return A > B; }
function bool CheckStatGreaterOrEqual(int A, int B ) { return A >= B; }
function bool CheckStatLessThan(int A, int B) { return A < B; }
function bool CheckStatLessThanOrEqual(int A, int B) { return A <= B; }
//------------------------------------------------------------------------------------------------
function float CalcMetersFromObjectiveOnAxis()
{
	local float fDist;
	local vector ClosestPointOnAxis;
	if( m_kBehavior.m_kPlayer != None )
	{
		ClosestPointOnAxis = m_kBehavior.m_kPlayer.m_kNav.GetNearestPointOnAxisOfPlay(m_kBehavior.GetGameStateLocation());
		fDist = VSize2D(ClosestPointOnAxis - m_kBehavior.m_kPlayer.m_kNav.m_kAxisOfPlay.v2); // Objective location
		return `UNITSTOMETERS(fDist);
	}
	`LogAIBT("Unable to calc distance from Objective.  Non-AI unit, no AI Player attached.");
	return 0;
}

function int PriorityObjectTargetedCount()
{
	local XComGameState_AIPlayerData AIData;
	local XComGameState_InteractiveObject PriorityObject;
	if( m_kBehavior.m_kPlayer != None )
	{
		AIData = m_kBehavior.GetAIPlayerData();
		if( AIData != None )
		{
			if( AIData.HasPriorityTargetObject(PriorityObject) )
			{
				return m_kBehavior.m_kPlayer.GetNumTimesUnitTargetedThisTurn(PriorityObject.ObjectID);
			}
		}
	}
	`LogAIBT("Error - No PriorityObject could be found!");
	return 0;
}

function bt_status HasLowHitChanceOnTarget()
{
	if (m_kBehavior.BT_GetHitChanceOnTarget() < 40)
		return BTS_SUCCESS;
	return BTS_FAILURE;
}
function bt_status HasHighHitChanceOnTarget()
{
	if (m_kBehavior.BT_GetHitChanceOnTarget() >= 80)
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status IsBestHitChanceOver50()
{
	if (m_kBehavior.BT_GetHitChanceOnBestTarget() > 50)
		return BTS_SUCCESS;
	return BTS_FAILURE;
}
function bt_status IsFlankingTarget()
{
	if (m_kBehavior.BT_IsFlankingTarget())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}
function bt_status IsTargetAffectedBy()
{
	local XComGameState_Unit kTarget;
	if( m_kBehavior.BT_GetTarget(kTarget) )
	{
		if( kTarget.IsUnitAffectedByEffectName(SplitNameParam) )
			return BTS_SUCCESS;
	}
	else
	{
		`LogAIBT("IsTargetAffectedBy- failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}
function bt_status IsTargetApplyingEffect()
{
	local XComGameState_Unit kTarget;
	if( m_kBehavior.BT_GetTarget(kTarget) )
	{
		if( kTarget.IsUnitApplyingEffectName(SplitNameParam) )
			return BTS_SUCCESS;
	}
	else
	{
		`LogAIBT("IsTargetApplyingEffect- failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}
function bt_status TargetIsKillable()
{
	if (m_kBehavior.BT_TargetIsKillable())
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsAdvent()
{
	if( m_kBehavior.BT_TargetIsAdvent() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status TargetIsAlien()
{
	if( m_kBehavior.BT_TargetIsAlien() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetIsRobotic()
{
	if( m_kBehavior.BT_TargetIsRobotic() )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status TargetTemplateNameCheck()
{
	local XComGameState_Unit TargetState;
	if( m_kBehavior.BT_GetTarget(TargetState) )
	{
		if( TargetState.GetMyTemplateName() == SplitNameParam )
		{
			return BTS_SUCCESS;
		}
	}
	else
	{
		`LogAIBT("TargetTemplateNameCheck failure - No current target or curr alert data active!  Unit# "$m_kUnitState.ObjectID);
	}
	return BTS_FAILURE;
}
function bt_status TemplateNameCheck()
{
	if( m_kUnitState.GetMyTemplateName() == SplitNameParam )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}
function bt_status IsTargetInMovementRange()
{
	if( m_kBehavior.BT_IsTargetInMovementRange(SplitNameParam) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status IsTargetInAttackRange()
{
	if( m_kBehavior.BT_IsTargetInAttackRange(SplitNameParam) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status AlertDataIsAbsoluteKnowledge()
{
	if( m_kBehavior.BT_AlertDataIsAbsoluteKnowledge() )
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status AlertDataWasSoundMade()
{
	if (m_kBehavior.BT_AlertDataWasSoundMade())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status AlertDataWasEnemyThere()
{
	if (m_kBehavior.BT_AlertDataWasEnemyThere())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status AlertDataIsCorpseThere()
{
	if (m_kBehavior.BT_AlertDataIsCorpseThere())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status AlertDataIsAggressive()
{
	if (m_kBehavior.BT_AlertDataIsAggressive())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status HasBTVar()
{
	if( m_kBehavior.BT_HasBTVar(SplitNameParam) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status AlertDataIsType()
{
	if( m_kBehavior.BT_AlertDataIsType(String(SplitNameParam)) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

function bt_status AlertDataTileIsVisible()
{
	if( m_kBehavior.BT_AlertDataTileIsVisible() )
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status HasValidAlertDataLocation()
{
	if (m_kBehavior.BT_HasValidAlertDataLocation())
		return BTS_SUCCESS;
	return BTS_FAILURE;
}

function bt_status AlertDataHasTag()
{
	if( m_kBehavior.BT_AlertDataHasTag(String(SplitNameParam)) )
	{
		return BTS_SUCCESS;
	}
	return BTS_FAILURE;
}

//------------------------------------------------------------------------------
// Functions used for debugging 
function string GetNodeDetails(const out array<BTDetailedInfo> TraversalData)
{
	local string strText;
	local name Param;
	local int iParam;
	strText = super.GetNodeDetails(TraversalData);

	strText @= "CONDITION\n";

	if (m_dConditionFn != None)
	{
		strText @= "delegate="$string(m_dConditionFn)$"\n";
	}
	if (m_dStatCompareFn != None)
	{
		strText @= "StatCmpFn="$string(m_dStatCompareFn)$"\n";
	}

	if (SplitNameParam != '')
	{
		strText @= "Ability Name="$SplitNameParam@"\n";
	}

	strText @= "\nParam count="$m_ParamList.Length$"\n";
	for (iParam=0; iParam<m_ParamList.Length; iParam++)
	{
		Param = m_ParamList[iParam];
		// handle html tag failures
		if( String(Param) == "<" )
		{
			Param = 'less than';
		}
		else if( String(Param) == "<=" )
		{
			Param = 'less than or equal';
		}
		strText @= "(Param "$iParam$")"@Param@"\n";
	}
	return strText;
}

defaultproperties
{
}