Addressed comments by Mr. Nice

Addressed comments by Mr. Nice

class X2AbilityToHitCalc_StatCheck extends X2AbilityToHitCalc abstract;

struct StatContestResultToEffectInfo
{
	var array<int> EffectIndices;
};

struct StatContestOverrideData
{
	var array<int> MultiTargetEffectsNumHits;
	var array<StatContestResultToEffectInfo> StatContestResultToEffectInfos;
};

var int BaseValue;

function int GetAttackValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }
function int GetDefendValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }

protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
	local int AttackVal, DefendVal;
	local ShotBreakdown EmptyShotBreakdown;
	// variables added for issue #467
	local XComGameState_Unit UnitState, TargetState;
	local XComGameState_Effect EffectState;
	local XComGameStateHistory History;
	local X2Effect_Persistent PersistentEffect;
	local StateObjectReference EffectRef;
	local array<ShotModifierInfo> ShotModifiers;
	local ShotModifierInfo ShotModifier;
	// end issue #467

	//reset shot breakdown
	m_ShotBreakdown = EmptyShotBreakdown;

	AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
	DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);

	// start issue #467: added conditions for X2Effect_ToHitModifier::GetHit(AsTarget)ModifiersForStatCheck
	History = `XCOMHISTORY;
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
	TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));

	// now lets check attacker's current effects
	foreach UnitState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		// check this effect's sub-class by attempting to cast it to what we need
		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}
		// everything checked out let's add it to the list
		PersistentEffect.GetToHitModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}
	// repeat same process for the target's current effects
	foreach TargetState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}

		PersistentEffect.GetToHitAsTargetModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}

	foreach ShotModifiers(ShotModifier)
	{
		AddModifier(ShotModifier.Value, ShotModifier.Reason, m_ShotBreakdown, ShotModifier.ModType, bDebugLog);
	}
	// end issue #467

	AddModifier(BaseValue, GetBaseString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(AttackVal, GetAttackString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(-DefendVal, GetDefendString(), m_ShotBreakdown, eHit_Success, bDebugLog);

	// Issue #467: modified the method of getting a final result from vanilla
	return m_ShotBreakdown.FinalHitChance;
}

function string GetBaseString() { return class'XLocalizedData'.default.BaseChance; }
function string GetAttackString() { return class'XLocalizedData'.default.OffenseStat; }
function string GetDefendString() { return class'XLocalizedData'.default.DefenseStat; }

function RollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, out AbilityResultContext ResultContext)
{
	local int MultiTargetIndex, AttackVal, DefendVal, TargetRoll, RandRoll, StatContestResultValue;
	local OverriddenEffectsByType EmptyOverriddenByType;
	local StatContestOverrideData StatContestOverrideInfo;
	local X2AbilityTemplate AbilityTemplate;

	`log("===RollForAbilityHit===",,'XCom_HitRolls');
	`log("Ability:" @ kAbility.GetMyTemplateName() @ "Target:" @ kTarget.PrimaryTarget.ObjectID,,'XCom_HitRolls');

	if (kTarget.PrimaryTarget.ObjectID > 0)
	{
		AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
		DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.HitResult = eHit_Success;
			else
				ResultContext.HitResult = eHit_Miss;
		}
		else
		{
			ResultContext.HitResult = eHit_Success;
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			ResultContext.StatContestResult = RollForEffectTier(kAbility, kTarget.PrimaryTarget, false);
		}
	}
	else
	{
		ResultContext.HitResult = eHit_Success;         //  mark success for the ability to go off
	}

	if( `CHEATMGR != None && `CHEATMGR.bDeadEyeStats )
	{
		`log("DeadEyeStats cheat forcing a hit.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Success;
	}
	else if( `CHEATMGR != None && `CHEATMGR.bNoLuckStats )
	{
		`log("NoLuckStats cheat forcing a miss.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Miss;
	}
	if( `CHEATMGR != None && `CHEATMGR.bForceAttackRollValue )
	{
		ResultContext.HitResult = eHit_Success;
		ResultContext.StatContestResult = `CHEATMGR.iForcedRollValue;
	}

	// Set up the lookups to more quickly find the correct Effects values based on Tiers
	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;

	AbilityTemplate = kAbility.GetMyTemplate();
	SetStatContestResultToEffectInfos(AbilityTemplate.AbilityMultiTargetEffects, GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects), StatContestOverrideInfo);

	for (MultiTargetIndex = 0; MultiTargetIndex < kTarget.AdditionalTargets.Length; ++MultiTargetIndex)
	{
		`log("Roll against multi target" @ kTarget.AdditionalTargets[MultiTargetIndex].ObjectID,,'XCom_HitRolls');
		AttackVal = GetAttackValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		DefendVal = GetDefendValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
			else
				ResultContext.MultiTargetHitResults.AddItem(eHit_Miss);
		}
		else
		{
			ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			StatContestResultValue = RollForEffectTier(kAbility, kTarget.AdditionalTargets[MultiTargetIndex], true);
			ResultContext.MultiTargetStatContestResult.AddItem(StatContestResultValue);

			if (ResultContext.MultiTargetEffectsOverrides.Length <= MultiTargetIndex)
			{
				// A new Override needs to be added
				ResultContext.MultiTargetEffectsOverrides.AddItem(EmptyOverriddenByType);
			}

			// Check to see if this value needs to be changed
			// Ignore if the StatContestResultValue is 0
			// AND
			// If there are no Effects that need to be Overridden
			// AND
			// This StatContestResultValue does not map to Effects that may be Overridden
			if ((StatContestResultValue > 0) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos.Length > StatContestResultValue) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length > 0))
			{
				DoStatContestResultOverrides(MultiTargetIndex, StatContestResultValue, kAbility, StatContestOverrideInfo, ResultContext);
			}
		}
		else
		{
			ResultContext.MultiTargetStatContestResult.AddItem(0);
		}
	}
}

function int RollForEffectTier(XComGameState_Ability kAbility, StateObjectReference TargetRef, bool bMultiTarget)
{
	local X2AbilityTemplate AbilityTemplate;
	local int MaxTier, MiddleTier, Idx, AttackVal, DefendVal;
	local array<float> TierValues;
	local float TierValue, LowTierValue, HighTierValue, TierValueSum, RandRoll;

	AbilityTemplate = kAbility.GetMyTemplate();
	if (TargetRef.ObjectID > 0)
	{
		`log("=RollForEffectTier=");
		AttackVal = GetAttackValue(kAbility, TargetRef);
		DefendVal = GetDefendValue(kAbility, TargetRef);
		if (bMultiTarget)
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects);
		else
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityTargetEffects);
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Max Tier:" @ MaxTier,,'XCom_HitRolls');

		//  It's possible the ability only cares about success or failure and has no specified ladder of results
		if (MaxTier < 0)
		{
			return 0;
		}

		MiddleTier = MaxTier / 2 + MaxTier % 2;
		TierValue = 100.0f / float(MaxTier);
		LowTierValue = TierValue * (float(DefendVal) / float(AttackVal));
		HighTierValue = TierValue * (float(AttackVal) / float(DefendVal));
		for (Idx = 1; Idx <= MaxTier; ++Idx)
		{
			if (Idx < MiddleTier)
			{
				TierValues.AddItem(LowTierValue);
			}
			else if (Idx == MiddleTier)
			{
				TierValues.AddItem(TierValue);
			}
			else
			{
				TierValues.AddItem(HighTierValue);
			}
			TierValueSum += TierValues[TierValues.Length - 1];
			`log("Tier" @ Idx $ ":" @ TierValues[TierValues.Length - 1],,'XCom_HitRolls');
		}
		//  Normalize the tier values
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			TierValues[Idx] = TierValues[Idx] / TierValueSum;
			if (Idx > 0)
				TierValues[Idx] += TierValues[Idx - 1];

			`log("Normalized Tier" @ Idx $ ":" @ TierValues[Idx],,'XCom_HitRolls');
		}
		RandRoll = `SYNC_FRAND;
		`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			if (RandRoll < TierValues[Idx])
			{
				`log("Matched tier" @ Idx,,'XCom_HitRolls');
				return Idx + 1;     //  the lowest possible tier is 1, not 0
			}
		}
		`log("Matched highest tier",,'XCom_HitRolls');
		return TierValues.Length;
	}
	return 0;
}

protected function int GetHighestTierPossible(const array<X2Effect> TargetEffects)
{
	local int Highest, Idx;

	Highest = -1;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		//  ignore a minimum of 0 as the effect should always be applied
		if (TargetEffects[Idx].MinStatContestResult > 0 && TargetEffects[Idx].MinStatContestResult > Highest)
			Highest = TargetEffects[Idx].MinStatContestResult;

		//  ignore a maximum of 0 as the effect should always be applied (assuming min is also 0, but if it isn't, then that was already checked above)
		if (TargetEffects[Idx].MaxStatContestResult > 0 && TargetEffects[Idx].MaxStatContestResult > Highest)
			Highest = TargetEffects[Idx].MaxStatContestResult;
	}
	return Highest;
}

protected function SetStatContestResultToEffectInfos(const array<X2Effect> TargetEffects, int NumTiers, out StatContestOverrideData StatContestOverrideInfo)
{
	local int Idx, i;
	local bool bFoundAMaxNumberAllowedEffect;

	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = NumTiers + 1;   // Just to make things easier, ignore the 0 index and use the StatResultContext values
	bFoundAMaxNumberAllowedEffect = false;

	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		// This section is used to store data that may be needed for MultiTargetEffects that have a limited number of hits allowed
		// Ignore MinStatContestResults of 0
		if ((TargetEffects[Idx].MinStatContestResult > 0) && (TargetEffects[Idx].MultiTargetStatContestInfo.MaxNumberAllowed > 0))
		{
			bFoundAMaxNumberAllowedEffect = true;

			i = TargetEffects[Idx].MinStatContestResult;
			StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);

			for (i = i + 1; i <= TargetEffects[Idx].MaxStatContestResult; ++i)
			{
				StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);
			}
		}

		// Zero out the NumHits for this Effect
		StatContestOverrideInfo.MultiTargetEffectsNumHits.AddItem(0);
	}

	if (!bFoundAMaxNumberAllowedEffect)
	{
		// Clear the length since this is used to early out future checks
		StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;
	}
}

protected function DoStatContestResultOverrides(int MultiTargetIndex, int StatContestResultValue, XComGameState_Ability kAbility,
												out StatContestOverrideData StatContestOverrideInfo, out AbilityResultContext ResultContext)
{
	local int Index, EffectIdx, ReplaceWithIdx;
	local X2AbilityTemplate AbilityTemplate;
	local bool bContinueLooking;
	local int TypeIndex;
	local OverriddenEffectsInfo NewOverrideInfo;

	TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Find('OverrideType', 'EffectOverride_StatContest');
	if (TypeIndex == INDEX_NONE)
	{
		TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Length;
		NewOverrideInfo.OverrideType = 'EffectOverride_StatContest';
		ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.AddItem(NewOverrideInfo);
	}

	AbilityTemplate = kAbility.GetMyTemplate();

	for (Index = 0; Index < StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length; ++Index)
	{
		// Loop over the Effects found at this StatContestResult and decide what effect should
		// actually be applied
		EffectIdx = StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices[Index];

		ReplaceWithIdx = EffectIdx;
		bContinueLooking = true;
		while ((ReplaceWithIdx != INDEX_NONE) &&
				(ReplaceWithIdx < StatContestOverrideInfo.MultiTargetEffectsNumHits.Length) &&
				bContinueLooking)
		{
			++StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx];

			// Continue Looking if the Effect has a MaxNumberAllowed (>0)
			// AND
			// That MaxNumberAllowed has been exceeded
			bContinueLooking = (AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed > 0) &&
							   (StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx] > AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed);
			if (bContinueLooking)
			{
				// The max number of this Effect is applied, change what Effect will actually get applied here
				ReplaceWithIdx = AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.EffectIdxToApplyOnMaxExceeded;
			}
		}

		if (EffectIdx != ReplaceWithIdx)
		{
			// This MultiTarget Effect has been applied too many times and needs to be replaced
			ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddenEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[EffectIdx]);
			if (ReplaceWithIdx != INDEX_NONE)
			{
				ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddingEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx]);
			}
		}
	}
}

DefaultProperties
{
	BaseValue = 75
}

class X2AbilityToHitCalc_StatCheck extends X2AbilityToHitCalc abstract;

struct StatContestResultToEffectInfo
{
	var array<int> EffectIndices;
};

struct StatContestOverrideData
{
	var array<int> MultiTargetEffectsNumHits;
	var array<StatContestResultToEffectInfo> StatContestResultToEffectInfos;
};

var int BaseValue;

function int GetAttackValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }
function int GetDefendValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }

protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
	local int AttackVal, DefendVal;
	local ShotBreakdown EmptyShotBreakdown;
	// variables added for issue #467
	local XComGameState_Unit UnitState, TargetState;
	local XComGameState_Effect EffectState;
	local XComGameStateHistory History;
	local X2Effect_Persistent PersistentEffect;
	local StateObjectReference EffectRef;
	local array<ShotModifierInfo> ShotModifiers;
	local ShotModifierInfo ShotModifier;
	// end issue #467

	//reset shot breakdown
	m_ShotBreakdown = EmptyShotBreakdown;

	AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
	DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);

	// start issue #467: added conditions for X2Effect_ToHitModifier::GetHit(AsTarget)ModifiersForStatCheck
	History = `XCOMHISTORY;
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
	TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));

	// now lets check attacker's current effects
	foreach UnitState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		// check this effect's sub-class by attempting to cast it to what we need
		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}
		// everything checked out let's add it to the list
		PersistentEffect.GetToHitModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}
	// repeat same process for the target's current effects
	foreach TargetState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}

		PersistentEffect.GetToHitAsTargetModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}

	foreach ShotModifiers(ShotModifier)
	{
		AddModifier(ShotModifier.Value, ShotModifier.Reason, m_ShotBreakdown, ShotModifier.ModType, bDebugLog);
	}
	// end issue #467

	AddModifier(BaseValue, GetBaseString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(AttackVal, GetAttackString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(-DefendVal, GetDefendString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	// start issue #467: modified the method of getting a final result from vanilla
	FinalizeHitChance(m_ShotBreakdown, bDebugLog);

	return m_ShotBreakdown.FinalHitChance;
	// end issue #467
}

function string GetBaseString() { return class'XLocalizedData'.default.BaseChance; }
function string GetAttackString() { return class'XLocalizedData'.default.OffenseStat; }
function string GetDefendString() { return class'XLocalizedData'.default.DefenseStat; }

function RollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, out AbilityResultContext ResultContext)
{
	local int MultiTargetIndex, AttackVal, DefendVal, TargetRoll, RandRoll, StatContestResultValue;
	local OverriddenEffectsByType EmptyOverriddenByType;
	local StatContestOverrideData StatContestOverrideInfo;
	local X2AbilityTemplate AbilityTemplate;

	`log("===RollForAbilityHit===",,'XCom_HitRolls');
	`log("Ability:" @ kAbility.GetMyTemplateName() @ "Target:" @ kTarget.PrimaryTarget.ObjectID,,'XCom_HitRolls');

	if (kTarget.PrimaryTarget.ObjectID > 0)
	{
		AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
		DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.HitResult = eHit_Success;
			else
				ResultContext.HitResult = eHit_Miss;
		}
		else
		{
			ResultContext.HitResult = eHit_Success;
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			ResultContext.StatContestResult = RollForEffectTier(kAbility, kTarget.PrimaryTarget, false);
		}
	}
	else
	{
		ResultContext.HitResult = eHit_Success;         //  mark success for the ability to go off
	}

	if( `CHEATMGR != None && `CHEATMGR.bDeadEyeStats )
	{
		`log("DeadEyeStats cheat forcing a hit.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Success;
	}
	else if( `CHEATMGR != None && `CHEATMGR.bNoLuckStats )
	{
		`log("NoLuckStats cheat forcing a miss.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Miss;
	}
	if( `CHEATMGR != None && `CHEATMGR.bForceAttackRollValue )
	{
		ResultContext.HitResult = eHit_Success;
		ResultContext.StatContestResult = `CHEATMGR.iForcedRollValue;
	}

	// Set up the lookups to more quickly find the correct Effects values based on Tiers
	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;

	AbilityTemplate = kAbility.GetMyTemplate();
	SetStatContestResultToEffectInfos(AbilityTemplate.AbilityMultiTargetEffects, GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects), StatContestOverrideInfo);

	for (MultiTargetIndex = 0; MultiTargetIndex < kTarget.AdditionalTargets.Length; ++MultiTargetIndex)
	{
		`log("Roll against multi target" @ kTarget.AdditionalTargets[MultiTargetIndex].ObjectID,,'XCom_HitRolls');
		AttackVal = GetAttackValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		DefendVal = GetDefendValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
			else
				ResultContext.MultiTargetHitResults.AddItem(eHit_Miss);
		}
		else
		{
			ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			StatContestResultValue = RollForEffectTier(kAbility, kTarget.AdditionalTargets[MultiTargetIndex], true);
			ResultContext.MultiTargetStatContestResult.AddItem(StatContestResultValue);

			if (ResultContext.MultiTargetEffectsOverrides.Length <= MultiTargetIndex)
			{
				// A new Override needs to be added
				ResultContext.MultiTargetEffectsOverrides.AddItem(EmptyOverriddenByType);
			}

			// Check to see if this value needs to be changed
			// Ignore if the StatContestResultValue is 0
			// AND
			// If there are no Effects that need to be Overridden
			// AND
			// This StatContestResultValue does not map to Effects that may be Overridden
			if ((StatContestResultValue > 0) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos.Length > StatContestResultValue) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length > 0))
			{
				DoStatContestResultOverrides(MultiTargetIndex, StatContestResultValue, kAbility, StatContestOverrideInfo, ResultContext);
			}
		}
		else
		{
			ResultContext.MultiTargetStatContestResult.AddItem(0);
		}
	}
}

function int RollForEffectTier(XComGameState_Ability kAbility, StateObjectReference TargetRef, bool bMultiTarget)
{
	local X2AbilityTemplate AbilityTemplate;
	local int MaxTier, MiddleTier, Idx, AttackVal, DefendVal;
	local array<float> TierValues;
	local float TierValue, LowTierValue, HighTierValue, TierValueSum, RandRoll;

	AbilityTemplate = kAbility.GetMyTemplate();
	if (TargetRef.ObjectID > 0)
	{
		`log("=RollForEffectTier=");
		AttackVal = GetAttackValue(kAbility, TargetRef);
		DefendVal = GetDefendValue(kAbility, TargetRef);
		if (bMultiTarget)
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects);
		else
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityTargetEffects);
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Max Tier:" @ MaxTier,,'XCom_HitRolls');

		//  It's possible the ability only cares about success or failure and has no specified ladder of results
		if (MaxTier < 0)
		{
			return 0;
		}

		MiddleTier = MaxTier / 2 + MaxTier % 2;
		TierValue = 100.0f / float(MaxTier);
		LowTierValue = TierValue * (float(DefendVal) / float(AttackVal));
		HighTierValue = TierValue * (float(AttackVal) / float(DefendVal));
		for (Idx = 1; Idx <= MaxTier; ++Idx)
		{
			if (Idx < MiddleTier)
			{
				TierValues.AddItem(LowTierValue);
			}
			else if (Idx == MiddleTier)
			{
				TierValues.AddItem(TierValue);
			}
			else
			{
				TierValues.AddItem(HighTierValue);
			}
			TierValueSum += TierValues[TierValues.Length - 1];
			`log("Tier" @ Idx $ ":" @ TierValues[TierValues.Length - 1],,'XCom_HitRolls');
		}
		//  Normalize the tier values
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			TierValues[Idx] = TierValues[Idx] / TierValueSum;
			if (Idx > 0)
				TierValues[Idx] += TierValues[Idx - 1];

			`log("Normalized Tier" @ Idx $ ":" @ TierValues[Idx],,'XCom_HitRolls');
		}
		RandRoll = `SYNC_FRAND;
		`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			if (RandRoll < TierValues[Idx])
			{
				`log("Matched tier" @ Idx,,'XCom_HitRolls');
				return Idx + 1;     //  the lowest possible tier is 1, not 0
			}
		}
		`log("Matched highest tier",,'XCom_HitRolls');
		return TierValues.Length;
	}
	return 0;
}

protected function int GetHighestTierPossible(const array<X2Effect> TargetEffects)
{
	local int Highest, Idx;

	Highest = -1;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		//  ignore a minimum of 0 as the effect should always be applied
		if (TargetEffects[Idx].MinStatContestResult > 0 && TargetEffects[Idx].MinStatContestResult > Highest)
			Highest = TargetEffects[Idx].MinStatContestResult;

		//  ignore a maximum of 0 as the effect should always be applied (assuming min is also 0, but if it isn't, then that was already checked above)
		if (TargetEffects[Idx].MaxStatContestResult > 0 && TargetEffects[Idx].MaxStatContestResult > Highest)
			Highest = TargetEffects[Idx].MaxStatContestResult;
	}
	return Highest;
}

protected function SetStatContestResultToEffectInfos(const array<X2Effect> TargetEffects, int NumTiers, out StatContestOverrideData StatContestOverrideInfo)
{
	local int Idx, i;
	local bool bFoundAMaxNumberAllowedEffect;

	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = NumTiers + 1;   // Just to make things easier, ignore the 0 index and use the StatResultContext values
	bFoundAMaxNumberAllowedEffect = false;

	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		// This section is used to store data that may be needed for MultiTargetEffects that have a limited number of hits allowed
		// Ignore MinStatContestResults of 0
		if ((TargetEffects[Idx].MinStatContestResult > 0) && (TargetEffects[Idx].MultiTargetStatContestInfo.MaxNumberAllowed > 0))
		{
			bFoundAMaxNumberAllowedEffect = true;

			i = TargetEffects[Idx].MinStatContestResult;
			StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);

			for (i = i + 1; i <= TargetEffects[Idx].MaxStatContestResult; ++i)
			{
				StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);
			}
		}

		// Zero out the NumHits for this Effect
		StatContestOverrideInfo.MultiTargetEffectsNumHits.AddItem(0);
	}

	if (!bFoundAMaxNumberAllowedEffect)
	{
		// Clear the length since this is used to early out future checks
		StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;
	}
}

protected function DoStatContestResultOverrides(int MultiTargetIndex, int StatContestResultValue, XComGameState_Ability kAbility,
												out StatContestOverrideData StatContestOverrideInfo, out AbilityResultContext ResultContext)
{
	local int Index, EffectIdx, ReplaceWithIdx;
	local X2AbilityTemplate AbilityTemplate;
	local bool bContinueLooking;
	local int TypeIndex;
	local OverriddenEffectsInfo NewOverrideInfo;

	TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Find('OverrideType', 'EffectOverride_StatContest');
	if (TypeIndex == INDEX_NONE)
	{
		TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Length;
		NewOverrideInfo.OverrideType = 'EffectOverride_StatContest';
		ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.AddItem(NewOverrideInfo);
	}

	AbilityTemplate = kAbility.GetMyTemplate();

	for (Index = 0; Index < StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length; ++Index)
	{
		// Loop over the Effects found at this StatContestResult and decide what effect should
		// actually be applied
		EffectIdx = StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices[Index];

		ReplaceWithIdx = EffectIdx;
		bContinueLooking = true;
		while ((ReplaceWithIdx != INDEX_NONE) &&
				(ReplaceWithIdx < StatContestOverrideInfo.MultiTargetEffectsNumHits.Length) &&
				bContinueLooking)
		{
			++StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx];

			// Continue Looking if the Effect has a MaxNumberAllowed (>0)
			// AND
			// That MaxNumberAllowed has been exceeded
			bContinueLooking = (AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed > 0) &&
							   (StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx] > AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed);
			if (bContinueLooking)
			{
				// The max number of this Effect is applied, change what Effect will actually get applied here
				ReplaceWithIdx = AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.EffectIdxToApplyOnMaxExceeded;
			}
		}

		if (EffectIdx != ReplaceWithIdx)
		{
			// This MultiTarget Effect has been applied too many times and needs to be replaced
			ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddenEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[EffectIdx]);
			if (ReplaceWithIdx != INDEX_NONE)
			{
				ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddingEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx]);
			}
		}
	}
}

DefaultProperties
{
	BaseValue = 75
}


Add debug logging to EncodeString

Add debug logging to EncodeString

class Utils extends Core.Object;

/**
 * Copyright (c) 2014-2015 Sergei Khoroshilov <kh.sergei@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * List of objective class names
 * @type array<string>
 */
var array<string> ObjectiveClass;

/**
 * List of procedure class names
 * @type array<string>
 */
var array<string> ProcedureClass;

/**
 * List of ammo class names
 * @type array<string>
 */
var array<string> AmmoClass;

/**
 * Return the last 8 characters of a md5 hash computed of given Key, Port and unix timestamp values
 *
 * @param   string Key
 * @param   string Port
 * @param   string Unixtime
 * @return  string
 */
static function string ComputeHash(coerce string Key, coerce string Port, coerce string Unixtime)
{
    return Right(ComputeMD5Checksum(Key $ Port $ Unixtime), 8);
}

/**
 * Encode a string by replacing it with its corresponding position in array Array
 *
 * @param   string String
 * @param   array<string> Array
 * @return  string
 */
static function string EncodeString(coerce string String, array<string> Array)
{
    local int i;
    i = class'Utils.ArrayUtils'.static.Search(Array, String, true);
    if (i == -1)
    {
        log("EncodeString: failed to decode \"" $ String $ "\"");
    }
    return string(i);
}

/**
 * Join non empty keys with Delimiter
 *
 * @param   string Key1
 * @param   string Key2 (optional)
 * @param   string Key3 (optional)
 * @param   string Key4 (optional)
 * @param   string Key5 (optional)
 * @return  string
 */
static function string FormatDelimitedKey(string Key1, optional string Key2, optional string Key3, optional string Key4, optional string Key5)
{
    local int i;
    local array<string> Keys;

    Keys[0] = Key1;
    Keys[1] = Key2;
    Keys[2] = Key3;
    Keys[3] = Key4;
    Keys[4] = Key5;

    // Remove empty keys
    for (i = Keys.Length-1; i >= 0; i--)
    {
        if (Keys[i] == "")
        {
            Keys.Remove(i, 1);
        }
    }

    return class'Utils.ArrayUtils'.static.Join(Keys, class'Extension'.const.KEY_DELIMITER);
}

/**
 * Enclose non empty key components in square brackets
 *
 * @param   string Key1
 * @param   string Key2 (optional)
 * @param   string Key3 (optional)
 * @param   string Key4 (optional)
 * @param   string Key5 (optional)
 * @return  string
 */
static function string FormatArrayKey(string Key1, optional string Key2, optional string Key3, optional string Key4, optional string Key5)
{
    local int i;
    local array<string> Keys;

    Keys[0] = Key2;
    Keys[1] = Key3;
    Keys[2] = Key4;
    Keys[3] = Key5;

    for (i = Keys.Length-1; i >= 0; i--)
    {
        // Remove empty components
        if (Keys[i] == "")
        {
            Keys.Remove(i, 1);
        }
        // Otherwise enclose them in a pair of square brackets
        else
        {
            Keys[i] = "[" $ Keys[i] $ "]";
        }
    }

    return Key1 $ class'Utils.ArrayUtils'.static.Join(Keys, "");
}

defaultproperties
{
    ProcedureClass(0)="Procedure_ArrestIncapacitatedSuspects";          // Bonus points for incapacitating a suspect (12,5/total each)
    ProcedureClass(1)="Procedure_ArrestUnIncapacitatedSuspects";        // Bonus points for arresting a suspect (25/1=25/total each)
    ProcedureClass(2)="Procedure_CompleteMission";                      // Bonus points for all objectives completed (40)
    ProcedureClass(3)="Procedure_EvacuateDownedOfficers";               // Penalty points for not reporting a downed officer (??)
    ProcedureClass(4)="Procedure_KillSuspects";                         // No bonus points for killing suspects
    ProcedureClass(5)="Procedure_NoCiviliansInjured";                   // Bonus points for not letting civilians to be injured (5 for all)
    ProcedureClass(6)="Procedure_NoHostageIncapacitated";               // Penalty points for incapacitating a civilian (-5 each)
    ProcedureClass(7)="Procedure_NoHostageKilled";                      // Penalty points for killing a civilian (-15 each)
    ProcedureClass(8)="Procedure_NoOfficerIncapacitated";               // Penalty points for killing an officer (-15 each)
    ProcedureClass(9)="Procedure_NoOfficerInjured";                     // Penalty points for injuring an officer (-5 each)
    ProcedureClass(10)="Procedure_NoOfficersDown";                      // Bonus points for staying alive (10/total each officer)
    ProcedureClass(11)="Procedure_NoSuspectsNeutralized";               // Bonus points for not letting suspects to be killed (5 for all)
    ProcedureClass(12)="Procedure_NoUnauthorizedUseOfDeadlyForce";      // Penalty points for using unauthorized deadly force (-10 each)
    ProcedureClass(13)="Procedure_NoUnauthorizedUseOfForce";            // Penalty points for using unauthorized force (-5 each)
    ProcedureClass(14)="Procedure_PlayerUninjured";                     // Bonus points for staying uninjured (5/total each)
    ProcedureClass(15)="Procedure_PreventEvidenceDestruction";          // Penalty points for an evidence destruction (-5 each)
    ProcedureClass(16)="Procedure_PreventSuspectEscape";                // Penalty points for a suspect escape (-5 each)
    ProcedureClass(17)="Procedure_ReportCharactersToTOC";               // Bonus points for TOC reports (5/total each)
    ProcedureClass(18)="Procedure_SecureAllWeapons";                    // Bonus points for securing all weapons (5/total each)

    ObjectiveClass(0)="Arrest_Jennings";
    ObjectiveClass(1)="Custom_NoCiviliansInjured";
    ObjectiveClass(2)="Custom_NoOfficersInjured";
    ObjectiveClass(3)="Custom_NoOfficersKilled";
    ObjectiveClass(4)="Custom_NoSuspectsKilled";
    ObjectiveClass(5)="Custom_PlayerUninjured";
    ObjectiveClass(6)="Custom_Timed";
    ObjectiveClass(7)="Disable_Bombs";
    ObjectiveClass(8)="Disable_Office_Bombs";
    ObjectiveClass(9)="Investigate_Laundromat";
    ObjectiveClass(10)="Neutralize_Alice";
    ObjectiveClass(11)="Neutralize_All_Enemies";
    ObjectiveClass(12)="Neutralize_Arias";
    ObjectiveClass(13)="Neutralize_CultLeader";
    ObjectiveClass(14)="Neutralize_Georgiev";
    ObjectiveClass(15)="Neutralize_Grover";
    ObjectiveClass(16)="Neutralize_GunBroker";
    ObjectiveClass(17)="Neutralize_Jimenez";
    ObjectiveClass(18)="Neutralize_Killer";
    ObjectiveClass(19)="Neutralize_Kiril";
    ObjectiveClass(20)="Neutralize_Koshka";
    ObjectiveClass(21)="Neutralize_Kruse";
    ObjectiveClass(22)="Neutralize_Norman";
    ObjectiveClass(23)="Neutralize_TerrorLeader";
    ObjectiveClass(24)="Neutralize_Todor";
    ObjectiveClass(25)="Rescue_Adams";
    ObjectiveClass(26)="Rescue_All_Hostages";
    ObjectiveClass(27)="Rescue_Altman";
    ObjectiveClass(28)="Rescue_Baccus";
    ObjectiveClass(29)="Rescue_Bettencourt";
    ObjectiveClass(30)="Rescue_Bogard";
    ObjectiveClass(31)="Rescue_CEO";
    ObjectiveClass(32)="Rescue_Diplomat";
    ObjectiveClass(33)="Rescue_Fillinger";
    ObjectiveClass(34)="Rescue_Kline";
    ObjectiveClass(35)="Rescue_Macarthur";
    ObjectiveClass(36)="Rescue_Rosenstein";
    ObjectiveClass(37)="Rescue_Sterling";
    ObjectiveClass(38)="Rescue_Victims";
    ObjectiveClass(39)="Rescue_Walsh";
    ObjectiveClass(40)="Rescue_Wilkins";
    ObjectiveClass(41)="Rescue_Winston";
    ObjectiveClass(42)="Secure_Briefcase";
    ObjectiveClass(43)="Secure_Weapon";

    AmmoClass(0)="None";
    AmmoClass(1)="M4Super90SGAmmo";
    AmmoClass(2)="M4Super90SGSabotAmmo";
    AmmoClass(3)="NovaPumpSGAmmo";
    AmmoClass(4)="NovaPumpSGSabotAmmo";
    AmmoClass(5)="LessLethalAmmo";
    AmmoClass(6)="CSBallLauncherAmmo";
    AmmoClass(7)="M4A1MG_JHP";
    AmmoClass(8)="M4A1MG_FMJ";
    AmmoClass(9)="AK47MG_FMJ";
    AmmoClass(10)="AK47MG_JHP";
    AmmoClass(11)="G36kMG_FMJ";
    AmmoClass(12)="G36kMG_JHP";
    AmmoClass(13)="UZISMG_FMJ";
    AmmoClass(14)="UZISMG_JHP";
    AmmoClass(15)="MP5SMG_JHP";
    AmmoClass(16)="MP5SMG_FMJ";
    AmmoClass(17)="UMP45SMG_FMJ";
    AmmoClass(18)="UMP45SMG_JHP";
    AmmoClass(19)="ColtM1911HG_JHP";
    AmmoClass(20)="ColtM1911HG_FMJ";
    AmmoClass(21)="Glock9mmHG_JHP";
    AmmoClass(22)="Glock9mmHG_FMJ";
    AmmoClass(23)="PythonRevolverHG_FMJ";
    AmmoClass(24)="PythonRevolverHG_JHP";
    AmmoClass(25)="TaserAmmo";
    AmmoClass(26)="VIPPistolAmmo_FMJ";
    AmmoClass(27)="ColtAR_FMJ";
    AmmoClass(28)="HK69GL_StingerGrenadeAmmo";
    AmmoClass(29)="HK69GL_FlashbangGrenadeAmmo";
    AmmoClass(30)="HK69GL_CSGasGrenadeAmmo";
    AmmoClass(31)="HK69GL_TripleBatonAmmo";
    AmmoClass(32)="SAWMG_JHP";
    AmmoClass(33)="SAWMG_FMJ";
    AmmoClass(34)="FNP90SMG_FMJ";
    AmmoClass(35)="FNP90SMG_JHP";
    AmmoClass(36)="DEHG_FMJ";
    AmmoClass(37)="DEHG_JHP";
    AmmoClass(38)="TEC9SMG_FMJ";
}

/* vim: set ft=java: */

class Utils extends Core.Object;

/**
 * Copyright (c) 2014-2015 Sergei Khoroshilov <kh.sergei@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * List of objective class names
 * @type array<string>
 */
var array<string> ObjectiveClass;

/**
 * List of procedure class names
 * @type array<string>
 */
var array<string> ProcedureClass;

/**
 * List of ammo class names
 * @type array<string>
 */
var array<string> AmmoClass;

/**
 * Return the last 8 characters of a md5 hash computed of given Key, Port and unix timestamp values
 *
 * @param   string Key
 * @param   string Port
 * @param   string Unixtime
 * @return  string
 */
static function string ComputeHash(coerce string Key, coerce string Port, coerce string Unixtime)
{
    return Right(ComputeMD5Checksum(Key $ Port $ Unixtime), 8);
}

/**
 * Encode a string by replacing it with its corresponding position in array Array
 *
 * @param   string String
 * @param   array<string> Array
 * @return  string
 */
static function string EncodeString(coerce string String, array<string> Array)
{
    return string(class'Utils.ArrayUtils'.static.Search(Array, String, true));
}

/**
 * Join non empty keys with Delimiter
 *
 * @param   string Key1
 * @param   string Key2 (optional)
 * @param   string Key3 (optional)
 * @param   string Key4 (optional)
 * @param   string Key5 (optional)
 * @return  string
 */
static function string FormatDelimitedKey(string Key1, optional string Key2, optional string Key3, optional string Key4, optional string Key5)
{
    local int i;
    local array<string> Keys;

    Keys[0] = Key1;
    Keys[1] = Key2;
    Keys[2] = Key3;
    Keys[3] = Key4;
    Keys[4] = Key5;

    // Remove empty keys
    for (i = Keys.Length-1; i >= 0; i--)
    {
        if (Keys[i] == "")
        {
            Keys.Remove(i, 1);
        }
    }

    return class'Utils.ArrayUtils'.static.Join(Keys, class'Extension'.const.KEY_DELIMITER);
}

/**
 * Enclose non empty key components in square brackets
 *
 * @param   string Key1
 * @param   string Key2 (optional)
 * @param   string Key3 (optional)
 * @param   string Key4 (optional)
 * @param   string Key5 (optional)
 * @return  string
 */
static function string FormatArrayKey(string Key1, optional string Key2, optional string Key3, optional string Key4, optional string Key5)
{
    local int i;
    local array<string> Keys;

    Keys[0] = Key2;
    Keys[1] = Key3;
    Keys[2] = Key4;
    Keys[3] = Key5;

    for (i = Keys.Length-1; i >= 0; i--)
    {
        // Remove empty components
        if (Keys[i] == "")
        {
            Keys.Remove(i, 1);
        }
        // Otherwise enclose them in a pair of square brackets
        else
        {
            Keys[i] = "[" $ Keys[i] $ "]";
        }
    }

    return Key1 $ class'Utils.ArrayUtils'.static.Join(Keys, "");
}

defaultproperties
{
    ProcedureClass(0)="Procedure_ArrestIncapacitatedSuspects";          // Bonus points for incapacitating a suspect (12,5/total each)
    ProcedureClass(1)="Procedure_ArrestUnIncapacitatedSuspects";        // Bonus points for arresting a suspect (25/1=25/total each)
    ProcedureClass(2)="Procedure_CompleteMission";                      // Bonus points for all objectives completed (40)
    ProcedureClass(3)="Procedure_EvacuateDownedOfficers";               // Penalty points for not reporting a downed officer (??)
    ProcedureClass(4)="Procedure_KillSuspects";                         // No bonus points for killing suspects
    ProcedureClass(5)="Procedure_NoCiviliansInjured";                   // Bonus points for not letting civilians to be injured (5 for all)
    ProcedureClass(6)="Procedure_NoHostageIncapacitated";               // Penalty points for incapacitating a civilian (-5 each)
    ProcedureClass(7)="Procedure_NoHostageKilled";                      // Penalty points for killing a civilian (-15 each)
    ProcedureClass(8)="Procedure_NoOfficerIncapacitated";               // Penalty points for killing an officer (-15 each)
    ProcedureClass(9)="Procedure_NoOfficerInjured";                     // Penalty points for injuring an officer (-5 each)
    ProcedureClass(10)="Procedure_NoOfficersDown";                      // Bonus points for staying alive (10/total each officer)
    ProcedureClass(11)="Procedure_NoSuspectsNeutralized";               // Bonus points for not letting suspects to be killed (5 for all)
    ProcedureClass(12)="Procedure_NoUnauthorizedUseOfDeadlyForce";      // Penalty points for using unauthorized deadly force (-10 each)
    ProcedureClass(13)="Procedure_NoUnauthorizedUseOfForce";            // Penalty points for using unauthorized force (-5 each)
    ProcedureClass(14)="Procedure_PlayerUninjured";                     // Bonus points for staying uninjured (5/total each)
    ProcedureClass(15)="Procedure_PreventEvidenceDestruction";          // Penalty points for an evidence destruction (-5 each)
    ProcedureClass(16)="Procedure_PreventSuspectEscape";                // Penalty points for a suspect escape (-5 each)
    ProcedureClass(17)="Procedure_ReportCharactersToTOC";               // Bonus points for TOC reports (5/total each)
    ProcedureClass(18)="Procedure_SecureAllWeapons";                    // Bonus points for securing all weapons (5/total each)

    ObjectiveClass(0)="Arrest_Jennings";
    ObjectiveClass(1)="Custom_NoCiviliansInjured";
    ObjectiveClass(2)="Custom_NoOfficersInjured";
    ObjectiveClass(3)="Custom_NoOfficersKilled";
    ObjectiveClass(4)="Custom_NoSuspectsKilled";
    ObjectiveClass(5)="Custom_PlayerUninjured";
    ObjectiveClass(6)="Custom_Timed";
    ObjectiveClass(7)="Disable_Bombs";
    ObjectiveClass(8)="Disable_Office_Bombs";
    ObjectiveClass(9)="Investigate_Laundromat";
    ObjectiveClass(10)="Neutralize_Alice";
    ObjectiveClass(11)="Neutralize_All_Enemies";
    ObjectiveClass(12)="Neutralize_Arias";
    ObjectiveClass(13)="Neutralize_CultLeader";
    ObjectiveClass(14)="Neutralize_Georgiev";
    ObjectiveClass(15)="Neutralize_Grover";
    ObjectiveClass(16)="Neutralize_GunBroker";
    ObjectiveClass(17)="Neutralize_Jimenez";
    ObjectiveClass(18)="Neutralize_Killer";
    ObjectiveClass(19)="Neutralize_Kiril";
    ObjectiveClass(20)="Neutralize_Koshka";
    ObjectiveClass(21)="Neutralize_Kruse";
    ObjectiveClass(22)="Neutralize_Norman";
    ObjectiveClass(23)="Neutralize_TerrorLeader";
    ObjectiveClass(24)="Neutralize_Todor";
    ObjectiveClass(25)="Rescue_Adams";
    ObjectiveClass(26)="Rescue_All_Hostages";
    ObjectiveClass(27)="Rescue_Altman";
    ObjectiveClass(28)="Rescue_Baccus";
    ObjectiveClass(29)="Rescue_Bettencourt";
    ObjectiveClass(30)="Rescue_Bogard";
    ObjectiveClass(31)="Rescue_CEO";
    ObjectiveClass(32)="Rescue_Diplomat";
    ObjectiveClass(33)="Rescue_Fillinger";
    ObjectiveClass(34)="Rescue_Kline";
    ObjectiveClass(35)="Rescue_Macarthur";
    ObjectiveClass(36)="Rescue_Rosenstein";
    ObjectiveClass(37)="Rescue_Sterling";
    ObjectiveClass(38)="Rescue_Victims";
    ObjectiveClass(39)="Rescue_Walsh";
    ObjectiveClass(40)="Rescue_Wilkins";
    ObjectiveClass(41)="Rescue_Winston";
    ObjectiveClass(42)="Secure_Briefcase";
    ObjectiveClass(43)="Secure_Weapon";

    AmmoClass(0)="None";
    AmmoClass(1)="M4Super90SGAmmo";
    AmmoClass(2)="M4Super90SGSabotAmmo";
    AmmoClass(3)="NovaPumpSGAmmo";
    AmmoClass(4)="NovaPumpSGSabotAmmo";
    AmmoClass(5)="LessLethalAmmo";
    AmmoClass(6)="CSBallLauncherAmmo";
    AmmoClass(7)="M4A1MG_JHP";
    AmmoClass(8)="M4A1MG_FMJ";
    AmmoClass(9)="AK47MG_FMJ";
    AmmoClass(10)="AK47MG_JHP";
    AmmoClass(11)="G36kMG_FMJ";
    AmmoClass(12)="G36kMG_JHP";
    AmmoClass(13)="UZISMG_FMJ";
    AmmoClass(14)="UZISMG_JHP";
    AmmoClass(15)="MP5SMG_JHP";
    AmmoClass(16)="MP5SMG_FMJ";
    AmmoClass(17)="UMP45SMG_FMJ";
    AmmoClass(18)="UMP45SMG_JHP";
    AmmoClass(19)="ColtM1911HG_JHP";
    AmmoClass(20)="ColtM1911HG_FMJ";
    AmmoClass(21)="Glock9mmHG_JHP";
    AmmoClass(22)="Glock9mmHG_FMJ";
    AmmoClass(23)="PythonRevolverHG_FMJ";
    AmmoClass(24)="PythonRevolverHG_JHP";
    AmmoClass(25)="TaserAmmo";
    AmmoClass(26)="VIPPistolAmmo_FMJ";
    AmmoClass(27)="ColtAR_FMJ";
    AmmoClass(28)="HK69GL_StingerGrenadeAmmo";
    AmmoClass(29)="HK69GL_FlashbangGrenadeAmmo";
    AmmoClass(30)="HK69GL_CSGasGrenadeAmmo";
    AmmoClass(31)="HK69GL_TripleBatonAmmo";
    AmmoClass(32)="SAWMG_JHP";
    AmmoClass(33)="SAWMG_FMJ";
    AmmoClass(34)="FNP90SMG_FMJ";
    AmmoClass(35)="FNP90SMG_JHP";
    AmmoClass(36)="DEHG_FMJ";
    AmmoClass(37)="DEHG_JHP";
    AmmoClass(38)="TEC9SMG_FMJ";
}

/* vim: set ft=java: */


Update filehandler.uc

Update filehandler.uc

Added \r for when interactive.uc is used instead of batch file

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.95 on 4/22/2014 9:34:09 AM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Token:: : ~~ Properties: ucStatementSep }

Criteria: 2
Comment:
Pass: 1

Criteria: 3
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 4
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>
         {@Var: FreeFileNum}

Criteria: 5
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: fstream file_{filenum} (string({filespec}).c_str(),
            {Input:   ios::in}
            {Output:  ios::out | ios::trunc}
            {Append:  ios::out | ios::app}
            {Binary:  ios::binary | ios::in | ios::out}
            {Random:  ios::binary | ios::in | ios::out}
            {Default: ios::binary | ios::in | ios::out}
         );

Criteria: 6
BackColor: Lime
Find: Close [#]{filenum}
Replace: file_{filenum}.close() ' PB: Close #{filenum}

Criteria: 7
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 8
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 9
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 10
BackColor: SlateBlue
Find: LOF({filenum})
Replace: PB_LOF({filenum})

Criteria: 11
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 12
Find: FreeFile
Replace: {@Eval: FreeFileNum += 1; FreeFileNum}

Criteria: 13
BackColor: Green
Find: Get$ [#] {filenum}, {count}, {StringVar}
Replace: {StringVar}.resize({count})
         file_{filenum}.read(&{StringVar}[0], {count})

Criteria: 14
BackColor: SandyBrown
Find: Put$ [#] {filenum}, {text}
Replace: file_{filenum}.write(&{text}[0], {text}.size())

Criteria: 15
Find: IsFile({file})
Replace: PB_ISFILE({file})

Criteria: 16
Find: Name {oldname} As {newname}
Replace: PB_NAME({oldname}, {newname})

Criteria: 17
BackColor: Gold
Find: EXE.Extn$
Replace: PB_EXE_EXTN

Criteria: 18
BackColor: Silver
Find: EXE.Full$
Replace: PB_EXE_FULL()

Criteria: 19
Find: EXE.Name$
Replace: PB_EXE_NAME

Criteria: 20
BackColor: CornflowerBlue
Find: EXE.Namex$
Replace: PB_EXE_NAMEX

Criteria: 21
Find: EXE.Path$
Replace: PB_EXE_PATH

Criteria: 22
Find: Dir$({mask})
Replace: PB_DIR({mask})

Criteria: 23
Find: Dir$([Next])
Replace: PB_DIR_NEXT()

Criteria: 24
Find: Print #{filenum}
Replace: file_{filenum} << endl

Criteria: 25
Find: Print #{filenum}, {text}
Replace: file_{filenum} << {text} << endl

Criteria: 26
Find: Print #{filenum}, {text};
Replace: file_{filenum} << {text}

Criteria: 27
PassOnce: False
Find: Print #{filenum}, {text}; {MoreText}
Replace: Print #{filenum}, {text} << {MoreText}

Criteria: 28
Comment:
Pass: 2

Criteria: 29
Selected: True
BackColor: Yellow
Find: fstream {file} ({args+})
Replace: {@Evaluate: Replace({@Self}, '{"[ \r\n]+"}', ' ')}

# End Search

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.95 on 4/17/2014 11:20:35 AM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Token:: : ~~ Properties: ucStatementSep }

Criteria: 2
Comment:
Pass: 1

Criteria: 3
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 4
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>
         {@Var: FreeFileNum}

Criteria: 5
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: fstream file_{filenum} (string({filespec}).c_str(),
            {Input:   ios::in}
            {Output:  ios::out | ios::trunc}
            {Append:  ios::out | ios::app}
            {Binary:  ios::binary | ios::in | ios::out}
            {Random:  ios::binary | ios::in | ios::out}
            {Default: ios::binary | ios::in | ios::out}
         );

Criteria: 6
BackColor: Lime
Find: Close [#]{filenum}
Replace: file_{filenum}.close() ' PB: Close #{filenum}

Criteria: 7
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 8
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 9
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 10
BackColor: SlateBlue
Find: LOF({filenum})
Replace: PB_LOF({filenum})

Criteria: 11
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 12
Find: FreeFile
Replace: {@Eval: FreeFileNum += 1; FreeFileNum}

Criteria: 13
BackColor: Green
Find: Get$ [#] {filenum}, {count}, {StringVar}
Replace: {StringVar}.resize({count})
         file_{filenum}.read(&{StringVar}[0], {count})

Criteria: 14
BackColor: SandyBrown
Find: Put$ [#] {filenum}, {text}
Replace: file_{filenum}.write(&{text}[0], {text}.size())

Criteria: 15
Find: IsFile({file})
Replace: PB_ISFILE({file})

Criteria: 16
Find: Name {oldname} As {newname}
Replace: PB_NAME({oldname}, {newname})

Criteria: 17
BackColor: Gold
Find: EXE.Extn$
Replace: PB_EXE_EXTN

Criteria: 18
BackColor: Silver
Find: EXE.Full$
Replace: PB_EXE_FULL()

Criteria: 19
Find: EXE.Name$
Replace: PB_EXE_NAME

Criteria: 20
BackColor: CornflowerBlue
Find: EXE.Namex$
Replace: PB_EXE_NAMEX

Criteria: 21
Find: EXE.Path$
Replace: PB_EXE_PATH

Criteria: 22
Find: Dir$({mask})
Replace: PB_DIR({mask})

Criteria: 23
Find: Dir$([Next])
Replace: PB_DIR_NEXT()

Criteria: 24
Find: Print #{filenum}
Replace: file_{filenum} << endl

Criteria: 25
Find: Print #{filenum}, {text}
Replace: file_{filenum} << {text} << endl

Criteria: 26
Find: Print #{filenum}, {text};
Replace: file_{filenum} << {text}

Criteria: 27
PassOnce: False
Find: Print #{filenum}, {text}; {MoreText}
Replace: Print #{filenum}, {text} << {MoreText}

Criteria: 28
Comment:
Pass: 2

Criteria: 29
Selected: True
BackColor: Yellow
Find: fstream {file} ({args+})
Replace: {@Evaluate: Replace({@Self}, '{"[ \n]+"}', ' ')}

# End Search


Update typespecifiers.uc

Update typespecifiers.uc

# typespecifiers.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 1/2/2014 4:45:59 PM
# Comment: Replaces data type specifiers with explicit type names

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Replaces data type specifiers with explicit type names
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Selected: True
Highlight: False
Find: {@Note:
         This transform does the following:

         * Adds explicit ByRef to args w/o ByVal or ByRef
         * Changes type specifiers to type names in param
           definitions and removes them elsewhere
         * Adds data type name to params that have neither
           an explicit type name or specifier, based on
           DefType definitions

         The section to the immediate right defines the
         required tables and an array to accomplish this.

         Bug: LCase() and UCase() are re-defined on the right
              due to a problem affecting pre-existing LCase/UCase.
              That is a temp solution.
      }
Replace: {@Define:
            LineContinue: " _"
            Var: DefTypeInitial(255) As String
            Var: Specifier As Table = _
                 {"%", "Integer", "&", "Long", "&&", "Quad", _
                  "?", "Byte", "??", "Word", "???", "Dword", _
                  "!", "Single", "#", "Double", "##", "Extended", _
                  "@", "Currency", "@@", "CurrencyX", "$", "String"}
            Var: DefType As Table = _
                 {"INT", "Integer", "LNG", "Long", "QUD", "Quad", _
                 "BYT", "Byte", "WRD", "Word", "DWD", "Dword", _
                 "SNG", "Single", "DBL", "Double", "EXT", "Extended", _
                 "CUR", "Currency", "CUX", "CurrencyX", "STR", "String"}
            Var: dType="(INT|LNG|QUD|BYT|WRD|DWD|SNG|DBL|EXT|CUR|CUX|STR)\b"
            Func: LCase(c As String) As String = IIF(Asc(c) > 64 and Asc(c) < 91, Chr(Asc(c)+32), c)
            Func: UCase(c As String) As String = IIF(Asc(c) > 96 and Asc(c) < 123, Chr(Asc(c)-32), c)
         }{@Define:: Token: \x27.* ~~ Properties: ucWhitespace}

Criteria: 2
Comment: Handles DefType declarations; also inserts <Arg> marker for easier parsing
Pass: 1

Criteria: 3
Comment: Stores the DefType statement associated with character initials
Highlight: False
BackColor: Purple
Find: {"Def"}{type:"{@Eval: dType}"} {char} {nl}
Replace: {@Eval:
            SetVar(DefTypeInitial(Asc(LCase("{char}"))), ReadStr(DefType, "{type}"))
            SetVar(DefTypeInitial(Asc(UCase("{char}"))), ReadStr(DefType, "{type}"))
         }

Criteria: 4
Comment: Changes a DefType range into multiple DefType statements
Highlight: False
BackColor: SlateBlue
PassOnce: False
Find: {"Def"}{type:"{@Eval: dType}"} {from}-{to} {nl}
Replace: {@Eval: Range(Asc("{from}"), Asc("{to}"), "'Def{type} '+Chr(x)+Chr(10)")}

Criteria: 5
Comment: Break DefType statements containing commas into multiple DefStype statements (one per line)
Highlight: False
BackColor: Pink
PassOnce: False
Find: {"Def"}{type:"{@Eval: dType}"} {etc}, {more} {nl}
Replace: Def{type} {etc}
         Def{type} {more}

Criteria: 6
Comment: Inserts <arg> marker for easier parsing
Highlight: False
BackColor: Red
Find: {nl}{decl: Sub | Function } {etc} ({args})
Replace: {nl}{decl} {etc}(<Arg>{@Eval: Replace("{args}", ",", ", <Arg>")})

Criteria: 7
Highlight: False
SkipOver: True
Find: {nl}Function =
Replace: [Skip over]

Criteria: 8
Comment: Inserts ByRef to params that have neither ByRef nor ByVal
Pass: 2

Criteria: 9
Comment: Inserts ByRef in front of params that do not explicitlly have ByVal or ByRef
Highlight: False
BackColor: SandyBrown
Find: <Arg> [{optional: Optional}] {name:1}
Replace: <Arg>{optional} ByRef {name}

Criteria: 10
Comment: Skips params that already have ByVal or ByRef
Highlight: False
BackColor: Green
SkipOver: True
Find: <Arg> [Optional] { ByVal | ByRef }
Replace: [Skip over]

Criteria: 11
Comment: Adds explicit data types to arg or variable declarations (functions also)
Pass: 3

Criteria: 12
Highlight: False
BackColor: RoyalBlue
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1}
Replace: <Arg>{by} {name} As {@Eval: DefTypeInitial(Asc("{name}"))}

Criteria: 13
Highlight: False
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1}{spec:"[!?@#$%&]+"}
Replace: <Arg>{by} {name} As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 14
Highlight: False
SkipOver: True
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1} As
Replace: [Skip over]

Criteria: 15
Highlight: False
BackColor: Tomato
PassOnce: False
Find: {decl: Dim | Global | Local | Static } {etc} {spec:"[!?@#$%&]+"}
Replace: {decl} {etc} As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 16
Comment: Adds the appropriate type for functions with no explicit type or specifier
Highlight: False
BackColor: CornflowerBlue
Find: {nl}Function {name:1}([{args%}])
Replace: {nl}Function {name}({args}) As {@Eval: DefTypeInitial(Asc("{name}"))}

Criteria: 17
Comment: Changes function type specifier to data type name
Highlight: False
Find: {nl}Function {name:1}{spec:"[!?@#$%&]+"}([{args%}])
Replace: {nl}Function {name}({args}) As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 18
Comment: Skips functions that already have an explicit type
Highlight: False
BackColor: Gold
Find: {nl}Function {name}([{args%}]) As {type}
Replace: {nl}Function {name}({args}) As {type}

Criteria: 19
Comment: Cleans up; removes <Arg> marker; as well as type specifiers
Pass: 4

Criteria: 20
Comment: Removes <Arg> that was inserted in earlier pass
Highlight: False
BackColor: SandyBrown
Find: <Arg>
Replace: {Nothing}

Criteria: 21
Comment: Removes statement specifiers
Highlight: False
BackColor: Violet
Find: {variable:"[a-z0-9_]+"}{spec:"[!?@#$%&]+"}
Replace: {variable}

# End Search

# TypeSpecifiers.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 10/28/2013 5:34:10 PM
# Comment: Replaces data type specifiers with explicit type names

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Replaces data type specifiers with explicit type names
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Highlight: False
Find: {@Note:
         This transform does the following:

         * Adds explicit ByRef to args w/o ByVal or ByRef
         * Changes type specifiers to type names in param
           definitions and removes them elsewhere
         * Adds data type name to params that have neither
           an explicit type name or specifier, based on
           DefType definitions

         The section to the immediate right defines the
         required tables and an array to accomplish this.
      }
Replace: {@Define:
            LineContinue: " _"
            Var: DefTypeInitial(255) As String
            Var: Specifier As Table = _
                 {"%", "Integer", "&", "Long", "&&", "Quad", _
                  "?", "Byte", "??", "Word", "???", "Dword", _
                  "!", "Single", "#", "Double", "##", "Extended", _
                  "@", "Currency", "@@", "CurrencyX", "$", "String"}
            Var: DefType As Table = _
                 {"INT", "Integer", "LNG", "Long", "QUD", "Quad", _
                 "BYT", "Byte", "WRD", "Word", "DWD", "Dword", _
                 "SNG", "Single", "DBL", "Double", "EXT", "Extended", _
                 "CUR", "Currency", "CUX", "CurrencyX", "STR", "String"}
            Var: dType="(INT|LNG|QUD|BYT|WRD|DWD|SNG|DBL|EXT|CUR|CUX|STR)\b"
         }{@Define:: Token: \x27.* ~~ Properties: ucWhitespace}

Criteria: 2
Comment: Handles DefType declarations; also inserts <Arg> marker for easier parsing
Pass: 1

Criteria: 3
Comment: Stores the DefType statement associated with character initials
Highlight: False
BackColor: Purple
Find: {"Def"}{type:"{@Eval: dType}"} {char} {nl}
Replace: {@Eval:
            SetVar(DefTypeInitial(Asc(LCase("{char}"))), ReadStr(DefType, "{type}"))
            SetVar(DefTypeInitial(Asc(UCase("{char}"))), ReadStr(DefType, "{type}"))
         }

Criteria: 4
Comment: Changes a DefType range into multiple DefType statements
Highlight: False
BackColor: SlateBlue
PassOnce: False
Find: {"Def"}{type:"{@Eval: dType}"} {from}-{to} {nl}
Replace: {@Eval: Range(Asc("{from}"), Asc("{to}"), "'Def{type} '+Chr(x)+Chr(10)")}

Criteria: 5
Comment: Break DefType statements containing commas into multiple DefStype statements (one per line)
Highlight: False
BackColor: Pink
PassOnce: False
Find: {"Def"}{type:"{@Eval: dType}"} {etc}, {more} {nl}
Replace: Def{type} {etc}
         Def{type} {more}

Criteria: 6
Comment: Inserts <arg> marker for easier parsing
Highlight: False
BackColor: Red
Find: {nl}{decl: Sub | Function } {etc} ({args})
Replace: {nl}{decl} {etc}(<Arg>{@Eval: Replace("{args}", ",", ", <Arg>")})

Criteria: 7
Highlight: False
SkipOver: True
Find: {nl}Function =
Replace: [Skip over]

Criteria: 8
Comment: Inserts ByRef to params that have neither ByRef nor ByVal
Pass: 2

Criteria: 9
Comment: Inserts ByRef in front of params that do not explicitlly have ByVal or ByRef
Highlight: False
BackColor: SandyBrown
Find: <Arg> [{optional: Optional}] {name:1}
Replace: <Arg>{optional} ByRef {name}

Criteria: 10
Comment: Skips params that already have ByVal or ByRef
Highlight: False
BackColor: Green
SkipOver: True
Find: <Arg> [Optional] { ByVal | ByRef }
Replace: [Skip over]

Criteria: 11
Comment: Adds explicit data types to arg or variable declarations (functions also)
Pass: 3

Criteria: 12
Highlight: False
BackColor: RoyalBlue
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1}
Replace: <Arg>{by} {name} As {@Eval: DefTypeInitial(Asc("{name}"))}

Criteria: 13
Highlight: False
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1}{spec:"[!?@#$%&]+"}
Replace: <Arg>{by} {name} As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 14
Selected: True
Highlight: False
SkipOver: True
Find: <Arg> [Optional] {by: ByVal | ByRef } {name:1} As
Replace: [Skip over]

Criteria: 15
Highlight: False
BackColor: Tomato
PassOnce: False
Find: {decl: Dim | Global | Local | Static } {etc} {spec:"[!?@#$%&]+"}
Replace: {decl} {etc} As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 16
Comment: Adds the appropriate type for functions with no explicit type or specifier
Highlight: False
BackColor: CornflowerBlue
Find: {nl}Function {name:1}([{args%}])
Replace: {nl}Function {name}({args}) As {@Eval: DefTypeInitial(Asc("{name}"))}

Criteria: 17
Comment: Changes function type specifier to data type name
Highlight: False
Find: {nl}Function {name:1}{spec:"[!?@#$%&]+"}([{args%}])
Replace: {nl}Function {name}({args}) As {@Eval: ReadStr(Specifier, "{spec}")}

Criteria: 18
Comment: Skips functions that already have an explicit type
Highlight: False
BackColor: Gold
Find: {nl}Function {name}([{args%}]) As {type}
Replace: {nl}Function {name}({args}) As {type}

Criteria: 19
Comment: Cleans up; removes <Arg> marker; as well as type specifiers
Pass: 4

Criteria: 20
Comment: Removes <Arg> that was inserted in earlier pass
Highlight: False
BackColor: SandyBrown
Find: <Arg>
Replace: {Nothing}

Criteria: 21
Comment: Removes statement specifiers
Highlight: False
BackColor: Violet
Find: {variable:"[a-z0-9_]+"}{spec:"[!?@#$%&]+"}
Replace: {variable}

# End Search