enum telemetryLevelGainReason
{
	Ignore = 0,
	Gameplay = 1,
	IsDebug = 2,
}

class PlayerDevelopmentData
{
	var m_owner : weak< GameObject >;
	private persistent var m_ownerID : EntityID;
	private persistent var m_queuedCombatExp : array< SExperiencePoints >;
	private persistent var m_proficiencies : array< SProficiency >;
	private persistent var m_attributes : array< SAttribute >;
	private persistent var m_perkAreas : array< SPerkArea >;
	private persistent var m_traits : array< STrait >;
	private persistent var m_devPoints : array< SDevelopmentPoints >;
	private persistent var m_skillPrereqs : array< SkillCheckPrereqState >;
	private persistent var m_statPrereqs : array< StatCheckPrereqState >;
	private persistent var m_knownRecipes : array< ItemRecipe >;
	private persistent var m_attributesData : array< SAttributeData >;
	private persistent var m_highestCompletedMinigameLevel : Int32;
	private const var m_startingLevel : Int32;
	default m_startingLevel = 1;
	private const var m_startingExperience : Int32;
	default m_startingExperience = 0;
	private persistent var m_lifePath : gamedataLifePath;
	private var m_displayActivityLog : Bool;
	default m_displayActivityLog = true;
	private var m_progressionBuildSetCompleted : Bool;
	default m_progressionBuildSetCompleted = true;

	public function OnAttach() {}

	public function OnDetach() {}

	public function OnNewGame()
	{
		SetProficiencies();
		InitializeAttributesData();
		InitializePerkAreas();
		InitializeTraits();
		SetDevelopmentPoints();
		UpdateUIBB();
	}

	public function EspionageAttributeRetrofix()
	{
		var attribute : SAttribute;
		if( m_attributes.Size() == 6 )
		{
			attribute.value = 0;
			attribute.attributeName = gamedataStatType.Espionage;
			attribute.id = TDBID.Create( "BaseStats." + ( ( String )( attribute.attributeName ) ) );
			m_attributes.Insert( 1, attribute );
		}
	}

	public function OnRestored( gameInstance : GameInstance )
	{
		var i, j, k : Int32;
		var statSys : StatsSystem;
		var statMod : gameConstantStatModifierData;
		var type : gamedataProficiencyType;
		var traitIndex : Int32;
		var traitType : gamedataTraitType;
		var shouldTraitUnlock : Bool;
		var proficiencyIndex : Int32;
		if( !( EntityID.IsDefined( m_ownerID ) ) )
		{
			m_owner = GetPlayer( gameInstance );
			m_ownerID = m_owner.GetEntityID();
		}
		else
		{
			m_owner = ( ( GameObject )( GameInstance.FindEntityByID( gameInstance, m_ownerID ) ) );
		}
		statSys = GameInstance.GetStatsSystem( gameInstance );
		for( i = 0; i < m_proficiencies.Size(); i += 1 )
		{
			type = m_proficiencies[ i ].type;
			proficiencyIndex = GetProficiencyIndexByType( type );
			RestoreProficiencyPassiveBonuses( proficiencyIndex, gameInstance );
			UpdateUIBB();
			if( IsProficiencyStatAdded( type ) )
			{
				continue;
			}
			SetProficiencyStat( m_proficiencies[ i ].type, m_proficiencies[ i ].currentLevel );
			m_proficiencies[ i ].maxLevel = GetProficiencyMaxLevel( m_proficiencies[ i ].type );
			m_proficiencies[ i ].isAtMaxLevel = m_proficiencies[ i ].maxLevel == m_proficiencies[ i ].currentLevel;
			m_proficiencies[ i ].expToLevel = GetRemainingExpForLevelUp( m_proficiencies[ i ].type );
			traitType = RPGManager.GetProficiencyRecord( type ).Trait().Type();
			traitIndex = GetTraitIndex( traitType );
			if( traitIndex >= 0 )
			{
				shouldTraitUnlock = IsTraitReqMet( m_traits[ traitIndex ].type );
				if( m_traits[ traitIndex ].unlocked && !( shouldTraitUnlock ) )
				{
					AddDevelopmentPoints( m_traits[ traitIndex ].currLevel, gamedataDevelopmentPointType.Primary );
					m_traits[ traitIndex ].currLevel = 0;
				}
				m_traits[ traitIndex ].unlocked = shouldTraitUnlock;
				if( m_traits[ traitIndex ].unlocked )
				{
					ActivateTraitBase( m_traits[ traitIndex ].type );
					EvaluateTraitInfiniteData( traitIndex );
				}
			}
		}
		GameInstance.GetLevelAssignmentSystem( gameInstance ).MarkPlayerLevelRestored();
		for( i = 0; i < m_attributes.Size(); i += 1 )
		{
			statMod = new gameConstantStatModifierData;
			statMod.statType = m_attributes[ i ].attributeName;
			statMod.value = ( ( Float )( m_attributes[ i ].value ) );
			statMod.modifierType = gameStatModifierType.Additive;
			statSys.ForceModifier( m_owner.GetEntityID(), statMod );
		}
		for( i = 0; i < m_perkAreas.Size(); i += 1 )
		{
			for( j = 0; j < m_perkAreas[ i ].boughtPerks.Size(); j += 1 )
			{
				ActivatePerkLevelData( i, GetPerkIndex( m_perkAreas[ i ].boughtPerks[ j ].type ) );
			}
		}
		if( m_attributesData.Size() == 0 )
		{
			InitializeAttributesData();
		}
		for( i = 0; i < m_attributesData.Size(); i += 1 )
		{
			for( j = 0; j < m_attributesData[ i ].unlockedPerks.Size(); j += 1 )
			{
				for( k = 1; k <= m_attributesData[ i ].unlockedPerks[ j ].currLevel; k += 1 )
				{
					ActivateNewPerk( m_attributesData[ i ].unlockedPerks[ j ].type, k - 1 );
				}
			}
		}
	}

	public function SetOwner( owner : GameObject )
	{
		m_owner = owner;
		m_ownerID = owner.GetEntityID();
	}

	public function GetOwner() : weak< GameObject >
	{
		return m_owner;
	}

	public function GetOwnerID() : EntityID
	{
		return m_ownerID;
	}

	public const function GetLifePath() : gamedataLifePath
	{
		return m_lifePath;
	}

	public const function GetProficiencyLevel( type : gamedataProficiencyType ) : Int32
	{
		var profIndex : Int32;
		profIndex = GetProficiencyIndexByType( type );
		if( profIndex >= 0 )
		{
			return m_proficiencies[ profIndex ].currentLevel;
		}
		else
		{
			return -1;
		}
	}

	public const function GetProficiencyAbsoluteMaxLevel( type : gamedataProficiencyType ) : Int32
	{
		return RPGManager.GetProficiencyRecord( type ).MaxLevel();
	}

	public const function GetCurrentLevelProficiencyExp( type : gamedataProficiencyType ) : Int32
	{
		var profIndex : Int32;
		profIndex = GetProficiencyIndexByType( type );
		if( profIndex >= 0 )
		{
			return m_proficiencies[ profIndex ].currentExp;
		}
		else
		{
			return -1;
		}
	}

	public const function GetTotalProfExperience( type : gamedataProficiencyType ) : Int32
	{
		var i : Int32;
		var maxLvl : Int32;
		var totalExp : Int32;
		var pIndex : Int32;
		var curvName : CName;
		var colName : CName;
		var statDataSys : StatsDataSystem;
		statDataSys = GameInstance.GetStatsDataSystem( m_owner.GetGame() );
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex < 0 )
		{
			return -1;
		}
		GetProficiencyExpCurveNames( type, curvName, colName );
		if( !( IsNameValid( curvName ) ) || !( IsNameValid( colName ) ) )
		{
			return -1;
		}
		maxLvl = GetProficiencyMaxLevel( type );
		for( i = 0; i <= maxLvl; i += 1 )
		{
			totalExp += ( ( Int32 )( statDataSys.GetValueFromCurve( curvName, ( ( Float )( i ) ), colName ) ) );
		}
		return totalExp;
	}

	public const function GetRemainingExpForLevelUp( type : gamedataProficiencyType ) : Int32
	{
		var pIndex : Int32;
		var exp : Int32;
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex >= 0 )
		{
			if( !( IsProficiencyMaxLvl( type ) ) )
			{
				exp = GetExperienceForNextLevel( type );
				return exp - m_proficiencies[ pIndex ].currentExp;
			}
			else
			{
				return -1;
			}
		}
		else
		{
			return -1;
		}
	}

	public const function GetDominatingCombatProficiency() : gamedataProficiencyType
	{
		var i : Int32;
		var profsToCheck : array< gamedataProficiencyType >;
		var highestLevel : Int32;
		var dominatingProf : gamedataProficiencyType;
		profsToCheck.PushBack( gamedataProficiencyType.StrengthSkill );
		profsToCheck.PushBack( gamedataProficiencyType.ReflexesSkill );
		profsToCheck.PushBack( gamedataProficiencyType.CoolSkill );
		profsToCheck.PushBack( gamedataProficiencyType.IntelligenceSkill );
		profsToCheck.PushBack( gamedataProficiencyType.TechnicalAbilitySkill );
		for( i = 0; i < m_proficiencies.Size(); i += 1 )
		{
			if( profsToCheck.Contains( m_proficiencies[ i ].type ) )
			{
				if( m_proficiencies[ i ].currentLevel > highestLevel )
				{
					highestLevel = m_proficiencies[ i ].currentLevel;
					dominatingProf = m_proficiencies[ i ].type;
				}
			}
		}
		return dominatingProf;
	}

	public const function GetHighestCompletedMinigameLevel() : Int32
	{
		return m_highestCompletedMinigameLevel;
	}

	public const function GetProficiencyRecordByIndex( index : Int32 ) : Proficiency_Record
	{
		var type : gamedataProficiencyType;
		type = m_proficiencies[ index ].type;
		return TweakDBInterface.GetProficiencyRecord( TDBID.Create( "Proficiencies." + EnumValueToString( "gamedataProficiencyType", ( ( Int32 )( type ) ) ) ) );
	}

	public function ReinitializeProficiencies()
	{
		SetProficiencies();
		UpdateProficiencyMaxLevels();
		SetDevelopmentPoints();
	}

	private function SetProficiencies()
	{
		var i : Int32;
		if( m_proficiencies.Size() == ( ( Int32 )( gamedataProficiencyType.Count ) ) )
		{
			return;
		}
		else
		{
			for( i = 0; i < ( ( Int32 )( gamedataProficiencyType.Count ) ); i += 1 )
			{
				if( GetProficiencyIndexByType( ( ( gamedataProficiencyType )( i ) ) ) < 0 )
				{
					AddProficiency( ( ( gamedataProficiencyType )( i ) ) );
				}
			}
		}
	}

	public const function GetProficiencyIndexByType( type : gamedataProficiencyType ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_proficiencies.Size(); i += 1 )
		{
			if( m_proficiencies[ i ].type == type )
			{
				return i;
			}
		}
		return -1;
	}

	public const function ResetAllProficienciesLevel()
	{
		var index, limit : Int32;
		for( index = 0, limit = ( ( Int32 )( gamedataProficiencyType.Count ) ); index < limit; index += 1 )
		{
			if( ( ( gamedataProficiencyType )( index ) ) != gamedataProficiencyType.StreetCred && ( ( gamedataProficiencyType )( index ) ) != gamedataProficiencyType.Level )
			{
				ResetProficiencyLevel( ( ( gamedataProficiencyType )( index ) ) );
			}
		}
	}

	private const function ResetProficiencyLevel( type : gamedataProficiencyType )
	{
		var pIndex : Int32;
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex < 0 )
		{
			return;
		}
		m_proficiencies[ pIndex ].currentLevel = m_startingLevel;
		m_proficiencies[ pIndex ].currentExp = m_startingExperience;
		SetProficiencyStat( type, m_startingLevel );
	}

	private const function GetProficiencyMaxLevel( type : gamedataProficiencyType ) : Int32
	{
		var pIndex : Int32;
		var absoluteMaxLevel : Int32;
		var attributeMaxLevel : Int32;
		var curveSetName : CName;
		var colName : CName;
		var proficiencyRec : Proficiency_Record;
		var attributeRec : Stat_Record;
		var attributeInt : Int32;
		var attributeType : gamedataStatType;
		var attributeValue : Int32;
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex < 0 )
		{
			return -1;
		}
		GetProficiencyExpCurveNames( type, curveSetName, colName );
		if( curveSetName == '' )
		{
			return -1;
		}
		proficiencyRec = RPGManager.GetProficiencyRecord( type );
		absoluteMaxLevel = proficiencyRec.MaxLevel();
		attributeRec = proficiencyRec.TiedAttribute();
		attributeInt = -1;
		if( attributeRec )
		{
			attributeInt = ( ( Int32 )( EnumValueFromString( "gamedataStatType", attributeRec.EnumName() ) ) );
		}
		if( attributeInt >= 0 )
		{
			attributeType = ( ( gamedataStatType )( attributeInt ) );
			attributeValue = m_attributes[ GetAttributeIndex( attributeType ) ].value;
			attributeMaxLevel = attributeValue;
			return Max( Min( absoluteMaxLevel, attributeMaxLevel ), 1 );
		}
		else
		{
			return absoluteMaxLevel;
		}
	}

	private const function GetProficiencyExpCurveNames( type : gamedataProficiencyType, out curvName : CName, out colName : CName )
	{
		var pIndex : Int32;
		var proficiencyRecord : weak< Proficiency_Record >;
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex < 0 )
		{
			curvName = '';
			return;
		}
		proficiencyRecord = RPGManager.GetProficiencyRecord( type );
		colName = proficiencyRecord.CurveName();
		curvName = proficiencyRecord.CurveSetName();
	}

	public const function ModifyProficiencyLevel( type : gamedataProficiencyType, optional isDebug : Bool, optional levelIncrease : Int32 )
	{
		var i : Int32;
		i = GetProficiencyIndexByType( type );
		if( i >= 0 )
		{
			ModifyProficiencyLevel( i, isDebug, levelIncrease );
			EvaluateTrait( type );
		}
		else
		{
		}
	}

	private const function ModifyProficiencyLevel( proficiencyIndex : Int32, isDebug : Bool, optional levelIncrease : Int32 )
	{
		var i : Int32;
		var Blackboard : IBlackboard;
		var level : LevelUpData;
		var statusEffectSys : StatusEffectSystem;
		var effects : array< StatusEffect >;
		var effectTags : array< CName >;
		if( levelIncrease == 0 )
		{
			levelIncrease = 1;
		}
		m_proficiencies[ proficiencyIndex ].currentLevel += levelIncrease;
		m_proficiencies[ proficiencyIndex ].currentExp = 0;
		m_proficiencies[ proficiencyIndex ].expToLevel = GetRemainingExpForLevelUp( m_proficiencies[ proficiencyIndex ].type );
		if( !( isDebug ) )
		{
			ModifyDevPoints( m_proficiencies[ proficiencyIndex ].type, m_proficiencies[ proficiencyIndex ].currentLevel );
		}
		level.lvl = m_proficiencies[ proficiencyIndex ].currentLevel;
		level.type = m_proficiencies[ proficiencyIndex ].type;
		level.perkPoints = GetDevPoints( gamedataDevelopmentPointType.Primary );
		level.attributePoints = GetDevPoints( gamedataDevelopmentPointType.Attribute );
		level.espionagePoints = GetDevPoints( gamedataDevelopmentPointType.Espionage );
		SetProficiencyStat( m_proficiencies[ proficiencyIndex ].type, m_proficiencies[ proficiencyIndex ].currentLevel );
		ProcessProficiencyPassiveBonus( proficiencyIndex );
		Blackboard = GameInstance.GetBlackboardSystem( m_owner.GetGame() ).Get( GetAllBlackboardDefs().UI_LevelUp );
		if( Blackboard && ( m_owner == GameInstance.GetPlayerSystem( m_owner.GetGame() ).GetLocalPlayerMainGameObject() ) )
		{
			Blackboard.SetVariant( GetAllBlackboardDefs().UI_LevelUp.level, level );
			Blackboard.SignalVariant( GetAllBlackboardDefs().UI_LevelUp.level );
		}
		SetAchievementProgress( proficiencyIndex );
		if( m_proficiencies[ proficiencyIndex ].currentLevel == RPGManager.GetProficiencyRecord( m_proficiencies[ proficiencyIndex ].type ).MaxLevel() )
		{
			if( m_proficiencies[ proficiencyIndex ].type == gamedataProficiencyType.StreetCred )
			{
				SendMaxStreetCredLevelReachedTrackingRequest();
			}
			else if( m_proficiencies[ proficiencyIndex ].type != gamedataProficiencyType.Level && m_proficiencies[ proficiencyIndex ].type != gamedataProficiencyType.Espionage )
			{
				CheckSpecialistAchievement( proficiencyIndex );
			}
		}
		if( m_proficiencies[ proficiencyIndex ].type == gamedataProficiencyType.Level )
		{
			ProcessTutorialFacts();
			if( GameInstance.GetStatsDataSystem( m_owner.GetGame() ).GetDifficulty() == gameDifficulty.Story )
			{
				GameInstance.GetStatPoolsSystem( m_owner.GetGame() ).RequestSettingStatPoolValue( m_owner.GetEntityID(), gamedataStatPoolType.Health, 100.0, m_owner );
				statusEffectSys = GameInstance.GetStatusEffectSystem( m_owner.GetGame() );
				statusEffectSys.GetAppliedEffects( m_owner.GetEntityID(), effects );
				for( i = 0; i < effects.Size(); i += 1 )
				{
					effectTags = effects[ i ].GetRecord().GameplayTags();
					if( ( effects[ i ].GetRemainingDuration() > 0.0 ) && effectTags.Contains( 'Debuff' ) )
					{
						statusEffectSys.RemoveStatusEffect( m_owner.GetEntityID(), effects[ i ].GetRecord().GetID(), effects[ i ].GetStackCount() );
					}
				}
			}
		}
	}

	private const function ProcessTutorialFacts()
	{
		var questSys : QuestsSystem;
		questSys = GameInstance.GetQuestsSystem( m_owner.GetGame() );
		if( ( questSys.GetFact( 'levelup_tutorial' ) == 0 ) && ( questSys.GetFact( 'disable_tutorials' ) == 0 ) )
		{
			questSys.SetFact( 'levelup_tutorial', 1 );
		}
	}

	private const function SendMaxStreetCredLevelReachedTrackingRequest()
	{
		var achievementRequest : AddAchievementRequest;
		var dataTrackingSystem : DataTrackingSystem;
		var achievement : gamedataAchievement;
		achievement = gamedataAchievement.YouKnowWhoIAm;
		dataTrackingSystem = ( ( DataTrackingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'DataTrackingSystem' ) ) );
		achievementRequest = new AddAchievementRequest;
		achievementRequest.achievement = achievement;
		dataTrackingSystem.QueueRequest( achievementRequest );
	}

	public const function CheckSpecialistAchievement( index : Int32 )
	{
		var achievementRequest : AddAchievementRequest;
		var dataTrackingSystem : DataTrackingSystem;
		var achievement : gamedataAchievement;
		achievement = gamedataAchievement.Specialist;
		dataTrackingSystem = ( ( DataTrackingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'DataTrackingSystem' ) ) );
		achievementRequest = new AddAchievementRequest;
		achievementRequest.achievement = achievement;
		dataTrackingSystem.QueueRequest( achievementRequest );
	}

	public const function CheckRelicMasterAchievement()
	{
		var achievementRequest : SetAchievementProgressRequest;
		var dataTrackingSystem : DataTrackingSystem;
		var i : Int32;
		achievementRequest = new SetAchievementProgressRequest;
		dataTrackingSystem = ( ( DataTrackingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'DataTrackingSystem' ) ) );
		achievementRequest.achievement = gamedataAchievement.RelicMaster;
		for( i = 0; i < m_attributesData[ ( ( Int32 )( gamedataAttributeDataType.EspionageAttributeData ) ) ].unlockedPerks.Size(); i += 1 )
		{
			if( m_attributesData[ ( ( Int32 )( gamedataAttributeDataType.EspionageAttributeData ) ) ].unlockedPerks[ i ].currLevel > 0 )
			{
				achievementRequest.currentValue += 1;
			}
		}
		dataTrackingSystem.QueueRequest( achievementRequest );
	}

	private const function SetAchievementProgress( index : Int32 )
	{
		var setAchievementRequest : SetAchievementProgressRequest;
		var dataTrackingSystem : DataTrackingSystem;
		var achievement : gamedataAchievement;
		setAchievementRequest = new SetAchievementProgressRequest;
		dataTrackingSystem = ( ( DataTrackingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'DataTrackingSystem' ) ) );
		if( m_proficiencies[ index ].type == gamedataProficiencyType.StreetCred )
		{
			achievement = gamedataAchievement.YouKnowWhoIAm;
		}
		else if( ( m_proficiencies[ index ].type != gamedataProficiencyType.StreetCred && m_proficiencies[ index ].type != gamedataProficiencyType.Level ) && m_proficiencies[ index ].type != gamedataProficiencyType.Espionage )
		{
			achievement = gamedataAchievement.Specialist;
		}
		else
		{
			return;
		}
		setAchievementRequest.achievement = achievement;
		setAchievementRequest.currentValue = m_proficiencies[ index ].currentLevel;
		dataTrackingSystem.QueueRequest( setAchievementRequest );
	}

	private const function IsProficiencyStatAdded( type : gamedataProficiencyType ) : Bool
	{
		var statString : String;
		var statType : gamedataStatType;
		var gi : GameInstance;
		gi = m_owner.GetGame();
		statString = EnumValueToString( "gamedataProficiencyType", ( ( Int32 )( type ) ) );
		statType = ( ( gamedataStatType )( ( ( Int32 )( EnumValueFromString( "gamedataStatType", statString ) ) ) ) );
		return GameInstance.GetStatsSystem( gi ).GetStatBoolValue( m_owner.GetEntityID(), statType );
	}

	private const function SetProficiencyStat( type : gamedataProficiencyType, level : Int32 )
	{
		var statString : String;
		var statType : gamedataStatType;
		var newMod : gameStatModifierData;
		statString = EnumValueToString( "gamedataProficiencyType", ( ( Int32 )( type ) ) );
		statType = ( ( gamedataStatType )( ( ( Int32 )( EnumValueFromString( "gamedataStatType", statString ) ) ) ) );
		newMod = RPGManager.CreateStatModifier( statType, gameStatModifierType.Additive, ( ( Float )( level ) ) );
		GameInstance.GetStatsSystem( m_owner.GetGame() ).ForceModifier( m_owner.GetEntityID(), newMod );
	}

	private const function ProcessProficiencyPassiveBonus( profIndex : Int32 )
	{
		var effectorRecord : Effector_Record;
		var bonusRecord : PassiveProficiencyBonus_Record;
		if( GetProficiencyRecordByIndex( profIndex ).GetPassiveBonusesCount() > 0 )
		{
			bonusRecord = GetProficiencyRecordByIndex( profIndex ).GetPassiveBonusesItem( m_proficiencies[ profIndex ].currentLevel - 1 );
			effectorRecord = bonusRecord.EffectorToTrigger();
			if( effectorRecord )
			{
				GameInstance.GetEffectorSystem( m_owner.GetGame() ).ApplyEffector( m_owner.GetEntityID(), m_owner, effectorRecord.GetID() );
			}
		}
	}

	private const function RestoreProficiencyPassiveBonuses( profIndex : Int32, gameInstance : GameInstance )
	{
		var effectorRecord : Effector_Record;
		var bonusRecord : PassiveProficiencyBonus_Record;
		var proficiencyRecord : Proficiency_Record;
		var effectorSystem : EffectorSystem;
		var i, maxLevel : Int32;
		proficiencyRecord = GetProficiencyRecordByIndex( profIndex );
		effectorSystem = GameInstance.GetEffectorSystem( gameInstance );
		maxLevel = proficiencyRecord.GetPassiveBonusesCount();
		for( i = 0; i < m_proficiencies[ profIndex ].currentLevel; i += 1 )
		{
			if( i >= maxLevel )
			{
				return;
			}
			bonusRecord = proficiencyRecord.GetPassiveBonusesItem( i );
			effectorRecord = bonusRecord.EffectorToTrigger();
			if( effectorRecord && !( effectorRecord.IsA( 'gamedataAddDevelopmentPointEffector_Record' ) ) )
			{
				effectorSystem.ApplyEffector( m_ownerID, m_owner, effectorRecord.GetID() );
			}
		}
	}

	private const function RefreshProficiencyStats()
	{
		var i : Int32;
		var pIndex : Int32;
		var profType : gamedataProficiencyType;
		for( i = 0; i < ( ( Int32 )( gamedataProficiencyType.Count ) ); i += 1 )
		{
			profType = ( ( gamedataProficiencyType )( i ) );
			pIndex = GetProficiencyIndexByType( profType );
			if( profType != gamedataProficiencyType.Level && ( pIndex >= 0 ) )
			{
				SetProficiencyStat( profType, m_proficiencies[ pIndex ].currentLevel );
			}
		}
	}

	private const function UpdateProficiencyMaxLevels()
	{
		var i : Int32;
		for( i = 0; i < m_proficiencies.Size(); i += 1 )
		{
			m_proficiencies[ i ].maxLevel = GetProficiencyMaxLevel( m_proficiencies[ i ].type );
		}
	}

	private function AddProficiency( type : gamedataProficiencyType )
	{
		var newProf : SProficiency;
		newProf.type = type;
		newProf.currentLevel = m_startingLevel;
		newProf.currentExp = m_startingExperience;
		newProf.maxLevel = GetProficiencyMaxLevel( type );
		newProf.expToLevel = GetRemainingExpForLevelUp( type );
		m_proficiencies.PushBack( newProf );
	}

	public const function AddExperience( amount : Int32, type : gamedataProficiencyType, telemetryGainReason : telemetryLevelGainReason, optional isDebug : Bool )
	{
		var pIndex : Int32;
		var reqExp : Int32;
		var awardedAmount : Int32;
		var telemetryEvt : TelemetryLevelGained;
		var proficiencyProgress : ProficiencyProgressEvent;
		pIndex = GetProficiencyIndexByType( type );
		if( ( pIndex >= 0 ) && !( IsProficiencyMaxLvl( type ) ) )
		{
			while( ( amount > 0 ) && !( IsProficiencyMaxLvl( type ) ) )
			{
				reqExp = GetRemainingExpForLevelUp( type );
				if( ( amount - reqExp ) >= 0 )
				{
					awardedAmount += reqExp;
					amount -= reqExp;
					m_proficiencies[ pIndex ].currentExp += reqExp;
					m_proficiencies[ pIndex ].expToLevel = GetRemainingExpForLevelUp( type );
					if( CanGainNextProficiencyLevel( pIndex ) )
					{
						ModifyProficiencyLevel( type, isDebug );
						UpdateUIBB();
						if( m_owner.IsPlayerControlled() && telemetryGainReason != telemetryLevelGainReason.Ignore )
						{
							telemetryEvt.playerPuppet = m_owner;
							telemetryEvt.proficiencyType = type;
							telemetryEvt.proficiencyValue = m_proficiencies[ pIndex ].currentLevel;
							telemetryEvt.isDebugEvt = telemetryGainReason == telemetryLevelGainReason.IsDebug;
							telemetryEvt.perkPointsAwarded = GetDevPointsForLevel( m_proficiencies[ pIndex ].currentLevel, type, gamedataDevelopmentPointType.Primary );
							telemetryEvt.attributePointsAwarded = GetDevPointsForLevel( m_proficiencies[ pIndex ].currentLevel, type, gamedataDevelopmentPointType.Attribute );
							GameInstance.GetTelemetrySystem( m_owner.GetGame() ).LogLevelGained( telemetryEvt );
						}
					}
					else
					{
						return;
					}
				}
				else
				{
					m_proficiencies[ pIndex ].currentExp += amount;
					m_proficiencies[ pIndex ].expToLevel = GetRemainingExpForLevelUp( type );
					awardedAmount += amount;
					amount -= amount;
				}
			}
			if( awardedAmount > 0 )
			{
				if( m_displayActivityLog )
				{
					if( ( ( ( type == gamedataProficiencyType.StreetCred && ( GameInstance.GetQuestsSystem( m_owner.GetGame() ).GetFact( 'street_cred_tutorial' ) == 0 ) ) && ( GameInstance.GetQuestsSystem( m_owner.GetGame() ).GetFact( 'disable_tutorials' ) == 0 ) ) && telemetryGainReason == telemetryLevelGainReason.Gameplay ) && ( GameInstance.GetQuestsSystem( m_owner.GetGame() ).GetFact( 'q001_show_sts_tut' ) > 0 ) )
					{
						GameInstance.GetQuestsSystem( m_owner.GetGame() ).SetFact( 'street_cred_tutorial', 1 );
					}
				}
				proficiencyProgress = new ProficiencyProgressEvent;
				proficiencyProgress.type = type;
				proficiencyProgress.expValue = GetCurrentLevelProficiencyExp( type );
				proficiencyProgress.delta = awardedAmount;
				proficiencyProgress.remainingXP = GetRemainingExpForLevelUp( type );
				proficiencyProgress.currentLevel = GetProficiencyLevel( type );
				proficiencyProgress.isLevelMaxed = ( GetProficiencyLevel( type ) + 1 ) == GetProficiencyAbsoluteMaxLevel( type );
				GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( proficiencyProgress );
				if( type == gamedataProficiencyType.Level )
				{
					UpdatePlayerXP();
				}
			}
		}
	}

	private const function UpdatePlayerXP()
	{
		var m_ownerStatsBB : IBlackboard;
		var gi : GameInstance;
		gi = m_owner.GetGame();
		m_ownerStatsBB = GameInstance.GetBlackboardSystem( gi ).Get( GetAllBlackboardDefs().UI_PlayerStats );
		m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.CurrentXP, GetCurrentLevelProficiencyExp( gamedataProficiencyType.Level ), true );
	}

	public function QueueCombatExperience( amount : Float, type : gamedataProficiencyType, entity : EntityID )
	{
		var expPackage : SExperiencePoints;
		expPackage.amount = amount;
		expPackage.forType = type;
		expPackage.entity = entity;
		m_queuedCombatExp.PushBack( expPackage );
	}

	public function ProcessQueuedCombatExperience( entity : EntityID )
	{
		var i, j : Int32;
		var toRemove : array< Int32 >;
		var removeIndex : Int32;
		var expAwarded : Bool;
		var expAmount : Float;
		for( i = 0; i < ( ( Int32 )( gamedataProficiencyType.Count ) ); i += 1 )
		{
			expAwarded = false;
			expAmount = 0.0;
			for( j = 0; j < m_queuedCombatExp.Size(); j += 1 )
			{
				if( ( m_queuedCombatExp[ j ].entity == entity ) && ( ( ( Int32 )( m_queuedCombatExp[ j ].forType ) ) == i ) )
				{
					expAmount += m_queuedCombatExp[ j ].amount;
					expAwarded = true;
					toRemove.PushBack( j );
				}
			}
			for( j = 0; j < toRemove.Size(); j += 1 )
			{
				removeIndex = toRemove.PopBack();
				m_queuedCombatExp.Erase( removeIndex );
			}
			if( expAwarded )
			{
				AddExperience( ( ( Int32 )( expAmount ) ), ( ( gamedataProficiencyType )( i ) ), telemetryLevelGainReason.Gameplay );
			}
		}
	}

	private const function CanGainNextProficiencyLevel( pIndex : Int32 ) : Bool
	{
		if( pIndex >= 0 )
		{
			if( GetProficiencyMaxLevel( m_proficiencies[ pIndex ].type ) > m_proficiencies[ pIndex ].currentLevel )
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}

	private const function GetExperienceForNextLevel( type : gamedataProficiencyType ) : Int32
	{
		var pIndex : Int32;
		var val : Int32;
		var curveSetName : CName;
		var colName : CName;
		var statDataSys : StatsDataSystem;
		statDataSys = GameInstance.GetStatsDataSystem( m_owner.GetGame() );
		if( IsProficiencyMaxLvl( type ) )
		{
			return -1;
		}
		pIndex = GetProficiencyIndexByType( type );
		if( pIndex < 0 )
		{
			return -1;
		}
		GetProficiencyExpCurveNames( type, curveSetName, colName );
		if( curveSetName == '' || colName == '' )
		{
			return -1;
		}
		val = ( ( Int32 )( statDataSys.GetValueFromCurve( curveSetName, ( ( Float )( m_proficiencies[ pIndex ].currentLevel + 1 ) ), colName ) ) );
		return val;
	}

	public const function GetExperiencePercentage() : Int32
	{
		var pIndex : Int32;
		var maxExp : Int32;
		var expPerc : Int32;
		pIndex = GetProficiencyIndexByType( gamedataProficiencyType.Level );
		maxExp = m_proficiencies[ pIndex ].currentExp + m_proficiencies[ pIndex ].expToLevel;
		expPerc = ( m_proficiencies[ pIndex ].currentExp * 100 ) / maxExp;
		return expPerc;
	}

	private const function AddLevels( type : gamedataProficiencyType, optional amount : Int32 )
	{
		var pIndex : Int32;
		var i : Int32;
		pIndex = GetProficiencyIndexByType( type );
		if( amount == 0 )
		{
			amount = 1;
		}
		if( pIndex >= 0 )
		{
			for( i = 0; i <= amount; i += 1 )
			{
				AddExperience( GetRemainingExpForLevelUp( type ), type, telemetryLevelGainReason.IsDebug );
			}
		}
	}

	public const function SetLevel( type : gamedataProficiencyType, lvl : Int32, telemetryGainReason : telemetryLevelGainReason, optional isDebug : Bool )
	{
		var pIndex : Int32;
		var i : Int32;
		var toAdd : Int32;
		var tempGainReason : telemetryLevelGainReason;
		pIndex = GetProficiencyIndexByType( type );
		if( ( pIndex >= 0 ) && ( m_proficiencies[ pIndex ].currentLevel != lvl ) )
		{
			if( lvl < m_proficiencies[ pIndex ].currentLevel )
			{
				ResetProficiencyLevel( type );
			}
			toAdd = lvl - m_proficiencies[ pIndex ].currentLevel;
			for( i = 0; i < toAdd; i += 1 )
			{
				tempGainReason = ( ( i == ( toAdd - 1 ) ) ? ( telemetryGainReason ) : ( telemetryLevelGainReason.Ignore ) );
				AddExperience( GetRemainingExpForLevelUp( type ), type, tempGainReason, isDebug );
			}
		}
	}

	public function BumpNetrunnerMinigameLevel( value : Int32 )
	{
		if( value > m_highestCompletedMinigameLevel )
		{
			m_highestCompletedMinigameLevel = value;
		}
	}

	public const function IsProficiencyMaxLvl( type : gamedataProficiencyType ) : Bool
	{
		var pIndex : Int32;
		pIndex = GetProficiencyIndexByType( type );
		if( ( pIndex != -1 ) && ( m_proficiencies[ pIndex ].currentLevel == GetProficiencyMaxLevel( type ) ) )
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	public const function GetDevPoints( type : gamedataDevelopmentPointType ) : Int32
	{
		return m_devPoints[ GetDevPointsIndex( type ) ].unspent;
	}

	private const function GetDevPointsIndex( type : gamedataDevelopmentPointType ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_devPoints.Size(); i += 1 )
		{
			if( m_devPoints[ i ].type == type )
			{
				return i;
			}
		}
		return -1;
	}

	private function SetDevelopmentPoints()
	{
		var i : Int32;
		var devPts : SDevelopmentPoints;
		for( i = 0; i < ( ( Int32 )( gamedataDevelopmentPointType.Count ) ); i += 1 )
		{
			if( GetDevPointsIndex( ( ( gamedataDevelopmentPointType )( i ) ) ) < 0 )
			{
				devPts.type = ( ( gamedataDevelopmentPointType )( i ) );
				devPts.spent = 0;
				devPts.unspent = 0;
				m_devPoints.PushBack( devPts );
			}
		}
	}

	public const function ModifyDevPoints( type : gamedataProficiencyType, level : Int32 )
	{
		var i : Int32;
		var val : Int32;
		val = 0;
		if( type == gamedataProficiencyType.Espionage )
		{
			val = GetDevPointsForLevel( level, gamedataProficiencyType.Espionage, gamedataDevelopmentPointType.Espionage );
			AddDevelopmentPoints( val, gamedataDevelopmentPointType.Espionage );
		}
		else
		{
			for( i = 0; i <= ( ( Int32 )( gamedataDevelopmentPointType.Count ) ); i += 1 )
			{
				val = GetDevPointsForLevel( level, type, ( ( gamedataDevelopmentPointType )( i ) ) );
				if( val > 0 )
				{
					AddDevelopmentPoints( val, ( ( gamedataDevelopmentPointType )( i ) ) );
				}
			}
		}
	}

	private const function GetDevPointsForLevel( level : Int32, profType : gamedataProficiencyType, devPtsType : gamedataDevelopmentPointType ) : Int32
	{
		var curve : CName;
		var col : CName;
		var dIndex : Int32;
		var statDataSys : StatsDataSystem;
		var awardFloat : Float;
		statDataSys = GameInstance.GetStatsDataSystem( m_owner.GetGame() );
		dIndex = GetDevPointsIndex( devPtsType );
		if( dIndex < 0 )
		{
			return -1;
		}
		GetProficiencyExpCurveNames( profType, curve, col );
		if( curve == '' || col == '' )
		{
			return -1;
		}
		if( profType == gamedataProficiencyType.Level )
		{
			if( devPtsType == gamedataDevelopmentPointType.Espionage )
			{
				return -1;
			}
			if( devPtsType == gamedataDevelopmentPointType.Attribute && ( ( level % 2 ) == 0 ) )
			{
				return 1;
			}
			awardFloat = statDataSys.GetValueFromCurve( 'player_levelToXP', ( ( Float )( level ) ), 'perk_point_award_from_level' );
			return ( ( Int32 )( awardFloat ) );
		}
		else if( profType == gamedataProficiencyType.Espionage && devPtsType == gamedataDevelopmentPointType.Espionage )
		{
			return 1;
		}
		return -1;
	}

	public const function AddDevelopmentPoints( amount : Int32, type : gamedataDevelopmentPointType )
	{
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( type );
		if( dIndex < 0 )
		{
			return;
		}
		m_devPoints[ dIndex ].unspent += amount;
	}

	private const function SpendDevelopmentPoint( type : gamedataDevelopmentPointType )
	{
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( type );
		if( dIndex < 0 )
		{
			return;
		}
		m_devPoints[ dIndex ].unspent -= 1;
		m_devPoints[ dIndex ].spent += 1;
	}

	private const function SpendDevelopmentPoint( type : gamedataDevelopmentPointType, amount : Int32 )
	{
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( type );
		if( dIndex < 0 )
		{
			return;
		}
		m_devPoints[ dIndex ].unspent -= amount;
		m_devPoints[ dIndex ].spent += amount;
	}

	private const function ResetDevelopmentPoints( type : gamedataDevelopmentPointType )
	{
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( type );
		if( dIndex < 0 )
		{
			return;
		}
		m_devPoints[ dIndex ].unspent += m_devPoints[ dIndex ].spent;
		m_devPoints[ dIndex ].spent = 0;
	}

	public const function ResetAllDevPoints()
	{
		var i : Int32;
		for( i = 1; i < m_devPoints.Size(); i += 1 )
		{
			ResetDevelopmentPoints( ( ( gamedataDevelopmentPointType )( i ) ) );
		}
	}

	public const function SetDevelopmentsPoint( type : gamedataDevelopmentPointType, value : Int32 )
	{
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( type );
		if( dIndex < 0 )
		{
			return;
		}
		m_devPoints[ dIndex ].unspent = value;
		m_devPoints[ dIndex ].spent = 0;
	}

	public const function ClearAllDevPoints()
	{
		var i : Int32;
		for( i = 0; i < m_devPoints.Size(); i += 1 )
		{
			m_devPoints[ i ].spent = 0;
			m_devPoints[ i ].unspent = 0;
		}
	}

	private function InitializeAttributesData()
	{
		var i : Int32;
		var attributeData : SAttributeData;
		for( i = 0; i < ( ( Int32 )( gamedataAttributeDataType.Count ) ); i += 1 )
		{
			attributeData.type = ( ( gamedataAttributeDataType )( i ) );
			attributeData.unlockedPerks.Clear();
			m_attributesData.PushBack( attributeData );
			UnlockFreeNewPerks( attributeData.type );
		}
	}

	public function GetAttributePoints( attributeDataType : gamedataAttributeDataType ) : Int32
	{
		var statType : gamedataStatType;
		statType = AttributeDataTypeToStatType( attributeDataType );
		return m_attributes[ GetAttributeIndex( statType ) ].value;
	}

	public function HasEnoughtAttributePoints( perkType : gamedataNewPerkType ) : Bool
	{
		var newPerkRec : NewPerk_Record;
		var tierRec : weak< NewPerkTier_Record >;
		var requiredPoints : Int32;
		var currentPoints : Int32;
		newPerkRec = RPGManager.GetNewPerkRecord( perkType );
		tierRec = newPerkRec.Tier();
		requiredPoints = tierRec.RequiredAttributePoints();
		currentPoints = GetAttributePoints( newPerkRec.Attribute().Type() );
		return currentPoints >= requiredPoints;
	}

	public function UnlockNewPerk( perkType : gamedataNewPerkType ) : Bool
	{
		var perk : SNewPerk;
		var perkRec : NewPerk_Record;
		var attributeDataType : gamedataAttributeDataType;
		var i, j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			return false;
		}
		else
		{
			perk.type = perkType;
			perk.currLevel = 0;
			perkRec = RPGManager.GetNewPerkRecord( perkType );
			attributeDataType = perkRec.Attribute().Type();
			m_attributesData[ ( ( Int32 )( attributeDataType ) ) ].unlockedPerks.PushBack( perk );
			return true;
		}
	}

	private const function FindNewPerk( perkType : gamedataNewPerkType, out i : Int32, out j : Int32 ) : Bool
	{
		var attributeData : SAttributeData;
		for( i = 0; i < m_attributesData.Size(); i += 1 )
		{
			attributeData = m_attributesData[ i ];
			for( j = 0; j < attributeData.unlockedPerks.Size(); j += 1 )
			{
				if( attributeData.unlockedPerks[ j ].type == perkType )
				{
					return true;
				}
			}
		}
		return false;
	}

	public function LockNewPerk( perkType : gamedataNewPerkType ) : Bool
	{
		var i : Int32;
		var j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			m_attributesData[ i ].unlockedPerks.EraseFast( j );
			return true;
		}
		return false;
	}

	public const function IsNewPerkUnlocked( perkType : gamedataNewPerkType ) : Bool
	{
		var i : Int32;
		var j : Int32;
		return FindNewPerk( perkType, i, j );
	}

	private const function GetNewPerkGLPackageTDBID( perkType : gamedataNewPerkType, perkLevel : Int32 ) : TweakDBID
	{
		var perkRec : NewPerk_Record;
		var levelData : weak< NewPerkLevelData_Record >;
		var gLPRec : weak< GameplayLogicPackage_Record >;
		perkRec = RPGManager.GetNewPerkRecord( perkType );
		if( perkRec.GetLevelsCount() <= perkLevel )
		{
			return TDBID.None();
		}
		levelData = perkRec.GetLevelsItem( perkLevel );
		gLPRec = levelData.DataPackage();
		return gLPRec.GetID();
	}

	private const function ActivateNewPerk( perkType : gamedataNewPerkType, perkLevel : Int32 )
	{
		var packageID : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		packageID = GetNewPerkGLPackageTDBID( perkType, perkLevel );
		GLPS.ApplyPackage( m_owner, m_owner, packageID );
	}

	private const function DeactivateNewPerk( perkType : gamedataNewPerkType, perkLevel : Int32 )
	{
		var packageID : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		packageID = GetNewPerkGLPackageTDBID( perkType, perkLevel );
		GLPS.RemovePackage( m_owner, packageID );
	}

	public const function GetPerkAttribute( perkType : gamedataNewPerkType ) : weak< AttributeData_Record >
	{
		var perkRec : NewPerk_Record;
		perkRec = RPGManager.GetNewPerkRecord( perkType );
		return perkRec.Attribute();
	}

	public function UnlockFreeNewPerks( attributeDataType : gamedataAttributeDataType )
	{
		var i, j, jLimit : Int32;
		var attribute : weak< AttributeData_Record >;
		var perks : array< weak< NewPerk_Record > >;
		var checkPerk : weak< NewPerk_Record >;
		var requiredPerks : array< weak< NewPerk_Record > >;
		var previousPerksUnlocked : Bool;
		var currLevel, maxLevel : Int32;
		attribute = RPGManager.GetAttributeDataRecord( attributeDataType );
		attribute.Perks( perks );
		for( i = 0; i < perks.Size(); i += 1 )
		{
			checkPerk = perks[ i ];
			requiredPerks.Clear();
			checkPerk.RequiresPerks( requiredPerks );
			previousPerksUnlocked = true;
			for( j = 0, jLimit = requiredPerks.Size(); j < jLimit; j += 1 )
			{
				currLevel = IsNewPerkBought( requiredPerks[ j ].Type() );
				maxLevel = GetNewPerkMaxLevel( requiredPerks[ j ].Type() );
				previousPerksUnlocked = ( currLevel == maxLevel ) && IsNewPerkUnlocked( requiredPerks[ j ].Type() );
				if( !( previousPerksUnlocked ) )
				{
					break;
				}
			}
			if( ( previousPerksUnlocked || ( checkPerk.GetRequiresPerksCount() == 0 ) ) && HasEnoughtAttributePoints( checkPerk.Type() ) )
			{
				UnlockNewPerk( checkPerk.Type() );
			}
		}
	}

	public function GetUnlockedPerkList( perkType : gamedataNewPerkType, out unlockedPerks : array< gamedataNewPerkType > )
	{
		var i, j, k : Int32;
		var attribute : weak< AttributeData_Record >;
		var attributeData : SAttributeData;
		var perks : array< weak< NewPerk_Record > >;
		var checkPerk : weak< NewPerk_Record >;
		var requiredPerks : array< weak< NewPerk_Record > >;
		var found : Bool;
		attribute = GetPerkAttribute( perkType );
		attribute.Perks( perks );
		for( i = 0; i < perks.Size(); i += 1 )
		{
			checkPerk = perks[ i ];
			requiredPerks.Clear();
			checkPerk.RequiresPerks( requiredPerks );
			found = false;
			for( j = 0; ( j < requiredPerks.Size() ) && !( found ); j += 1 )
			{
				if( requiredPerks[ j ].Type() == perkType )
				{
					found = true;
				}
			}
			if( found )
			{
				if( requiredPerks.Size() == 1 )
				{
					unlockedPerks.PushBack( checkPerk.Type() );
				}
				else
				{
					attributeData = m_attributesData[ ( ( Int32 )( attribute.Type() ) ) ];
					for( j = 0; ( j < requiredPerks.Size() ) && found; j += 1 )
					{
						found = false;
						for( k = 0; ( k < attributeData.unlockedPerks.Size() ) && !( found ); k += 1 )
						{
							if( attributeData.unlockedPerks[ k ].type == requiredPerks[ j ].Type() )
							{
								if( ( attributeData.unlockedPerks[ k ].currLevel > 0 ) && ( attributeData.unlockedPerks[ k ].currLevel == GetNewPerkMaxLevel( attributeData.unlockedPerks[ k ].type ) ) )
								{
									found = true;
								}
							}
						}
					}
					if( found )
					{
						unlockedPerks.PushBack( checkPerk.Type() );
					}
				}
			}
		}
	}

	private const function CanNewPerkBeBought( perkType : gamedataNewPerkType, isEspionagePerk : Bool, isEspionageMilestonePerk : Bool ) : Bool
	{
		var i, j, k, kLimit : Int32;
		var primDevIndex : Int32;
		var requiredPerks : array< weak< NewPerk_Record > >;
		var newPerkRecord : weak< NewPerk_Record >;
		var menuNotification : UIMenuNotificationEvent;
		if( !( FindNewPerk( perkType, i, j ) ) )
		{
			return false;
		}
		if( GetNewPerkMaxLevel( perkType ) <= m_attributesData[ i ].unlockedPerks[ j ].currLevel )
		{
			menuNotification = new UIMenuNotificationEvent;
			menuNotification.m_notificationType = UIMenuNotificationType.MaxLevelPerks;
			GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( menuNotification );
			return false;
		}
		primDevIndex = GetDevPointsIndex( ( ( isEspionagePerk ) ? ( gamedataDevelopmentPointType.Espionage ) : ( gamedataDevelopmentPointType.Primary ) ) );
		if( primDevIndex < 0 )
		{
			return false;
		}
		if( ( m_devPoints[ primDevIndex ].unspent <= 0 ) || ( isEspionageMilestonePerk && ( m_devPoints[ primDevIndex ].unspent < 3 ) ) )
		{
			menuNotification = new UIMenuNotificationEvent;
			menuNotification.m_notificationType = UIMenuNotificationType.NoPerksPoints;
			GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( menuNotification );
			return false;
		}
		newPerkRecord = RPGManager.GetNewPerkRecord( perkType );
		newPerkRecord.RequiresPerks( requiredPerks );
		for( k = 0, kLimit = requiredPerks.Size(); k < kLimit; k += 1 )
		{
			if( FindNewPerk( requiredPerks[ k ].Type(), i, j ) )
			{
				if( m_attributesData[ i ].unlockedPerks[ j ].currLevel < GetNewPerkMaxLevel( m_attributesData[ i ].unlockedPerks[ j ].type ) )
				{
					return false;
				}
			}
		}
		return true;
	}

	public function CheckIfAllnewPerkParentSold( perkType : gamedataNewPerkType ) : Bool
	{
		var i, j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			return CheckIfAllNewPerkParentSold( i, m_attributesData[ i ].unlockedPerks[ j ] );
		}
		return false;
	}

	private function CheckIfAllNewPerkParentSold( attributeIdx : Int32, perkRecord : ref< SNewPerk > ) : Bool
	{
		var i, j, jLimit : Int32;
		var requiredPerks : array< weak< NewPerk_Record > >;
		var newPerkRecord : weak< NewPerk_Record >;
		var attributeData : SAttributeData;
		attributeData = m_attributesData[ attributeIdx ];
		for( i = 0; i < attributeData.unlockedPerks.Size(); i += 1 )
		{
			requiredPerks.Clear();
			newPerkRecord = RPGManager.GetNewPerkRecord( attributeData.unlockedPerks[ i ].type );
			newPerkRecord.RequiresPerks( requiredPerks );
			for( j = 0, jLimit = requiredPerks.Size(); j < jLimit; j += 1 )
			{
				if( requiredPerks[ j ].Type() == perkRecord.type && ( attributeData.unlockedPerks[ i ].currLevel > 0 ) )
				{
					return false;
				}
			}
		}
		return true;
	}

	private function LockAllNewPerkParents( attributeIdx : Int32, perkRecord : ref< SNewPerk > )
	{
		var i, j, jLimit : Int32;
		var requiredPerks : array< weak< NewPerk_Record > >;
		var newPerkRecord : weak< NewPerk_Record >;
		var attributeData : SAttributeData;
		var newPerkLockedEvent : NewPerkLockedEvent;
		attributeData = m_attributesData[ attributeIdx ];
		for( i = 0; i < attributeData.unlockedPerks.Size(); i += 1 )
		{
			requiredPerks.Clear();
			newPerkRecord = RPGManager.GetNewPerkRecord( attributeData.unlockedPerks[ i ].type );
			newPerkRecord.RequiresPerks( requiredPerks );
			for( j = 0, jLimit = requiredPerks.Size(); j < jLimit; j += 1 )
			{
				if( requiredPerks[ j ].Type() == perkRecord.type )
				{
					LockNewPerk( attributeData.unlockedPerks[ i ].type );
					newPerkLockedEvent = new NewPerkLockedEvent;
					newPerkLockedEvent.perkType = attributeData.unlockedPerks[ i ].type;
					GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( newPerkLockedEvent );
				}
			}
		}
	}

	public function BuyNewPerk( perkType : gamedataNewPerkType, optional forceBuy : Bool ) : Bool
	{
		var i, j, currlevel : Int32;
		var canBeBought : Bool;
		var isEspionagePerk : Bool;
		var isEspionageMilestonePerk : Bool;
		var devPoints : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			isEspionagePerk = m_attributesData[ i ].type == gamedataAttributeDataType.EspionageAttributeData;
			isEspionageMilestonePerk = IsEspionageMilestonePerk( m_attributesData[ i ].unlockedPerks[ j ].type );
			canBeBought = forceBuy || CanNewPerkBeBought( perkType, isEspionagePerk, isEspionageMilestonePerk );
			if( canBeBought )
			{
				currlevel = m_attributesData[ i ].unlockedPerks[ j ].currLevel;
				m_attributesData[ i ].unlockedPerks[ j ].currLevel += 1;
				ActivateNewPerk( perkType, currlevel );
				HandleAddingPerkLevel( i, j );
				if( !( forceBuy ) )
				{
					if( isEspionagePerk )
					{
						devPoints = ( ( isEspionageMilestonePerk ) ? ( 3 ) : ( 1 ) );
						SpendDevelopmentPoint( gamedataDevelopmentPointType.Espionage, devPoints );
						CheckRelicMasterAchievement();
					}
					else
					{
						SpendDevelopmentPoint( gamedataDevelopmentPointType.Primary );
					}
				}
				return true;
			}
		}
		return false;
	}

	public function SellNewPerk( perkType : gamedataNewPerkType, out perkLevelSold : Int32 ) : Bool
	{
		var i, j : Int32;
		var previousPerksSold : Bool;
		perkLevelSold = 0;
		if( FindNewPerk( perkType, i, j ) )
		{
			previousPerksSold = CheckIfAllNewPerkParentSold( i, m_attributesData[ i ].unlockedPerks[ j ] );
			if( previousPerksSold && ( m_attributesData[ i ].unlockedPerks[ j ].currLevel > 0 ) )
			{
				LockAllNewPerkParents( i, m_attributesData[ i ].unlockedPerks[ j ] );
				FindNewPerk( perkType, i, j );
				perkLevelSold = HandleRemovingPerkLevel( i, j );
				return true;
			}
		}
		return false;
	}

	public function ForceSellNewPerk( perkType : gamedataNewPerkType, out perkLevelSold : Int32 ) : Bool
	{
		var i, j : Int32;
		perkLevelSold = 0;
		if( FindNewPerk( perkType, i, j ) )
		{
			if( m_attributesData[ i ].unlockedPerks[ j ].currLevel > 0 )
			{
				perkLevelSold = HandleRemovingPerkLevel( i, j );
				return true;
			}
		}
		return false;
	}

	private function HandleAddingPerkLevel( i : Int32, j : Int32 )
	{
		if( m_attributesData[ i ].unlockedPerks[ j ].type == gamedataNewPerkType.Tech_Central_Milestone_3 && ( m_attributesData[ i ].unlockedPerks[ j ].currLevel == 3 ) )
		{
			PowerUpCyberwareEffector.PowerUpCyberwareInSlot( m_owner, gamedataEquipmentArea.MusculoskeletalSystemCW, 0 );
			PowerUpCyberwareEffector.PowerUpCyberwareInSlot( m_owner, gamedataEquipmentArea.MusculoskeletalSystemCW, 1 );
			PowerUpCyberwareEffector.PowerUpCyberwareInSlot( m_owner, gamedataEquipmentArea.MusculoskeletalSystemCW, 2 );
		}
	}

	private function HandleRemovingTech_Central_Milestone_3( playerData : EquipmentSystemPlayerData )
	{
		var transactionSystem : TransactionSystem;
		var itemList : array< weak< gameItemData > >;
		var slotCount : Int32;
		var itemID : ItemID;
		var i : Int32;
		var j : Int32;
		var equipRequest : EquipRequest;
		var partData : InnerItemData;
		var statsShardSlotTDBID : TweakDBID;
		var rngSeed : Uint32;
		transactionSystem = GameInstance.GetTransactionSystem( m_owner.GetGame() );
		slotCount = playerData.GetNumberOfSlots( gamedataEquipmentArea.MusculoskeletalSystemCW );
		statsShardSlotTDBID = T"AttachmentSlots.StatsShardSlot";
		if( PlayerDevelopmentSystem.GetData( m_owner ).IsNewPerkBought( gamedataNewPerkType.Tech_Central_Milestone_3 ) < 2 )
		{
			return;
		}
		transactionSystem.GetItemList( m_owner, itemList );
		for( i = 0; i < slotCount; i += 1 )
		{
			itemID = playerData.GetItemInEquipSlot( gamedataEquipmentArea.MusculoskeletalSystemCW, i );
			if( !( ItemID.IsValid( itemID ) ) )
			{
				continue;
			}
			EquipmentSystem.RequestUnequipItem( m_owner, gamedataEquipmentArea.MusculoskeletalSystemCW, i );
			for( j = 0; j < itemList.Size(); j += 1 )
			{
				if( ( ( itemList[ j ].GetID() != itemID ) && TDB.GetItemRecord( ItemID.GetTDBID( itemList[ j ].GetID() ) ).Quality().Type() == TDB.GetItemRecord( ItemID.GetTDBID( itemID ) ).Quality().Type() ) && TDB.GetCName( ItemID.GetTDBID( itemList[ j ].GetID() ) + T".cyberwareType" ) == TDB.GetCName( ItemID.GetTDBID( itemID ) + T".cyberwareType" ) )
				{
					transactionSystem.GetItemData( m_owner, itemList[ j ].GetID() ).GetItemPart( partData, statsShardSlotTDBID );
					rngSeed = ItemID.GetRngSeed( InnerItemData.GetItemID( partData ) );
					transactionSystem.GetItemData( m_owner, itemID ).GetItemPart( partData, statsShardSlotTDBID );
					if( rngSeed == ItemID.GetRngSeed( InnerItemData.GetItemID( partData ) ) )
					{
						equipRequest = new EquipRequest;
						equipRequest.itemID = itemList[ j ].GetID();
						equipRequest.owner = m_owner;
						equipRequest.addToInventory = false;
						EquipmentSystem.GetInstance( equipRequest.owner ).QueueRequest( equipRequest );
						break;
					}
				}
			}
		}
		EquipmentSystem.RequestUnequipItem( m_owner, gamedataEquipmentArea.MusculoskeletalSystemCW, 2 );
	}

	private function HandleRemovingPerkLevel( i : Int32, j : Int32 ) : Int32
	{
		var perkType : gamedataNewPerkType;
		var perkLevelSold : Int32;
		var slotIdx, maxSlots : Int32;
		var itemID : ItemID;
		var equipmentSystem : EquipmentSystem;
		var playerData : EquipmentSystemPlayerData;
		var inventoryManager : InventoryDataManagerV2;
		var inventoryItems : array< InventoryItemData >;
		var removePartRequest : RemoveItemPart;
		var inventoryIndex : Int32;
		perkType = m_attributesData[ i ].unlockedPerks[ j ].type;
		equipmentSystem = EquipmentSystem.GetInstance( m_owner );
		playerData = equipmentSystem.GetPlayerData( m_owner );
		perkLevelSold = m_attributesData[ i ].unlockedPerks[ j ].currLevel;
		m_attributesData[ i ].unlockedPerks[ j ].currLevel -= 1;
		DeactivateNewPerk( perkType, m_attributesData[ i ].unlockedPerks[ j ].currLevel );
		if( perkType == gamedataNewPerkType.Tech_Central_Milestone_3 )
		{
			HandleRemovingTech_Central_Milestone_3( playerData );
		}
		if( perkType == gamedataNewPerkType.Tech_Central_Perk_3_2 )
		{
			EquipmentSystem.RequestUnequipItem( m_owner, gamedataEquipmentArea.HandsCW, 1 );
		}
		if( perkType == gamedataNewPerkType.Tech_Central_Perk_3_3 )
		{
			maxSlots = playerData.GetNumberOfSlots( gamedataEquipmentArea.IntegumentarySystemCW );
			for( slotIdx = 0; slotIdx < maxSlots; slotIdx += 1 )
			{
				itemID = playerData.GetItemInEquipSlot( gamedataEquipmentArea.IntegumentarySystemCW, slotIdx );
				if( ItemID.IsValid( itemID ) && RPGManager.IsItemAdaptiveStemCells( ItemID.GetTDBID( itemID ) ) )
				{
					EquipmentSystem.RequestUnequipItem( m_owner, gamedataEquipmentArea.IntegumentarySystemCW, slotIdx );
				}
			}
		}
		if( perkType == gamedataNewPerkType.Espionage_Central_Milestone_1 )
		{
			itemID = playerData.GetItemInEquipSlot( gamedataEquipmentArea.ArmsCW, slotIdx );
			if( ItemID.IsValid( itemID ) )
			{
				inventoryManager = new InventoryDataManagerV2;
				inventoryManager.Initialize( ( ( PlayerPuppet )( m_owner ) ) );
				inventoryItems = inventoryManager.GetPlayerInventoryData( gamedataEquipmentArea.ArmsCW );
				for( inventoryIndex = 0; inventoryIndex < inventoryItems.Size(); inventoryIndex += 1 )
				{
					if( InventoryItemData.GetItemType( inventoryItems[ inventoryIndex ] ) == gamedataItemType.Cyb_NanoWires && ( inventoryItems[ inventoryIndex ].ID == itemID ) )
					{
						removePartRequest = new RemoveItemPart;
						removePartRequest.obj = m_owner;
						removePartRequest.baseItem = itemID;
						removePartRequest.slotToEmpty = T"AttachmentSlots.NanoWiresQuickhackSlot";
						GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'ItemModificationSystem' ).QueueRequest( removePartRequest );
					}
				}
			}
		}
		ReturnDevelopmentPointForSoldPerk( i, j );
		return perkLevelSold;
	}

	private function ReturnDevelopmentPointForSoldPerk( i : Int32, j : Int32 )
	{
		var isEspionagePerk : Bool;
		var isEspionageMilestonePerk : Bool;
		var devPoints : Int32;
		isEspionagePerk = m_attributesData[ i ].type == gamedataAttributeDataType.EspionageAttributeData;
		isEspionageMilestonePerk = IsEspionageMilestonePerk( m_attributesData[ i ].unlockedPerks[ j ].type );
		if( isEspionagePerk )
		{
			devPoints = ( ( isEspionageMilestonePerk ) ? ( 3 ) : ( 1 ) );
			AddDevelopmentPoints( devPoints, gamedataDevelopmentPointType.Espionage );
		}
		else
		{
			AddDevelopmentPoints( 1, gamedataDevelopmentPointType.Primary );
		}
	}

	public function ResetNewPerks()
	{
		var perkIndex : Int32;
		var perkType : gamedataNewPerkType;
		var perkLevelSold : Int32;
		var newPerkSoldEvent : NewPerkSoldEvent;
		var newPerkLockedEvent : NewPerkLockedEvent;
		var developmentData : PlayerDevelopmentData;
		developmentData = PlayerDevelopmentSystem.GetData( m_owner );
		for( perkIndex = 0; perkIndex < ( ( Int32 )( gamedataNewPerkType.Count ) ); perkIndex += 1 )
		{
			perkType = ( ( gamedataNewPerkType )( perkIndex ) );
			while( developmentData.ForceSellNewPerk( perkType, perkLevelSold ) )
			{
				GameInstance.GetTelemetrySystem( m_owner.GetGame() ).LogNewPerkRemoved( perkType );
				newPerkSoldEvent = new NewPerkSoldEvent;
				newPerkSoldEvent.perkType = perkType;
				newPerkSoldEvent.perkLevelSold = perkLevelSold;
				GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( newPerkSoldEvent );
				m_owner.QueueEvent( newPerkSoldEvent );
			}
			LockNewPerk( perkType );
			newPerkLockedEvent = new NewPerkLockedEvent;
			newPerkLockedEvent.perkType = perkType;
			GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( newPerkLockedEvent );
		}
	}

	public const function IsNewPerkBought( perkType : gamedataNewPerkType ) : Int32
	{
		var i : Int32;
		var j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			return m_attributesData[ i ].unlockedPerks[ j ].currLevel;
		}
		return 0;
	}

	public const function IsNewPerkBoughtAnyLevel( perkType : gamedataNewPerkType ) : Bool
	{
		return IsNewPerkBought( perkType ) > 0;
	}

	public const function GetNewPerkMaxLevel( perkType : gamedataNewPerkType ) : Int32
	{
		var i : Int32;
		var j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			return RPGManager.GetNewPerkRecord( perkType ).GetLevelsCount();
		}
		return 0;
	}

	public const function IsNewPerkEspionage( perkType : gamedataNewPerkType ) : Bool
	{
		var i : Int32;
		var j : Int32;
		if( FindNewPerk( perkType, i, j ) )
		{
			return m_attributesData[ i ].type == gamedataAttributeDataType.EspionageAttributeData;
		}
		return false;
	}

	public const function IsEspionageMilestonePerk( type : gamedataNewPerkType ) : Bool
	{
		return ( type == gamedataNewPerkType.Espionage_Central_Milestone_1 || type == gamedataNewPerkType.Espionage_Left_Milestone_Perk ) || type == gamedataNewPerkType.Espionage_Right_Milestone_1;
	}

	private const function GetEspionagePerksCount() : Int32
	{
		var espionageAttributeData : weak< AttributeData_Record >;
		espionageAttributeData = TweakDBInterface.GetAttributeDataRecord( T"NewPerks.EspionageAttributeData" );
		return espionageAttributeData.GetPerksCount();
	}

	private function GetAttributeData( type : gamedataAttributeDataType ) : SAttributeData
	{
		return m_attributesData[ ( ( Int32 )( type ) ) ];
	}

	public static function StatTypeToAttributeDataType( type : gamedataStatType ) : gamedataAttributeDataType
	{
		switch( type )
		{
			case gamedataStatType.Strength:
				return gamedataAttributeDataType.BodyAttributeData;
			case gamedataStatType.Intelligence:
				return gamedataAttributeDataType.IntelligenceAttributeData;
			case gamedataStatType.Cool:
				return gamedataAttributeDataType.CoolAttributeData;
			case gamedataStatType.TechnicalAbility:
				return gamedataAttributeDataType.TechnicalAbilityAttributeData;
			case gamedataStatType.Reflexes:
				return gamedataAttributeDataType.ReflexesAttributeData;
			case gamedataStatType.Espionage:
				return gamedataAttributeDataType.EspionageAttributeData;
			default:
				return gamedataAttributeDataType.Invalid;
		}
	}

	public static function AttributeDataTypeToStatType( type : gamedataAttributeDataType ) : gamedataStatType
	{
		switch( type )
		{
			case gamedataAttributeDataType.BodyAttributeData:
				return gamedataStatType.Strength;
			case gamedataAttributeDataType.IntelligenceAttributeData:
				return gamedataStatType.Intelligence;
			case gamedataAttributeDataType.CoolAttributeData:
				return gamedataStatType.Cool;
			case gamedataAttributeDataType.TechnicalAbilityAttributeData:
				return gamedataStatType.TechnicalAbility;
			case gamedataAttributeDataType.ReflexesAttributeData:
				return gamedataStatType.Reflexes;
			case gamedataAttributeDataType.EspionageAttributeData:
				return gamedataStatType.Espionage;
			default:
				return gamedataStatType.Invalid;
		}
	}

	private function GetAttributeData( statType : gamedataStatType, out attrData : SAttributeData ) : Bool
	{
		var dataType : gamedataAttributeDataType;
		dataType = StatTypeToAttributeDataType( statType );
		if( dataType == gamedataAttributeDataType.Invalid )
		{
			return false;
		}
		attrData = m_attributesData[ ( ( Int32 )( dataType ) ) ];
		return true;
	}

	private function InitializePerkAreas()
	{
		var i : Int32;
		for( i = 0; i <= ( ( Int32 )( gamedataPerkArea.Count ) ); i += 1 )
		{
			InitializePerkArea( ( ( gamedataPerkArea )( i ) ) );
		}
	}

	private function InitializePerkArea( areaType : gamedataPerkArea )
	{
		var newPerkArea : SPerkArea;
		if( IsPerkAreaValid( areaType ) )
		{
			newPerkArea.type = areaType;
			newPerkArea.unlocked = ShouldPerkAreaBeAvailable( areaType );
			newPerkArea.boughtPerks.Clear();
			m_perkAreas.PushBack( newPerkArea );
		}
	}

	private const function InitializePerk( perkType : gamedataPerkType ) : SPerk
	{
		var newPerk : SPerk;
		newPerk.type = perkType;
		newPerk.currLevel = 0;
		return newPerk;
	}

	private const function IncreasePerkLevel( areaIndex : Int32, perkIndex : Int32 )
	{
		m_perkAreas[ areaIndex ].boughtPerks[ perkIndex ].currLevel += 1;
	}

	public const function RefreshPerkAreas()
	{
		var i : Int32;
		for( i = 0; i < m_proficiencies.Size(); i += 1 )
		{
			EvaluatePerkAreas( m_proficiencies[ i ].type );
		}
	}

	private const function EvaluatePerkAreas( prof : gamedataProficiencyType )
	{
		var i : Int32;
		var pIndex : Int32;
		var aIndex : Int32;
		var perkAreas : array< weak< PerkArea_Record > >;
		pIndex = GetProficiencyIndexByType( prof );
		if( pIndex < 0 )
		{
			return;
		}
		RPGManager.GetProficiencyRecord( prof ).PerkAreas( perkAreas );
		for( i = 0; i < perkAreas.Size(); i += 1 )
		{
			aIndex = GetPerkAreaIndex( perkAreas[ i ].Type() );
			if( aIndex < 0 )
			{
				continue;
			}
			m_perkAreas[ aIndex ].unlocked = IsPerkAreaReqMet( perkAreas[ i ] );
		}
	}

	public const function GetPerkAreaIndex( areaType : gamedataPerkArea ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_perkAreas.Size(); i += 1 )
		{
			if( m_perkAreas[ i ].type == areaType )
			{
				return i;
			}
		}
		return -1;
	}

	private const function GetPerkIndex( areaIndex : Int32, perkType : gamedataPerkType ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_perkAreas[ areaIndex ].boughtPerks.Size(); i += 1 )
		{
			if( m_perkAreas[ areaIndex ].boughtPerks[ i ].type == perkType )
			{
				return i;
			}
		}
		return -1;
	}

	public const function GetPerkIndex( areaType : gamedataPerkArea, perkType : gamedataPerkType ) : Int32
	{
		var i : Int32;
		var pAreaIndex : Int32;
		pAreaIndex = GetPerkAreaIndex( areaType );
		if( pAreaIndex < 0 )
		{
			return -1;
		}
		for( i = 0; i < m_perkAreas[ pAreaIndex ].boughtPerks.Size(); i += 1 )
		{
			if( m_perkAreas[ pAreaIndex ].boughtPerks[ i ].type == perkType )
			{
				return i;
			}
		}
		return -1;
	}

	private const function GetPerkIndex( perkType : gamedataPerkType ) : Int32
	{
		var pIndex : Int32;
		pIndex = GetPerkIndex( GetPerkAreaFromPerk( perkType ), perkType );
		return pIndex;
	}

	public const function UnlockPerkArea( areaType : gamedataPerkArea )
	{
		var pAreaIndex : Int32;
		pAreaIndex = GetPerkAreaIndex( areaType );
		if( pAreaIndex < 0 )
		{
			return;
		}
		m_perkAreas[ pAreaIndex ].unlocked = true;
	}

	public const function LockPerkArea( areaType : gamedataPerkArea )
	{
		var pAreaIndex : Int32;
		pAreaIndex = GetPerkAreaIndex( areaType );
		if( pAreaIndex < 0 )
		{
			return;
		}
		m_perkAreas[ pAreaIndex ].unlocked = false;
	}

	public const function BuyPerk( perkType : gamedataPerkType ) : Bool
	{
		var profIndex : Int32;
		var pIndex : Int32;
		var pAreaIndex : Int32;
		var newPerk : SPerk;
		var canBeBought : Bool;
		pAreaIndex = GetPerkAreaIndex( GetPerkAreaFromPerk( perkType ) );
		if( pAreaIndex < 0 )
		{
			return false;
		}
		pIndex = GetPerkIndex( perkType );
		canBeBought = CanPerkBeBought( perkType );
		if( ( pIndex < 0 ) && canBeBought )
		{
			newPerk = InitializePerk( perkType );
			m_perkAreas[ pAreaIndex ].boughtPerks.PushBack( newPerk );
			pIndex = GetPerkIndex( perkType );
		}
		if( !( IsPerkMaxLevel( perkType ) ) && canBeBought )
		{
			profIndex = GetProficiencyIndexFromPerkArea( m_perkAreas[ pAreaIndex ].type );
			DeactivatePerkLevelData( pAreaIndex, pIndex );
			IncreasePerkLevel( pAreaIndex, pIndex );
			ActivatePerkLevelData( pAreaIndex, pIndex );
			SpendDevelopmentPoint( gamedataDevelopmentPointType.Primary );
			m_proficiencies[ profIndex ].spentPerkPoints += 1;
			EvaluatePerkAreas( m_proficiencies[ profIndex ].type );
			return true;
		}
		else
		{
			return false;
		}
	}

	public const function RemovePerk( perkType : gamedataPerkType ) : Bool
	{
		var pIndex : Int32;
		var pAreaIndex : Int32;
		var profIndex : Int32;
		var dIndex : Int32;
		var tempPDevPts : Int32;
		var areaType : gamedataPerkArea;
		var currentPerkLevel : Int32;
		areaType = GetPerkAreaFromPerk( perkType );
		pAreaIndex = GetPerkAreaIndex( areaType );
		if( pAreaIndex < 0 )
		{
			return false;
		}
		pIndex = GetPerkIndex( perkType );
		if( pIndex >= 0 )
		{
			DeactivatePerkLevelData( pAreaIndex, pIndex );
			currentPerkLevel = m_perkAreas[ pAreaIndex ].boughtPerks[ pIndex ].currLevel;
			tempPDevPts = ( ( Int32 )( GameInstance.GetStatsDataSystem( m_owner.GetGame() ).GetValueFromCurve( GetPerkAreaRecord( areaType ).Curve().CurveSetName(), ( ( Float )( currentPerkLevel ) ), 'Primary' ) ) );
			if( tempPDevPts > 0 )
			{
				profIndex = GetProficiencyIndexFromPerkArea( m_perkAreas[ pAreaIndex ].type );
				dIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Primary );
				m_devPoints[ dIndex ].unspent += tempPDevPts;
				m_devPoints[ dIndex ].spent -= tempPDevPts;
				m_proficiencies[ profIndex ].spentPerkPoints -= tempPDevPts;
				RemovePerkRecipes( perkType );
			}
			m_perkAreas[ pAreaIndex ].boughtPerks.EraseFast( pIndex );
			return true;
		}
		return false;
	}

	public const function RemoveAllPerks( free : Bool )
	{
		var i : Int32;
		var perkType : gamedataPerkType;
		var perkResetEvent : PerkResetEvent;
		var respecCost : Int32;
		var perksRemoved : array< gamedataPerkType >;
		respecCost = 0;
		if( !( free ) )
		{
			respecCost = GetTotalRespecCost();
			GameInstance.GetTransactionSystem( m_owner.GetGame() ).RemoveItem( m_owner, MarketSystem.Money(), respecCost );
		}
		for( i = 0; i < ( ( Int32 )( gamedataPerkType.Count ) ); i += 1 )
		{
			perkType = ( ( gamedataPerkType )( i ) );
			if( HasPerk( perkType ) )
			{
				RemovePerk( perkType );
				perksRemoved.PushBack( perkType );
			}
		}
		for( i = 0; i < ( ( Int32 )( gamedataTraitType.Count ) ); i += 1 )
		{
			RemoveTrait( ( ( gamedataTraitType )( i ) ) );
		}
		GameInstance.GetTelemetrySystem( m_owner.GetGame() ).LogPerksRemoved( respecCost, perksRemoved );
		perkResetEvent = new PerkResetEvent;
		GameInstance.GetUISystem( m_owner.GetGame() ).QueueEvent( perkResetEvent );
	}

	public const function RemoveDeprecatedPerkPoints()
	{
		var i : Int32;
		var profIndex : Int32;
		var profRecord : Proficiency_Record;
		var currLevel : Int32;
		var bonusRecord : weak< PassiveProficiencyBonus_Record >;
		var bonusEffectorRecord : Effector_Record;
		var deprecatedPoints : Int32;
		i = 0;
		deprecatedPoints = 0;
		for( ; i < m_proficiencies.Size(); i += 1 )
		{
			profIndex = GetProficiencyIndexByType( m_proficiencies[ i ].type );
			profRecord = GetProficiencyRecordByIndex( profIndex );
			if( !( profRecord.IsA( 'gamedataNewSkillsProficiency_Record' ) ) )
			{
				for( currLevel = 0; ( currLevel < m_proficiencies[ i ].currentLevel ) && ( currLevel < profRecord.GetPassiveBonusesCount() ); currLevel += 1 )
				{
					bonusRecord = profRecord.GetPassiveBonusesItem( currLevel );
					bonusEffectorRecord = bonusRecord.EffectorToTrigger();
					if( bonusEffectorRecord.IsA( 'gamedataAddDevelopmentPointEffector_Record' ) )
					{
						deprecatedPoints += 1;
					}
				}
			}
		}
		AddDevelopmentPoints( -( deprecatedPoints ), gamedataDevelopmentPointType.Primary );
	}

	public const function GetTotalRespecCost() : Int32
	{
		var cost : Int32;
		var basePrice : Int32;
		var singlePerkPrice : Int32;
		basePrice = ( ( Int32 )( TweakDBInterface.GetConstantStatModifierRecord( T"Price.RespecBase" ).Value() ) );
		singlePerkPrice = ( ( Int32 )( TweakDBInterface.GetConstantStatModifierRecord( T"Price.RespecSinglePerk" ).Value() ) );
		cost = basePrice + ( singlePerkPrice * ( GetSpentPerkPoints() + GetSpentTraitPoints() ) );
		return cost;
	}

	public function CheckPlayerRespecCost() : Bool
	{
		var resetCost : Int32;
		var userMoney : Int32;
		resetCost = GetTotalRespecCost();
		userMoney = GameInstance.GetTransactionSystem( m_owner.GetGame() ).GetItemQuantity( m_owner, MarketSystem.Money() );
		return userMoney >= resetCost;
	}

	private const function RemovePerkRecipes( perkType : gamedataPerkType )
	{
		var perkLevels : array< weak< PerkLevelData_Record > >;
		var perkLevelEffectors : array< weak< Effector_Record > >;
		var addItemsEffector : AddItemsEffector_Record;
		var itemsToAdd : array< weak< InventoryItem_Record > >;
		var hideRecipeRequest : HideRecipeRequest;
		var craftingSystem : CraftingSystem;
		var i, j, k : Int32;
		GetPerkRecord( perkType ).Levels( perkLevels );
		for( i = 0; i < perkLevels.Size(); i += 1 )
		{
			perkLevels[ i ].DataPackage().Effectors( perkLevelEffectors );
			for( j = 0; j < perkLevelEffectors.Size(); j += 1 )
			{
				addItemsEffector = ( ( AddItemsEffector_Record )( perkLevelEffectors[ j ] ) );
				if( addItemsEffector )
				{
					addItemsEffector.ItemsToAdd( itemsToAdd );
					for( k = 0; k < itemsToAdd.Size(); k += 1 )
					{
						craftingSystem = ( ( CraftingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'CraftingSystem' ) ) );
						hideRecipeRequest = new HideRecipeRequest;
						hideRecipeRequest.recipe = ( ( ItemRecipe_Record )( itemsToAdd[ k ].Item() ) ).CraftingResult().Item().GetID();
						craftingSystem.QueueRequest( hideRecipeRequest );
					}
				}
			}
		}
	}

	public const function GetPerkRecord( perkType : gamedataPerkType ) : Perk_Record
	{
		return TweakDBInterface.GetPerkRecord( TDBID.Create( "Perks." + EnumValueToString( "gamedataPerkType", ( ( Int32 )( perkType ) ) ) ) );
	}

	public const function GetPerkAreaRecord( areaType : gamedataPerkArea ) : PerkArea_Record
	{
		return TweakDBInterface.GetPerkAreaRecord( TDBID.Create( "Perks." + EnumValueToString( "gamedataPerkArea", ( ( Int32 )( areaType ) ) ) ) );
	}

	public const function GetPerkPackageTDBID( areaIndex : Int32, perkIndex : Int32 ) : TweakDBID
	{
		var packageID : TweakDBID;
		packageID = GetPerkPackageTDBID( m_perkAreas[ areaIndex ].boughtPerks[ perkIndex ].type );
		return packageID;
	}

	public const function GetPerkPackageTDBID( perkType : gamedataPerkType ) : TweakDBID
	{
		var pAreaIndex : Int32;
		var pIndex : Int32;
		var packageID : TweakDBID;
		var levelsData : array< weak< PerkLevelData_Record > >;
		GetPerkRecord( perkType ).Levels( levelsData );
		pAreaIndex = GetPerkAreaIndex( GetPerkAreaFromPerk( perkType ) );
		if( pAreaIndex < 0 )
		{
			return TDBID.None();
		}
		pIndex = GetPerkIndex( pAreaIndex, perkType );
		if( pIndex < 0 )
		{
			return TDBID.None();
		}
		packageID = levelsData[ m_perkAreas[ pAreaIndex ].boughtPerks[ pIndex ].currLevel - 1 ].DataPackage().GetID();
		return packageID;
	}

	public const function HasPerk( perkType : gamedataPerkType ) : Bool
	{
		var pIndex : Int32;
		pIndex = GetPerkIndex( GetPerkAreaFromPerk( perkType ), perkType );
		return pIndex >= 0;
	}

	public const function GetInvestedPerkPoints( profType : gamedataProficiencyType ) : Int32
	{
		var pIndex : Int32;
		pIndex = GetProficiencyIndexByType( profType );
		return m_proficiencies[ pIndex ].spentPerkPoints;
	}

	public const function ShouldPerkAreaBeAvailable( areaType : gamedataPerkArea ) : Bool
	{
		var areaRecord : PerkArea_Record;
		areaRecord = GetPerkAreaRecord( areaType );
		if( TDBID.IsValid( areaRecord.GetID() ) )
		{
			if( IsPerkAreaReqMet( areaRecord ) )
			{
				return true;
			}
		}
		return false;
	}

	public const function IsPerkAreaUnlocked( area : gamedataPerkArea ) : Bool
	{
		var aIndex : Int32;
		aIndex = GetPerkAreaIndex( area );
		if( aIndex < 0 )
		{
			return false;
		}
		return m_perkAreas[ aIndex ].unlocked;
	}

	public const function IsPerkAreaUnlocked( aIndex : Int32 ) : Bool
	{
		if( aIndex < 0 )
		{
			return false;
		}
		return m_perkAreas[ aIndex ].unlocked;
	}

	private const function IsPerkAreaValid( areaType : gamedataPerkArea ) : Bool
	{
		var i : Int32;
		for( i = 0; i < ( ( Int32 )( gamedataPerkArea.Count ) ); i += 1 )
		{
			if( areaType == ( ( gamedataPerkArea )( i ) ) )
			{
				return true;
			}
		}
		return false;
	}

	private const function IsPerkMaxLevel( perkType : gamedataPerkType ) : Bool
	{
		return GetPerkLevel( perkType ) >= GetPerkMaxLevel( perkType );
	}

	public const function GetPerkMaxLevel( perkType : gamedataPerkType ) : Int32
	{
		return GetPerkAreaRecord( GetPerkAreaFromPerk( perkType ) ).MaxLevel();
	}

	public const function GetPerkLevel( perkType : gamedataPerkType ) : Int32
	{
		var pIndex : Int32;
		var pAreaIndex : Int32;
		pAreaIndex = GetPerkAreaIndex( GetPerkAreaFromPerk( perkType ) );
		if( pAreaIndex < 0 )
		{
			return -1;
		}
		pIndex = GetPerkIndex( pAreaIndex, perkType );
		if( pIndex < 0 )
		{
			return -1;
		}
		return m_perkAreas[ pAreaIndex ].boughtPerks[ pIndex ].currLevel;
	}

	private const function CanPerkBeBought( perkType : gamedataPerkType ) : Bool
	{
		var pIndex : Int32;
		var pAreaIndex : Int32;
		var primDevIndex : Int32;
		pAreaIndex = GetPerkAreaIndex( GetPerkAreaFromPerk( perkType ) );
		if( pAreaIndex < 0 )
		{
			return false;
		}
		pIndex = GetPerkIndex( pAreaIndex, perkType );
		if( ( pIndex >= 0 ) && ( GetPerkMaxLevel( perkType ) <= m_perkAreas[ pAreaIndex ].boughtPerks[ pIndex ].currLevel ) )
		{
			return false;
		}
		primDevIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Primary );
		if( primDevIndex < 0 )
		{
			return false;
		}
		if( m_devPoints[ primDevIndex ].unspent <= 0 )
		{
			return false;
		}
		return true;
	}

	public const function CanTraitBeBought() : Bool
	{
		var primDevIndex : Int32;
		primDevIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Primary );
		if( primDevIndex < 0 )
		{
			return false;
		}
		if( m_devPoints[ primDevIndex ].unspent <= 0 )
		{
			return false;
		}
		return true;
	}

	public const function IsPerkAreaReqMet( areaRecord : PerkArea_Record ) : Bool
	{
		return IsPerkAreaBaseReqMet( areaRecord );
	}

	public const function IsPerkAreaMasteryReqMet( areaRecord : PerkArea_Record ) : Bool
	{
		var prereqID : TweakDBID;
		var prereq : StatPrereq;
		prereqID = areaRecord.MasteryLevel().GetID();
		prereq = ( ( StatPrereq )( StatPrereq.CreatePrereq( prereqID ) ) );
		return prereq.IsFulfilled( m_owner.GetGame(), m_owner );
	}

	private const function IsPerkAreaBaseReqMet( areaRecord : PerkArea_Record ) : Bool
	{
		var prereqID : TweakDBID;
		var prereq : IPrereq;
		prereqID = areaRecord.Requirement().GetID();
		prereq = IPrereq.CreatePrereq( prereqID );
		return prereq.IsFulfilled( m_owner.GetGame(), m_owner );
	}

	public const function GetRemainingRequiredPerkPoints( areaRecord : PerkArea_Record, out amount : Int32 ) : Bool
	{
		var required : Int32;
		var invested : Int32;
		var prereq : InvestedPerksPrereq;
		prereq = ( ( InvestedPerksPrereq )( IPrereq.CreatePrereq( areaRecord.Requirement().GetID() ) ) );
		if( prereq )
		{
			required = prereq.GetRequiredAmount();
			invested = GetInvestedPerkPoints( prereq.GetProficiencyType() );
			amount = required - invested;
			if( amount > 0 )
			{
				return true;
			}
			return false;
		}
		amount = -1;
		return false;
	}

	public const function GetRemainingRequiredPerkPoints( traitRecord : Trait_Record, out amount : Int32 ) : Bool
	{
		var required : Int32;
		var invested : Int32;
		var prereq : InvestedPerksPrereq;
		prereq = ( ( InvestedPerksPrereq )( IPrereq.CreatePrereq( traitRecord.Requirement().GetID() ) ) );
		if( prereq )
		{
			required = prereq.GetRequiredAmount();
			invested = GetInvestedPerkPoints( prereq.GetProficiencyType() );
			amount = required - invested;
			if( amount > 0 )
			{
				return true;
			}
			return false;
		}
		amount = -1;
		return false;
	}

	public const function GetPerkAreaFromPerk( perkType : gamedataPerkType ) : gamedataPerkArea
	{
		var areaString : String;
		areaString = EnumValueToString( "gamedataPerkType", ( ( Int32 )( perkType ) ) );
		areaString = StrBeforeFirst( areaString, "_Perk" );
		return ( ( gamedataPerkArea )( ( ( Int32 )( EnumValueFromString( "gamedataPerkArea", areaString ) ) ) ) );
	}

	public const function GetProficiencyFromPerkArea( perkArea : gamedataPerkArea ) : gamedataProficiencyType
	{
		var proficiencyString : String;
		proficiencyString = EnumValueToString( "gamedataPerkArea", ( ( Int32 )( perkArea ) ) );
		proficiencyString = StrBeforeFirst( proficiencyString, "_Area" );
		return ( ( gamedataProficiencyType )( ( ( Int32 )( EnumValueFromString( "gamedataProficiencyType", proficiencyString ) ) ) ) );
	}

	public const function GetProficiencyIndexFromPerkArea( perkArea : gamedataPerkArea ) : Int32
	{
		var pIndex : Int32;
		pIndex = GetProficiencyIndexByType( GetProficiencyFromPerkArea( perkArea ) );
		return pIndex;
	}

	private const function ActivatePerkLevelData( areaIndex : Int32, perkIndex : Int32 )
	{
		var packageID : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		packageID = GetPerkPackageTDBID( areaIndex, perkIndex );
		GLPS.ApplyPackage( m_owner, m_owner, packageID );
	}

	private const function DeactivatePerkLevelData( areaIndex : Int32, perkIndex : Int32 )
	{
		var packageID : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		packageID = GetPerkPackageTDBID( areaIndex, perkIndex );
		GLPS.RemovePackage( m_owner, packageID );
	}

	public const function GetSpentPerkPoints() : Int32
	{
		var i, count : Int32;
		var sum : Int32;
		count = m_proficiencies.Size();
		for( i = 0; i < count; i += 1 )
		{
			sum += m_proficiencies[ i ].spentPerkPoints;
		}
		return sum;
	}

	public const function GetPerks() : array< SPerk >
	{
		var i, j : Int32;
		var allPerks : array< SPerk >;
		for( i = 0; i < m_perkAreas.Size(); i += 1 )
		{
			for( j = 0; j < m_perkAreas[ i ].boughtPerks.Size(); j += 1 )
			{
				allPerks.PushBack( m_perkAreas[ i ].boughtPerks[ j ] );
			}
		}
		return allPerks;
	}

	private function InitializeTraits()
	{
		var i : Int32;
		for( i = 0; i < ( ( Int32 )( gamedataTraitType.Count ) ); i += 1 )
		{
			AddTrait( ( ( gamedataTraitType )( i ) ) );
		}
	}

	private function AddTrait( traitType : gamedataTraitType )
	{
		var newTrait : STrait;
		newTrait.type = traitType;
		newTrait.unlocked = false;
		newTrait.currLevel = 0;
		m_traits.PushBack( newTrait );
	}

	public const function IncreaseTraitLevel( traitType : gamedataTraitType ) : Bool
	{
		var primDevIndex : Int32;
		var traitIndex : Int32;
		traitIndex = GetTraitIndex( traitType );
		if( ( traitIndex < 0 ) || !( IsTraitUnlocked( traitIndex ) ) )
		{
			return false;
		}
		if( CanTraitBeBought() )
		{
			m_traits[ traitIndex ].currLevel += 1;
			primDevIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Primary );
			m_devPoints[ primDevIndex ].unspent -= 1;
			m_devPoints[ primDevIndex ].spent += 1;
			EvaluateTraitInfiniteData( traitIndex );
			return true;
		}
		return false;
	}

	private const function RemoveTrait( traitType : gamedataTraitType ) : Bool
	{
		var primDevIndex : Int32;
		var traitIndex : Int32;
		var traitLevel : Int32;
		traitIndex = GetTraitIndex( traitType );
		if( ( traitIndex < 0 ) || !( IsTraitUnlocked( traitIndex ) ) )
		{
			return false;
		}
		traitLevel = m_traits[ traitIndex ].currLevel;
		if( m_traits[ traitIndex ].currLevel > 0 )
		{
			ClearTraitInfiniteData( traitIndex );
			m_traits[ traitIndex ].currLevel = 0;
			primDevIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Primary );
			m_devPoints[ primDevIndex ].unspent += traitLevel;
			m_devPoints[ primDevIndex ].spent -= traitLevel;
			return true;
		}
		return false;
	}

	public const function GetSpentTraitPoints() : Int32
	{
		var i : Int32;
		var spentPoints : Int32;
		for( i = 0; i < ( ( Int32 )( gamedataTraitType.Count ) ); i += 1 )
		{
			if( IsTraitUnlocked( i ) )
			{
				spentPoints += GetTraitLevel( i );
			}
		}
		return spentPoints;
	}

	private const function EvaluateTraitInfiniteData( traitIndex : Int32 )
	{
		var traitPackage : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		var traitLevel : Uint32;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		traitPackage = RPGManager.GetTraitRecord( m_traits[ traitIndex ].type ).InfiniteTraitData().DataPackage().GetID();
		traitLevel = ( ( Uint32 )( m_traits[ traitIndex ].currLevel ) );
		GLPS.RemovePackages( m_owner, traitPackage, traitLevel );
		GLPS.ApplyPackages( m_owner, m_owner, traitPackage, traitLevel );
	}

	private const function ClearTraitInfiniteData( traitIndex : Int32 )
	{
		var traitPackage : TweakDBID;
		var GLPS : GameplayLogicPackageSystem;
		var traitLevel : Uint32;
		GLPS = GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() );
		traitPackage = RPGManager.GetTraitRecord( m_traits[ traitIndex ].type ).InfiniteTraitData().DataPackage().GetID();
		traitLevel = ( ( Uint32 )( m_traits[ traitIndex ].currLevel ) );
		GLPS.RemovePackages( m_owner, traitPackage, traitLevel );
	}

	private const function EvaluateTrait( profType : gamedataProficiencyType )
	{
		var traitIndex : Int32;
		var traitType : gamedataTraitType;
		traitType = RPGManager.GetProficiencyRecord( profType ).Trait().Type();
		traitIndex = GetTraitIndex( traitType );
		if( ( traitIndex < 0 ) || IsTraitUnlocked( traitIndex ) )
		{
			return;
		}
		if( IsTraitReqMet( m_traits[ traitIndex ].type ) )
		{
			m_traits[ traitIndex ].unlocked = true;
			ActivateTraitBase( m_traits[ traitIndex ].type );
		}
	}

	private const function ActivateTraitBase( traitType : gamedataTraitType )
	{
		var traitPackage : TweakDBID;
		traitPackage = RPGManager.GetTraitRecord( traitType ).BaseTraitData().DataPackage().GetID();
		GameInstance.GetGameplayLogicPackageSystem( m_owner.GetGame() ).ApplyPackage( m_owner, m_owner, traitPackage );
	}

	public const function IsTraitUnlocked( traitType : gamedataTraitType ) : Bool
	{
		return IsTraitUnlocked( GetTraitIndex( traitType ) );
	}

	private const function IsTraitUnlocked( traitIndex : Int32 ) : Bool
	{
		if( traitIndex < 0 )
		{
			return false;
		}
		return m_traits[ traitIndex ].unlocked;
	}

	private const function IsTraitReqMet( traitType : gamedataTraitType ) : Bool
	{
		var prereqID : TweakDBID;
		var prereq : StatPrereq;
		var traitRecord : Trait_Record;
		traitRecord = GetTraitRecord( traitType );
		prereqID = traitRecord.Requirement().GetID();
		prereq = ( ( StatPrereq )( StatPrereq.CreatePrereq( prereqID ) ) );
		return prereq.IsFulfilled( m_owner.GetGame(), m_owner );
	}

	public const function GetTraitLevel( traitType : gamedataTraitType ) : Int32
	{
		return GetTraitLevel( GetTraitIndex( traitType ) );
	}

	private const function GetTraitLevel( traitIndex : Int32 ) : Int32
	{
		if( traitIndex < 0 )
		{
			return 0;
		}
		return m_traits[ traitIndex ].currLevel;
	}

	private const function GetTraitIndex( traitType : gamedataTraitType ) : Int32
	{
		var i : Int32;
		for( i = 0; m_traits.Size(); i += 1 )
		{
			if( m_traits[ i ].type == traitType )
			{
				return i;
			}
		}
		return -1;
	}

	private const function GetTraitRecord( traitType : gamedataTraitType ) : Trait_Record
	{
		return TweakDBInterface.GetTraitRecord( TDBID.Create( "Traits." + EnumValueToString( "gamedataTraitType", ( ( Int32 )( traitType ) ) ) ) );
	}

	public const function BuyAttribute( type : gamedataStatType ) : Bool
	{
		var cost : Int32;
		var dIndex : Int32;
		dIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Attribute );
		if( dIndex < 0 )
		{
			return false;
		}
		if( !( CanAttributeBeBought( type ) ) )
		{
			return false;
		}
		else
		{
			cost = GetAttributeNextLevelCost( type );
			ModifyAttribute( type, 1.0 );
			m_devPoints[ dIndex ].unspent -= cost;
			m_devPoints[ dIndex ].spent += cost;
		}
		return true;
	}

	public const function ResetAttribute( type : gamedataStatType )
	{
		var statSys : StatsSystem;
		var amount : Float;
		var dIndex : Int32;
		if( !( IsStatValid( type ) ) )
		{
			return;
		}
		if( !( IsAttribute( type ) ) )
		{
			return;
		}
		statSys = GameInstance.GetStatsSystem( m_owner.GetGame() );
		amount = statSys.GetStatValue( m_owner.GetEntityID(), type );
		SetAttribute( type, 3.0 );
		dIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Attribute );
		m_devPoints[ dIndex ].unspent += ( ( Int32 )( ( amount - 3.0 ) ) );
		m_devPoints[ dIndex ].spent -= ( ( Int32 )( ( amount - 3.0 ) ) );
	}

	public const function ResetAttributes()
	{
		ResetAttribute( gamedataStatType.Reflexes );
		ResetAttribute( gamedataStatType.TechnicalAbility );
		ResetAttribute( gamedataStatType.Cool );
		ResetAttribute( gamedataStatType.Intelligence );
		ResetAttribute( gamedataStatType.Strength );
	}

	public const function SellAllAttributes()
	{
		var attributes : array< SAttribute >;
		var index, limit : Int32;
		attributes = GetAttributes();
		for( index = 0, limit = attributes.Size(); index < limit; index += 1 )
		{
			SellAttribute( attributes[ index ].attributeName );
		}
	}

	public const function SellAttribute( type : gamedataStatType ) : Bool
	{
		var dIndex : Int32;
		var attVal : Float;
		var defaultAttVal : Float;
		var diff : Float;
		defaultAttVal = 3.0;
		dIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Attribute );
		if( dIndex < 0 )
		{
			return false;
		}
		attVal = GetAttributeValue( type );
		SetAttribute( type, defaultAttVal );
		diff = attVal - defaultAttVal;
		m_devPoints[ dIndex ].unspent += ( ( Int32 )( diff ) );
		m_devPoints[ dIndex ].spent -= ( ( Int32 )( diff ) );
		return true;
	}

	public const function SetAttribute( type : gamedataStatType, amount : Float )
	{
		var statSys : StatsSystem;
		var newMod : gameStatModifierData;
		var aIndex : Int32;
		if( !( IsStatValid( type ) ) )
		{
			return;
		}
		if( !( IsAttribute( type ) ) )
		{
			return;
		}
		statSys = GameInstance.GetStatsSystem( m_owner.GetGame() );
		newMod = RPGManager.CreateStatModifier( type, gameStatModifierType.Additive, amount );
		statSys.ForceModifier( m_owner.GetEntityID(), newMod );
		aIndex = GetAttributeIndex( type );
		m_attributes[ aIndex ].value = ( ( Int32 )( statSys.GetStatValue( m_owner.GetEntityID(), type ) ) );
		UpdateProficiencyMaxLevels();
		RefreshPerkAreas();
	}

	public const function GetAttributes() : array< SAttribute >
	{
		return m_attributes;
	}

	public const function GetAttributeValue( type : gamedataStatType ) : Float
	{
		var ss : StatsSystem;
		ss = GameInstance.GetStatsSystem( m_owner.GetGame() );
		if( IsAttribute( type ) )
		{
			return ss.GetStatValue( m_owner.GetEntityID(), type );
		}
		return -1.0;
	}

	private function SetAttributes()
	{
		var i : Int32;
		var attVal : Float;
		var ss : StatsSystem;
		var attribute : SAttribute;
		ss = GameInstance.GetStatsSystem( m_owner.GetGame() );
		for( i = 0; i < ( ( Int32 )( gamedataStatType.Count ) ); i += 1 )
		{
			if( IsAttribute( ( ( gamedataStatType )( i ) ) ) )
			{
				if( m_owner )
				{
					attVal = ss.GetStatValue( m_owner.GetEntityID(), ( ( gamedataStatType )( i ) ) );
				}
				else
				{
					attVal = 2.0;
				}
				attribute.value = ( ( Int32 )( attVal ) );
				attribute.attributeName = ( ( gamedataStatType )( i ) );
				attribute.id = TDBID.Create( "BaseStats." + ( ( String )( attribute.attributeName ) ) );
				m_attributes.PushBack( attribute );
			}
		}
	}

	private const function ModifyAttribute( type : gamedataStatType, amount : Float )
	{
		var newValue : Float;
		if( !( IsStatValid( type ) ) )
		{
			return;
		}
		newValue = GameInstance.GetStatsSystem( m_owner.GetGame() ).GetStatValue( m_owner.GetEntityID(), type ) + amount;
		SetAttribute( type, newValue );
	}

	private const function GetAttributeIndex( type : gamedataStatType ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_attributes.Size(); i += 1 )
		{
			if( m_attributes[ i ].attributeName == type )
			{
				return i;
			}
		}
		return -1;
	}

	private const function GetAttributeDevCap( type : gamedataStatType ) : Int32
	{
		var value : Int32;
		value = ( ( Int32 )( GetAttributeRecord( type ).Max() ) );
		return value;
	}

	public const function CanAttributeBeBought( type : gamedataStatType ) : Bool
	{
		var dIndex : Int32;
		var currVal : Int32;
		var enoughPoints : Bool;
		var maxLvlNotReached : Bool;
		var objectID : StatsObjectID;
		objectID = m_owner.GetEntityID();
		dIndex = GetDevPointsIndex( gamedataDevelopmentPointType.Attribute );
		if( dIndex < 0 )
		{
			return false;
		}
		currVal = ( ( Int32 )( GameInstance.GetStatsSystem( m_owner.GetGame() ).GetStatValue( objectID, type ) ) );
		enoughPoints = m_devPoints[ dIndex ].unspent >= GetAttributeNextLevelCost( type );
		maxLvlNotReached = GetAttributeDevCap( type ) > currVal;
		if( enoughPoints && maxLvlNotReached )
		{
			return true;
		}
		else
		{
			if( !( enoughPoints ) )
			{
			}
			else
			{
			}
			return false;
		}
	}

	private const function IsStatValid( type : gamedataStatType ) : Bool
	{
		if( ( ( Int32 )( type ) ) >= ( ( Int32 )( gamedataStatType.Count ) ) )
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public static function IsAttribute( type : gamedataStatType ) : Bool
	{
		switch( type )
		{
			case gamedataStatType.Strength:
			case gamedataStatType.Intelligence:
			case gamedataStatType.Cool:
			case gamedataStatType.TechnicalAbility:
			case gamedataStatType.Reflexes:
			case gamedataStatType.Gunslinger:
			case gamedataStatType.Espionage:
				return true;
			default:
				return false;
		}
	}

	public static function IsProfficiencyObsolete( profficeinct : gamedataProficiencyType ) : Bool
	{
		switch( profficeinct )
		{
			case gamedataProficiencyType.CoolSkill:
			case gamedataProficiencyType.IntelligenceSkill:
			case gamedataProficiencyType.ReflexesSkill:
			case gamedataProficiencyType.StrengthSkill:
			case gamedataProficiencyType.TechnicalAbilitySkill:
				return false;
			default:
				return true;
		}
	}

	private const function GetAttributeNextLevelCost( type : gamedataStatType ) : Int32
	{
		var cost : Int32;
		var level : Float;
		var statName : CName;
		var statSystem : StatsSystem;
		var statDataSystem : StatsDataSystem;
		var objectID : StatsObjectID;
		statSystem = GameInstance.GetStatsSystem( m_owner.GetGame() );
		statDataSystem = GameInstance.GetStatsDataSystem( m_owner.GetGame() );
		objectID = m_owner.GetEntityID();
		level = statSystem.GetStatValue( objectID, type );
		statName = EnumValueToName( 'gamedataStatType', ( ( Int64 )( type ) ) );
		cost = ( ( Int32 )( statDataSystem.GetValueFromCurve( 'player_attributeLevelToCostIncrease', level + 1.0, statName ) ) );
		return cost;
	}

	public const function GetAttributeRecord( type : gamedataStatType ) : Stat_Record
	{
		if( IsAttribute( type ) )
		{
			return TweakDBInterface.GetStatRecord( TDBID.Create( "BaseStats." + EnumValueToString( "gamedataStatType", ( ( Int32 )( type ) ) ) ) );
		}
		else
		{
			return NULL;
		}
	}

	public function RegisterSkillCheckPrereq( skillPrereq : SkillCheckPrereqState )
	{
		if( !( m_skillPrereqs.Contains( skillPrereq ) ) )
		{
			m_skillPrereqs.PushBack( skillPrereq );
		}
	}

	public function RegisterStatCheckPrereq( statPrereq : StatCheckPrereqState )
	{
		if( !( m_statPrereqs.Contains( statPrereq ) ) )
		{
			m_statPrereqs.PushBack( statPrereq );
		}
	}

	public function UnregisterSkillCheckPrereq( skillPrereq : SkillCheckPrereqState )
	{
		m_skillPrereqs.Remove( skillPrereq );
	}

	public function UnregisterStatCheckPrereq( statPrereq : StatCheckPrereqState )
	{
		m_statPrereqs.Remove( statPrereq );
	}

	private const function UpdateSkillPrereqs( changedSkill : gamedataProficiencyType, newLevel : Int32 )
	{
		var i : Int32;
		for( i = 0; i < m_skillPrereqs.Size(); i += 1 )
		{
			if( m_skillPrereqs[ i ].GetSkillToCheck() == changedSkill )
			{
			}
		}
	}

	private const function UpdateStatPrereqs( changedStat : gamedataStatType, newValue : Float )
	{
		var i : Int32;
		for( i = 0; i < m_statPrereqs.Size(); i += 1 )
		{
			if( m_statPrereqs[ i ].GetStatToCheck() == changedStat )
			{
			}
		}
	}

	public function SetProgressionBuild( build : gamedataBuildType, optional isDebugBuild : Bool )
	{
		var buildString : String;
		buildString = EnumValueToString( "gamedataBuildType", ( ( Int32 )( build ) ) );
		ProcessProgressionBuild( TweakDBInterface.GetProgressionBuildRecord( TDBID.Create( "ProgressionBuilds." + buildString ) ), isDebugBuild );
	}

	public function SetProgressionBuild( buildID : TweakDBID, optional isDebugBuild : Bool )
	{
		ProcessProgressionBuild( TweakDBInterface.GetProgressionBuildRecord( buildID ), isDebugBuild );
	}

	public function SetLifePath( lifePath : TweakDBID )
	{
		ProcessLifePath( TweakDBInterface.GetLifePathRecord( lifePath ) );
	}

	public function UpdateAttributes( const attributes : ref< array< CharacterCustomizationAttribute > > )
	{
		var i : Int32;
		for( i = 0; i < attributes.Size(); i += 1 )
		{
			SetAttribute( attributes[ i ].type, ( ( Float )( attributes[ i ].value ) ) );
		}
	}

	public const function GetIsProgressionBuildSetCompleted() : Bool
	{
		return m_progressionBuildSetCompleted;
	}

	public function SetIsProgressionBuildSetCompleted( value : Bool )
	{
		m_progressionBuildSetCompleted = value;
	}

	private function ProcessProgressionBuild( buildRecord : ProgressionBuild_Record, optional isDebugBuild : Bool )
	{
		var buildAttributes : array< weak< BuildAttribute_Record > >;
		var buildProficiencies : array< weak< BuildProficiency_Record > >;
		var buildPerks : array< weak< BuildPerk_Record > >;
		var buildNewPerks : array< weak< BuildNewPerk_Record > >;
		var buildItems : array< weak< InventoryItem_Record > >;
		var buildEquipmentItems : array< weak< BuildEquipment_Record > >;
		var buildCyberwareItems : array< weak< BuildCyberware_Record > >;
		var buildProgramItems : array< weak< BuildProgram_Record > >;
		var buildCraftableItems : weak< Craftable_Record >;
		var buildPerkSet : weak< BuildPerkSet_Record >;
		var buildProficiencySet : weak< BuildProficiencySet_Record >;
		var buildCyberwareSet : weak< BuildCyberwareSet_Record >;
		var buildAttributeSet : weak< BuildAttributeSet_Record >;
		var buildEquipmentSet : weak< BuildEquipmentSet_Record >;
		var inventoryItemSet : weak< InventoryItemSet_Record >;
		var clearAllDevPointsRequest : ClearAllDevPointsRequest;
		var randomizeClothing : Bool;
		var i : Int32;
		m_displayActivityLog = false;
		randomizeClothing = buildRecord.RandomizeClothing();
		FlushDevelopment();
		if( isDebugBuild )
		{
			ClearAllDevPoints();
		}
		buildAttributeSet = buildRecord.AttributeSet();
		buildProficiencySet = buildRecord.ProficiencySet();
		buildPerkSet = buildRecord.PerkSet();
		inventoryItemSet = buildRecord.InventorySet();
		buildEquipmentSet = buildRecord.EquipmentSet();
		buildCyberwareSet = buildRecord.CyberwareSet();
		if( buildAttributeSet )
		{
			buildAttributeSet.Attributes( buildAttributes );
		}
		if( buildProficiencySet )
		{
			buildProficiencySet.Proficiencies( buildProficiencies );
		}
		if( buildPerkSet )
		{
			buildPerkSet.Perks( buildPerks );
		}
		for( i = 0; i < buildRecord.GetPerkSetsCount(); i += 1 )
		{
			buildRecord.GetPerkSetsItem( i ).Perks( buildPerks );
		}
		if( inventoryItemSet )
		{
			inventoryItemSet.Items( buildItems );
		}
		if( buildEquipmentSet )
		{
			buildEquipmentSet.Equipment( buildEquipmentItems );
		}
		if( buildCyberwareSet )
		{
			buildCyberwareSet.Cyberware( buildCyberwareItems );
			buildCyberwareSet.Programs( buildProgramItems );
		}
		ProcessBuildItems( buildItems );
		ProcessBuildAttributes( buildAttributes );
		ProcessBuildProficiencies( buildProficiencies, isDebugBuild );
		ProcessBuildPerks( buildPerks );
		ProcessBuildEquipment( buildEquipmentItems, randomizeClothing );
		ProcessBuildCyberware( buildCyberwareItems );
		ProcessBuildPrograms( buildProgramItems );
		buildRecord.StartingAttributes( buildAttributes );
		buildRecord.StartingProficiencies( buildProficiencies );
		buildRecord.StartingPerks( buildPerks );
		buildRecord.StartingNewPerks( buildNewPerks );
		buildRecord.StartingItems( buildItems );
		buildRecord.StartingEquipment( buildEquipmentItems );
		buildRecord.StartingCyberware( buildCyberwareItems );
		buildRecord.StartingPrograms( buildProgramItems );
		ProcessBuildItems( buildItems );
		ProcessBuildAttributes( buildAttributes );
		ProcessBuildProficiencies( buildProficiencies, isDebugBuild );
		ProcessBuildPerks( buildPerks );
		ProcessBuildNewPerks( buildNewPerks, isDebugBuild );
		ProcessBuildEquipment( buildEquipmentItems, randomizeClothing );
		ProcessBuildCyberware( buildCyberwareItems );
		ProcessBuildPrograms( buildProgramItems );
		if( isDebugBuild )
		{
			clearAllDevPointsRequest = new ClearAllDevPointsRequest;
			clearAllDevPointsRequest.Set( m_owner );
			PlayerDevelopmentSystem.GetInstance( m_owner ).QueueRequest( clearAllDevPointsRequest );
			ScaleNPCsToPlayerLevel();
		}
		buildCraftableItems = buildRecord.CraftBook();
		ProcessCraftbook( buildCraftableItems );
		if( randomizeClothing )
		{
			RandomizeClothing();
		}
		ScaleItems();
		m_displayActivityLog = true;
	}

	private function ScaleItems()
	{
		var statMod : gameStatModifierData;
		var inventory : array< weak< gameItemData > >;
		var i : Int32;
		var itemData : weak< gameItemData >;
		statMod = RPGManager.CreateStatModifier( gamedataStatType.PowerLevel, gameStatModifierType.Additive, ( ( Float )( GetProficiencyLevel( gamedataProficiencyType.Level ) ) ) );
		GameInstance.GetTransactionSystem( m_owner.GetGame() ).GetItemList( m_owner, inventory );
		for( i = 0; i < inventory.Size(); i += 1 )
		{
			itemData = inventory[ i ];
			if( ( !( RPGManager.GetItemRecord( itemData.GetID() ).IsSingleInstance() ) && ( ItemID.GetTDBID( itemData.GetID() ) != T"Items.Preset_V_Unity_Cutscene" ) ) && ( ItemID.GetTDBID( itemData.GetID() ) != T"Items.w_melee_004__fists_a" ) )
			{
				GameInstance.GetStatsSystem( m_owner.GetGame() ).RemoveAllModifiers( itemData.GetStatsObjectID(), gamedataStatType.PowerLevel );
				GameInstance.GetStatsSystem( m_owner.GetGame() ).AddSavedModifier( itemData.GetStatsObjectID(), statMod );
			}
		}
	}

	public function ScaleNPCsToPlayerLevel()
	{
		var statMod : gameConstantStatModifierData;
		var i : Int32;
		var npc : NPCPuppet;
		var gameObject : GameObject;
		var entityList : array< Entity >;
		var statsSystem : StatsSystem;
		var playerLevel : Float;
		var isPreventionMT : Bool;
		statsSystem = GameInstance.GetStatsSystem( m_owner.GetGame() );
		playerLevel = statsSystem.GetStatValue( m_owner.GetEntityID(), gamedataStatType.Level );
		statMod = ( ( gameConstantStatModifierData )( RPGManager.CreateStatModifier( gamedataStatType.PowerLevel, gameStatModifierType.Additive, playerLevel ) ) );
		entityList = GameInstance.GetEntityList( m_owner.GetGame() );
		for( i = 0; i < entityList.Size(); i += 1 )
		{
			gameObject = ( ( GameObject )( entityList[ i ] ) );
			if( !( gameObject ) )
			{
				continue;
			}
			npc = ( ( NPCPuppet )( gameObject ) );
			isPreventionMT = NPCManager.HasTag( npc.GetRecordID(), 'MaxTac_Prevention' );
			if( ( npc && TDBID.IsValid( npc.GetRecord().ContentAssignmentHandle().GetID() ) ) || gameObject.IsDevice() )
			{
				if( isPreventionMT && ( playerLevel < 25.0 ) )
				{
					statMod = ( ( gameConstantStatModifierData )( RPGManager.CreateStatModifier( gamedataStatType.PowerLevel, gameStatModifierType.Additive, 25.0 ) ) );
				}
				statsSystem.RemoveAllModifiers( gameObject.GetEntityID(), gamedataStatType.PowerLevel, true );
				statsSystem.AddSavedModifier( gameObject.GetEntityID(), statMod );
			}
		}
	}

	private function FlushDevelopment()
	{
		var i : Int32;
		var attributeType : gamedataStatType;
		var proficiencyType : gamedataProficiencyType;
		var perkType : gamedataPerkType;
		var newPerkType : gamedataNewPerkType;
		var gi : GameInstance;
		var clearRequest : ClearEquipmentRequest;
		var es : EquipmentSystem;
		var playerItems : array< weak< gameItemData > >;
		var newPerkLevel : Int32;
		gi = m_owner.GetGame();
		for( i = 0; i < ( ( Int32 )( gamedataStatType.Count ) ); i += 1 )
		{
			attributeType = ( ( gamedataStatType )( i ) );
			if( IsAttribute( attributeType ) )
			{
				SetAttribute( attributeType, 0.0 );
			}
		}
		GameInstance.GetStatsSystem( m_owner.GetGame() ).RemoveAllModifiers( m_owner.GetEntityID(), gamedataStatType.Level );
		for( i = 0; i < ( ( Int32 )( gamedataProficiencyType.Count ) ); i += 1 )
		{
			proficiencyType = ( ( gamedataProficiencyType )( i ) );
			SetLevel( proficiencyType, 0, telemetryLevelGainReason.Ignore );
		}
		for( i = 0; i < ( ( Int32 )( gamedataPerkType.Count ) ); i += 1 )
		{
			perkType = ( ( gamedataPerkType )( i ) );
			if( HasPerk( perkType ) )
			{
				RemovePerk( perkType );
			}
		}
		for( i = 0; i < ( ( Int32 )( gamedataNewPerkType.Count ) ); i += 1 )
		{
			newPerkType = ( ( gamedataNewPerkType )( i ) );
			if( IsNewPerkUnlocked( newPerkType ) )
			{
				while( ForceSellNewPerk( newPerkType, newPerkLevel ) )
				{
				}
				LockNewPerk( newPerkType );
			}
		}
		es = ( ( EquipmentSystem )( GameInstance.GetScriptableSystemsContainer( gi ).Get( 'EquipmentSystem' ) ) );
		clearRequest = new ClearEquipmentRequest;
		clearRequest.owner = m_owner;
		es.QueueRequest( clearRequest );
		GameInstance.GetTransactionSystem( gi ).GetItemList( m_owner, playerItems );
		for( i = 0; i < playerItems.Size(); i += 1 )
		{
			if( !( playerItems[ i ].HasTag( 'Quest' ) ) )
			{
				GameInstance.GetTransactionSystem( gi ).RemoveItem( m_owner, playerItems[ i ].GetID(), playerItems[ i ].GetQuantity() );
			}
		}
	}

	private const function ProcessBuildEquipment( const equipment : ref< array< weak< BuildEquipment_Record > > >, randomizeClothing : Bool )
	{
		var i : Int32;
		var gi : GameInstance;
		var es : EquipmentSystem;
		var transactionSystem : TransactionSystem;
		var itemID : ItemID;
		var equipRequest : GameplayEquipRequest;
		var drawItemRequest : DrawItemRequest;
		var isWeaponEquipped : Bool;
		gi = m_owner.GetGame();
		isWeaponEquipped = false;
		transactionSystem = GameInstance.GetTransactionSystem( gi );
		es = ( ( EquipmentSystem )( GameInstance.GetScriptableSystemsContainer( gi ).Get( 'EquipmentSystem' ) ) );
		for( i = 0; i < equipment.Size(); i += 1 )
		{
			itemID = ItemID.FromTDBID( equipment[ i ].Equipment().GetID() );
			if( randomizeClothing && EquipmentSystem.IsClothing( itemID ) )
			{
				continue;
			}
			transactionSystem.GiveItem( m_owner, itemID, 1 );
			equipRequest = new GameplayEquipRequest;
			equipRequest.owner = m_owner;
			equipRequest.itemID = itemID;
			equipRequest.blockUpdateWeaponActiveSlots = true;
			es.QueueRequest( equipRequest );
			if( IsMultiplayer() && !( isWeaponEquipped ) )
			{
				if( TweakDBInterface.GetItemRecord( equipment[ i ].Equipment().GetID() ).ItemCategory().Type() == gamedataItemCategory.Weapon )
				{
					isWeaponEquipped = true;
					drawItemRequest = new DrawItemRequest;
					drawItemRequest.owner = m_owner;
					drawItemRequest.itemID = itemID;
					es.QueueRequest( drawItemRequest );
				}
			}
		}
	}

	private const function ProcessBuildCyberware( const cyberware : ref< array< weak< BuildCyberware_Record > > > )
	{
		var i : Int32;
		var ts : TransactionSystem;
		var es : EquipmentSystem;
		var gi : GameInstance;
		var installModuleRequest : EquipRequest;
		gi = m_owner.GetGame();
		ts = GameInstance.GetTransactionSystem( m_owner.GetGame() );
		es = ( ( EquipmentSystem )( GameInstance.GetScriptableSystemsContainer( gi ).Get( 'EquipmentSystem' ) ) );
		for( i = 0; i < cyberware.Size(); i += 1 )
		{
			installModuleRequest = new EquipRequest;
			installModuleRequest.owner = m_owner;
			installModuleRequest.itemID = ItemID.FromTDBID( cyberware[ i ].Cyberware().GetID() );
			if( !( ts.HasItem( m_owner, ItemID.CreateQuery( cyberware[ i ].Cyberware().GetID() ) ) ) )
			{
				installModuleRequest.addToInventory = true;
			}
			es.QueueRequest( installModuleRequest );
		}
	}

	private const function ProcessBuildPrograms( const programs : ref< array< weak< BuildProgram_Record > > > )
	{
		var i : Int32;
		var transactionSystem : TransactionSystem;
		var equipProgramsRequest : GameplayEquipProgramsRequest;
		var es : EquipmentSystem;
		transactionSystem = GameInstance.GetTransactionSystem( m_owner.GetGame() );
		for( i = 0; i < programs.Size(); i += 1 )
		{
			transactionSystem.GiveItemByTDBID( m_owner, programs[ i ].Program().GetID(), 1 );
		}
		if( programs.Size() > 0 )
		{
			equipProgramsRequest = new GameplayEquipProgramsRequest;
			equipProgramsRequest.owner = m_owner;
			for( i = 0; i < programs.Size(); i += 1 )
			{
				equipProgramsRequest.programIDs.PushBack( ItemID.CreateQuery( programs[ i ].Program().GetID() ) );
			}
			es = ( ( EquipmentSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'EquipmentSystem' ) ) );
			es.QueueRequest( equipProgramsRequest );
		}
	}

	private const function ProcessBuildAttributes( const attributes : ref< array< weak< BuildAttribute_Record > > > )
	{
		var i : Int32;
		for( i = 0; i < attributes.Size(); i += 1 )
		{
			SetAttribute( attributes[ i ].Attribute().StatType(), ( ( Float )( attributes[ i ].Level() ) ) );
		}
	}

	private const function ProcessBuildProficiencies( const proficiencies : ref< array< weak< BuildProficiency_Record > > >, isDebugBuild : Bool )
	{
		var i, level : Int32;
		var type : gamedataProficiencyType;
		for( i = 0; i < proficiencies.Size(); i += 1 )
		{
			type = proficiencies[ i ].Proficiency().Type();
			level = proficiencies[ i ].Level();
			SetLevel( type, level, telemetryLevelGainReason.Ignore, isDebugBuild );
		}
	}

	private function ProcessBuildPerks( const perks : ref< array< weak< BuildPerk_Record > > > )
	{
		var i, j : Int32;
		for( i = 0; i < perks.Size(); i += 1 )
		{
			for( j = 0; j < perks[ i ].Level(); j += 1 )
			{
				BuyPerk( perks[ i ].Perk().Type() );
			}
		}
	}

	private function ProcessBuildNewPerks( const perks : ref< array< weak< BuildNewPerk_Record > > >, optional forceBuy : Bool )
	{
		var i, j : Int32;
		var isEspionageUnlocked : Bool;
		isEspionageUnlocked = GetFact( m_owner.GetGame(), 'ep1_tree_unlocked' ) > 0;
		for( i = 0; i < perks.Size(); i += 1 )
		{
			for( j = 0; j < perks[ i ].Level(); j += 1 )
			{
				UnlockNewPerk( perks[ i ].Perk().Type() );
				BuyNewPerk( perks[ i ].Perk().Type(), forceBuy );
				if( ( forceBuy && !( isEspionageUnlocked ) ) && IsNewPerkEspionage( perks[ i ].Perk().Type() ) )
				{
					AddFact( m_owner.GetGame(), 'ep1_tree_unlocked' );
					isEspionageUnlocked = true;
				}
			}
		}
	}

	private const function ProcessBuildItems( const items : ref< array< weak< InventoryItem_Record > > > )
	{
		var i : Int32;
		var transactionSystem : TransactionSystem;
		transactionSystem = GameInstance.GetTransactionSystem( m_owner.GetGame() );
		for( i = 0; i < items.Size(); i += 1 )
		{
			transactionSystem.GiveItemByTDBID( m_owner, items[ i ].Item().GetID(), items[ i ].Quantity() );
		}
	}

	private const function ProcessCraftbook( recipes : weak< Craftable_Record > )
	{
		var craftingSystem : CraftingSystem;
		craftingSystem = ( ( CraftingSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'CraftingSystem' ) ) );
		craftingSystem.GetPlayerCraftBook().InitializeCraftBook( m_owner, recipes );
	}

	private function ProcessLifePath( lifePath : weak< LifePath_Record > )
	{
		m_lifePath = lifePath.Type();
	}

	private function RandomizeClothing()
	{
		var i : Int32;
		var random : Int32;
		var tdbid : TweakDBID;
		var slots : array< gamedataEquipmentArea >;
		var setRecord : weak< BuildEquipmentSet_Record >;
		var items : array< weak< BuildEquipment_Record > >;
		var itemID : ItemID;
		var equipRequest : EquipRequest;
		var es : EquipmentSystem;
		slots = EquipmentSystem.GetClothingEquipmentAreas();
		es = ( ( EquipmentSystem )( GameInstance.GetScriptableSystemsContainer( m_owner.GetGame() ).Get( 'EquipmentSystem' ) ) );
		for( i = 0; i < slots.Size(); i += 1 )
		{
			items.Clear();
			tdbid = TDBID.Create( "BuildSets." + EnumValueToString( "gamedataEquipmentArea", ( ( Int32 )( slots[ i ] ) ) ) );
			setRecord = TweakDBInterface.GetBuildEquipmentSetRecord( tdbid );
			setRecord.Equipment( items );
			if( items.Size() > 0 )
			{
				random = RandRange( 0, items.Size() );
				itemID = ItemID.FromTDBID( items[ random ].Equipment().GetID() );
				equipRequest = new EquipRequest;
				equipRequest.owner = m_owner;
				equipRequest.itemID = itemID;
				equipRequest.addToInventory = true;
				es.QueueRequest( equipRequest );
			}
		}
	}

	public function RefreshDevelopmentSystemOnNewGameStarted()
	{
		var playerPuppet : PlayerPuppet;
		var charCreationAttributes : array< CharacterCustomizationAttribute >;
		var unset : Uint32;
		playerPuppet = ( ( PlayerPuppet )( m_owner ) );
		RefreshDevelopmentSystem();
		unset = GameInstance.GetCharacterCustomizationSystem( playerPuppet.GetGame() ).GetState().GetAttributePointsAvailable();
		AddDevelopmentPoints( ( ( Int32 )( unset ) ), gamedataDevelopmentPointType.Attribute );
		charCreationAttributes = GameInstance.GetCharacterCustomizationSystem( playerPuppet.GetGame() ).GetState().GetAttributes();
		UpdateAttributes( charCreationAttributes );
		GameInstance.GetCharacterCustomizationSystem( playerPuppet.GetGame() ).ClearState();
	}

	public function RefreshDevelopmentSystem()
	{
		RefreshProficiencyStats();
		SetAttributes();
		UpdateProficiencyMaxLevels();
		if( GetLifePath() == gamedataLifePath.StreetKid )
		{
			SetProgressionBuild( gamedataBuildType.StreetKidStarting );
		}
		else if( GetLifePath() == gamedataLifePath.Nomad )
		{
			SetProgressionBuild( gamedataBuildType.NomadStarting );
		}
		else if( GetLifePath() == gamedataLifePath.Corporate )
		{
			SetProgressionBuild( gamedataBuildType.CorporateStarting );
		}
		else
		{
			SetProgressionBuild( gamedataBuildType.StartingBuild );
		}
	}

	public const function UpdatePerkAreaBB( areaIndex : Int32 )
	{
		var m_ownerStatsBB : IBlackboard;
		var gi : GameInstance;
		gi = m_owner.GetGame();
		if( m_owner == GameInstance.GetPlayerSystem( gi ).GetLocalPlayerMainGameObject() )
		{
			m_ownerStatsBB = GameInstance.GetBlackboardSystem( gi ).Get( GetAllBlackboardDefs().UI_PlayerStats );
			m_ownerStatsBB.SetVariant( GetAllBlackboardDefs().UI_PlayerStats.ModifiedPerkArea, m_perkAreas[ areaIndex ], true );
		}
	}

	public const function UpdateUIBB()
	{
		var m_ownerStatsBB : IBlackboard;
		var gi : GameInstance;
		gi = m_owner.GetGame();
		m_ownerStatsBB = GameInstance.GetBlackboardSystem( gi ).Get( GetAllBlackboardDefs().UI_PlayerStats );
		if( m_ownerStatsBB && ( m_owner == GameInstance.GetPlayerSystem( gi ).GetLocalPlayerMainGameObject() ) )
		{
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.Level, GetProficiencyLevel( gamedataProficiencyType.Level ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.CurrentXP, GetCurrentLevelProficiencyExp( gamedataProficiencyType.Level ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.RequiredXP, GetExperienceForNextLevel( gamedataProficiencyType.Level ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.StreetCredLevel, GetProficiencyLevel( gamedataProficiencyType.StreetCred ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.StreetCredPoints, GetCurrentLevelProficiencyExp( gamedataProficiencyType.StreetCred ), true );
			m_ownerStatsBB.SetVariant( GetAllBlackboardDefs().UI_PlayerStats.DevelopmentPoints, m_devPoints, true );
			m_ownerStatsBB.SetVariant( GetAllBlackboardDefs().UI_PlayerStats.Proficiency, m_proficiencies, true );
			m_ownerStatsBB.SetVariant( GetAllBlackboardDefs().UI_PlayerStats.Perks, m_perkAreas, true );
			m_ownerStatsBB.SetVariant( GetAllBlackboardDefs().UI_PlayerStats.Attributes, m_attributes, true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.PhysicalResistance, ( ( Int32 )( GameInstance.GetStatsSystem( gi ).GetStatValue( m_owner.GetEntityID(), gamedataStatType.PhysicalResistance ) ) ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.ThermalResistance, ( ( Int32 )( GameInstance.GetStatsSystem( gi ).GetStatValue( m_owner.GetEntityID(), gamedataStatType.ThermalDamage ) ) ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.EnergyResistance, ( ( Int32 )( GameInstance.GetStatsSystem( gi ).GetStatValue( m_owner.GetEntityID(), gamedataStatType.ElectricResistance ) ) ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.ChemicalResistance, ( ( Int32 )( GameInstance.GetStatsSystem( gi ).GetStatValue( m_owner.GetEntityID(), gamedataStatType.ChemicalResistance ) ) ), true );
			m_ownerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.weightMax, ( ( Int32 )( GameInstance.GetStatsSystem( gi ).GetStatValue( m_owner.GetEntityID(), gamedataStatType.CarryCapacity ) ) ), true );
		}
	}

}

class PlayerDevelopmentSystem extends ScriptableSystem
{
	private persistent var m_playerData : array< PlayerDevelopmentData >;
	private var m_playerDevelopmentUpdated : Bool;
	private var m_progressionBuildUpdated : Bool;

	public static function GetInstance( owner : GameObject ) : PlayerDevelopmentSystem
	{
		var PDS : PlayerDevelopmentSystem;
		PDS = ( ( PlayerDevelopmentSystem )( GameInstance.GetScriptableSystemsContainer( owner.GetGame() ).Get( 'PlayerDevelopmentSystem' ) ) );
		return PDS;
	}

	private export function OnPlayerAttach( request : PlayerAttachRequest )
	{
		var data : PlayerDevelopmentData;
		var updatePDS : UpdatePlayerDevelopment;
		if( !( GetDevelopmentData( request.owner ) ) )
		{
			data = new PlayerDevelopmentData;
			data.SetOwner( request.owner );
			data.SetLifePath( GameInstance.GetCharacterCustomizationSystem( request.owner.GetGame() ).GetState().GetLifePath() );
			updatePDS = new UpdatePlayerDevelopment;
			updatePDS.Set( request.owner );
			updatePDS.ForceRefresh = request.owner.IsVRReplacer();
			GameInstance.GetScriptableSystemsContainer( request.owner.GetGame() ).Get( 'PlayerDevelopmentSystem' ).QueueRequest( updatePDS );
			m_playerData.PushBack( data );
			data.OnNewGame();
		}
		else
		{
			data = GetDevelopmentData( request.owner );
		}
		data.OnAttach();
	}

	private function OnPlayerDetach( request : PlayerDetachRequest )
	{
		var i : Int32;
		var entID : EntityID;
		if( EntityID.IsDefined( request.owner.GetEntityID() ) )
		{
			entID = request.owner.GetEntityID();
		}
		else
		{
			entID = request.ownerID;
		}
		for( i = 0; i < m_playerData.Size(); i += 1 )
		{
			if( m_playerData[ i ].GetOwnerID() == entID )
			{
				m_playerData[ i ].OnDetach();
				if( m_playerData[ i ].GetOwnerID() != GameInstance.GetPlayerSystem( request.owner.GetGame() ).GetLocalPlayerMainGameObject().GetEntityID() )
				{
					m_playerData.Remove( m_playerData[ i ] );
				}
				return;
			}
		}
	}

	private const function GetDevelopmentData( owner : GameObject ) : PlayerDevelopmentData
	{
		var i : Int32;
		for( i = 0; i < m_playerData.Size(); i += 1 )
		{
			if( m_playerData[ i ].GetOwnerID() == owner.GetEntityID() )
			{
				return m_playerData[ i ];
			}
		}
		return NULL;
	}

	public static function GetData( owner : GameObject ) : PlayerDevelopmentData
	{
		var playerDevSystem : PlayerDevelopmentSystem;
		playerDevSystem = ( ( PlayerDevelopmentSystem )( GameInstance.GetScriptableSystemsContainer( owner.GetGame() ).Get( 'PlayerDevelopmentSystem' ) ) );
		return playerDevSystem.GetDevelopmentData( owner );
	}

	private export override function OnRestored( saveVersion : Int32, gameVersion : Int32 )
	{
		var i : Int32;
		var gameInstance : GameInstance;
		var factVal : Int32;
		factVal = GetFact( GetGameInstance(), 'EspionageAttributeRetrofix' );
		if( ( factVal <= 0 ) && ( ( ( Int32 )( gameGameVersion.Current ) ) >= 2000 ) )
		{
			for( i = 0; i < m_playerData.Size(); i += 1 )
			{
				m_playerData[ i ].EspionageAttributeRetrofix();
			}
			SetFactValue( GetGameInstance(), 'EspionageAttributeRetrofix', 1 );
		}
		gameInstance = GetGameInstance();
		for( i = 0; i < m_playerData.Size(); i += 1 )
		{
			m_playerData[ i ].OnRestored( gameInstance );
		}
		factVal = GetFact( GetGameInstance(), 'FreePerkRespec' );
		if( ( ( factVal <= 0 ) && ( ( ( Int32 )( gameGameVersion.Current ) ) >= 1500 ) ) && ( saveVersion <= 212 ) )
		{
			GrantFreeRespec();
			SetFactValue( GetGameInstance(), 'FreePerkRespec', 1 );
		}
		factVal = GetFact( GetGameInstance(), 'Perks2' );
		if( factVal <= 0 )
		{
			factVal = GetFact( GetGameInstance(), 'ep1_standalone' );
			if( factVal <= 0 )
			{
				ResetProgressionForNewPerks();
			}
			SetFactValue( GetGameInstance(), 'Perks2', 1 );
		}
		factVal = GetFact( GetGameInstance(), 'CalculateXPOldSaves' );
		if( ( ( factVal <= 0 ) && ( ( ( Int32 )( gameGameVersion.Current ) ) >= 2000 ) ) && ( saveVersion <= 212 ) )
		{
			SetFactValue( GetGameInstance(), 'CalculateXPOldSaves', 1 );
		}
		factVal = GetFact( GetGameInstance(), 'RelicDevelopmentData' );
		if( factVal <= 0 )
		{
			ReinitializeProficiencies();
			SetFactValue( GetGameInstance(), 'RelicDevelopmentData', 1 );
		}
		factVal = GetFact( GetGameInstance(), 'Skills2' );
		if( ( ( factVal <= 0 ) && ( ( ( Int32 )( gameGameVersion.Current ) ) >= 1699 ) ) && ( saveVersion <= 224 ) )
		{
			UpgradeExperienceToSkills2();
			SetFactValue( GetGameInstance(), 'Skills2', 1 );
		}
		factVal = GetFact( GetGameInstance(), 'RetrofixCraftingComponents' );
		if( ( ( factVal <= 0 ) && ( ( ( Int32 )( gameGameVersion.Current ) ) >= 2000 ) ) && ( saveVersion <= 224 ) )
		{
			RetrofixCraftingComponents();
			SetFactValue( GetGameInstance(), 'RetrofixCraftingComponents', 1 );
		}
	}

	private function GrantFreeRespec()
	{
		var removeAllPerks : RemoveAllPerks;
		var player : GameObject;
		removeAllPerks = new RemoveAllPerks;
		player = GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject();
		removeAllPerks.Set( player, true, true );
		QueueRequest( removeAllPerks );
	}

	private function ResetProgressionForNewPerks()
	{
		var resetProgressionForNewPerks : ResetProgressionForNewPerks;
		var player : GameObject;
		resetProgressionForNewPerks = new ResetProgressionForNewPerks;
		player = GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject();
		resetProgressionForNewPerks.Set( player );
		QueueRequest( resetProgressionForNewPerks );
	}

	private function ReinitializeProficiencies()
	{
		var request : ReinitializeProficiencies;
		request = new ReinitializeProficiencies;
		request.owner = GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject();
		QueueRequest( request );
	}

	private function UpgradeExperienceToSkills2()
	{
		var player : weak< GameObject >;
		var request : SetProficiencyLevel;
		var brawling, athletics, demolition : Int32;
		var coldBlood, gunslinger, stealth : Int32;
		var combatHacking, hacking : Int32;
		var crafting, engineering : Int32;
		var assault, kenjutsu : Int32;
		var body, cool, int, tech, reflex : Int32;
		player = GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject();
		if( !( player ) )
		{
			return;
		}
		brawling = GetProficiencyLevel( player, gamedataProficiencyType.Brawling );
		athletics = GetProficiencyLevel( player, gamedataProficiencyType.Athletics );
		demolition = GetProficiencyLevel( player, gamedataProficiencyType.Demolition );
		body = Max( GetProficiencyLevel( player, gamedataProficiencyType.StrengthSkill ), 0 );
		body += ( ( brawling + athletics ) + demolition );
		coldBlood = GetProficiencyLevel( player, gamedataProficiencyType.ColdBlood );
		gunslinger = GetProficiencyLevel( player, gamedataProficiencyType.Gunslinger );
		stealth = GetProficiencyLevel( player, gamedataProficiencyType.Stealth );
		cool = Max( GetProficiencyLevel( player, gamedataProficiencyType.CoolSkill ), 0 );
		cool += ( ( coldBlood + gunslinger ) + stealth );
		combatHacking = GetProficiencyLevel( player, gamedataProficiencyType.CombatHacking );
		hacking = GetProficiencyLevel( player, gamedataProficiencyType.Hacking );
		int = Max( GetProficiencyLevel( player, gamedataProficiencyType.IntelligenceSkill ), 0 );
		int += ( combatHacking + hacking );
		crafting = GetProficiencyLevel( player, gamedataProficiencyType.Crafting );
		engineering = GetProficiencyLevel( player, gamedataProficiencyType.Engineering );
		tech = Max( GetProficiencyLevel( player, gamedataProficiencyType.TechnicalAbilitySkill ), 0 );
		tech += ( crafting + engineering );
		assault = GetProficiencyLevel( player, gamedataProficiencyType.Assault );
		kenjutsu = GetProficiencyLevel( player, gamedataProficiencyType.Kenjutsu );
		reflex = Max( GetProficiencyLevel( player, gamedataProficiencyType.ReflexesSkill ), 0 );
		reflex += ( assault + kenjutsu );
		request = new SetProficiencyLevel;
		request.Set( player, body, gamedataProficiencyType.StrengthSkill, telemetryLevelGainReason.Gameplay );
		QueueRequest( request );
		request = new SetProficiencyLevel;
		request.Set( player, cool, gamedataProficiencyType.CoolSkill, telemetryLevelGainReason.Gameplay );
		QueueRequest( request );
		request = new SetProficiencyLevel;
		request.Set( player, int, gamedataProficiencyType.IntelligenceSkill, telemetryLevelGainReason.Gameplay );
		QueueRequest( request );
		request = new SetProficiencyLevel;
		request.Set( player, tech, gamedataProficiencyType.TechnicalAbilitySkill, telemetryLevelGainReason.Gameplay );
		QueueRequest( request );
		request = new SetProficiencyLevel;
		request.Set( player, reflex, gamedataProficiencyType.ReflexesSkill, telemetryLevelGainReason.Gameplay );
		QueueRequest( request );
	}

	public const function GetIsProgressionBuildSetCompleted( owner : GameObject ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetIsProgressionBuildSetCompleted();
	}

	public const function SetIsProgressionBuildSetCompleted( owner : GameObject, value : Bool )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		developmentData.SetIsProgressionBuildSetCompleted( value );
	}

	private function RetrofixCraftingComponents()
	{
		var player : PlayerPuppet;
		var transactionSystem : TransactionSystem;
		var itemIDs : array< TweakDBID >;
		player = GetPlayer( GetGameInstance() );
		transactionSystem = GameInstance.GetTransactionSystem( GetGameInstance() );
		if( !( player ) || !( transactionSystem ) )
		{
			return;
		}
		if( RetrofixCraftingComponent( player, transactionSystem, T"LootPrereqs.PlayerLevel_Tier_4_to_5_Exclusion_Prereq", itemIDs ) )
		{
			return;
		}
		itemIDs.PushBack( T"Items.LegendaryMaterial1" );
		itemIDs.PushBack( T"Items.QuickHackLegendaryMaterial1" );
		if( RetrofixCraftingComponent( player, transactionSystem, T"LootPrereqs.PlayerLevel_Tier_3_to_4_Exclusion_Prereq", itemIDs ) )
		{
			return;
		}
		itemIDs.PushBack( T"Items.EpicMaterial1" );
		itemIDs.PushBack( T"Items.QuickHackEpicMaterial1" );
		if( RetrofixCraftingComponent( player, transactionSystem, T"LootPrereqs.PlayerLevel_Tier_2_to_3_Exclusion_Prereq", itemIDs ) )
		{
			return;
		}
		itemIDs.PushBack( T"Items.RareMaterial1" );
		itemIDs.PushBack( T"Items.QuickHackRareMaterial1" );
		if( RetrofixCraftingComponent( player, transactionSystem, T"LootPrereqs.PlayerLevel_Tier_1_to_2_Exclusion_Prereq", itemIDs ) )
		{
			return;
		}
		itemIDs.PushBack( T"Items.UncommonMaterial1" );
		itemIDs.PushBack( T"Items.QuickHackUncommonMaterial1" );
		RetrofixCraftingComponent( player, transactionSystem, T"LootPrereqs.PlayerLevel_Tier_1_Exclusion_Prereq", itemIDs );
	}

	private function RetrofixCraftingComponent( player : PlayerPuppet, transactionSystem : TransactionSystem, requirementID : TweakDBID, itemIDs : array< TweakDBID > ) : Bool
	{
		var prereq : IPrereq;
		var itemData : gameItemData;
		var quantity : Int32;
		var i : Int32;
		prereq = IPrereq.CreatePrereq( requirementID );
		if( prereq.IsFulfilled( player.GetGame(), player ) )
		{
			for( i = 0; i < itemIDs.Size(); i += 1 )
			{
				itemData = transactionSystem.GetItemDataByTDBID( player, itemIDs[ i ] );
				if( !( itemData ) )
				{
					continue;
				}
				quantity = itemData.GetQuantity();
				transactionSystem.RemoveItemByTDBID( player, itemIDs[ i ], quantity );
			}
			return true;
		}
		return false;
	}

	public const function IsProficiencyMaxLvl( owner : GameObject, type : gamedataProficiencyType ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.IsProficiencyMaxLvl( type );
	}

	public const function GetProficiencyLevel( owner : GameObject, type : gamedataProficiencyType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetProficiencyLevel( type );
	}

	public const function GetProficiencyAbsoluteMaxLevel( owner : GameObject, type : gamedataProficiencyType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetProficiencyAbsoluteMaxLevel( type );
	}

	public const function GetCurrentLevelProficiencyExp( owner : GameObject, type : gamedataProficiencyType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetCurrentLevelProficiencyExp( type );
	}

	public const function GetTotalProfExperience( owner : GameObject, type : gamedataProficiencyType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetTotalProfExperience( type );
	}

	public const function GetRemainingExpForLevelUp( owner : GameObject, type : gamedataProficiencyType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetRemainingExpForLevelUp( type );
	}

	public const function GetDominatingCombatProficiency( owner : GameObject ) : gamedataProficiencyType
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetDominatingCombatProficiency();
	}

	public const function GetDevPoints( owner : GameObject, type : gamedataDevelopmentPointType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetDevPoints( type );
	}

	private export function OnClearAllDevPoints( evt : ClearAllDevPointsRequest )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( evt.owner );
		developmentData.ClearAllDevPoints();
	}

	public const function GetPerkLevel( owner : GameObject, type : gamedataPerkType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetPerkLevel( type );
	}

	public const function GetPerkMaxLevel( owner : GameObject, type : gamedataPerkType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetPerkMaxLevel( type );
	}

	public const function HasPerk( owner : GameObject, type : gamedataPerkType ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.HasPerk( type );
	}

	public const function GetPerks( owner : GameObject ) : array< SPerk >
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetPerks();
	}

	public const function IsPerkImplemented( owner : GameObject, perk : gamedataPerkType ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		var levelsData : array< weak< PerkLevelData_Record > >;
		developmentData = GetDevelopmentData( owner );
		developmentData.GetPerkRecord( perk ).Levels( levelsData );
		return levelsData.Size() > 0;
	}

	public const function GetPerkLevel( owner : GameObject, type : gamedataNewPerkType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.IsNewPerkBought( type );
	}

	public const function GetPerkMaxLevel( owner : GameObject, type : gamedataNewPerkType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetNewPerkMaxLevel( type );
	}

	public const function BuyAttribute( owner : GameObject, obj : GameObject, type : gamedataStatType ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.BuyAttribute( type );
	}

	public const function SetAttribute( owner : GameObject, obj : GameObject, type : gamedataStatType, amount : Float )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.SetAttribute( type, amount );
	}

	public const function GetAttributes( owner : GameObject ) : array< SAttribute >
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetAttributes();
	}

	private function UnlockNewPerk( owner : GameObject, perkType : gamedataNewPerkType ) : Bool
	{
		var success : Bool;
		var newPerkUnlockedEvent : NewPerkUnlockedEvent;
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		success = developmentData.UnlockNewPerk( perkType );
		if( success )
		{
			newPerkUnlockedEvent = new NewPerkUnlockedEvent;
			newPerkUnlockedEvent.perkType = perkType;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( newPerkUnlockedEvent );
		}
		return success;
	}

	private function LockNewPerk( owner : GameObject, perkType : gamedataNewPerkType ) : Bool
	{
		var success : Bool;
		var newPerkLockedEvent : NewPerkLockedEvent;
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		success = developmentData.LockNewPerk( perkType );
		if( success )
		{
			newPerkLockedEvent = new NewPerkLockedEvent;
			newPerkLockedEvent.perkType = perkType;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( newPerkLockedEvent );
		}
		return success;
	}

	public const function IsNewPerkUnlocked( owner : GameObject, perkType : gamedataNewPerkType ) : Bool
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.IsNewPerkUnlocked( perkType );
	}

	private function BuyNewPerk( owner : GameObject, perkType : gamedataNewPerkType ) : Bool
	{
		var success : Bool;
		var newPerkBoughtEvent : NewPerkBoughtEvent;
		var developmentData : PlayerDevelopmentData;
		var unlockedPerkList : array< gamedataNewPerkType >;
		var currLevel, maxLevel : Int32;
		var i : Int32;
		developmentData = GetDevelopmentData( owner );
		success = developmentData.BuyNewPerk( perkType );
		if( success )
		{
			newPerkBoughtEvent = new NewPerkBoughtEvent;
			newPerkBoughtEvent.perkType = perkType;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( newPerkBoughtEvent );
			currLevel = developmentData.IsNewPerkBought( perkType );
			maxLevel = developmentData.GetNewPerkMaxLevel( perkType );
			if( currLevel == maxLevel )
			{
				developmentData.GetUnlockedPerkList( perkType, unlockedPerkList );
				for( i = 0; i < unlockedPerkList.Size(); i += 1 )
				{
					if( developmentData.HasEnoughtAttributePoints( unlockedPerkList[ i ] ) )
					{
						UnlockNewPerk( owner, unlockedPerkList[ i ] );
					}
				}
			}
			if( developmentData.IsNewPerkEspionage( perkType ) )
			{
				GameInstance.GetTelemetrySystem( GetGameInstance() ).LogNewPerkUpgraded( perkType, currLevel, gamedataDevelopmentPointType.Espionage );
			}
			else
			{
				GameInstance.GetTelemetrySystem( GetGameInstance() ).LogNewPerkUpgraded( perkType, currLevel, gamedataDevelopmentPointType.Primary );
			}
		}
		return success;
	}

	private function SellNewPerk( owner : GameObject, perkType : gamedataNewPerkType ) : Bool
	{
		var success : Bool;
		var newPerkSoldEvent : NewPerkSoldEvent;
		var perkLevelSold : Int32;
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		success = developmentData.SellNewPerk( perkType, perkLevelSold );
		if( success )
		{
			GameInstance.GetTelemetrySystem( GetGameInstance() ).LogNewPerkRemoved( perkType );
			newPerkSoldEvent = new NewPerkSoldEvent;
			newPerkSoldEvent.perkType = perkType;
			newPerkSoldEvent.perkLevelSold = perkLevelSold;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( newPerkSoldEvent );
			owner.QueueEvent( newPerkSoldEvent );
		}
		return success;
	}

	public static function CanSellNewPerk( player : PlayerPuppet, perkType : gamedataNewPerkType ) : CanSellNewPerkResult
	{
		var perkLevel : Int32;
		var statsSystem : StatsSystem;
		var statValue : Float;
		var humanityAvailable : Float;
		var playerData : EquipmentSystemPlayerData;
		var maxSlots : Int32;
		var items : array< ItemID >;
		var slotIdx : Int32;
		var itemID : ItemID;
		var result : CanSellNewPerkResult;
		perkLevel = PlayerDevelopmentSystem.GetData( player ).IsNewPerkBought( perkType );
		statsSystem = GameInstance.GetStatsSystem( player.GetGame() );
		statValue = 0.0;
		humanityAvailable = 0.0;
		playerData = EquipmentSystem.GetData( player );
		result.success = true;
		result.title = GetLocalizedText( RPGManager.GetNewPerkRecord( perkType ).Loc_name_key() );
		humanityAvailable = statsSystem.GetStatValue( player.GetEntityID(), gamedataStatType.HumanityAvailable );
		if( perkType == gamedataNewPerkType.Tech_Central_Perk_2_2 )
		{
			statValue = statsSystem.GetStatValue( player.GetEntityID(), gamedataStatType.Tech_Central_Perk_2_2_Humanity );
		}
		else if( perkType == gamedataNewPerkType.Tech_Master_Perk_3 )
		{
			statValue = statsSystem.GetStatValue( player.GetEntityID(), gamedataStatType.Tech_Master_Perk_3_Humanity );
		}
		else if( perkType == gamedataNewPerkType.Tech_Central_Milestone_2 )
		{
			statValue = statsSystem.GetStatValue( player.GetEntityID(), gamedataStatType.Tech_Central_Milestone_2_Discount );
		}
		if( humanityAvailable < statValue )
		{
			result.perkType = perkType;
			result.success = false;
			result.message = "UI-Notifications-RespecCyberwareCapacityBlocked";
			return result;
		}
		if( perkType == gamedataNewPerkType.Tech_Central_Milestone_3 && ( perkLevel == 3 ) )
		{
			items = EquipmentSystem.GetItemsInArea( player, gamedataEquipmentArea.MusculoskeletalSystemCW );
			if( ( items.Size() == 3 ) && ItemID.IsValid( items[ 2 ] ) )
			{
				result.success = false;
				result.message = "UI-Notifications-RespecCyberwareSlotBlocked";
				return result;
			}
		}
		if( perkType == gamedataNewPerkType.Tech_Central_Perk_3_2 )
		{
			items = EquipmentSystem.GetItemsInArea( player, gamedataEquipmentArea.HandsCW );
			if( ( items.Size() == 2 ) && ItemID.IsValid( items[ 1 ] ) )
			{
				result.success = false;
				result.message = "UI-Notifications-RespecCyberwareSlotBlocked";
				return result;
			}
		}
		if( perkType == gamedataNewPerkType.Tech_Central_Perk_3_3 )
		{
			maxSlots = playerData.GetNumberOfSlots( gamedataEquipmentArea.IntegumentarySystemCW );
			for( slotIdx = 0; slotIdx < maxSlots; slotIdx += 1 )
			{
				itemID = playerData.GetItemInEquipSlot( gamedataEquipmentArea.IntegumentarySystemCW, slotIdx );
				if( ItemID.IsValid( itemID ) && RPGManager.IsItemAdaptiveStemCells( ItemID.GetTDBID( itemID ) ) )
				{
					result.success = false;
					result.message = "UI-Notifications-RespecCyberwareMasterBlocked";
					return result;
				}
			}
		}
		return result;
	}

	public static function CanSellNewPerks( player : PlayerPuppet ) : CanSellNewPerkResult
	{
		var result : CanSellNewPerkResult;
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Central_Perk_2_2 );
		if( !( result.success ) )
		{
			return result;
		}
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Master_Perk_3 );
		if( !( result.success ) )
		{
			return result;
		}
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Central_Milestone_2 );
		if( !( result.success ) )
		{
			return result;
		}
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Central_Milestone_3 );
		if( !( result.success ) )
		{
			return result;
		}
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Central_Perk_3_2 );
		if( !( result.success ) )
		{
			return result;
		}
		result = CanSellNewPerk( player, gamedataNewPerkType.Tech_Central_Perk_3_3 );
		if( !( result.success ) )
		{
			return result;
		}
		return result;
	}

	public const function IsNewPerkBought( owner : GameObject, perkType : gamedataNewPerkType ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.IsNewPerkBought( perkType );
	}

	private function OnUnlockNewPerk( request : UnlockNewPerk )
	{
		UnlockNewPerk( request.owner, request.m_perkType );
	}

	private function OnLockNewPerk( request : LockNewPerk )
	{
		LockNewPerk( request.owner, request.m_perkType );
	}

	private function OnBuyNewPerk( request : BuyNewPerk )
	{
		BuyNewPerk( request.owner, request.m_perkType );
	}

	private function OnSellNewPerk( request : SellNewPerk )
	{
		SellNewPerk( request.owner, request.m_perkType );
	}

	public const function GetHighestCompletedMinigameLevel( owner : GameObject ) : Int32
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return developmentData.GetHighestCompletedMinigameLevel();
	}

	public export const function GetLifePath( owner : GameObject ) : gamedataLifePath
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( owner );
		return ( ( developmentData ) ? ( developmentData.GetLifePath() ) : ( gamedataLifePath.Invalid ) );
	}

	private export function OnExperienceQueued( request : QueueCombatExperience )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.QueueCombatExperience( request.m_amount, request.m_experienceType, request.m_entity );
	}

	private export function OnProcessQueuedExperience( request : ProcessQueuedCombatExperience )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.ProcessQueuedCombatExperience( request.m_entity );
	}

	private export function OnExperienceAdded( request : AddExperience )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.AddExperience( request.m_amount, request.m_experienceType, ( ( request.m_debug ) ? ( telemetryLevelGainReason.IsDebug ) : ( telemetryLevelGainReason.Gameplay ) ) );
		if( request.m_debug )
		{
			developmentData.ScaleNPCsToPlayerLevel();
		}
	}

	private function OnSetProficiencyLevel( request : SetProficiencyLevel )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetLevel( request.m_proficiencyType, request.m_newLevel, request.m_telemetryLevelGainReason );
		if( request.m_telemetryLevelGainReason == telemetryLevelGainReason.IsDebug )
		{
			developmentData.ScaleNPCsToPlayerLevel();
		}
	}

	private function OnLevelUpProficiency( request : LevelUpProficiency )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetLevel( request.proficiencyType, developmentData.GetProficiencyLevel( request.proficiencyType ) + 1, telemetryLevelGainReason.Ignore );
	}

	private function OnPerkBought( request : BuyPerk )
	{
		var buyResult : Bool;
		var perkBoughtEvent : PerkBoughtEvent;
		var developmentData : PlayerDevelopmentData;
		var playerStatsBB : IBlackboard;
		developmentData = GetDevelopmentData( request.owner );
		buyResult = developmentData.BuyPerk( request.m_perkType );
		if( buyResult )
		{
			perkBoughtEvent = new PerkBoughtEvent;
			perkBoughtEvent.perkType = request.m_perkType;
			playerStatsBB = GameInstance.GetBlackboardSystem( GetGameInstance() ).Get( GetAllBlackboardDefs().UI_PlayerStats );
			playerStatsBB.SetInt( GetAllBlackboardDefs().UI_PlayerStats.weightMax, ( ( Int32 )( GameInstance.GetStatsSystem( GetGameInstance() ).GetStatValue( request.owner.GetEntityID(), gamedataStatType.CarryCapacity ) ) ), true );
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( perkBoughtEvent );
			GameInstance.GetTelemetrySystem( GetGameInstance() ).LogPerkUpgraded( request.m_perkType, developmentData.GetPerkLevel( request.m_perkType ) );
		}
	}

	private function OnTraitLevelIncreased( request : IncreaseTraitLevel )
	{
		var buyResult : Bool;
		var traitBoughtEvent : TraitBoughtEvent;
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		buyResult = developmentData.IncreaseTraitLevel( request.m_trait );
		if( buyResult )
		{
			traitBoughtEvent = new TraitBoughtEvent;
			traitBoughtEvent.traitType = request.m_trait;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( traitBoughtEvent );
		}
	}

	private function OnPerkRemoved( request : RemovePerk )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.RemovePerk( request.m_perkType );
	}

	private function OnAllPerksRemoved( request : RemoveAllPerks )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.RemoveAllPerks( request.m_removeCost );
		if( request.m_unequipPerkItems )
		{
			EquipmentSystem.UnequipPrereqItems( request.owner );
		}
	}

	private function OnUnlockPerkArea( request : UnlockPerkArea )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.UnlockPerkArea( request.m_perkArea );
	}

	private function OnLockPerkArea( request : LockPerkArea )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.LockPerkArea( request.m_perkArea );
	}

	private function OnAttributeSet( request : SetAttribute )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetAttribute( request.m_attributeType, request.m_statLevel );
	}

	private function OnAttributeBuy( request : BuyAttribute )
	{
		var developmentData : PlayerDevelopmentData;
		var result : Bool;
		var attributeBoughtEvent : AttributeBoughtEvent;
		var blackboard : IBlackboard;
		var newStatValue : Int32;
		var uiAttributeBoughtData : AttributeBoughtData;
		developmentData = GetDevelopmentData( request.owner );
		if( !( PlayerDevelopmentData.IsAttribute( request.m_attributeType ) ) )
		{
			return;
		}
		if( request.m_grantAttributePoint )
		{
			developmentData.AddDevelopmentPoints( 1, gamedataDevelopmentPointType.Attribute );
		}
		result = developmentData.BuyAttribute( request.m_attributeType );
		if( result )
		{
			newStatValue = RoundF( GameInstance.GetStatsSystem( GetGameInstance() ).GetStatValue( request.owner.GetEntityID(), request.m_attributeType ) );
			attributeBoughtEvent = new AttributeBoughtEvent;
			attributeBoughtEvent.attributeType = request.m_attributeType;
			GameInstance.GetUISystem( GetGameInstance() ).QueueEvent( attributeBoughtEvent );
			blackboard = GameInstance.GetBlackboardSystem( request.owner.GetGame() ).Get( GetAllBlackboardDefs().UI_AttributeBought );
			if( blackboard )
			{
				uiAttributeBoughtData.attribute = request.m_attributeType;
				uiAttributeBoughtData.value = newStatValue;
				blackboard.SetVariant( GetAllBlackboardDefs().UI_AttributeBought.attribute, uiAttributeBoughtData );
				blackboard.SignalVariant( GetAllBlackboardDefs().UI_AttributeBought.attribute );
			}
			GameInstance.GetTelemetrySystem( GetGameInstance() ).LogAttributeUpgraded( request.m_attributeType, newStatValue );
		}
	}

	private export function OnDevelopmentPointsAdded( request : AddDevelopmentPoints )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.AddDevelopmentPoints( request.amountOfPoints, request.developmentPointType );
		if( request.developmentPointType == gamedataDevelopmentPointType.Espionage )
		{
			developmentData.ModifyProficiencyLevel( gamedataProficiencyType.Espionage, true, request.amountOfPoints );
		}
	}

	private function OnSkillCheckPrereqModified( request : ModifySkillCheckPrereq )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		if( request.m_register )
		{
			developmentData.RegisterSkillCheckPrereq( request.m_skillCheckState );
		}
		else
		{
			developmentData.UnregisterSkillCheckPrereq( request.m_skillCheckState );
		}
	}

	private function OnStatCheckPrereqModified( request : ModifyStatCheckPrereq )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		if( request.m_register )
		{
			developmentData.RegisterStatCheckPrereq( request.m_statCheckState );
		}
		else
		{
			developmentData.UnregisterStatCheckPrereq( request.m_statCheckState );
		}
	}

	private export function OnUpdatePlayerDevelopment( request : UpdatePlayerDevelopment )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		if( request.ForceRefresh || ( !( WasRestored() ) && !( m_playerDevelopmentUpdated ) ) )
		{
			developmentData.RefreshDevelopmentSystemOnNewGameStarted();
			m_playerDevelopmentUpdated = true;
		}
	}

	private function OnSetProgressionBuild( request : SetProgressionBuild )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetProgressionBuild( request.m_buildType, request.m_isDebug );
		SetIsProgressionBuildSetCompleted( request.owner, true );
	}

	private export function OnSetProgressionBuild( request : questSetProgressionBuildRequest )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetProgressionBuild( request.buildID, true );
	}

	private function OnSetProgressionBuild( request : gameSetProgressionBuildRequest )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		if( !( WasRestored() ) && !( m_progressionBuildUpdated ) )
		{
			developmentData.RefreshDevelopmentSystem();
			m_progressionBuildUpdated = true;
		}
		developmentData.SetProgressionBuild( request.buildID );
	}

	private export function OnRefreshPerkAreas( request : RefreshPerkAreas )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.RefreshPerkAreas();
	}

	private export function OnSetLifePath( request : questSetLifePathRequest )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.SetLifePath( request.lifePathID );
	}

	private function OnBumpNetrunnerMinigameLevel( request : BumpNetrunnerMinigameLevel )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.BumpNetrunnerMinigameLevel( request.completedMinigameLevel );
	}

	private function OnResetProgressionForNewPerks( request : ResetProgressionForNewPerks )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.RemoveAllPerks( true );
		developmentData.RemoveDeprecatedPerkPoints();
		developmentData.ScaleNPCsToPlayerLevel();
		EquipmentSystem.UnequipPrereqItems( request.owner );
	}

	private function OnReinitializeProficiencies( request : ReinitializeProficiencies )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.ReinitializeProficiencies();
	}

	private const function GetProficiencyRecord( type : gamedataProficiencyType ) : Proficiency_Record
	{
		return TweakDBInterface.GetProficiencyRecord( TDBID.Create( "Proficiencies." + EnumValueToString( "gamedataProficiencyType", ( ( Int32 )( type ) ) ) ) );
	}

	private export function OnRequestStatsBB( request : RequestStatsBB )
	{
		var developmentData : PlayerDevelopmentData;
		developmentData = GetDevelopmentData( request.owner );
		developmentData.UpdateUIBB();
	}

}

struct SExperiencePoints
{
	persistent var amount : Float;
	persistent var forType : gamedataProficiencyType;
	persistent var entity : EntityID;
}

struct SProficiency
{
	persistent var type : gamedataProficiencyType;
	persistent var currentLevel : Int32;
	var maxLevel : Int32;
	var isAtMaxLevel : Bool;
	persistent var currentExp : Int32;
	var expToLevel : Int32;
	persistent var spentPerkPoints : Int32;
}

struct SDevelopmentPoints
{
	persistent var type : gamedataDevelopmentPointType;
	persistent var spent : Int32;
	persistent var unspent : Int32;
}

struct SPerkArea
{
	persistent var type : gamedataPerkArea;
	persistent var unlocked : Bool;
	persistent var boughtPerks : array< SPerk >;
}

struct SPerk
{
	persistent var type : gamedataPerkType;
	persistent var currLevel : Int32;
}

struct SAttribute
{
	persistent var attributeName : gamedataStatType;
	persistent var value : Int32;
	persistent var id : TweakDBID;
}

struct SNewPerk
{
	persistent var type : gamedataNewPerkType;
	persistent var currLevel : Int32;
}

struct SAttributeData
{
	persistent var type : gamedataAttributeDataType;
	persistent var unlockedPerks : array< SNewPerk >;
}

struct STrait
{
	persistent var type : gamedataTraitType;
	persistent var unlocked : Bool;
	persistent var currLevel : Int32;
}

class ExperiencePointsEvent extends Event
{
	var amount : Int32;
	var type : gamedataProficiencyType;
	var isDebug : Bool;
}

class LevelUpdateEvent extends Event
{
	var lvl : Int32;
	var type : gamedataProficiencyType;
	var devPoints : array< SDevelopmentPoints >;
}

import class RequestStats extends Event
{
}

class PerkBoughtEvent extends Event
{
	var perkType : gamedataPerkType;
}

class TraitBoughtEvent extends Event
{
	var traitType : gamedataTraitType;
}

class ActivePerkChangedEvent extends Event
{
	var perkArea : gamedataPerkArea;
	var perkType : gamedataPerkType;
}

class AttributeBoughtEvent extends Event
{
	var attributeType : gamedataStatType;
}

class PerkResetEvent extends Event
{
}

class NewPerkBoughtEvent extends Event
{
	var perkType : gamedataNewPerkType;
}

class NewPerkSoldEvent extends Event
{
	var perkType : gamedataNewPerkType;
	var perkLevelSold : Int32;
}

class NewPerkUnlockedEvent extends Event
{
	var perkType : gamedataNewPerkType;
}

class NewPerkLockedEvent extends Event
{
	var perkType : gamedataNewPerkType;
}

struct CanSellNewPerkResult
{
	var success : Bool;
	var title : String;
	var message : String;
	var perkType : gamedataNewPerkType;
}

