Update implicit-convert.uc

Update implicit-convert.uc

Converts Len() depending on whether it's a string


Added XComGameState_item for issue #104 - dont stack items that have ComponentObjects with them

//---------------------------------------------------------------------------------------
//  FILE:    XComGameState_Item.uc
//  AUTHOR:  Ryan McFall  --  10/10/2013
//  PURPOSE: This object represents the instance data for an item in the tactical game for
//           X-Com
//
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------
class XComGameState_Item extends XComGameState_BaseObject
	dependson(XGInventoryNativeBase)
	implements(UIQueryInterfaceItem, X2VisualizedInterface)
	native(Core);

//Instance-only variables
var() int Quantity;
var int LastQuantityChange;
var() int MergedItemCount;
var() bool bMergedOut;
var() StateObjectReference LoadedAmmo;
var() StateObjectReference CosmeticUnitRef; //Specifies a 'unit' associated with this item. Used with the gremlin, which is technically an item but moves and acts like a unit
var() StateObjectReference AttachedUnitRef; //Certain items such as the gremlin can be temporarily attached to another unit. This field tracks that attachment
var() StateObjectReference TechRef; // Certain items act as requirements for specific tech states (Facility Leads)
var() StateObjectReference LinkedEntity; // Certain items are linked to other gamestates and need to store this information (needed for chosen)

//Templated variables
var() ELocation ItemLocation; //Items not located on a unit will have ELocation_None - visual attachment point
var() int Ammo;
var() EInventorySlot InventorySlot;     //  Gameplay location - item is pretty much in the templated slot or in a backpack

var() protected name            m_TemplateName;
var() protected X2ItemTemplate  m_ItemTemplate;

//  Weapon upgrade system
var() protected array<name>                     m_arrWeaponUpgradeNames;
var() protected array<X2WeaponUpgradeTemplate>  m_arrWeaponUpgradeTemplates;

var() StateObjectReference          OwnerStateObject;
var() array<StateObjectReference>   ContainedItems;         //  XComGameState_Items which are inside of this one.
var() StateObjectReference          SoldierKitOwner;        //  if this item is a soldier kit, this is the soldier that died

var() array<StatBoost>				StatBoosts;  // Used for Personal Combat Sims to hold rolled on stat boost data

var() TWeaponAppearance WeaponAppearance; // Used for storing customization state for Primary Weapons
var() string Nickname; //Storage for the the name of the item

function SetInitialState(XGItem Visualizer)
{
	local XGInventoryItem Item;

	Item = XGInventoryItem(Visualizer);
	ItemLocation = Item.m_eSlot;
}

function Actor FindOrCreateVisualizer( optional XComGameState Gamestate = none )
{
	local Actor ItemVisualizer;

	ItemVisualizer = `XCOMHISTORY.GetVisualizer( ObjectID );
	if (ItemVisualizer == none)
	{
		class'XGItem'.static.CreateVisualizer( self );
	}

	return ItemVisualizer;
}

function SyncVisualizer(optional XComGameState GameState = none)
{
	local X2WeaponTemplate WeaponTemplate;
	local XGWeapon WeaponVis;
	local XComWeapon WeaponMeshVis;

	if (Ammo == 0)
	{
		WeaponTemplate = X2WeaponTemplate(GetMyTemplate());
		if (WeaponTemplate != None && WeaponTemplate.bHideWithNoAmmo)
		{
			WeaponVis = XGWeapon(GetVisualizer());
			if (WeaponVis != None)
			{
				WeaponMeshVis = WeaponVis.GetEntity();
				if (WeaponMeshVis != None)
				{
					WeaponMeshVis.Mesh.SetHidden(true);
				}
			}
		}
	}
}

function AppendAdditionalSyncActions( out VisualizationActionMetadata ActionMetadata, const XComGameStateContext Context)
{
}

static function X2ItemTemplateManager GetMyTemplateManager()
{
	return class'X2ItemTemplateManager'.static.GetItemTemplateManager();
}

simulated function name GetMyTemplateName()
{
	return m_TemplateName;
}

simulated native function X2ItemTemplate GetMyTemplate();

simulated native function TTile GetTileLocation();

/// <summary>
/// Called immediately prior to loading, this method is called on each state object so that its resources can be ready when the map completes loading. Request resources
/// should output an array of strings containing archetype paths to load
/// </summary>
event RequestResources(out array<string> ArchetypesToLoad)
{
	local X2EquipmentTemplate EquipmentTemplate;
	local int i;

	super.RequestResources(ArchetypesToLoad);

	//Load our related resources
	EquipmentTemplate = X2EquipmentTemplate(GetMyTemplate());
	if(EquipmentTemplate != none)
	{
		if(EquipmentTemplate.GameArchetype != "")
		{
			ArchetypesToLoad.AddItem(EquipmentTemplate.GameArchetype);
		}

		if(EquipmentTemplate.AltGameArchetype != "")
		{
			ArchetypesToLoad.AddItem(EquipmentTemplate.AltGameArchetype);
		}

		if(EquipmentTemplate.CosmeticUnitTemplate != "")
		{
			ArchetypesToLoad.AddItem(EquipmentTemplate.CosmeticUnitTemplate);
		}

		for (i = 0; i < EquipmentTemplate.AltGameArchetypeArray.Length; ++i)
		{
			if (EquipmentTemplate.AltGameArchetypeArray[i].ArchetypeString != "")
			{
				ArchetypesToLoad.AddItem(EquipmentTemplate.AltGameArchetypeArray[i].ArchetypeString);
			}
		}
	}
}

event OnCreation(optional X2DataTemplate Template)
{
	local X2ItemTemplateManager ItemManager;
	local X2EquipmentTemplate EquipmentTemplate;
	local X2WeaponTemplate WeaponTemplate;
	local StatBoost ItemStatBoost;
	local int idx;

	super.OnCreation(Template);

	m_ItemTemplate = X2ItemTemplate(Template);
	m_TemplateName = Template.DataName;

	Ammo = GetClipSize();

	EquipmentTemplate = X2EquipmentTemplate(m_ItemTemplate);

	if(EquipmentTemplate != none)
	{
		ItemManager = GetMyTemplateManager();

		for(idx = 0; idx < EquipmentTemplate.StatsToBoost.Length; idx++)
		{
			if(ItemManager.GetItemStatBoost(EquipmentTemplate.StatBoostPowerLevel, EquipmentTemplate.StatsToBoost[idx], ItemStatBoost))
			{
				StatBoosts.AddItem(ItemStatBoost);
			}
		}
		WeaponTemplate = X2WeaponTemplate(m_ItemTemplate);

		if( WeaponTemplate != None )
		{
			//  primary weapon goes in the right hand, everything else is stowed
			if( WeaponTemplate.InventorySlot == eInvSlot_PrimaryWeapon )
			{
				ItemLocation = eSlot_RightHand;
			}
			else
			{
				ItemLocation = WeaponTemplate.StowedLocation;
			}
		}
	}
}

function OnBeginTacticalPlay(XComGameState NewGameState)
{
	RegisterForCosmeticUnitEvents(NewGameState);
}

protected function RegisterForCosmeticUnitEvents(XComGameState NewGameState)
{
	local X2EventManager EventManager;
	local Object ThisObj;

	super.OnBeginTacticalPlay(NewGameState);

	if( CosmeticUnitRef.ObjectID > 0 )
	{
		//Only items with cosmetic units need to listen for these. If you expand this conditional, make sure you need to as
		//having too many items respond to these events would be costly.
		EventManager = `XEVENTMGR;
		ThisObj = self;

		EventManager.RegisterForEvent( ThisObj, 'AbilityActivated', OnAbilityActivated, ELD_OnStateSubmitted,,); //Move if we're ordered to
		EventManager.RegisterForEvent( ThisObj, 'UnitDied', OnUnitDied, ELD_OnStateSubmitted,,); //Return to owner if target unit dies or play death anim if owner dies
		EventManager.RegisterForEvent( ThisObj, 'UnitEvacuated', OnUnitEvacuated, ELD_OnStateSubmitted,,); //For gremlin, to evacuate with its owner
		EventManager.RegisterForEvent( ThisObj, 'ItemRecalled', OnItemRecalled, ELD_OnStateSubmitted,,); //Return to owner when specifically requested
		EventManager.RegisterForEvent( ThisObj, 'ForceItemRecalled', OnForceItemRecalled, ELD_OnStateSubmitted,,); //Return to owner when specifically told
		EventManager.RegisterForEvent(ThisObj, 'UnitIcarusJumped', OnUnitIcarusJumped, ELD_OnStateSubmitted, , ); //Return to owner when specifically told
	}
}

function OnEndTacticalPlay(XComGameState NewGameState)
{
	local StateObjectReference EmptyReference;
	local X2EventManager EventManager;
	local Object ThisObj;

	super.OnEndTacticalPlay(NewGameState);

	EventManager = `XEVENTMGR;
	ThisObj = self;
	if( CosmeticUnitRef.ObjectID > 0 )
	{
		EventManager.UnRegisterFromEvent( ThisObj, 'UnitMoveFinished' );
		EventManager.UnRegisterFromEvent( ThisObj, 'AbilityActivated' );
		EventManager.UnRegisterFromEvent( ThisObj, 'UnitDied' );
		EventManager.UnRegisterFromEvent( ThisObj, 'ItemRecalled' );
		EventManager.UnRegisterFromEvent( ThisObj, 'ForceItemRecalled' );
	}

	Ammo = GetClipSize();
	MergedItemCount = 0;
	bMergedOut = false;
	CosmeticUnitRef = EmptyReference;
	LoadedAmmo = EmptyReference;
}

function CreateCosmeticItemUnit(XComGameState NewGameState)
{
	local X2EquipmentTemplate EquipmentTemplate;
	local XComGameState_Unit OwningUnitState;
	local XComGameState_Unit CosmeticUnit;
	local TTile CosmeticUnitTile;
	local vector SpawnLocation;

	EquipmentTemplate = X2EquipmentTemplate(GetMyTemplate());
	if(EquipmentTemplate != none
		&& EquipmentTemplate.CosmeticUnitTemplate != ""
		&& CosmeticUnitRef.ObjectID <= 0) // only if we didn't already create one
	{
		OwningUnitState = XComGameState_Unit(NewGameState.GetGameStateForObjectID(OwnerStateObject.ObjectID));
		if(OwningUnitState == none)
		{
			OwningUnitState = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(OwnerStateObject.ObjectID));
			if(OwningUnitState == none)
			{
				return;
			}
		}

		// if the owning unit has been removed from play, we don't need to exist
		if(OwningUnitState.bRemovedFromPlay)
		{
			return;
		}

		// we default to being attached to our owner
		AttachedUnitRef = OwningUnitState.GetReference();

		CosmeticUnitTile = OwningUnitState.GetDesiredTileForAttachedCosmeticUnit();

		SpawnLocation = `XWORLD.GetPositionFromTileCoordinates(CosmeticUnitTile);
		CosmeticUnitRef = `SPAWNMGR.CreateUnit(
			SpawnLocation,
			name(EquipmentTemplate.CosmeticUnitTemplate),
			OwningUnitState.GetTeam(),
			false,
			,
			NewGameState );

		//Force the appearance to use the soldier's settings
		CosmeticUnit = XComGameState_Unit(NewGameState.GetGameStateForObjectID(CosmeticUnitRef.ObjectID));
		CosmeticUnit.kAppearance.nmPatterns = OwningUnitState.kAppearance.nmWeaponPattern;
		CosmeticUnit.kAppearance.iArmorTint = OwningUnitState.kAppearance.iWeaponTint;
		CosmeticUnit.kAppearance.iArmorTintSecondary = OwningUnitState.kAppearance.iArmorTintSecondary;
		XGUnit(CosmeticUnit.GetVisualizer()).GetPawn().SetAppearance(CosmeticUnit.kAppearance);

		if (OwningUnitState.GetMyTemplate().OnCosmeticUnitCreatedFn != None)
		{
			OwningUnitState.GetMyTemplate().OnCosmeticUnitCreatedFn(CosmeticUnit, OwningUnitState, self, NewGameState);
		}

		// now that we have a unit, register for events
		RegisterForCosmeticUnitEvents(NewGameState);
	}
}

function EventListenerReturn OnAbilityActivated(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameStateContext_Ability AbilityContext;
	local X2GameRulesetVisibilityInterface TargetInterface;
	local XComGameStateHistory History;
	local XComGameState NewGameState;
	local XComGameState_Item NewItemState;
	local X2AbilityTemplate AbilityTemplate;
	local XComGameState_Unit AttachedUnitState, NewCosmeticState;

	if( CosmeticUnitRef.ObjectID > 0 && GameState.GetContext().InterruptionStatus != eInterruptionStatus_Interrupt)
	{
		History = `XCOMHISTORY;

		AbilityContext = XComGameStateContext_Ability(GameState.GetContext());
		if (AbilityContext != none && AbilityContext.InputContext.ItemObject.ObjectID == ObjectID) //If we were used in this ability, then process
		{
			AbilityTemplate = class'X2AbilityTemplateManager'.static.GetAbilityTemplateManager().FindAbilityTemplate(AbilityContext.InputContext.AbilityTemplateName);
			if (AbilityTemplate.bStationaryWeapon)
				return ELR_NoInterrupt;

			if( AbilityContext.InputContext.PrimaryTarget.ObjectID > 0 )
			{
				TargetInterface = X2GameRulesetVisibilityInterface(History.GetGameStateForObjectID(AbilityContext.InputContext.PrimaryTarget.ObjectID));
				if( TargetInterface != none )
				{
					NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Equipment reassigned");

					//Update the attached unit for this item
					NewItemState = XComGameState_Item(NewGameState.ModifyStateObject(self.Class, ObjectID));
					NewItemState.AttachedUnitRef = AbilityContext.InputContext.PrimaryTarget;

					AttachedUnitState = XComGameState_Unit(History.GetGameStateForObjectID(NewItemState.AttachedUnitRef.ObjectID));
					if (AttachedUnitState != none)
					{
						NewCosmeticState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', CosmeticUnitRef.ObjectID));
						NewCosmeticState.SetVisibilityLocation( AttachedUnitState.TileLocation );
					}

					`GAMERULES.SubmitGameState(NewGameState);
				}
				else
				{
					`redscreen("Gremlin was unable to move to target for ability"@AbilityContext.InputContext.AbilityTemplateName@" with target object:"@AbilityContext.InputContext.PrimaryTarget.ObjectID);
				}
			}
			else if( AbilityContext.InputContext.TargetLocations.Length > 0 )
			{
				// don't do anything, but this case shouldn't cause a redscreen either
			}
			else
			{
				`redscreen("Gremlin was unable to find a target to move to for ability"@AbilityContext.InputContext.AbilityTemplateName);
			}
		}
	}

	return ELR_NoInterrupt;
}

function EventListenerReturn OnItemRecalled(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Ability AbilityState;
	local XComGameState_Item RecalledItem;

	if (CosmeticUnitRef.ObjectID > 0)
	{
		AbilityState = XComGameState_Ability(EventData);
		RecalledItem = AbilityState.GetSourceWeapon();
		if (RecalledItem.ObjectID == ObjectID)
		{
			RecallItem(RecalledItem, GameState);
		}
	}
	return ELR_NoInterrupt;
}

function EventListenerReturn OnForceItemRecalled(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Item RecalledItem;
	local XComGameStateHistory History;

	if (CosmeticUnitRef.ObjectID > 0)
	{
		// Check if this unit is targeted by the ability
		AbilityContext = XComGameStateContext_Ability(GameState.GetContext());

		if( (AbilityContext.InputContext.PrimaryTarget.ObjectID == CosmeticUnitRef.ObjectID) ||
			(AbilityContext.InputContext.MultiTargets.Find('ObjectID', CosmeticUnitRef.ObjectID) != INDEX_NONE) )
		{
			// This item was a target of the ability
			History = `XCOMHISTORY;

			RecalledItem = XComGameState_Item(History.GetGameStateForObjectID(CosmeticUnitRef.ObjectID));
			RecallItem(RecalledItem, GameState);
		}
	}
	return ELR_NoInterrupt;
}

private function RecallItem(const XComGameState_Item RecalledItem, XComGameState GameState)
{
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Unit OwnerState;
	local XComGameState_Item NewRecalledItem;
	local XComGameState NewGameState;
	local XGUnit Visualizer;
	local vector MoveToLoc;
	local XComGameStateHistory History;
	local XComGameState_Unit CosmeticUnitState;
	local bool MoveFromTarget;
	local TTile OwnerStateDesiredAttachTile;

	History = `XCOMHISTORY;
	AbilityContext = XComGameStateContext_Ability( GameState.GetContext( ) );

	OwnerState = XComGameState_Unit(History.GetGameStateForObjectID(OwnerStateObject.ObjectID));
	OwnerStateDesiredAttachTile = OwnerState.GetDesiredTileForAttachedCosmeticUnit();
	if ( OwnerStateDesiredAttachTile != RecalledItem.GetTileLocation())
	{
		if (AttachedUnitRef != OwnerStateObject)
		{
			NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Equipment recalled");
			//Update the attached unit for this item
			NewRecalledItem = XComGameState_Item(NewGameState.ModifyStateObject(self.Class, ObjectID));
			NewRecalledItem.AttachedUnitRef = OwnerStateObject;

			`GAMERULES.SubmitGameState(NewGameState);
		}

		CosmeticUnitState = XComGameState_Unit( History.GetGameStateForObjectID( RecalledItem.CosmeticUnitRef.ObjectID ) );

		MoveFromTarget = (OwnerStateDesiredAttachTile == RecalledItem.GetTileLocation()) && (AbilityContext != none) && (AbilityContext.InputContext.SourceObject.ObjectID == OwnerState.ObjectID);

		if (MoveFromTarget && AbilityContext.InputContext.TargetLocations.Length > 0)
		{
			MoveToLoc = AbilityContext.InputContext.TargetLocations[0];
			CosmeticUnitState.SetVisibilityLocationFromVector( MoveToLoc );
		}

		//  Now move it move it
		Visualizer = XGUnit(History.GetVisualizer(CosmeticUnitRef.ObjectID));
		MoveToLoc = `XWORLD.GetPositionFromTileCoordinates(OwnerStateDesiredAttachTile);
		Visualizer.MoveToLocation(MoveToLoc, CosmeticUnitState);
	}
}

function EventListenerReturn OnUnitDied(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameStateHistory History;
	local XComGameState_Unit UnitState, CosmeticUnit;
	local XComGameState NewGameState;
	local XComGameState_Item ItemState;
	local vector NewLocation;
	local XComGameStateContext_ChangeContainer ChangeContext;

	UnitState = XComGameState_Unit(EventData);
	//  was this the unit we are attached to or our owner?
	if (UnitState.ObjectID == AttachedUnitRef.ObjectID || UnitState.ObjectID == OwnerStateObject.ObjectID)
	{
		History = `XCOMHISTORY;
		UnitState = XComGameState_Unit(History.GetGameStateForObjectID(OwnerStateObject.ObjectID));
		CosmeticUnit = XComGameState_Unit(History.GetGameStateForObjectID(CosmeticUnitRef.ObjectID));
		//  if it was not our owner, reattach to them
		if ( ( UnitState.ObjectID != OwnerStateObject.ObjectID ) || (OwnerStateObject.ObjectID != AttachedUnitRef.ObjectID) )
		{
			NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Attached Unit Died");
			ItemState = XComGameState_Item(NewGameState.ModifyStateObject(Class, ObjectID));
			ItemState.AttachedUnitRef = OwnerStateObject;
			`GAMERULES.SubmitGameState(NewGameState);

			if (UnitState != none && CosmeticUnit != none && CosmeticUnit.TileLocation != UnitState.TileLocation)
			{
				NewLocation = `XWORLD.GetPositionFromTileCoordinates(UnitState.TileLocation);
				XGUnit(CosmeticUnit.GetVisualizer()).MoveToLocation(NewLocation);
			}
		}
		//  if it was our owner, we have to die too
		else
		{
			if (CosmeticUnit != none)
			{
				NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Owner Unit Died");
				ChangeContext = XComGameStateContext_ChangeContainer(NewGameState.GetContext());
				ChangeContext.BuildVisualizationFn = ItemOwnerDeathVisualization;
				CosmeticUnit = XComGameState_Unit(NewGameState.ModifyStateObject(CosmeticUnit.Class, CosmeticUnit.ObjectID));
				CosmeticUnit.SetCurrentStat(eStat_HP, 0);
				`GAMERULES.SubmitGameState(NewGameState);
			}
		}
	}

	return ELR_NoInterrupt;
}

function ItemOwnerDeathVisualization(XComGameState VisualizeGameState)
{
	local VisualizationActionMetadata DeathTrack;
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;

	//  This game state should contain just the cosmetic unit state having its HP set to 0.
	foreach VisualizeGameState.IterateByClassType(class'XComGameState_Unit', UnitState)
	{
		break;
	}
	History = `XCOMHISTORY;
	DeathTrack.StateObject_OldState = History.GetGameStateForObjectID(UnitState.ObjectID, , VisualizeGameState.HistoryIndex - 1);
	DeathTrack.StateObject_NewState = UnitState;
	DeathTrack.VisualizeActor = UnitState.GetVisualizer();

	class'X2Action_Death'.static.AddToVisualizationTree(DeathTrack, VisualizeGameState.GetContext());
}

function EventListenerReturn OnUnitEvacuated(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameStateHistory History;
	local XComGameState_Unit UnitState, CosmeticUnit;
	local XComGameState NewGameState;
	local XComGameStateContext_ChangeContainer ChangeContext;

	UnitState = XComGameState_Unit(EventData);

	// If the unit that evacuated was our owner, then we must also evacuate.
	if (UnitState.ObjectID == OwnerStateObject.ObjectID)
	{
		History = `XCOMHISTORY;
		CosmeticUnit = XComGameState_Unit(History.GetGameStateForObjectID(CosmeticUnitRef.ObjectID));

		if (CosmeticUnit != none)
		{
			NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Owner Unit Evacuated");
			ChangeContext = XComGameStateContext_ChangeContainer(NewGameState.GetContext());
			ChangeContext.BuildVisualizationFn = ItemOwnerEvacVisualization;

			// Visualize this context in the same visblock as the evacuating unit.
			ChangeContext.SetDesiredVisualizationBlockIndex(GameState.HistoryIndex);

			CosmeticUnit = XComGameState_Unit(NewGameState.ModifyStateObject(CosmeticUnit.Class, CosmeticUnit.ObjectID));
			CosmeticUnit.EvacuateUnit(NewGameState);
			`GAMERULES.SubmitGameState(NewGameState);
		}
	}

	return ELR_NoInterrupt;
}

function ItemOwnerEvacVisualization(XComGameState VisualizeGameState)
{
	local VisualizationActionMetadata EvacTrack;
	local XComGameState_Unit UnitState;
	local XComGameStateHistory History;
	local X2Action_Evac EvacAction;

	//  This game state should contain just the cosmetic unit state that's evacuating.
	foreach VisualizeGameState.IterateByClassType(class'XComGameState_Unit', UnitState)
	{
		break;
	}

	History = `XCOMHISTORY;
	EvacTrack.StateObject_OldState = History.GetGameStateForObjectID(UnitState.ObjectID, , VisualizeGameState.HistoryIndex - 1);
	EvacTrack.StateObject_NewState = UnitState;
	EvacTrack.VisualizeActor = UnitState.GetVisualizer();

	EvacAction = X2Action_Evac(class'X2Action_Evac'.static.AddToVisualizationTree(EvacTrack, VisualizeGameState.GetContext()));
	EvacAction.bIsVisualizingGremlin = true;

	//Hide the pawn explicitly now - in case the vis block doesn't complete immediately to trigger an update
	class'X2Action_RemoveUnit'.static.AddToVisualizationTree(EvacTrack, VisualizeGameState.GetContext());
}

function EventListenerReturn OnUnitIcarusJumped(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameStateContext_ChangeContainer ChangeContext;
	local XComGameState NewGameState;
	local XComGameState_Unit EventUnitState, AttachedUnitState;
	local XComGameStateHistory History;

	EventUnitState = XComGameState_Unit(EventData);

	History = `XCOMHISTORY;
	AttachedUnitState = XComGameState_Unit(History.GetGameStateForObjectID(AttachedUnitRef.ObjectID));

	if (EventUnitState != none && AttachedUnitState == EventUnitState)
	{
		NewGameState = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Owner Unit Icarus Jumped");
		ChangeContext = XComGameStateContext_ChangeContainer(NewGameState.GetContext());
		ChangeContext.BuildVisualizationFn = ItemOwnerIcarusJumpVisualization;

		// Visualize this context in the same visblock as the evacuating unit.
		ChangeContext.SetDesiredVisualizationBlockIndex(GameState.HistoryIndex);

		NewGameState.ModifyStateObject(Class, ObjectID);

		`GAMERULES.SubmitGameState(NewGameState);
	}

	return ELR_NoInterrupt;
}

function ItemOwnerIcarusJumpVisualization(XComGameState VisualizeGameState)
{
	local VisualizationActionMetadata IJTrack;
	local XComGameState_Unit UnitState, AttachedUnitState;
	local XComGameState_Item GremlinItemState;
	local XComGameStateHistory History;
	local X2Action_IcarusJumpGremlin IJGremlin;
	local XGUnit GremlinUnit;

	//  This game state should contain just the cosmetic unit state that's evacuating.
	foreach VisualizeGameState.IterateByClassType(class'XComGameState_Item', GremlinItemState)
	{
		break;
	}

	History = `XCOMHISTORY;

	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(GremlinItemState.CosmeticUnitRef.ObjectID));
	GremlinUnit = XGUnit(UnitState.GetVisualizer());

	IJTrack.StateObject_OldState = History.GetGameStateForObjectID(UnitState.ObjectID, , VisualizeGameState.HistoryIndex - 1);
	IJTrack.StateObject_NewState = UnitState;
	IJTrack.VisualizeActor = GremlinUnit;

	IJGremlin = X2Action_IcarusJumpGremlin(class'X2Action_IcarusJumpGremlin'.static.AddToVisualizationTree(IJTrack, VisualizeGameState.GetContext()));
	AttachedUnitState = XComGameState_Unit(History.GetGameStateForObjectID(AttachedUnitRef.ObjectID));
	IJGremlin.MoveLocation = `XWORLD.GetPositionFromTileCoordinates(AttachedUnitState.TileLocation);

	IJGremlin = X2Action_IcarusJumpGremlin(class'X2Action_IcarusJumpGremlin'.static.AddToVisualizationTree(IJTrack, VisualizeGameState.GetContext()));
	IJGremlin.bPlayInReverse = true;
}

simulated function array<name> GetMyWeaponUpgradeTemplateNames()
{
	return m_arrWeaponUpgradeNames;
}

simulated function array<X2WeaponUpgradeTemplate> GetMyWeaponUpgradeTemplates()
{
	local X2ItemTemplateManager ItemMgr;
	local int i;

	ItemMgr = class'X2ItemTemplateManager'.static.GetItemTemplateManager();
	if (m_arrWeaponUpgradeTemplates.Length > m_arrWeaponUpgradeNames.Length)
		m_arrWeaponUpgradeTemplates.Length = 0;

	for (i = 0; i < m_arrWeaponUpgradeNames.Length; ++i)
	{
		if (m_arrWeaponUpgradeTemplates[i] == none || m_arrWeaponUpgradeTemplates[i].DataName != m_arrWeaponUpgradeNames[i])
			m_arrWeaponUpgradeTemplates[i] = X2WeaponUpgradeTemplate(ItemMgr.FindItemTemplate(m_arrWeaponUpgradeNames[i]));
	}

	return m_arrWeaponUpgradeTemplates;
}

simulated function array<string> GetMyWeaponUpgradeTemplatesCategoryIcons()
{
	local array<X2WeaponUpgradeTemplate> Templates;
	local int TemplateIdx, LocalIdx;
	local array<string> LocalIcons, FinalIcons;

	Templates = GetMyWeaponUpgradeTemplates();
	for( TemplateIdx = 0; TemplateIdx < Templates.length; TemplateIdx++ )
	{
		LocalIcons = Templates[TemplateIdx].GetAttachmentInventoryCategoryImages(self);

		for( LocalIdx = 0; LocalIdx < LocalIcons.length; LocalIdx++ )
			FinalIcons.AddItem(LocalIcons[LocalIdx]);
	}
	return FinalIcons;
}

//  Note: this should only be called after verifying the upgrade can be applied successfully, there is no error checking here.
simulated function ApplyWeaponUpgradeTemplate(X2WeaponUpgradeTemplate UpgradeTemplate, optional int SlotIndex = -1)
{
	// If a specific slot was not provided or the slot is past any equipped upgrades, add it to the end of the upgrade list
	if (SlotIndex == -1 || SlotIndex >= m_arrWeaponUpgradeNames.Length)
	{
		m_arrWeaponUpgradeNames.AddItem(UpgradeTemplate.DataName);
		m_arrWeaponUpgradeTemplates.AddItem(UpgradeTemplate);
	}
	else // Otherwise replace the specific slot index
	{
		m_arrWeaponUpgradeNames[SlotIndex] = UpgradeTemplate.DataName;
		m_arrWeaponUpgradeTemplates[SlotIndex] = UpgradeTemplate;
	}

	//  adjust anything upgrades could affect
	Ammo = GetClipSize();
}

simulated function X2WeaponUpgradeTemplate DeleteWeaponUpgradeTemplate(int SlotIndex)
{
	local X2WeaponUpgradeTemplate UpgradeTemplate;

	// If an upgrade template exists at the slot index, delete it
	if (SlotIndex < m_arrWeaponUpgradeNames.Length && m_arrWeaponUpgradeNames[SlotIndex] != '')
	{
		UpgradeTemplate = m_arrWeaponUpgradeTemplates[SlotIndex];

		m_arrWeaponUpgradeNames[SlotIndex] = '';
		m_arrWeaponUpgradeTemplates[SlotIndex] = none;
	}

	return UpgradeTemplate;
}

simulated function WipeUpgradeTemplates()
{
	m_arrWeaponUpgradeNames.Length = 0;
	m_arrWeaponUpgradeTemplates.Length = 0;

	//  adjust anything upgrades could affect
	Ammo = GetClipSize();
}

simulated function bool HasBeenModified()
{
	//start issue #104: added check for whether a item gamestate has any attached component object ids. If so, don't put it in a stack since a mod has attached something to it.
	return Nickname != "" || GetMyWeaponUpgradeTemplateNames().Length > 0 || ComponentObjectIds.Length > 0;
	//end issue #104
}

simulated function bool IsStartingItem()
{
	return GetMyTemplate().StartingItem && !HasBeenModified();
}

simulated function bool HasLoadedAmmo()
{
	return LoadedAmmo.ObjectID != 0;
}

simulated native function X2ItemTemplate GetLoadedAmmoTemplate(const XComGameState_Ability AbilityState);

//If the template for this item is of type X2EquipmentTemplate or derived, this will return / load
//the archetypes for that template
simulated function Object GetGameArchetype(optional bool bAlt = false)
{
	local Object GameArchetype;
	local string strArchetype;

	GetMyTemplate();

	if( bAlt )
	{
		strArchetype = X2EquipmentTemplate(m_ItemTemplate).AltGameArchetype;
	}
	else
	{
		strArchetype = X2EquipmentTemplate(m_ItemTemplate).GameArchetype;
	}

	GameArchetype = `CONTENT.RequestGameArchetype(strArchetype);

	return GameArchetype;
}

simulated function array<WeaponAttachment> GetWeaponAttachments(optional bool bGetContent=true)
{
	local X2WeaponTemplate WeaponTemplate;
	local array<WeaponAttachment> Attachments;
	local array<X2WeaponUpgradeTemplate> UpgradeTemplates;
	local int i, j, k;
	local bool bReplaced;
	local delegate<X2TacticalGameRulesetDataStructures.CheckUpgradeStatus> ValidateAttachmentFn;
	local XComGameState_Unit OwnerState;

	WeaponTemplate = X2WeaponTemplate(GetMyTemplate());
	if (WeaponTemplate != none)
	{
		//  copy all default attachments from the weapon template
		for (i = 0; i < WeaponTemplate.DefaultAttachments.Length; ++i)
		{
			Attachments.AddItem(WeaponTemplate.DefaultAttachments[i]);
		}
		UpgradeTemplates = GetMyWeaponUpgradeTemplates();
		for (i = 0; i < UpgradeTemplates.Length; ++i)
		{
			for (j = 0; j < UpgradeTemplates[i].UpgradeAttachments.Length; ++j)
			{
				if (UpgradeTemplates[i].UpgradeAttachments[j].ApplyToWeaponTemplate != WeaponTemplate.DataName)
					continue;

				ValidateAttachmentFn = UpgradeTemplates[i].UpgradeAttachments[j].ValidateAttachmentFn;
				if( ValidateAttachmentFn != None && !ValidateAttachmentFn(UpgradeTemplates) )
				{
					continue;
				}

				bReplaced = false;
				//  look for sockets already known that an upgrade will replace
				for (k = 0; k < Attachments.Length; ++k)
				{
					if( Attachments[k].AttachSocket == UpgradeTemplates[i].UpgradeAttachments[j].AttachSocket )
					{
						Attachments[k].AttachMeshName = UpgradeTemplates[i].UpgradeAttachments[j].AttachMeshName;
						bReplaced = true;
						break;
					}
				}
				//  if not replacing an existing mesh, add the upgrade attachment as a new one
				if( !bReplaced )
				{
					Attachments.AddItem(UpgradeTemplates[i].UpgradeAttachments[j]);
				}
			}
		}
		if (bGetContent)
		{
			OwnerState = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(OwnerStateObject.ObjectID));
			for (i = 0; i < Attachments.Length; ++i)
			{
				if (Attachments[i].RequiredGender == eGender_None || (OwnerState != None && OwnerState.kAppearance.iGender == Attachments[i].RequiredGender))
				{
					// If the attachment is only valid for a specific gender, only attach it if the owner matches the gender
					Attachments[i].LoadedObject = `CONTENT.RequestGameArchetype(Attachments[i].AttachMeshName);
					Attachments[i].LoadedProjectileTemplate = `CONTENT.RequestGameArchetype(Attachments[i].AttachProjectileName);
				}
				else if (OwnerState == none && Attachments[i].RequiredGender == eGender_Female)
				{
					// The owner doesn't exist (character pool), so attach a version of the asset so something appears
					Attachments[i].LoadedObject = `CONTENT.RequestGameArchetype(Attachments[i].AttachMeshName);
					Attachments[i].LoadedProjectileTemplate = `CONTENT.RequestGameArchetype(Attachments[i].AttachProjectileName);
				}
			}
		}
	}
	return Attachments;
}

simulated function int GetClipSize()
{
	local XComGameStateHistory History;
	local XComGameState_Item SpecialAmmo;
	local int i, ClipSize, AdjustedClipSize;

	ClipSize = -1;
	GetMyTemplate();
	GetMyWeaponUpgradeTemplates();

	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
	{
		ClipSize = X2WeaponTemplate(m_ItemTemplate).iClipSize;
		History = `XCOMHISTORY;
		for (i = 0; i < m_arrWeaponUpgradeTemplates.Length; ++i)
		{
			if (m_arrWeaponUpgradeTemplates[i].AdjustClipSizeFn != none)
			{
				if (m_arrWeaponUpgradeTemplates[i].AdjustClipSizeFn(m_arrWeaponUpgradeTemplates[i], self, ClipSize, AdjustedClipSize))
					ClipSize = AdjustedClipSize;
			}
		}
		if (LoadedAmmo.ObjectID != 0)
		{
			SpecialAmmo = XComGameState_Item(History.GetGameStateForObjectID(LoadedAmmo.ObjectID));
			if (SpecialAmmo != none)
			{
				ClipSize += SpecialAmmo.GetClipSize();
			}
		}
	}
	else if (m_ItemTemplate.IsA('X2AmmoTemplate'))
	{
		ClipSize = X2AmmoTemplate(m_ItemTemplate).ModClipSize;
	}

	return ClipSize;
}

simulated function bool HasInfiniteAmmo()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).InfiniteAmmo;

	return false;
}

simulated function int GetItemSize()
{
	return GetMyTemplate().iItemSize;
}

simulated native function int GetItemRange(const XComGameState_Ability AbilityState);
simulated native function int GetItemRadius(const XComGameState_Ability AbilityState);
simulated native function float GetItemCoverage(const XComGameState_Ability AbilityState);

native function bool Validate(XComGameState HistoryGameState, INT GameStateIndex) const;

simulated function GetBaseWeaponDamageValue(XComGameState_BaseObject TargetObjectState, out WeaponDamageValue DamageValue)
{
	local X2WeaponTemplate WeaponTemplate;

	WeaponTemplate = X2WeaponTemplate(GetMyTemplate());
	if (WeaponTemplate != none)
	{
		DamageValue = WeaponTemplate.BaseDamage;
	}
}

simulated function GetWeaponDamageValue(XComGameState_BaseObject TargetObjectState, name Tag, out WeaponDamageValue DamageValue)
{
	local X2WeaponTemplate WeaponTemplate;
	local int i;

	WeaponTemplate = X2WeaponTemplate(GetMyTemplate());
	if (WeaponTemplate != none && Tag != '')
	{
		for (i = 0; i < WeaponTemplate.ExtraDamage.Length; ++i)
		{
			if (WeaponTemplate.ExtraDamage[i].Tag == Tag)
			{
				DamageValue = WeaponTemplate.ExtraDamage[i];
				return;
			}
		}
	}
}

simulated function int GetItemEnvironmentDamage()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).iEnvironmentDamage;

	return class'X2WeaponTemplate'.default.iEnvironmentDamage;
}

simulated function int GetItemSoundRange()
{
	local int iSoundRange;

	iSoundRange = class'X2WeaponTemplate'.default.iSoundRange;
	GetMyTemplate();

	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
	{
		iSoundRange = X2WeaponTemplate(m_ItemTemplate).iSoundRange;
	}

	return iSoundRange;
}

// Used only for stat displays. GetClipSize() is functional method for tactical.
simulated function int GetItemClipSize()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).iClipSize;

	return class'X2WeaponTemplate'.default.iClipSize;
}

simulated function int GetItemAimModifier()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).Aim;

	return class'X2WeaponTemplate'.default.Aim;
}

simulated function int GetItemCritChance()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).CritChance;

	return class'X2WeaponTemplate'.default.CritChance;
}

simulated function int GetItemPierceValue()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).BaseDamage.Pierce;

	return class'X2WeaponTemplate'.default.BaseDamage.Pierce;
}

simulated function bool SoundOriginatesFromOwnerLocation()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).bSoundOriginatesFromOwnerLocation;
	return true;
}

simulated function bool CanWeaponBeDodged()
{
	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return X2WeaponTemplate(m_ItemTemplate).bCanBeDodged;
	return true;
}

simulated function name GetWeaponCategory()
{
	local X2WeaponTemplate Template;

	Template = X2WeaponTemplate(GetMyTemplate());
	if (Template != none)
		return Template.WeaponCat;

	return '';
}

simulated function name GetWeaponTech()
{
	local X2WeaponTemplate Template;

	Template = X2WeaponTemplate(GetMyTemplate());
	if (Template != none)
		return Template.WeaponTech;

	return '';
}

simulated function array<string> GetWeaponPanelImages()
{
	local array<X2WeaponUpgradeTemplate> Upgrades;
	local X2WeaponUpgradeTemplate UpgradeTemplate;
	local array<WeaponAttachment> UpgradeAttachments;
	local array<string> Images;
	local X2WeaponTemplate WeaponTemplate;
	local int i;
	local int iUpgrade;
	local delegate<X2TacticalGameRulesetDataStructures.CheckUpgradeStatus> ValidateAttachmentFn;
	local bool bUpgradeImageFound;

	GetMyTemplate();
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
	{
		WeaponTemplate = X2WeaponTemplate(m_ItemTemplate);

		if (m_ItemTemplate.strImage != "")
			Images.AddItem(m_ItemTemplate.strImage);
		else if( X2WeaponTemplate(m_ItemTemplate).WeaponPanelImage != "" )
			Images.AddItem(X2WeaponTemplate(m_ItemTemplate).WeaponPanelImage);

		//First check all Upgrade Images to find valid ones
		Upgrades = GetMyWeaponUpgradeTemplates();
		for (iUpgrade = 0; iUpgrade < Upgrades.length; iUpgrade++)
		{
			UpgradeTemplate = Upgrades[iUpgrade];
			for (i = 0; i < UpgradeTemplate.UpgradeAttachments.length; i++)
			{
				ValidateAttachmentFn = UpgradeTemplate.UpgradeAttachments[i].ValidateAttachmentFn;
				if (ValidateAttachmentFn != None && !ValidateAttachmentFn(Upgrades))
				{
					continue;
				}

				if (UpgradeTemplate.UpgradeAttachments[i].ApplyToWeaponTemplate == GetMyTemplateName())
				{
					UpgradeAttachments.AddItem(UpgradeTemplate.UpgradeAttachments[i]);
					if (UpgradeTemplate.UpgradeAttachments[i].AttachIconName != "")
						Images.AddItem(UpgradeTemplate.UpgradeAttachments[i].AttachIconName);
				}
			}
		}

		//Cycle through base images
		for( i = 0; i < WeaponTemplate.DefaultAttachments.length; i++ )
		{
			bUpgradeImageFound = false;
			if (UpgradeAttachments.length > 0)
			{
				// Look for a replacement image among the attached upgrades
				for (iUpgrade = 0; iUpgrade < UpgradeAttachments.length; iUpgrade++)
				{
					if (UpgradeAttachments[iUpgrade].AttachSocket == WeaponTemplate.DefaultAttachments[i].AttachSocket)
					{
						bUpgradeImageFound = true;
						break;
					}
				}
			}

			// If an upgrade image has already been added for that slot, don't add it to the stack
			if (!bUpgradeImageFound && WeaponTemplate.DefaultAttachments[i].AttachIconName != "")
				Images.AddItem(WeaponTemplate.DefaultAttachments[i].AttachIconName);
		}
	}
	else
	{
		if (m_ItemTemplate.strImage != "")
			Images.AddItem(m_ItemTemplate.strImage);
		else if( X2WeaponTemplate(m_ItemTemplate).WeaponPanelImage != "" )
			Images.AddItem(X2WeaponTemplate(m_ItemTemplate).WeaponPanelImage);
	}

	return Images;
}

//Items can receive nicknames in certain situations.
function String GenerateNickname()
{
	local int iGenderChoice;
	local int iNumChoices, iChoice;
	local XComGameState_Unit OwnerState;
	local X2SoldierClassTemplate Template;

	OwnerState = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(OwnerStateObject.ObjectID));

	Template = OwnerState.GetSoldierClassTemplate();
	iNumChoices = Template.RandomNickNames.Length;

	//Randomly choose a gender
	iGenderChoice = `SYNC_RAND(2);
	if (iGenderChoice == 0)
	{
		iNumChoices += Template.RandomNickNames_Female.Length;
	}
	else
	{
		iNumChoices += Template.RandomNickNames_Male.Length;
	}

	iChoice = `SYNC_RAND(iNumChoices);

	if(iChoice < Template.RandomNickNames.Length)
	{
		return Template.RandomNickNames[iChoice];
	}
	else
	{
		iChoice -= Template.RandomNickNames.Length;
	}

	if (iGenderChoice == 0)
	{
		return Template.RandomNickNames_Female[iChoice];
	}
	else
	{
		return Template.RandomNickNames_Male[iChoice];
	}

	return "";
}

simulated function bool AllowsHeavyWeapon()
{
	local X2ArmorTemplate ArmorTemplate;

	ArmorTemplate = X2ArmorTemplate(GetMyTemplate());
	return ArmorTemplate != None && ArmorTemplate.bHeavyWeapon;
}

simulated function EUISummary_WeaponStats GetWeaponStatsForUI()
{
	local EUISummary_WeaponStats Summary;
	local int BaseClipSize;
	local WeaponDamageValue DamageValue;
	local array<X2WeaponUpgradeTemplate> WeaponUpgradeTemplates;
	local X2WeaponUpgradeTemplate UpgradeTemplate;

	GetMyTemplate();
	GetBaseWeaponDamageValue(none, DamageValue);

	Summary.Damage		= DamageValue.Damage;
	Summary.Crit		= GetItemCritChance();
	Summary.Aim			= GetItemAimModifier();
	Summary.ClipSize	= GetClipSize();
	Summary.Range       = GetItemRange(none);

	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		BaseClipSize = X2WeaponTemplate(m_ItemTemplate).iClipSize;
	Summary.bIsClipSizeModified = BaseClipSize != Summary.ClipSize;

	Summary.SpecialAmmo = GetLoadedAmmoTemplate(none);
	Summary.bIsAmmoModified = Summary.SpecialAmmo != none;

	WeaponUpgradeTemplates = GetMyWeaponUpgradeTemplates();
	foreach WeaponUpgradeTemplates(UpgradeTemplate)
	{
		if (UpgradeTemplate.AddHitChanceModifierFn != none)
			Summary.bIsAimModified = true;

		if (UpgradeTemplate.AddCritChanceModifierFn != none)
			Summary.bIsCritModified = true;

		//  Upgrades cannot modify damage, or range -jbouscher
		Summary.bIsDamageModified = false;
		Summary.bIsRangeModified = false;
	}

	return Summary;
}

simulated function array<EUISummary_WeaponUpgrade> GetWeaponUpgradesForTooltipUI()
{
	local array<X2WeaponUpgradeTemplate> Upgrades;
	local array<EUISummary_WeaponUpgrade> Entries;
	local EUISummary_WeaponUpgrade SummaryEntry;
	local int iUpgrade;

	Upgrades = GetMyWeaponUpgradeTemplates();

	for( iUpgrade = 0; iUpgrade < Upgrades.length; iUpgrade++ )
	{
		SummaryEntry.UpgradeTemplate = Upgrades[iUpgrade];

		//TODO: @gameplay: Add label/values for things that this upgrade modifies on the weapon.
		//foreach modifiedStats(stat) ...
		//{
			SummaryEntry.Labels.AddItem("STAT"); //TEMP
			SummaryEntry.Values.AddItem("-1");	 //TEMP
		//}

		Entries.AddItem(SummaryEntry);
	}

	return Entries;
}

simulated function string GetUpgradeEffectForUI(X2WeaponUpgradeTemplate UpgradeTemplate)
{
	local string StatModifiers;
	local EUISummary_WeaponStats UpgradeStats;

	if(UpgradeTemplate.TinySummary != "")
	{
		return UpgradeTemplate.TinySummary;
	}
	else
	{
		UpgradeStats = GetUpgradeModifiersForUI(UpgradeTemplate);

		if(UpgradeStats.bIsDamageModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.DamageLabel, UpgradeStats.Damage);
		if(UpgradeStats.bIsAimModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.AimLabel, UpgradeStats.Aim);
		if(UpgradeStats.bIsCritModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.CritChanceLabel, UpgradeStats.Crit);
		if(UpgradeStats.bIsClipSizeModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.ClipSizeLabel, UpgradeStats.ClipSize);
		if (UpgradeStats.bIsFreeFirePctModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.FreeFireLabel, UpgradeStats.FreeFirePct);
		if (UpgradeStats.bIsFreeFirePctModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.FreeReloadLabel, UpgradeStats.FreeReloads);
		if (UpgradeStats.bIsMissDamageModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.MissDamageLabel, UpgradeStats.MissDamage);
		if (UpgradeStats.bIsFreeKillPctModified)
			StatModifiers $= AddStatModifier(StatModifiers != "", class'XLocalizedData'.default.FreeKillLabel, UpgradeStats.FreeKillPct);

		return StatModifiers;
	}
}

simulated function string AddStatModifier(bool bAddCommaSeparator, string Label, int Value, optional int ColorState = eUIState_Normal, optional string PostFix, optional bool bSymbolOnRight)
{
	local string Result;
	if(bAddCommaSeparator) Result $= ", ";
	Result $= Label;
	if(bSymbolOnRight)
		Result @= Value $ (Value < 0 ? "-" : "+");
	else
		Result @= (Value < 0 ? "-" : "+") $ Value;
	Result = class'UIUtilities_Text'.static.GetColoredText(Result $ PostFix, ColorState);
	Result = class'UIUtilities_Text'.static.FormatCommaSeparatedNouns(Result);
	return Result;
}

simulated function EUISummary_WeaponStats GetUpgradeModifiersForUI(X2WeaponUpgradeTemplate UpgradeTemplate)
{
	local int i, tmp;
	local GameRulesCache_VisibilityInfo VisInfo; // Aim Upgrades require VisibilityInfo since the aim benefit changes based on current cover state of the target
	local EUISummary_WeaponStats TotalUpgradeSummary;
	local array<X2WeaponUpgradeTemplate> WeaponUpgrades;
	local XComGameState_HeadquartersXCom XComHQ;
	local StateObjectReference ObjectRef;
	local XComGameState_Tech BreakthroughTech;
	local X2TechTemplate TechTemplate;
	local X2AbilityTemplateManager AbilityTemplateMan;
	local X2AbilityTemplate AbilityTemplate;
	local X2Effect TargetEffect;
	local X2Effect_BonusWeaponDamage BonusDamage;

	WeaponUpgrades = GetMyWeaponUpgradeTemplates();

	if(UpgradeTemplate != none)
		WeaponUpgrades.AddItem(UpgradeTemplate);

	for(i = 0; i < WeaponUpgrades.Length; ++i)
	{
		UpgradeTemplate = WeaponUpgrades[i];

		//TODO: @gameplay: ensure all upgrade stat modifications are accounted for here
		if(UpgradeTemplate == none)
			continue;

		if(UpgradeTemplate.AddHitChanceModifierFn != none)
		{
			VisInfo.TargetCover = CT_MAX;
			TotalUpgradeSummary.bIsAimModified = true;
			UpgradeTemplate.AddHitChanceModifierFn(UpgradeTemplate, VisInfo, tmp); // we only want the modifier for max cover
			TotalUpgradeSummary.Aim = tmp;
		}
		if (UpgradeTemplate.AddCritChanceModifierFn != None)
		{
			TotalUpgradeSummary.bIsCritModified = true;
			UpgradeTemplate.AddCritChanceModifierFn(UpgradeTemplate, tmp);
			TotalUpgradeSummary.Crit = tmp;
		}
		if(UpgradeTemplate.AdjustClipSizeFn != none)
		{
			TotalUpgradeSummary.bIsClipSizeModified = true;
			UpgradeTemplate.AdjustClipSizeFn(UpgradeTemplate, self, 0, tmp); // we only want the modifier, so pass 0 for current
			TotalUpgradeSummary.ClipSize = tmp;
		}
		if (UpgradeTemplate.FreeFireChance > 0)
		{
			TotalUpgradeSummary.bIsFreeFirePctModified = true;
			TotalUpgradeSummary.FreeFirePct = UpgradeTemplate.GetBonusAmountFn(UpgradeTemplate);
		}
		if (UpgradeTemplate.NumFreeReloads > 0)
		{
			TotalUpgradeSummary.bIsFreeReloadsModified = true;
			TotalUpgradeSummary.FreeReloads = UpgradeTemplate.GetBonusAmountFn(UpgradeTemplate);
		}
		if (UpgradeTemplate.BonusDamage.Damage > 0)
		{
			TotalUpgradeSummary.bIsMissDamageModified = true;
			TotalUpgradeSummary.MissDamage = UpgradeTemplate.GetBonusAmountFn(UpgradeTemplate);
		}
		if (UpgradeTemplate.FreeKillChance > 0)
		{
			TotalUpgradeSummary.bIsFreeKillPctModified = true;
			TotalUpgradeSummary.FreeKillPct = UpgradeTemplate.GetBonusAmountFn(UpgradeTemplate);
		}
	}

	XComHQ = XComGameState_HeadquartersXCom( `XCOMHISTORY.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom', true ) );
	if (XComHQ != none)
	{
		AbilityTemplateMan = class'X2AbilityTemplateManager'.static.GetAbilityTemplateManager();

		foreach XComHQ.TacticalTechBreakthroughs(ObjectRef)
		{
			BreakthroughTech = XComGameState_Tech(`XCOMHISTORY.GetGameStateForObjectID(ObjectRef.ObjectID));
			TechTemplate = BreakthroughTech.GetMyTemplate();

			if (TechTemplate.BreakthroughCondition != none && TechTemplate.BreakthroughCondition.MeetsCondition(self))
			{
				AbilityTemplate = AbilityTemplateMan.FindAbilityTemplate( TechTemplate.RewardName );
				foreach AbilityTemplate.AbilityTargetEffects( TargetEffect )
				{
					BonusDamage = X2Effect_BonusWeaponDamage( TargetEffect );
					if (BonusDamage != none)
					{
						TotalUpgradeSummary.bIsDamageModified = true;
						TotalUpgradeSummary.Damage += BonusDamage.BonusDmg;
					}
				}
			}
		}
	}

	return TotalUpgradeSummary;
}

simulated function array<UISummary_ItemStat> GetUISummary_ItemBasicStats()
{
	local array<UISummary_ItemStat> Result;

	// TODO: @gameplay: Other stat functions and types
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
	{
		Result = GetUISummary_WeaponStats();
	}
	else
	{
		Result = GetUISummary_DefaultStats();
	}

	return Result;
}

simulated function array<UISummary_ItemStat> GetUISummary_ItemSecondaryStats()
{
	// TODO: @gameplay: Other stat functions and types
	if (m_ItemTemplate.IsA('X2WeaponTemplate'))
		return GetUISummary_WeaponUpgradeStats();
	else
		return GetUISummary_DefaultStats();
}

simulated function array<UISummary_ItemStat> GetUISummary_DefaultStats()
{
	local XComGameState_HeadquartersXCom XComHQ;
	local XComGameState_Tech BreakthroughTech;
	local X2AbilityTemplateManager AbilityTemplateMan;
	local StateObjectReference ObjectRef;
	local X2TechTemplate TechTemplate;
	local X2AbilityTemplate AbilityTemplate;
	local X2Effect TargetEffect;
	local array<X2Effect_PersistentStatChange> StatChangeEffects;
	local X2Effect_PersistentStatChange StatChangeEffect;
	local StatChange Change;
	local array<UISummary_ItemStat> Stats;
	local UISummary_ItemStat		Item;
	local int Index, idx;
	local X2EquipmentTemplate EquipmentTemplate;
	local delegate<X2StrategyGameRulesetDataStructures.SpecialRequirementsDelegate> ShouldStatDisplayFn;

	EquipmentTemplate = X2EquipmentTemplate(m_ItemTemplate);

	if( EquipmentTemplate != None )
	{
		// Search XComHQ for any breakthrough techs which modify the stats on this item, and store those stat changes
		XComHQ = XComGameState_HeadquartersXCom(`XCOMHISTORY.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersXCom'));
		if (XComHQ != none)
		{
			AbilityTemplateMan = class'X2AbilityTemplateManager'.static.GetAbilityTemplateManager();

			foreach XComHQ.TacticalTechBreakthroughs(ObjectRef)
			{
				BreakthroughTech = XComGameState_Tech(`XCOMHISTORY.GetGameStateForObjectID(ObjectRef.ObjectID));
				TechTemplate = BreakthroughTech.GetMyTemplate();

				if (TechTemplate.BreakthroughCondition != none && TechTemplate.BreakthroughCondition.MeetsCondition(self))
				{
					AbilityTemplate = AbilityTemplateMan.FindAbilityTemplate(TechTemplate.RewardName);
					foreach AbilityTemplate.AbilityTargetEffects(TargetEffect)
					{
						StatChangeEffect = X2Effect_PersistentStatChange(TargetEffect);
						if (StatChangeEffect != none)
						{
							StatChangeEffects.AddItem(StatChangeEffect);
						}
					}
				}
			}
		}

		for( Index = 0; Index < EquipmentTemplate.UIStatMarkups.Length; ++Index )
		{
			ShouldStatDisplayFn = EquipmentTemplate.UIStatMarkups[Index].ShouldStatDisplayFn;
			if (ShouldStatDisplayFn != None && !ShouldStatDisplayFn())
			{
				continue;
			}

			if( EquipmentTemplate.UIStatMarkups[Index].StatModifier != 0 || EquipmentTemplate.UIStatMarkups[Index].bForceShow )
			{
				// Start with the value from the stat markup
				Item.Label = EquipmentTemplate.UIStatMarkups[Index].StatLabel;
				Item.Value = string(EquipmentTemplate.UIStatMarkups[Index].StatModifier);

				// Then check all of the stat change effects from techs and add any appropriate modifiers
				foreach StatChangeEffects(StatChangeEffect)
				{
					for (idx = 0; idx < StatChangeEffect.m_aStatChanges.Length; ++idx)
					{
						Change = StatChangeEffect.m_aStatChanges[idx];
						if ((Change.StatType == EquipmentTemplate.UIStatMarkups[idx].StatType) && (Change.ModOp == MODOP_Addition))
						{
							Item.Value $= AddStatModifier(false, "", Change.StatAmount, eUIState_Good);
						}
					}
				}

				Stats.AddItem(Item);
			}
		}
	}

	return Stats;
}
simulated function array<UISummary_ItemStat> GetUISummary_WeaponStats(optional X2WeaponUpgradeTemplate PreviewUpgradeStats)
{
	local array<UISummary_ItemStat> Stats;
	local UISummary_ItemStat		Item;
	local UIStatMarkup				StatMarkup;
	local WeaponDamageValue         DamageValue;
	local EUISummary_WeaponStats    UpgradeStats;
	local X2WeaponTemplate WeaponTemplate;
	local delegate<X2StrategyGameRulesetDataStructures.SpecialRequirementsDelegate> ShouldStatDisplayFn;
	local int Index;

	// Safety check: you need to be a weapon to use this.
	WeaponTemplate = X2WeaponTemplate(m_ItemTemplate);
	if( WeaponTemplate == none )
		return Stats;

	if(PreviewUpgradeStats != none)
		UpgradeStats = GetUpgradeModifiersForUI(PreviewUpgradeStats);
	else
		UpgradeStats = GetUpgradeModifiersForUI(X2WeaponUpgradeTemplate(m_ItemTemplate));

	// Damage-----------------------------------------------------------------------
	if (!WeaponTemplate.bHideDamageStat)
	{
		Item.Label = class'XLocalizedData'.default.DamageLabel;
		GetBaseWeaponDamageValue(none, DamageValue);
		if (DamageValue.Damage == 0 && UpgradeStats.bIsDamageModified)
		{
			Item.Value = AddStatModifier(false, "", UpgradeStats.Damage, eUIState_Good);
			Stats.AddItem(Item);
		}
		else if (DamageValue.Damage > 0)
		{
			if (DamageValue.Spread > 0 || DamageValue.PlusOne > 0)
				Item.Value = string(DamageValue.Damage - DamageValue.Spread) $ "-" $ string(DamageValue.Damage + DamageValue.Spread + (DamageValue.PlusOne > 0) ? 1 : 0);
			else
				Item.Value = string(DamageValue.Damage);

			if (UpgradeStats.bIsDamageModified)
				Item.Value $= AddStatModifier(false, "", UpgradeStats.Damage, eUIState_Good);
			Stats.AddItem(Item);
		}
	}
	//TODO: Item.ValueState = bIsDamageModified ? eUIState_Good : eUIState_Normal;

	// Clip Size --------------------------------------------------------------------
	if (m_ItemTemplate.ItemCat == 'weapon' && !WeaponTemplate.bHideClipSizeStat)
	{
		Item.Label = class'XLocalizedData'.default.ClipSizeLabel;
		if (PopulateWeaponStat(GetItemClipSize(), UpgradeStats.bIsClipSizeModified, UpgradeStats.ClipSize, Item))
			Stats.AddItem(Item);
	}

	// Crit -------------------------------------------------------------------------
	Item.Label = class'XLocalizedData'.default.CriticalChanceLabel;
	if (PopulateWeaponStat(GetItemCritChance(), UpgradeStats.bIsCritModified, UpgradeStats.Crit, Item, true))
		Stats.AddItem(Item);

	// Ensure that any items which are excluded from stat boosts show values that show up in the Soldier Header
	if (class'UISoldierHeader'.default.EquipmentExcludedFromStatBoosts.Find(m_ItemTemplate.DataName) == INDEX_NONE)
	{
		// Aim -------------------------------------------------------------------------
		Item.Label = class'XLocalizedData'.default.AimLabel;
		if (PopulateWeaponStat(GetItemAimModifier(), UpgradeStats.bIsAimModified, UpgradeStats.Aim, Item, true))
			Stats.AddItem(Item);
	}

	// Free Fire
	Item.Label = class'XLocalizedData'.default.FreeFireLabel;
	if (PopulateWeaponStat(0, UpgradeStats.bIsFreeFirePctModified, UpgradeStats.FreeFirePct, Item, true))
		Stats.AddItem(Item);

	// Free Reloads
	Item.Label = class'XLocalizedData'.default.FreeReloadLabel;
	if (PopulateWeaponStat(0, UpgradeStats.bIsFreeReloadsModified, UpgradeStats.FreeReloads, Item))
		Stats.AddItem(Item);

	// Miss Damage
	Item.Label = class'XLocalizedData'.default.MissDamageLabel;
	if (PopulateWeaponStat(0, UpgradeStats.bIsMissDamageModified, UpgradeStats.MissDamage, Item))
		Stats.AddItem(Item);

	// Free Kill
	Item.Label = class'XLocalizedData'.default.FreeKillLabel;
	if (PopulateWeaponStat(0, UpgradeStats.bIsFreeKillPctModified, UpgradeStats.FreeKillPct, Item, true))
		Stats.AddItem(Item);

	// Add any extra stats and benefits
	for (Index = 0; Index < WeaponTemplate.UIStatMarkups.Length; ++Index)
	{
		StatMarkup = WeaponTemplate.UIStatMarkups[Index];
		ShouldStatDisplayFn = StatMarkup.ShouldStatDisplayFn;
		if (ShouldStatDisplayFn != None && !ShouldStatDisplayFn())
		{
			continue;
		}

		if (StatMarkup.StatModifier != 0 || StatMarkup.bForceShow)
		{
			Item.Label = StatMarkup.StatLabel;
			Item.Value = string(StatMarkup.StatModifier) $ StatMarkup.StatUnit;
			Stats.AddItem(Item);
		}
	}

	return Stats;
}

simulated function bool PopulateWeaponStat(int Value, bool bIsStatModified, int UpgradeValue, out UISummary_ItemStat Item, optional bool bIsPercent)
{
	if (Value > 0)
	{
		if (bIsStatModified)
		{
			Item.Value = AddStatModifier(false, "", UpgradeValue, eUIState_Good, "", true);
			Item.Value $= string(Value) $ (bIsPercent ? "%" : "");
		}
		else
		{
			Item.Value = string(Value) $ (bIsPercent ? "%" : "");
		}
		return true;
	}
	else if (bIsStatModified)
	{
		Item.Value = AddStatModifier(false, "", UpgradeValue, eUIState_Good, (bIsPercent ? "%" : ""), false);
		return true;
	}

	return false;
}

simulated function array<UISummary_ItemStat> GetUISummary_AmmoStats()
{
	local X2ItemTemplate			SpecialAmmo;
	local array<UISummary_ItemStat> Stats;
	local UISummary_ItemStat		Item;

	// Safety check: you need to be a weapon to use this.
	if( !m_ItemTemplate.IsA('X2WeaponTemplate') ) return Stats;

	// Ammo Type ----------------------------------------------------------------
	SpecialAmmo = GetLoadedAmmoTemplate(none);

	if( SpecialAmmo != none )
	{
		Item.Label = class'XLocalizedData'.default.AmmoTypeHeader;
		Item.LabelStyle = eUITextStyle_Tooltip_H1;
		Stats.AddItem(Item);

		Item.Label = SpecialAmmo.GetItemFriendlyName();
		Item.LabelStyle = eUITextStyle_Tooltip_H2;

		Item.Value = SpecialAmmo.GetItemBriefSummary();
		Item.ValueStyle = eUITextStyle_Tooltip_Body;

		Stats.AddItem(Item);
	}
	// -------------------------------------------------------------------------

	return Stats;
}

simulated function array<UISummary_ItemStat> GetUISummary_WeaponUpgradeStats()
{
	local array<X2WeaponUpgradeTemplate> Upgrades;
	local X2WeaponUpgradeTemplate UpgradeTemplate;
	local array<UISummary_ItemStat> Stats;
	local UISummary_ItemStat Item;
	local int iUpgrade;

	Upgrades = GetMyWeaponUpgradeTemplates();

	if( Upgrades.length > 0 )
	{
		Item.Label = class'XLocalizedData'.default.UpgradesHeader;
		Item.LabelStyle = eUITextStyle_Tooltip_H1;
		Stats.AddItem(Item);
	}

	for( iUpgrade = 0; iUpgrade < Upgrades.length; iUpgrade++ )
	{
		UpgradeTemplate = Upgrades[iUpgrade];

		Item.Label = UpgradeTemplate.GetItemFriendlyName();
		Item.LabelStyle = eUITextStyle_Tooltip_H2;

		Item.Value = GetUpgradeEffectForUI(UpgradeTemplate);
		Item.ValueStyle = eUITextStyle_Tooltip_Body;

		Stats.AddItem(Item);
	}

	return Stats;
}

simulated function array<UISummary_TacaticalText> GetUISummary_TacticalTextAbilities()
{
	local bool bIsIn3D;
	local X2EquipmentTemplate       EquipmentTemplate;
	local X2AbilityTemplateManager  AbilityTemplateManager;
	local X2AbilityTemplate         AbilityTemplate;
	local name                      AbilityName;
	local UISummary_Ability        UISummaryAbility;
	local UISummary_TacaticalText  Data;
	local array<UISummary_TacaticalText> Items;

	EquipmentTemplate = X2EquipmentTemplate(m_ItemTemplate);
	if( EquipmentTemplate == none ) return Items;  //Empty.

	bIsIn3D = `SCREENSTACK.GetCurrentScreen().bIsIn3D;

	AbilityTemplateManager = class'X2AbilityTemplateManager'.static.GetAbilityTemplateManager();
	foreach EquipmentTemplate.Abilities(AbilityName)
	{
		AbilityTemplate = AbilityTemplateManager.FindAbilityTemplate(AbilityName);
		if( AbilityTemplate != none  && AbilityTemplate.bDisplayInUITacticalText )
		{
			UISummaryAbility = AbilityTemplate.GetUISummary_Ability();
			Data.Name = class'UIUtilities_Text'.static.AddFontInfo(UISummaryAbility.Name, bIsIn3D, true, true);
			Data.Description = class'UIUtilities_Text'.static.AddFontInfo(UISummaryAbility.Description, bIsIn3D, false);
			Data.Icon = UISummaryAbility.Icon;
			Items.AddItem(Data);
		}
	}

	return Items;
}

simulated function array<UISummary_TacaticalText> GetUISummary_TacticalTextUpgrades()
{
	local array<X2WeaponUpgradeTemplate> Upgrades;
	local X2WeaponUpgradeTemplate UpgradeTemplate;
	local UISummary_TacaticalText Data;
	local array<UISummary_TacaticalText> Items;
	local int iUpgrade;
	local array<string> UpgradeIcons;

	Upgrades = GetMyWeaponUpgradeTemplates();

	for( iUpgrade = 0; iUpgrade < Upgrades.length; iUpgrade++ )
	{
		UpgradeTemplate = Upgrades[iUpgrade];

		Data.Name = UpgradeTemplate.GetItemFriendlyName();
		Data.Description = UpgradeTemplate.GetItemBriefSummary();

		UpgradeIcons = UpgradeTemplate.GetAttachmentInventoryCategoryImages(self);
		if( UpgradeIcons.length > 0 )
			Data.Icon = UpgradeIcons[0];
		else
			Data.Icon = "";

		Items.AddItem(Data);
	}

	return Items;
}

simulated function array<UISummary_TacaticalText> GetUISummary_TacticalText()
{
	local bool bIsIn3D;
	local int FontSize;
	local string TacticalText;
	local EUIState ColorState;
	local UISummary_TacaticalText Data;
	local array<UISummary_TacaticalText> Items;

	ColorState = eUIState_Normal;
	bIsIn3D = `SCREENSTACK.GetCurrentScreen().bIsIn3D;
	FontSize = bIsIn3D ? class'UIUtilities_Text'.const.BODY_FONT_SIZE_3D : class'UIUtilities_Text'.const.BODY_FONT_SIZE_2D;

	if(GetMyTemplate() != none)
		TacticalText = GetMyTemplate().GetItemTacticalText();

	if( TacticalText == "" )
	{
		ColorState = eUIState_Bad;
		TacticalText = "DEBUG: @Design: Missing TacticalText in '" $ GetMyTemplateName() $ "' template.";
	}

	Data.Description = class'UIUtilities_Text'.static.GetColoredText(TacticalText, ColorState, FontSize);
	Items.AddItem(Data);

	return Items;
}

//---------------------------------------------------------------------------------------
function OnItemBuilt(XComGameState NewGameState)
{
	if (GetMyTemplate().OnBuiltFn != none)
	{
		GetMyTemplate().OnBuiltFn(NewGameState, self);
	}
}

//---------------------------------------------------------------------------------------
function bool IsNeededForGoldenPath()
{
	local XComGameStateHistory History;
	local XComGameState_HeadquartersXCom XComHQ;
	local XComGameState_Objective ObjectiveState;
	local XComGameState_Tech TechState;
	local X2ObjectiveTemplate ObjTemplate;
	local name TechName;

	History = `XCOMHISTORY;
	XComHQ = XComGameState_HeadquartersXCom(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersXCom'));

	foreach History.IterateByClassType(class'XComGameState_Objective', ObjectiveState)
	{
		if(ObjectiveState.ObjState < eObjectiveState_InProgress)
		{
			ObjTemplate = ObjectiveState.GetMyTemplate();
			if (ObjTemplate != none)
			{
				if (ObjTemplate.AssignmentRequirements.RequiredItems.Find(GetMyTemplateName()) != INDEX_NONE)
				{
					return true;
				}

				foreach ObjTemplate.AssignmentRequirements.RequiredTechs(TechName)
				{
					foreach History.IterateByClassType(class'XComGameState_Tech', TechState)
					{
						if (!XComHQ.TechIsResearched(TechState.GetReference()) && TechState.GetMyTemplateName() == TechName &&
							TechState.GetMyTemplate().Requirements.RequiredItems.Find(GetMyTemplateName()) != INDEX_NONE)
						{
							return true;
						}
					}
				}
			}
		}

		if(ObjectiveState.ObjState < eObjectiveState_Completed)
		{
			ObjTemplate = ObjectiveState.GetMyTemplate();
			if (ObjTemplate != none)
			{
				if (ObjTemplate.CompletionRequirements.RequiredItems.Find(GetMyTemplateName()) != INDEX_NONE)
				{
					return true;
				}

				foreach ObjTemplate.CompletionRequirements.RequiredTechs(TechName)
				{
					foreach History.IterateByClassType(class'XComGameState_Tech', TechState)
					{
						if (!XComHQ.TechIsResearched(TechState.GetReference()) && TechState.GetMyTemplateName() == TechName &&
							TechState.GetMyTemplate().Requirements.RequiredItems.Find(GetMyTemplateName()) != INDEX_NONE)
						{
							return true;
						}
					}
				}
			}
		}
	}

	return false;
}

static function FilterOutGoldenPathItems(out array<StateObjectReference> ItemsToFilter)
{
	local XComGameStateHistory History;
	local XComGameState_HeadquartersXCom XComHQ;
	local XComGameState_Objective ObjectiveState;
	local XComGameState_Tech TechState;
	local X2ObjectiveTemplate ObjTemplate;
	local name TechName;

	local name ItemTemplateName;
	local array<name> ItemTemplatesToFilter;

	local int i;
	local XComGameState_Item ItemState;

	History = `XCOMHISTORY;
	XComHQ = XComGameState_HeadquartersXCom(History.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersXCom'));

	foreach History.IterateByClassType(class'XComGameState_Objective', ObjectiveState)
	{
		if(ObjectiveState.ObjState < eObjectiveState_InProgress)
		{
			ObjTemplate = ObjectiveState.GetMyTemplate();
			if (ObjTemplate != none)
			{
				foreach ObjTemplate.AssignmentRequirements.RequiredItems(ItemTemplateName)
				{
					if(ItemTemplatesToFilter.Find(ItemTemplateName) == INDEX_NONE)
						ItemTemplatesToFilter.AddItem(ItemTemplateName);
				}

				foreach ObjTemplate.AssignmentRequirements.RequiredTechs(TechName)
				{
					foreach History.IterateByClassType(class'XComGameState_Tech', TechState)
					{
						if (!XComHQ.TechIsResearched(TechState.GetReference()) && TechState.GetMyTemplateName() == TechName)
						{
							foreach TechState.GetMyTemplate().Requirements.RequiredItems(ItemTemplateName)
							{
								if(ItemTemplatesToFilter.Find(ItemTemplateName) == INDEX_NONE)
									ItemTemplatesToFilter.AddItem(ItemTemplateName);
							}
						}
					}
				}
			}
		}

		if(ObjectiveState.ObjState < eObjectiveState_Completed)
		{
			ObjTemplate = ObjectiveState.GetMyTemplate();
			if (ObjTemplate != none)
			{
				foreach ObjTemplate.CompletionRequirements.RequiredItems(ItemTemplateName)
				{
					if(ItemTemplatesToFilter.Find(ItemTemplateName) == INDEX_NONE)
						ItemTemplatesToFilter.AddItem(ItemTemplateName);
				}

				foreach ObjTemplate.CompletionRequirements.RequiredTechs(TechName)
				{
					foreach History.IterateByClassType(class'XComGameState_Tech', TechState)
					{
						if (!XComHQ.TechIsResearched(TechState.GetReference()) && TechState.GetMyTemplateName() == TechName)
						{
							foreach TechState.GetMyTemplate().Requirements.RequiredItems(ItemTemplateName)
							{
								if(ItemTemplatesToFilter.Find(ItemTemplateName) == INDEX_NONE)
									ItemTemplatesToFilter.AddItem(ItemTemplateName);
							}
						}
					}
				}
			}
		}
	}

	for(i = ItemsToFilter.Length - 1; i >= 0; i--)
	{
		ItemState = XComGameState_Item(History.GetGameStateForObjectID(ItemsToFilter[i].ObjectID));
		if(ItemTemplatesToFilter.Find(ItemState.GetMyTemplateName()) != INDEX_NONE)
			ItemsToFilter.Remove(i, 1);
	}
}

function bool ShouldDisplayWeaponAndAmmo()
{
	local X2WeaponTemplate WeaponTemplate;
	local bool bDisplayWeaponAndAmmo;

	bDisplayWeaponAndAmmo = false;
	WeaponTemplate = X2WeaponTemplate(m_ItemTemplate);

	if( WeaponTemplate != None )
	{
		bDisplayWeaponAndAmmo = WeaponTemplate.bDisplayWeaponAndAmmo;
	}

	return bDisplayWeaponAndAmmo;
}

function bool IsMissionObjectiveItem()
{
	local X2ItemTemplate ItemTemplate;

	ItemTemplate = GetMyTemplate();

	if (ItemTemplate.ItemCat == 'goldenpath' || ItemTemplate.ItemCat == 'quest' || (ItemTemplate.IsObjectiveItemFn != none && ItemTemplate.IsObjectiveItemFn()))
		return true;

	return false;
}

DefaultProperties
{
	Quantity=1
}

error: pathspec 'X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameState_Item.uc' did not match any file(s) known to git


Unprotect X2SoldierClassTemplate::SoldierRanks.

//---------------------------------------------------------------------------------------
//  *********   FIRAXIS SOURCE CODE   ******************
//  FILE:    X2SoldierClassTemplate.uc
//  AUTHOR:  Timothy Talley  --  01/18/2014
//---------------------------------------------------------------------------------------
//  Copyright (c) 2014 Firaxis Games Inc. All rights reserved.
//---------------------------------------------------------------------------------------

class X2SoldierClassTemplate extends X2DataTemplate
	dependson(X2TacticalGameRulesetDataStructures)
	config(ClassData)
	native(Core);

var config /*protected*/ array<SoldierClassRank> SoldierRanks; // Issue #1005 - unprotected
var config array<SoldierClassWeaponType>    AllowedWeapons;
var config array<name>						AllowedArmors;
var config array<name>                      ExcludedAbilities;  //  Abilities that are not eligible to roll from AWC for this calss
var config array<name>						AcceptedCharacterTemplates;		//	Some classes may be limited to specific templates, not just those marked bIsSoldier
var config name					SquaddieLoadout;
var config string				IconImage;
var config int					NumInForcedDeck;
var config int					NumInDeck;
var config int					ClassPoints;    // Number of "points" associated with using this class type, i.e. Multiplayer or Daily Challenge
var config int                  KillAssistsPerKill;     //  Number of kill assists that count as a kill for ranking up
var config int                  PsiCreditsPerKill;      //  Number of psi credits that count as a kill for ranking up
var config bool					bAllowAWCAbilities; // If this class should receive or share AWC abilities
var config bool					bUniqueTacticalToStrategyTransfer; // If this class has unique tactical to strategy transfer code, used for DLC and modding
var config bool					bIgnoreInjuries; // This class can go on missions even if they are wounded
var config bool					bBlockRankingUp; // Do not let soldiers of this class rank up in the normal way from XP
var config bool					bNoSecondaryWeapon;
var config array<EInventorySlot> CannotEditSlots; // Slots which cannot be edited in the armory loadout
var config protectedwrite bool  bMultiplayerOnly;
var config protectedwrite bool	bHideInCharacterPool;
var config name					RequiredCharacterClass; // Used in the character pool if this class has a required character template
var config bool					bHasClassMovie; // Does this class show a class movie upon first time getting a unit of this class
var config bool					bCanHaveBonds; // Can this class type have bonds with other soldiers and engage with that system
var config array<name>			FavoredClasses; // Will usually have higher compatibility with these classes in the bond system
var config array<name>			UnfavoredClasses; // Will usually have lower compatibility with these classes in the bond system
var config float				ForcedCompatibiliy; // If greater than zero, this is used as compatibility score with all other soldiers (if you want to force zero set bCanHaveBonds to false)
var config array<SoldierClassRandomAbilityDeck> RandomAbilityDecks; // Needed if slots in the ability tree pick a random ability
var config float				MinSuperConcealedDistance;	//	used by GetConcealmentDetectionDistance
var config int					BaseAbilityPointsPerPromotion; // The amount of ability points granted each time a unit of this class ranks up
var config int					AbilityPointsIncrementPerPromotion; // The amount a soldiers ability point reward is incremented each successive time they rank up

var localized string			DisplayName;
var localized string			ClassSummary;
var localized string			LeftAbilityTreeTitle;	//  don't use for all future classes
var localized string			RightAbilityTreeTitle;  //  don't use for all future classes
var localized array<string>		AbilityTreeTitles;		//  need one for each tree (base game classes have 2, xpack classes have 4)
var localized array<string>		RankNames;				//  there should be one name for each rank; e.g. Rookie, Squaddie, etc.
var localized array<string>		ShortNames;				//  the abbreviated rank name; e.g. Rk., Sq., etc.
var localized array<string>		RankIcons;				//  strings of image names for specialized rank icons
var localized array<String>     RandomNickNames;        //  Selected randomly when the soldier hits a certain rank, if the player has not set one already.
var localized array<String>     RandomNickNames_Female; //  Female only nicknames.
var localized array<String>     RandomNickNames_Male;   //  Male only nicknames.
var localized array<String>     PhotoboothSoloBLines_Female; //  Female only photobooth solo lines.
var localized array<String>     PhotoboothSoloBLines_Male;   //  Male only photobooth solo lines.

var delegate<CheckSpecialGuaranteedHitDelegate> CheckSpecialGuaranteedHitFn;
var delegate<CheckSpecialCritLabelDelegate> CheckSpecialCritLabelFn;
var delegate<GetTargetingMethodPostProcessDelegate> GetTargetingMethodPostProcessFn;

delegate name CheckSpecialGuaranteedHitDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState);
delegate string CheckSpecialCritLabelDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState);
delegate bool GetTargetingMethodPostProcessDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState, out name EnabledPostProcess, out name DisabledPostProcess);

function int GetMaxConfiguredRank()
{
	return SoldierRanks.Length;
}

function array<SoldierClassAbilitySlot> GetAbilitySlots(int Rank)
{
	if(Rank < 0 || Rank >= SoldierRanks.Length)
	{
		`RedScreen(string(GetFuncName()) @ "called with invalid Rank" @ Rank @ "for template" @ DataName @ DisplayName);
		return SoldierRanks[0].AbilitySlots;
	}

	return SoldierRanks[Rank].AbilitySlots;
}

function array<SoldierClassStatType> GetStatProgression(int Rank)
{
	if (Rank < 0 || Rank >= SoldierRanks.Length)
	{
		`RedScreen(string(GetFuncName()) @ "called with invalid Rank" @ Rank @ "for template" @ DataName @ DisplayName);
		return SoldierRanks[0].aStatProgression;
	}
	return SoldierRanks[Rank].aStatProgression;
}

function array<SoldierClassAbilityType> GetAllPossibleAbilities()
{
	local array<SoldierClassAbilityType> AllPossibleAbilities;
	local int DeckIndex, AbIndex, RankIndex, SlotIndex;

	AllPossibleAbilities.Length = 0;

	// Grab all abilities from the normal slots
	for(RankIndex = 0; RankIndex < SoldierRanks.Length; RankIndex++)
	{
		for(SlotIndex = 0; SlotIndex < SoldierRanks[RankIndex].AbilitySlots.Length; SlotIndex++)
		{
			if(SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType.AbilityName != '' &&
			   AllPossibleAbilities.Find('AbilityName', SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType.AbilityName) == INDEX_NONE)
			{
				AllPossibleAbilities.AddItem(SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType);
			}
		}
	}

	// Grab all abilities from random decks
	for(DeckIndex = 0; DeckIndex < RandomAbilityDecks.Length; DeckIndex++)
	{
		for(AbIndex = 0; AbIndex < RandomAbilityDecks[DeckIndex].Abilities.Length; AbIndex++)
		{
			if(RandomAbilityDecks[DeckIndex].Abilities[AbIndex].AbilityName != '' &&
			   AllPossibleAbilities.Find('AbilityName', RandomAbilityDecks[DeckIndex].Abilities[AbIndex].AbilityName) == INDEX_NONE)
			{
				AllPossibleAbilities.AddItem(RandomAbilityDecks[DeckIndex].Abilities[AbIndex]);
			}
		}
	}

	return AllPossibleAbilities;
}

function bool IsWeaponAllowedByClass(X2WeaponTemplate WeaponTemplate)
{
	local int i;

	switch(WeaponTemplate.InventorySlot)
	{
	case eInvSlot_PrimaryWeapon: break;
	case eInvSlot_SecondaryWeapon: break;
	default:
		return true;
	}

	for (i = 0; i < AllowedWeapons.Length; ++i)
	{
		if (WeaponTemplate.InventorySlot == AllowedWeapons[i].SlotType &&
			WeaponTemplate.WeaponCat == AllowedWeapons[i].WeaponType)
			return true;
	}
	return false;
}

function bool IsArmorAllowedByClass(X2ArmorTemplate ArmorTemplate)
{
	local int i;

	switch (ArmorTemplate.InventorySlot)
	{
	case eInvSlot_Armor: break;
	default:
		return true;
	}

	for (i = 0; i < AllowedArmors.Length; ++i)
	{
		if (ArmorTemplate.ArmorCat == AllowedArmors[i])
			return true;
	}
	return false;
}

function string X2SoldierClassTemplate_ToString()
{
	local string str;
	local int rankIdx, subIdx;

	str = " X2SoldierClassTemplate:" @ `ShowVar(DataName) @ `ShowVar(SoldierRanks.Length, 'Num Ranks') @ `ShowVar(SquaddieLoadout) @ `ShowVar(AllowedWeapons.Length, 'Weapons') $ "\n";
	for(subIdx = 0; subIdx < AllowedWeapons.Length; ++subIdx)
	{
		str $= "        Weapon Type(" $ subIdx $ ") - " $ `ShowVar(AllowedWeapons[subIdx].WeaponType, 'Weapon Type') @ `ShowVar(AllowedWeapons[subIdx].SlotType, 'Slot Type') $ "\n";
	}
	for(rankIdx = 0; rankIdx < SoldierRanks.Length; ++rankIdx)
	{
		str $= "    Rank(" $ rankIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].AbilitySlots.Length, 'Abilities') @ `ShowVar(SoldierRanks[rankIdx].aStatProgression.Length, 'StatProgressions') $ "\n";
		//for(subIdx = 0; subIdx < SoldierRanks[rankIdx].aAbilityTree.Length; ++subIdx)
		//{
		//	str $= "        Ability(" $ subIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].aAbilityTree[subIdx].AbilityName, 'Ability Name') $ "\n";
		//}
		for(subIdx = 0; subIdx < SoldierRanks[rankIdx].aStatProgression.Length; ++subIdx)
		{
			str $= "        Stat Progression(" $ subIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].aStatProgression[subIdx].StatType, 'Stat Type') @ `ShowVar(SoldierRanks[rankIdx].aStatProgression[subIdx].StatAmount, 'Stat Amount') $ "\n";
		}
	}
	return str;
}

function int GetPointValue()
{
	return ClassPoints;
}

defaultproperties
{
	bShouldCreateDifficultyVariants = true
}

//---------------------------------------------------------------------------------------
//  *********   FIRAXIS SOURCE CODE   ******************
//  FILE:    X2SoldierClassTemplate.uc
//  AUTHOR:  Timothy Talley  --  01/18/2014
//---------------------------------------------------------------------------------------
//  Copyright (c) 2014 Firaxis Games Inc. All rights reserved.
//---------------------------------------------------------------------------------------

class X2SoldierClassTemplate extends X2DataTemplate
	dependson(X2TacticalGameRulesetDataStructures)
	config(ClassData)
	native(Core);

var config protected array<SoldierClassRank> SoldierRanks;
var config array<SoldierClassWeaponType>    AllowedWeapons;
var config array<name>						AllowedArmors;
var config array<name>                      ExcludedAbilities;  //  Abilities that are not eligible to roll from AWC for this calss
var config array<name>						AcceptedCharacterTemplates;		//	Some classes may be limited to specific templates, not just those marked bIsSoldier
var config name					SquaddieLoadout;
var config string				IconImage;
var config int					NumInForcedDeck;
var config int					NumInDeck;
var config int					ClassPoints;    // Number of "points" associated with using this class type, i.e. Multiplayer or Daily Challenge
var config int                  KillAssistsPerKill;     //  Number of kill assists that count as a kill for ranking up
var config int                  PsiCreditsPerKill;      //  Number of psi credits that count as a kill for ranking up
var config bool					bAllowAWCAbilities; // If this class should receive or share AWC abilities
var config bool					bUniqueTacticalToStrategyTransfer; // If this class has unique tactical to strategy transfer code, used for DLC and modding
var config bool					bIgnoreInjuries; // This class can go on missions even if they are wounded
var config bool					bBlockRankingUp; // Do not let soldiers of this class rank up in the normal way from XP
var config bool					bNoSecondaryWeapon;
var config array<EInventorySlot> CannotEditSlots; // Slots which cannot be edited in the armory loadout
var config protectedwrite bool  bMultiplayerOnly;
var config protectedwrite bool	bHideInCharacterPool;
var config name					RequiredCharacterClass; // Used in the character pool if this class has a required character template
var config bool					bHasClassMovie; // Does this class show a class movie upon first time getting a unit of this class
var config bool					bCanHaveBonds; // Can this class type have bonds with other soldiers and engage with that system
var config array<name>			FavoredClasses; // Will usually have higher compatibility with these classes in the bond system
var config array<name>			UnfavoredClasses; // Will usually have lower compatibility with these classes in the bond system
var config float				ForcedCompatibiliy; // If greater than zero, this is used as compatibility score with all other soldiers (if you want to force zero set bCanHaveBonds to false)
var config array<SoldierClassRandomAbilityDeck> RandomAbilityDecks; // Needed if slots in the ability tree pick a random ability
var config float				MinSuperConcealedDistance;	//	used by GetConcealmentDetectionDistance
var config int					BaseAbilityPointsPerPromotion; // The amount of ability points granted each time a unit of this class ranks up
var config int					AbilityPointsIncrementPerPromotion; // The amount a soldiers ability point reward is incremented each successive time they rank up

var localized string			DisplayName;
var localized string			ClassSummary;
var localized string			LeftAbilityTreeTitle;	//  don't use for all future classes
var localized string			RightAbilityTreeTitle;  //  don't use for all future classes
var localized array<string>		AbilityTreeTitles;		//  need one for each tree (base game classes have 2, xpack classes have 4)
var localized array<string>		RankNames;				//  there should be one name for each rank; e.g. Rookie, Squaddie, etc.
var localized array<string>		ShortNames;				//  the abbreviated rank name; e.g. Rk., Sq., etc.
var localized array<string>		RankIcons;				//  strings of image names for specialized rank icons
var localized array<String>     RandomNickNames;        //  Selected randomly when the soldier hits a certain rank, if the player has not set one already.
var localized array<String>     RandomNickNames_Female; //  Female only nicknames.
var localized array<String>     RandomNickNames_Male;   //  Male only nicknames.
var localized array<String>     PhotoboothSoloBLines_Female; //  Female only photobooth solo lines.
var localized array<String>     PhotoboothSoloBLines_Male;   //  Male only photobooth solo lines.

var delegate<CheckSpecialGuaranteedHitDelegate> CheckSpecialGuaranteedHitFn;
var delegate<CheckSpecialCritLabelDelegate> CheckSpecialCritLabelFn;
var delegate<GetTargetingMethodPostProcessDelegate> GetTargetingMethodPostProcessFn;

delegate name CheckSpecialGuaranteedHitDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState);
delegate string CheckSpecialCritLabelDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState);
delegate bool GetTargetingMethodPostProcessDelegate(XComGameState_Unit UnitState, XComGameState_Ability AbilityState, XComGameState_Item WeaponState, XComGameState_Unit TargetState, out name EnabledPostProcess, out name DisabledPostProcess);

function int GetMaxConfiguredRank()
{
	return SoldierRanks.Length;
}

function array<SoldierClassAbilitySlot> GetAbilitySlots(int Rank)
{
	if(Rank < 0 || Rank >= SoldierRanks.Length)
	{
		`RedScreen(string(GetFuncName()) @ "called with invalid Rank" @ Rank @ "for template" @ DataName @ DisplayName);
		return SoldierRanks[0].AbilitySlots;
	}

	return SoldierRanks[Rank].AbilitySlots;
}

function array<SoldierClassStatType> GetStatProgression(int Rank)
{
	if (Rank < 0 || Rank >= SoldierRanks.Length)
	{
		`RedScreen(string(GetFuncName()) @ "called with invalid Rank" @ Rank @ "for template" @ DataName @ DisplayName);
		return SoldierRanks[0].aStatProgression;
	}
	return SoldierRanks[Rank].aStatProgression;
}

function array<SoldierClassAbilityType> GetAllPossibleAbilities()
{
	local array<SoldierClassAbilityType> AllPossibleAbilities;
	local int DeckIndex, AbIndex, RankIndex, SlotIndex;

	AllPossibleAbilities.Length = 0;

	// Grab all abilities from the normal slots
	for(RankIndex = 0; RankIndex < SoldierRanks.Length; RankIndex++)
	{
		for(SlotIndex = 0; SlotIndex < SoldierRanks[RankIndex].AbilitySlots.Length; SlotIndex++)
		{
			if(SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType.AbilityName != '' &&
			   AllPossibleAbilities.Find('AbilityName', SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType.AbilityName) == INDEX_NONE)
			{
				AllPossibleAbilities.AddItem(SoldierRanks[RankIndex].AbilitySlots[SlotIndex].AbilityType);
			}
		}
	}

	// Grab all abilities from random decks
	for(DeckIndex = 0; DeckIndex < RandomAbilityDecks.Length; DeckIndex++)
	{
		for(AbIndex = 0; AbIndex < RandomAbilityDecks[DeckIndex].Abilities.Length; AbIndex++)
		{
			if(RandomAbilityDecks[DeckIndex].Abilities[AbIndex].AbilityName != '' &&
			   AllPossibleAbilities.Find('AbilityName', RandomAbilityDecks[DeckIndex].Abilities[AbIndex].AbilityName) == INDEX_NONE)
			{
				AllPossibleAbilities.AddItem(RandomAbilityDecks[DeckIndex].Abilities[AbIndex]);
			}
		}
	}

	return AllPossibleAbilities;
}

function bool IsWeaponAllowedByClass(X2WeaponTemplate WeaponTemplate)
{
	local int i;

	switch(WeaponTemplate.InventorySlot)
	{
	case eInvSlot_PrimaryWeapon: break;
	case eInvSlot_SecondaryWeapon: break;
	default:
		return true;
	}

	for (i = 0; i < AllowedWeapons.Length; ++i)
	{
		if (WeaponTemplate.InventorySlot == AllowedWeapons[i].SlotType &&
			WeaponTemplate.WeaponCat == AllowedWeapons[i].WeaponType)
			return true;
	}
	return false;
}

function bool IsArmorAllowedByClass(X2ArmorTemplate ArmorTemplate)
{
	local int i;

	switch (ArmorTemplate.InventorySlot)
	{
	case eInvSlot_Armor: break;
	default:
		return true;
	}

	for (i = 0; i < AllowedArmors.Length; ++i)
	{
		if (ArmorTemplate.ArmorCat == AllowedArmors[i])
			return true;
	}
	return false;
}

function string X2SoldierClassTemplate_ToString()
{
	local string str;
	local int rankIdx, subIdx;

	str = " X2SoldierClassTemplate:" @ `ShowVar(DataName) @ `ShowVar(SoldierRanks.Length, 'Num Ranks') @ `ShowVar(SquaddieLoadout) @ `ShowVar(AllowedWeapons.Length, 'Weapons') $ "\n";
	for(subIdx = 0; subIdx < AllowedWeapons.Length; ++subIdx)
	{
		str $= "        Weapon Type(" $ subIdx $ ") - " $ `ShowVar(AllowedWeapons[subIdx].WeaponType, 'Weapon Type') @ `ShowVar(AllowedWeapons[subIdx].SlotType, 'Slot Type') $ "\n";
	}
	for(rankIdx = 0; rankIdx < SoldierRanks.Length; ++rankIdx)
	{
		str $= "    Rank(" $ rankIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].AbilitySlots.Length, 'Abilities') @ `ShowVar(SoldierRanks[rankIdx].aStatProgression.Length, 'StatProgressions') $ "\n";
		//for(subIdx = 0; subIdx < SoldierRanks[rankIdx].aAbilityTree.Length; ++subIdx)
		//{
		//	str $= "        Ability(" $ subIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].aAbilityTree[subIdx].AbilityName, 'Ability Name') $ "\n";
		//}
		for(subIdx = 0; subIdx < SoldierRanks[rankIdx].aStatProgression.Length; ++subIdx)
		{
			str $= "        Stat Progression(" $ subIdx $ ") - " $ `ShowVar(SoldierRanks[rankIdx].aStatProgression[subIdx].StatType, 'Stat Type') @ `ShowVar(SoldierRanks[rankIdx].aStatProgression[subIdx].StatAmount, 'Stat Amount') $ "\n";
		}
	}
	return str;
}

function int GetPointValue()
{
	return ClassPoints;
}

defaultproperties
{
	bShouldCreateDifficultyVariants = true
}


Update strings.uc

Update strings.uc

Added Space$()

# strings.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.5 on 1/27/2014 5:47:33 PM
# Comment: Converts string-related code from PowerBASIC to C++

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: Converts string-related code from PowerBASIC to C++
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
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 2
Find: String
Replace: string

Criteria: 3
Find: WString
Replace: wstring

Criteria: 4
Find: {@Start}
Replace: #include <string>
         using namespace std;{nl}{nl}

Criteria: 5
BackColor: DodgerBlue
PassOnce: False
Find: InStr([{n=1},] {MainStr}, {MatchStr})

Replace: ({MainStr}.find({MatchStr}, ({n})-1)+1)

Criteria: 6
BackColor: Tomato
PassOnce: False
Find: InStr([{n=1},] {MainStr}, Any {MatchStr})
Replace: ({MainStr}.find_first_of({MatchStr}, ({n})-1)+1)

Criteria: 7
BackColor: DarkKhaki
PassOnce: False
Find: [{Sep: {nl} | : | Then | Else}] Mid$({Str}, {Start})
Replace: {Sep} Mid$({Str}, {Start}, {Str}.length())

Criteria: 8
BackColor: Red
PassOnce: False
Find: Left$({Str}, {n})
Replace: {Str}.substr(0, {n})

Criteria: 9
BackColor: Brown
PassOnce: False
Find: Right$({Str}, {n})
Replace: {Str}.substr({Str}.length()-({n}), {n})

Criteria: 10
BackColor: Orange
PassOnce: False
Find: Mid$({Str}, {Start}, {Length})
Replace: {Str}.substr(({Start})-1, {Length})

Criteria: 11
BackColor: DeepSkyBlue
PassOnce: False
Find: {Sep: {nl} | : | Then | Else }
      Mid$({Str}, {Start}, {Length}) = {Txt%}
Replace: {Sep} {Str}.replace(({Start})-1, {Length}, {Txt})

Criteria: 12
BackColor: RoyalBlue
PassOnce: False
Find: Val({Str})
Replace: stold({Str})

Criteria: 13
BackColor: Pink
PassOnce: False
Find: Str$({Number})
Replace: to_string({Number})

Criteria: 14
Find: Space$({count})
Replace: string({count}, " ")

# End Search

# strings.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 1/7/2014 8:21:28 AM
# Comment: Converts string-related code from PowerBASIC to C++

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: Converts string-related code from PowerBASIC to C++
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
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 2
Find: String
Replace: string

Criteria: 3
Selected: True
Find: WString
Replace: wstring

Criteria: 4
Find: {@Start}
Replace: #include <string>
         using namespace std;{nl}{nl}

Criteria: 5
BackColor: DodgerBlue
PassOnce: False
Find: InStr([{n=1},] {MainStr}, {MatchStr})

Replace: ({MainStr}.find({MatchStr}, ({n})-1)+1)

Criteria: 6
BackColor: Tomato
PassOnce: False
Find: InStr([{n=1},] {MainStr}, Any {MatchStr})
Replace: ({MainStr}.find_first_of({MatchStr}, ({n})-1)+1)

Criteria: 7
BackColor: DarkKhaki
PassOnce: False
Find: [{Sep: {nl} | : | Then | Else}] Mid$({Str}, {Start})
Replace: {Sep} Mid$({Str}, {Start}, {Str}.length())

Criteria: 8
BackColor: Red
PassOnce: False
Find: Left$({Str}, {n})
Replace: {Str}.substr(0, {n})

Criteria: 9
BackColor: Brown
PassOnce: False
Find: Right$({Str}, {n})
Replace: {Str}.substr({Str}.length()-({n}), {n})

Criteria: 10
BackColor: Orange
PassOnce: False
Find: Mid$({Str}, {Start}, {Length})
Replace: {Str}.substr(({Start})-1, {Length})

Criteria: 11
BackColor: DeepSkyBlue
PassOnce: False
Find: {Sep: {nl} | : | Then | Else }
      Mid$({Str}, {Start}, {Length}) = {Txt%}
Replace: {Sep} {Str}.replace(({Start})-1, {Length}, {Txt})

Criteria: 12
BackColor: RoyalBlue
PassOnce: False
Find: Val({Str})
Replace: stold({Str})

Criteria: 13
BackColor: Pink
PassOnce: False
Find: Str$({Number})
Replace: to_string({Number})

# End Search


luci-base: sys.uc: reliably skip TIME_WAIT connections in conntrack list

The TIME_WAIT indicator might not appear at the beginning of the tuple
string in some cases, so match it anywhere.

Signed-off-by: Kevin Darbyshire-Bryant <4499545e88bc2fc047e1f8ecc9978bd6eb652283@darbyshire-bryant.me.uk>
[add commit message]
Signed-off-by: Jo-Philipp Wich <bd73d35759d75cc215150d1bbc94f1b1078bee01@mein.io>

// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.

import { basename, readlink, readfile, open, popen, stat, glob } from 'fs';

export function process_list() {
	const top = popen('/bin/busybox top -bn1');
	let line, list = [];

	for (let line = top.read('line'); length(line); line = top.read('line')) {
		let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][<NW ][<N ]) +([0-9]+m?) +([0-9]+%) +([0-9]+%) +(.+)$/);

		if (m && m[8] != '/bin/busybox top -bn1') {
			push(list, {
				PID: m[1],
				PPID: m[2],
				USER: trim(m[3]),
				STAT: m[4],
				VSZ: m[5],
				'%MEM': m[6],
				'%CPU': m[7],
				COMMAND: m[8]
			});
		}
	}

	top.close();

	return list;
};

export function conntrack_list(callback) {
	const etcpr = open('/etc/protocols');
	const protos = {};

	if (etcpr) {
		for (let line = etcpr.read('line'); length(line); line = etcpr.read('line')) {
			const m = match(line, /^([^# \t\n]+)\s+([0-9]+)\s+/);

			if (m)
				protos[m[2]] = m[1];
		}

		etcpr.close();
	}

	const nfct = open('/proc/net/nf_conntrack', 'r');
	let connt;

	if (nfct) {
		for (let line = nfct.read('line'); length(line); line = nfct.read('line')) {
			let m = match(line, /^(ipv[46]) +([0-9]+) +\S+ +([0-9]+)( +.+)\n$/);

			if (!m)
				continue;

			let fam = m[1];
			let l3 = m[2];
			let l4 = m[3];
			let tuples = m[4];
			let timeout = null;

			m = match(tuples, /^ +([0-9]+)( .+)$/);

			if (m) {
				timeout = m[1];
				tuples = m[2];
			}

			if (index(tuples, 'TIME_WAIT') !== -1)
				continue;

			let e = {
				bytes: 0,
				packets: 0,
				layer3: fam,
				layer4: protos[l4] ?? 'unknown',
				timeout: +timeout
			};

			for (let kv in match(tuples, / (\w+)=(\S+)/g)) {
				switch (kv[1]) {
				case 'bytes':
				case 'packets':
					e[kv[1]] += +kv[2];
					break;

				case 'src':
				case 'dst':
					e[kv[1]] ??= arrtoip(iptoarr(kv[2]));
					break;

				case 'sport':
				case 'dport':
					e[kv[1]] ??= +kv[2];
					break;

				default:
					e[kv[1]] = kv[2];
				}
			}

			if (callback)
				callback(e);
			else
				push(connt ??= [], e);
		}

		nfct.close();
	}

	return callback ? true : (connt ?? []);
};

export function init_list() {
	return map(filter(glob('/etc/init.d/*'), path => {
		const s = stat(path);

		return s?.type == 'file' && s?.perm?.user_exec;
	}), basename);
};

export function init_index(name) {
	const src = readfile(`/etc/init.d/${basename(name)}`, 2048);
	const idx = [];

	for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) {
		switch (m[1]) {
		case 'START': idx[0] = +trim(m[2], '"\''); break;
		case 'STOP':  idx[1] = +trim(m[2], '"\''); break;
		}
	}

	return length(idx) ? idx : null;
};

export function init_enabled(name) {
	for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) {
		const ln = readlink(path);
		const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`);
		const s2 = stat(`/etc/init.d/${basename(name)}`);

		if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec)
			return true;
	}

	return false;
};

export function init_action(name, action) {
	const s = stat(`/etc/init.d/${basename(name)}`);

	if (s?.type != 'file' || s?.user_exec == false)
		return false;

	return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`);
};

// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.

import { basename, readlink, readfile, open, popen, stat, glob } from 'fs';

export function process_list() {
	const top = popen('/bin/busybox top -bn1');
	let line, list = [];

	for (let line = top.read('line'); length(line); line = top.read('line')) {
		let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][<NW ][<N ]) +([0-9]+m?) +([0-9]+%) +([0-9]+%) +(.+)$/);

		if (m && m[8] != '/bin/busybox top -bn1') {
			push(list, {
				PID: m[1],
				PPID: m[2],
				USER: trim(m[3]),
				STAT: m[4],
				VSZ: m[5],
				'%MEM': m[6],
				'%CPU': m[7],
				COMMAND: m[8]
			});
		}
	}

	top.close();

	return list;
};

export function conntrack_list(callback) {
	const etcpr = open('/etc/protocols');
	const protos = {};

	if (etcpr) {
		for (let line = etcpr.read('line'); length(line); line = etcpr.read('line')) {
			const m = match(line, /^([^# \t\n]+)\s+([0-9]+)\s+/);

			if (m)
				protos[m[2]] = m[1];
		}

		etcpr.close();
	}

	const nfct = open('/proc/net/nf_conntrack', 'r');
	let connt;

	if (nfct) {
		for (let line = nfct.read('line'); length(line); line = nfct.read('line')) {
			let m = match(line, /^(ipv[46]) +([0-9]+) +\S+ +([0-9]+)( +.+)\n$/);

			if (!m)
				continue;

			let fam = m[1];
			let l3 = m[2];
			let l4 = m[3];
			let tuples = m[4];
			let timeout = null;

			m = match(tuples, /^ +([0-9]+)( .+)$/);

			if (m) {
				timeout = m[1];
				tuples = m[2];
			}

			if (index(tuples, 'TIME_WAIT ') === 0)
				continue;

			let e = {
				bytes: 0,
				packets: 0,
				layer3: fam,
				layer4: protos[l4] ?? 'unknown',
				timeout: +timeout
			};

			for (let kv in match(tuples, / (\w+)=(\S+)/g)) {
				switch (kv[1]) {
				case 'bytes':
				case 'packets':
					e[kv[1]] += +kv[2];
					break;

				case 'src':
				case 'dst':
					e[kv[1]] ??= arrtoip(iptoarr(kv[2]));
					break;

				case 'sport':
				case 'dport':
					e[kv[1]] ??= +kv[2];
					break;

				default:
					e[kv[1]] = kv[2];
				}
			}

			if (callback)
				callback(e);
			else
				push(connt ??= [], e);
		}

		nfct.close();
	}

	return callback ? true : (connt ?? []);
};

export function init_list() {
	return map(filter(glob('/etc/init.d/*'), path => {
		const s = stat(path);

		return s?.type == 'file' && s?.perm?.user_exec;
	}), basename);
};

export function init_index(name) {
	const src = readfile(`/etc/init.d/${basename(name)}`, 2048);
	const idx = [];

	for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) {
		switch (m[1]) {
		case 'START': idx[0] = +trim(m[2], '"\''); break;
		case 'STOP':  idx[1] = +trim(m[2], '"\''); break;
		}
	}

	return length(idx) ? idx : null;
};

export function init_enabled(name) {
	for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) {
		const ln = readlink(path);
		const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`);
		const s2 = stat(`/etc/init.d/${basename(name)}`);

		if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec)
			return true;
	}

	return false;
};

export function init_action(name, action) {
	const s = stat(`/etc/init.d/${basename(name)}`);

	if (s?.type != 'file' || s?.user_exec == false)
		return false;

	return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`);
};