<commit_before>//---------------------------------------------------------------------------------------
//  FILE:    AnalyticsManager.uc
//  AUTHOR:  <NAME>  --  4/6/2015
//  PURPOSE: Global manager
//           
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------
class AnalyticsManager extends Object
	native(Core)
	dependson(X2StrategyGameRulesetDataStructures, XComGameState);

// data duped from XComGameState_CampaignSettings
struct native CampaignSettings
{
	var string StartTime;					// Tracks when the player initiated this campaign.
	var int DifficultySetting;				// 0:Easy 1:Normal 2:Classic 3:Impossible
	var bool bIronmanEnabled;				// TRUE indicates that this campaign was started with Ironman enabled
	var bool bTutorialEnabled;				// TRUE indicates that this campaign was started with the tutorial enabled
	var bool bXPackNarrativeEnabled;		// TRUE indicates that this campaign was started with XPack narrative tutorial enabled
	var bool bIntegratedDLCEnabled;			// TRUE indicates that this campaign was started withe Integrated DLC enabled
	var bool bSuppressFirstTimeNarrative;	// TRUE, the tutorial narrative moments will be skipped
};

var private{private} bool bShouldSubmitGameState;

var private{private} bool bAvaibleWorldStats;
var private{private} bool bWaitingOnWorldStats;

var private native Map_Mirror GlobalEndgameStats{ TMap<FString, double> };
var private int NumEndgameStatSubmissions;

var XComGameState_Analytics		PrevDebugAnalytics;

const WORLD_STAT_TIMEOUT_SECONDS = 30.0f;

native function Init();

function bool ShouldSubmitGameState()
{
	return bShouldSubmitGameState;
}

native static function SendCampaignEndGame( );

function EndgameStatsTimeout()
{
	if (bWaitingOnWorldStats)
	{
		bWaitingOnWorldStats = false;
		`FXSLIVE.ClearReceivedStatsKVPDelegate( StatsRecieved );
		CloseWorldStatsDialog( );
	}
}

function DebugDoEndgameStats( bool Win )
{
	local X2FiraxisLiveClient LiveClient;
	local XComEarth Earth;

	if (Win)
		SendCampaignEndGame( );

	LiveClient = `FXSLIVE;
	Earth = `EARTH; // for no particular reason, we just need a strategy available actor (we are just a lowley object without timers).

	bAvaibleWorldStats = false;
	bWaitingOnWorldStats = true;
	LiveClient.AddReceivedStatsKVPDelegate( StatsRecieved );

	Earth.SetTimer( WORLD_STAT_TIMEOUT_SECONDS, false, nameof(EndgameStatsTimeout), self );

	LiveClient.GetStats( eKVPSCOPE_GLOBAL );
}

static function bool SkipAddAnalyticObject(XComGameState GameState)
{
	local XComGameState_BattleData Battle;
	Battle = XComGameState_BattleData(`XCOMHISTORY.GetSingleGameStateObjectForClass(class'XComGameState_BattleData', true));

	// Don't add any objects while in Multiplayer
	if( (Battle != none) && (Battle.m_strDesc ~= "Multiplayer") )
	{
		return true;
	}

	// Don't add objects for interrupted gamestates.  Only create analytics for completed (none or resume contexts)
	if (GameState.GetContext().InterruptionStatus == eInterruptionStatus_Interrupt)
	{
		return true;
	}

	return false;
}


static function EventListenerReturn OnSoldierTacticalToStrategy(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddSoldierTacticalToStrategy(XComGameState_Unit(EventData), GameState);
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnTacticalGameStart(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddTacticalGameStart( );
	}

	SendMissionStartTelemetry( History );

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnTacticalGameEnd(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddTacticalGameEnd();
	}

	SendMissionEndTelemetry( History );

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnPlayerTurnEnded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if ((AnalyticsObject != none) && (XComGameState_Player(EventData).TeamFlag == eTeam_XCom))
	{
		AnalyticsObject.AddPlayerTurnEnd( );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitMoved(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		// report move
		AnalyticsObject.AddUnitMoved( XComGameState_Unit( EventSource ), GameState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnKillMail(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("KillMail");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		// report kill
		AnalyticsObject.AddKillMail(XComGameState_Unit(EventSource), XComGameState_Unit(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnBreakWindow(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("BreakWindow");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddBreakWindow(XComGameState_Unit(EventSource), XComGameStateContext_Ability(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnBreakDoor(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("BreakDoor");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddBreakDoor(XComGameState_Unit(EventSource), XComGameStateContext_Ability(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnWeaponKillType(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Weapon Kill Type");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddWeaponKill(XComGameState_Unit(EventSource), XComGameState_Ability(EventData));
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnMissionObjectiveComplete(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Mission Objective Complete");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddMissionObjectiveComplete();
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnCivilianRescued(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Civilian Rescued");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCivilianRescued(XComGameState_Unit(EventData), XComGameState_Unit(EventSource));
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitDamaged(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Unit DamageSource;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		if (AbilityContext != none)
		{
			DamageSource = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.SourceObject.ObjectID));
		}

		AnalyticsObject.AddUnitDamage( XComGameState_Unit( EventSource ), DamageSource, GameState.GetContext() );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnAbilityActivation(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Unit Shooter, Target;
	local XComGameState_Item Tool;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext( ) );
		`assert( AbilityContext != none );

		Shooter = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.SourceObject.ObjectID));
		Target = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.PrimaryTarget.ObjectID));
		Tool = XComGameState_Item(History.GetGameStateForObjectID(AbilityContext.InputContext.ItemObject.ObjectID));

		AnalyticsObject.AddUnitTakenShot( Shooter, Target, Tool, AbilityContext, XComGameState_Ability(EventData) );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitHealComplete(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_HeadquartersProjectHealSoldier HealProject;
	local XComGameState_Unit UnitState;
	local TDateTime CurrentDate;
	local int TimeDiffHours;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UnitState = XComGameState_Unit( EventData );
		HealProject = XComGameState_HeadquartersProjectHealSoldier( EventSource );
		CurrentDate = `STRATEGYRULES.GameTime;

		TimeDiffHours = class'X2StrategyGameRulesetDataStructures'.static.DifferenceInHours( CurrentDate, HealProject.StartDateTime );
		TimeDiffHours += HealProject.ProjectTimeBeforePausesHours;

		AnalyticsObject.AddUnitHealCompleted( UnitState, TimeDiffHours );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnNewCrewAdded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCrewAddition( XComGameState_Unit(EventData) );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResearchCompleted( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Tech Tech;
	local XComGameState_HeadquartersProjectResearch ResearchProject;
	local XComGameState_HeadquartersXCom XComHQ;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Tech = XComGameState_Tech( EventData );
		ResearchProject = XComGameState_HeadquartersProjectResearch( EventSource );

		if (Tech.GetMyTemplate().bProvingGround)
		{
			XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
			XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComHQ.ObjectID, , GameState.HistoryIndex ) );

			AnalyticsObject.AddProvingGroundCompletion( Tech, XComHQ, ResearchProject, GameState );
		}
		else
		{
			AnalyticsObject.AddResearchCompletion( Tech, ResearchProject );
		}

	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResistanceActivityComplete( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local X2ResistanceActivityTemplate ActivityTemplate;
	local XComGameState_HeadquartersResistance ResistanceHQ;
	local int Delta, idx;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	if (EventData == none)
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		ActivityTemplate = X2ResistanceActivityTemplate( EventData );
		ResistanceHQ = XComGameState_HeadquartersResistance( EventSource );

		idx = ResistanceHQ.ResistanceActivities.Find('ActivityTemplateName', ActivityTemplate.DataName );
		Delta = ResistanceHQ.ResistanceActivities[idx].LastIncrement;

		AnalyticsObject.AddResistanceActivity( ActivityTemplate, Delta );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResourceAdded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item Resource;
	
	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Resource = XComGameState_Item( EventData );

		AnalyticsObject.AddResource( Resource, Resource.LastQuantityChange );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBlackMarketPurchase( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_BlackMarket BlackMarket;
	local XComGameState_Reward RewardState;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		RewardState = XComGameState_Reward( EventData );
		BlackMarket = XComGameState_BlackMarket( History.GetPreviousGameStateForObject( XComGameState_BlackMarket( EventSource ) ) );

		AnalyticsObject.AddBlackMarketPurchase( BlackMarket, RewardState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBlackMarketGoodsSold( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local StateObjectReference ObjRef;
	local XComGameState_Item ItemState, PrevState;
	local XComGameState_HeadquartersXCom XComHQ;
	local int Supplies, PrevSupplies;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
		foreach XComHQ.Inventory(ObjRef)
		{
			ItemState = XComGameState_Item( History.GetGameStateForObjectID( ObjRef.ObjectID ) );

			if (ItemState != none && ItemState.GetMyTemplateName( ) == 'Supplies')
			{
				break;
			}
		}

		PrevState = XComGameState_Item( History.GetPreviousGameStateForObject( ItemState ) );

		Supplies = ItemState.Quantity;
		PrevSupplies = PrevState.Quantity;

		AnalyticsObject.AddBlackmarketSupplies( Supplies - PrevSupplies );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnSupplyDrop( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local StateObjectReference ObjRef;
	local XComGameState_Item ItemState, PrevState;
	local XComGameState_HeadquartersXCom XComHQ;
	local int Supplies, PrevSupplies;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComGameState_BaseObject(EventData).ObjectID ) );
		foreach XComHQ.Inventory(ObjRef)
		{
			ItemState = XComGameState_Item( History.GetGameStateForObjectID( ObjRef.ObjectID ) );

			if (ItemState != none && ItemState.GetMyTemplateName( ) == 'Supplies')
			{
				break;
			}
		}

		PrevState = XComGameState_Item( History.GetPreviousGameStateForObject( ItemState ) );

		Supplies = ItemState.Quantity;
		PrevSupplies = PrevState.Quantity;

		AnalyticsObject.AddSupplyDropSupplies( Supplies - PrevSupplies );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenDefeated( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Unit KilledUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		KilledUnit = XComGameState_Unit(EventData);

		// ignore everybody who is not a chosen
		if (!KilledUnit.GetMyTemplate().bIsChosen)
			return ELR_NoInterrupt;

		AnalyticsObject.AddChosenDefeated( KilledUnit );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenInterrogation( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Ability AbilityState;
	local XComGameState_Unit Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityState = XComGameState_Ability( EventData );
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		Chosen = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.SourceObject.ObjectID ) );
		CapturedUnit = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.PrimaryTarget.ObjectID ) );

		AnalyticsObject.AddChosenInterrogation( Chosen, CapturedUnit, AbilityState, AbilityContext );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenKidnap( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Ability AbilityState;
	local XComGameState_Unit Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityState = XComGameState_Ability( EventData );
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		Chosen = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.SourceObject.ObjectID ) );
		CapturedUnit = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.PrimaryTarget.ObjectID ) );

		AnalyticsObject.AddChosenKidnapping( Chosen, CapturedUnit, AbilityState, AbilityContext );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenCapture( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_AdventChosen Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Chosen = XComGameState_AdventChosen( EventSource );
		CapturedUnit = XComGameState_Unit( EventData );

		AnalyticsObject.AddChosenCapture( Chosen, CapturedUnit, none, none );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnPCSApplied( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Unit UpdatedUnit;
	local XComGameState_Item UpdatedImplant;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UpdatedUnit = XComGameState_Unit( EventData );
		UpdatedImplant = XComGameState_Item( EventSource );

		AnalyticsObject.AddPCSApplied( UpdatedUnit, UpdatedImplant );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnWeaponUpgraded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item UpdatedWeapon;
	local XComGameState_Item UpdatedUpgrade;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UpdatedWeapon = XComGameState_Item( EventData );
		UpdatedUpgrade = XComGameState_Item( EventSource );

		AnalyticsObject.AddWeaponUpgrade( UpdatedWeapon, UpdatedUpgrade );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnItemConstruction( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item NewItem;
	local XComGameState_HeadquartersXCom XComHQ;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		NewItem = XComGameState_Item( EventData );

		XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
		XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComHQ.ObjectID, , GameState.HistoryIndex ) );

		AnalyticsObject.AddItemConstruction( NewItem, XComHQ );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnCoverActionComplete( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCovertActionCompletion( );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnFacilityConstruction( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_FacilityXCom Facility;
	local XComGameState_HeadquartersProjectBuildFacility ProjectState;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Facility = XComGameState_FacilityXCom( EventData );
		ProjectState = XComGameState_HeadquartersProjectBuildFacility( EventSource );

		AnalyticsObject.AddFacilityConstruction( Facility, ProjectState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBondLevelUp( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{<commit_msg>#752 - Allow mods to prevent CA completion from counting towards campaign stats<commit_after>//---------------------------------------------------------------------------------------
//  FILE:    AnalyticsManager.uc
//  AUTHOR:  <NAME>  --  4/6/2015
//  PURPOSE: Global manager
//           
//---------------------------------------------------------------------------------------
//  Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
//---------------------------------------------------------------------------------------
class AnalyticsManager extends Object
	native(Core)
	dependson(X2StrategyGameRulesetDataStructures, XComGameState);

// data duped from XComGameState_CampaignSettings
struct native CampaignSettings
{
	var string StartTime;					// Tracks when the player initiated this campaign.
	var int DifficultySetting;				// 0:Easy 1:Normal 2:Classic 3:Impossible
	var bool bIronmanEnabled;				// TRUE indicates that this campaign was started with Ironman enabled
	var bool bTutorialEnabled;				// TRUE indicates that this campaign was started with the tutorial enabled
	var bool bXPackNarrativeEnabled;		// TRUE indicates that this campaign was started with XPack narrative tutorial enabled
	var bool bIntegratedDLCEnabled;			// TRUE indicates that this campaign was started withe Integrated DLC enabled
	var bool bSuppressFirstTimeNarrative;	// TRUE, the tutorial narrative moments will be skipped
};

var private{private} bool bShouldSubmitGameState;

var private{private} bool bAvaibleWorldStats;
var private{private} bool bWaitingOnWorldStats;

var private native Map_Mirror GlobalEndgameStats{ TMap<FString, double> };
var private int NumEndgameStatSubmissions;

var XComGameState_Analytics		PrevDebugAnalytics;

const WORLD_STAT_TIMEOUT_SECONDS = 30.0f;

native function Init();

function bool ShouldSubmitGameState()
{
	return bShouldSubmitGameState;
}

native static function SendCampaignEndGame( );

function EndgameStatsTimeout()
{
	if (bWaitingOnWorldStats)
	{
		bWaitingOnWorldStats = false;
		`FXSLIVE.ClearReceivedStatsKVPDelegate( StatsRecieved );
		CloseWorldStatsDialog( );
	}
}

function DebugDoEndgameStats( bool Win )
{
	local X2FiraxisLiveClient LiveClient;
	local XComEarth Earth;

	if (Win)
		SendCampaignEndGame( );

	LiveClient = `FXSLIVE;
	Earth = `EARTH; // for no particular reason, we just need a strategy available actor (we are just a lowley object without timers).

	bAvaibleWorldStats = false;
	bWaitingOnWorldStats = true;
	LiveClient.AddReceivedStatsKVPDelegate( StatsRecieved );

	Earth.SetTimer( WORLD_STAT_TIMEOUT_SECONDS, false, nameof(EndgameStatsTimeout), self );

	LiveClient.GetStats( eKVPSCOPE_GLOBAL );
}

static function bool SkipAddAnalyticObject(XComGameState GameState)
{
	local XComGameState_BattleData Battle;
	Battle = XComGameState_BattleData(`XCOMHISTORY.GetSingleGameStateObjectForClass(class'XComGameState_BattleData', true));

	// Don't add any objects while in Multiplayer
	if( (Battle != none) && (Battle.m_strDesc ~= "Multiplayer") )
	{
		return true;
	}

	// Don't add objects for interrupted gamestates.  Only create analytics for completed (none or resume contexts)
	if (GameState.GetContext().InterruptionStatus == eInterruptionStatus_Interrupt)
	{
		return true;
	}

	return false;
}


static function EventListenerReturn OnSoldierTacticalToStrategy(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddSoldierTacticalToStrategy(XComGameState_Unit(EventData), GameState);
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnTacticalGameStart(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddTacticalGameStart( );
	}

	SendMissionStartTelemetry( History );

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnTacticalGameEnd(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddTacticalGameEnd();
	}

	SendMissionEndTelemetry( History );

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnPlayerTurnEnded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if ((AnalyticsObject != none) && (XComGameState_Player(EventData).TeamFlag == eTeam_XCom))
	{
		AnalyticsObject.AddPlayerTurnEnd( );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitMoved(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		// report move
		AnalyticsObject.AddUnitMoved( XComGameState_Unit( EventSource ), GameState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnKillMail(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("KillMail");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		// report kill
		AnalyticsObject.AddKillMail(XComGameState_Unit(EventSource), XComGameState_Unit(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnBreakWindow(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("BreakWindow");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddBreakWindow(XComGameState_Unit(EventSource), XComGameStateContext_Ability(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnBreakDoor(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("BreakDoor");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddBreakDoor(XComGameState_Unit(EventSource), XComGameStateContext_Ability(EventData));
	}

	return ELR_NoInterrupt;
}


static function EventListenerReturn OnWeaponKillType(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if( SkipAddAnalyticObject(GameState) )
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Weapon Kill Type");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddWeaponKill(XComGameState_Unit(EventSource), XComGameState_Ability(EventData));
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnMissionObjectiveComplete(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Mission Objective Complete");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddMissionObjectiveComplete();
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnCivilianRescued(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject(GameState))
	{
		return ELR_NoInterrupt;
	}

	`ANALYTICSLOG("Civilian Rescued");

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics(History.GetSingleGameStateObjectForClass(class'XComGameState_Analytics', true));
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCivilianRescued(XComGameState_Unit(EventData), XComGameState_Unit(EventSource));
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitDamaged(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Unit DamageSource;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		if (AbilityContext != none)
		{
			DamageSource = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.SourceObject.ObjectID));
		}

		AnalyticsObject.AddUnitDamage( XComGameState_Unit( EventSource ), DamageSource, GameState.GetContext() );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnAbilityActivation(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Unit Shooter, Target;
	local XComGameState_Item Tool;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext( ) );
		`assert( AbilityContext != none );

		Shooter = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.SourceObject.ObjectID));
		Target = XComGameState_Unit(History.GetGameStateForObjectID(AbilityContext.InputContext.PrimaryTarget.ObjectID));
		Tool = XComGameState_Item(History.GetGameStateForObjectID(AbilityContext.InputContext.ItemObject.ObjectID));

		AnalyticsObject.AddUnitTakenShot( Shooter, Target, Tool, AbilityContext, XComGameState_Ability(EventData) );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnUnitHealComplete(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_HeadquartersProjectHealSoldier HealProject;
	local XComGameState_Unit UnitState;
	local TDateTime CurrentDate;
	local int TimeDiffHours;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UnitState = XComGameState_Unit( EventData );
		HealProject = XComGameState_HeadquartersProjectHealSoldier( EventSource );
		CurrentDate = `STRATEGYRULES.GameTime;

		TimeDiffHours = class'X2StrategyGameRulesetDataStructures'.static.DifferenceInHours( CurrentDate, HealProject.StartDateTime );
		TimeDiffHours += HealProject.ProjectTimeBeforePausesHours;

		AnalyticsObject.AddUnitHealCompleted( UnitState, TimeDiffHours );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnNewCrewAdded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCrewAddition( XComGameState_Unit(EventData) );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResearchCompleted( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Tech Tech;
	local XComGameState_HeadquartersProjectResearch ResearchProject;
	local XComGameState_HeadquartersXCom XComHQ;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Tech = XComGameState_Tech( EventData );
		ResearchProject = XComGameState_HeadquartersProjectResearch( EventSource );

		if (Tech.GetMyTemplate().bProvingGround)
		{
			XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
			XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComHQ.ObjectID, , GameState.HistoryIndex ) );

			AnalyticsObject.AddProvingGroundCompletion( Tech, XComHQ, ResearchProject, GameState );
		}
		else
		{
			AnalyticsObject.AddResearchCompletion( Tech, ResearchProject );
		}

	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResistanceActivityComplete( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local X2ResistanceActivityTemplate ActivityTemplate;
	local XComGameState_HeadquartersResistance ResistanceHQ;
	local int Delta, idx;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	if (EventData == none)
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		ActivityTemplate = X2ResistanceActivityTemplate( EventData );
		ResistanceHQ = XComGameState_HeadquartersResistance( EventSource );

		idx = ResistanceHQ.ResistanceActivities.Find('ActivityTemplateName', ActivityTemplate.DataName );
		Delta = ResistanceHQ.ResistanceActivities[idx].LastIncrement;

		AnalyticsObject.AddResistanceActivity( ActivityTemplate, Delta );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnResourceAdded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item Resource;
	
	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Resource = XComGameState_Item( EventData );

		AnalyticsObject.AddResource( Resource, Resource.LastQuantityChange );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBlackMarketPurchase( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_BlackMarket BlackMarket;
	local XComGameState_Reward RewardState;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		RewardState = XComGameState_Reward( EventData );
		BlackMarket = XComGameState_BlackMarket( History.GetPreviousGameStateForObject( XComGameState_BlackMarket( EventSource ) ) );

		AnalyticsObject.AddBlackMarketPurchase( BlackMarket, RewardState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBlackMarketGoodsSold( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local StateObjectReference ObjRef;
	local XComGameState_Item ItemState, PrevState;
	local XComGameState_HeadquartersXCom XComHQ;
	local int Supplies, PrevSupplies;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
		foreach XComHQ.Inventory(ObjRef)
		{
			ItemState = XComGameState_Item( History.GetGameStateForObjectID( ObjRef.ObjectID ) );

			if (ItemState != none && ItemState.GetMyTemplateName( ) == 'Supplies')
			{
				break;
			}
		}

		PrevState = XComGameState_Item( History.GetPreviousGameStateForObject( ItemState ) );

		Supplies = ItemState.Quantity;
		PrevSupplies = PrevState.Quantity;

		AnalyticsObject.AddBlackmarketSupplies( Supplies - PrevSupplies );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnSupplyDrop( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local StateObjectReference ObjRef;
	local XComGameState_Item ItemState, PrevState;
	local XComGameState_HeadquartersXCom XComHQ;
	local int Supplies, PrevSupplies;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComGameState_BaseObject(EventData).ObjectID ) );
		foreach XComHQ.Inventory(ObjRef)
		{
			ItemState = XComGameState_Item( History.GetGameStateForObjectID( ObjRef.ObjectID ) );

			if (ItemState != none && ItemState.GetMyTemplateName( ) == 'Supplies')
			{
				break;
			}
		}

		PrevState = XComGameState_Item( History.GetPreviousGameStateForObject( ItemState ) );

		Supplies = ItemState.Quantity;
		PrevSupplies = PrevState.Quantity;

		AnalyticsObject.AddSupplyDropSupplies( Supplies - PrevSupplies );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenDefeated( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Unit KilledUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		KilledUnit = XComGameState_Unit(EventData);

		// ignore everybody who is not a chosen
		if (!KilledUnit.GetMyTemplate().bIsChosen)
			return ELR_NoInterrupt;

		AnalyticsObject.AddChosenDefeated( KilledUnit );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenInterrogation( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Ability AbilityState;
	local XComGameState_Unit Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityState = XComGameState_Ability( EventData );
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		Chosen = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.SourceObject.ObjectID ) );
		CapturedUnit = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.PrimaryTarget.ObjectID ) );

		AnalyticsObject.AddChosenInterrogation( Chosen, CapturedUnit, AbilityState, AbilityContext );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenKidnap( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameStateContext_Ability AbilityContext;
	local XComGameState_Ability AbilityState;
	local XComGameState_Unit Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AbilityState = XComGameState_Ability( EventData );
		AbilityContext = XComGameStateContext_Ability( GameState.GetContext() );
		Chosen = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.SourceObject.ObjectID ) );
		CapturedUnit = XComGameState_Unit( History.GetGameStateForObjectID( AbilityContext.InputContext.PrimaryTarget.ObjectID ) );

		AnalyticsObject.AddChosenKidnapping( Chosen, CapturedUnit, AbilityState, AbilityContext );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnChosenCapture( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_AdventChosen Chosen;
	local XComGameState_Unit CapturedUnit;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Chosen = XComGameState_AdventChosen( EventSource );
		CapturedUnit = XComGameState_Unit( EventData );

		AnalyticsObject.AddChosenCapture( Chosen, CapturedUnit, none, none );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnPCSApplied( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Unit UpdatedUnit;
	local XComGameState_Item UpdatedImplant;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UpdatedUnit = XComGameState_Unit( EventData );
		UpdatedImplant = XComGameState_Item( EventSource );

		AnalyticsObject.AddPCSApplied( UpdatedUnit, UpdatedImplant );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnWeaponUpgraded( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item UpdatedWeapon;
	local XComGameState_Item UpdatedUpgrade;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		UpdatedWeapon = XComGameState_Item( EventData );
		UpdatedUpgrade = XComGameState_Item( EventSource );

		AnalyticsObject.AddWeaponUpgrade( UpdatedWeapon, UpdatedUpgrade );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnItemConstruction( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_Item NewItem;
	local XComGameState_HeadquartersXCom XComHQ;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		NewItem = XComGameState_Item( EventData );

		XComHQ = XComGameState_HeadquartersXCom( History.GetSingleGameStateObjectForClass( class'XComGameState_HeadquartersXCom' ) );
		XComHQ = XComGameState_HeadquartersXCom( History.GetGameStateForObjectID( XComHQ.ObjectID, , GameState.HistoryIndex ) );

		AnalyticsObject.AddItemConstruction( NewItem, XComHQ );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnCoverActionComplete( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	// Start issue #752
	if (!TriggerAllowOnCovertActionComplete(EventData, EventSource, GameState, Event, CallbackData))
	{
		return ELR_NoInterrupt;
	}
	// End issue #752

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		AnalyticsObject.AddCovertActionCompletion( );
	}

	return ELR_NoInterrupt;
}

// Start issue #752
/// HL-Docs: feature:AllowOnCovertActionCompleteAnalytics; issue:752; tags:strategy
/// Allows mods to prevent CA completion from counting towards campaign stats.
///
/// This event is triggered from `AnalyticsManager::OnCoverActionComplete` and passes all
/// original listener arguments in the tuple.
///
/// ```event
/// EventID: AllowOnCovertActionCompleteAnalytics,
/// EventData: [
///   inout bool bAllow,
///   in Object OriginalEventData,
///   in Object OriginalEventSource,
///   in XComGameState OriginalGameState,
///   in name OriginalEvent,
///   in Object OriginalCallbackData
/// ],
/// EventSource: none,
/// NewGameState: none
/// ```
///
/// Note 1: The `OriginalEventSource` should be the `XComGameState_CovertAction` that was
/// just completed, although the AnalyticsManager code does not validate that.
///
/// Note 2: you **must** subscribe with `ELD_Immediate` deferral to modify `bAllow`
private static function bool TriggerAllowOnCovertActionComplete (Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
{
	local XComLWTuple Tuple;

	Tuple = new class'XComLWTuple';
	Tuple.Data.Add(6);
	Tuple.Data[0].kind = XComLWTVBool;
	Tuple.Data[0].b = true; // Allow by default
	Tuple.Data[1].kind = XComLWTVObject;
	Tuple.Data[1].o = EventData;
	Tuple.Data[2].kind = XComLWTVObject;
	Tuple.Data[2].o = EventSource;
	Tuple.Data[3].kind = XComLWTVObject;
	Tuple.Data[3].o = GameState;
	Tuple.Data[4].kind = XComLWTVName;
	Tuple.Data[4].n = Event;
	Tuple.Data[5].kind = XComLWTVObject;
	Tuple.Data[5].o = CallbackData;

	`XEVENTMGR.TriggerEvent('AllowOnCovertActionCompleteAnalytics', Tuple);

	return Tuple.Data[0].b;
}
// End issue #752

static function EventListenerReturn OnFacilityConstruction( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
	local XComGameState_Analytics AnalyticsObject;
	local XComGameStateHistory History;
	local XComGameState_FacilityXCom Facility;
	local XComGameState_HeadquartersProjectBuildFacility ProjectState;

	if (SkipAddAnalyticObject( GameState ))
	{
		return ELR_NoInterrupt;
	}

	History = `XCOMHISTORY;

	// check if we have analytics object
	AnalyticsObject = XComGameState_Analytics( History.GetSingleGameStateObjectForClass( class'XComGameState_Analytics', true ) );
	if (AnalyticsObject != none)
	{
		Facility = XComGameState_FacilityXCom( EventData );
		ProjectState = XComGameState_HeadquartersProjectBuildFacility( EventSource );

		AnalyticsObject.AddFacilityConstruction( Facility, ProjectState );
	}

	return ELR_NoInterrupt;
}

static function EventListenerReturn OnBondLevelUp( Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData )
{
{'fraction_non_alphanumeric': 0.08108634284045556, 'fraction_numerical': 0.001265453129562932, 'mean_word_length': 6.106998731696068, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5831642', 'n_tokens_mistral': 19784, 'n_tokens_neox': 18743, 'n_words': 4139}