class NewPerksCategoriesGameController extends gameuiMenuGameController
{
	private editable var m_tooltipsManagerRef : inkWidgetRef;
	private editable var m_buttonHintsManagerRef : inkWidgetRef;
	private editable var m_perksCategoriesContainer : inkWidgetRef;
	private editable var m_tabsContainer : inkWidgetRef;
	private editable var m_perksScreenContainer : inkWidgetRef;
	private editable var m_espionageScreenContainer : inkWidgetRef;
	private editable var m_skillsScreenContainer : inkWidgetRef;
	private editable var m_pointsDisplay : inkWidgetRef;
	private editable var m_playerLevel : inkTextRef;
	private editable var m_resetAttributesButton : inkWidgetRef;
	private editable var m_skillsScreenButton : inkWidgetRef;
	private editable var m_espionageAttributeMask : inkWidgetRef;
	private editable var m_espionagePointsRef : inkWidgetRef;
	private editable var m_attributeTooltipHolderRight : inkWidgetRef;
	private editable var m_attributeTooltipHolderLeft : inkWidgetRef;
	private const editable var m_centerHiglightParts : array< inkWidgetRef >;
	private editable var m_perkTooltipPlacementLeft : inkWidgetRef;
	private editable var m_perkTooltipPlacementRight : inkWidgetRef;
	private editable var m_perkTooltipBgLeft : inkWidgetRef;
	private editable var m_perkTooltipBgRight : inkWidgetRef;
	private var m_perkTooltipBgAnimProxy : inkAnimProxy;
	private var m_menuEventDispatcher : weak< inkMenuEventDispatcher >;
	private var m_tabsController : weak< NewPerkTabsController >;
	private var m_perksScreenController : weak< NewPerksScreenLogicController >;
	private var m_espionageScreenController : weak< NewPerksScreenLogicController >;
	private var m_skillScreenController : weak< NewPerkSkillsLogicController >;
	private var m_tooltipsManager : weak< gameuiTooltipsManager >;
	private var m_buttonHintsController : weak< ButtonHints >;
	private var m_dataManager : PlayerDevelopmentDataManager;
	private var m_questSystem : weak< QuestsSystem >;
	private var m_attributesControllersList : array< weak< PerksMenuAttributeItemController > >;
	private var m_perksMenuItemCreatedQueue : array< PerksMenuAttributeItemCreated >;
	private var m_pointsDisplayController : weak< PerksPointsDisplayController >;
	private var m_playerStatsBlackboard : weak< IBlackboard >;
	private var m_characterLevelListener : CallbackHandle;
	private var m_player : weak< PlayerPuppet >;
	private var m_previousScreen : NewPeksActiveScreen;
	default m_previousScreen = NewPeksActiveScreen.Invalid;
	private var m_currentScreen : NewPeksActiveScreen;
	private var m_currentStatScreen : gamedataStatType;
	default m_currentStatScreen = gamedataStatType.Invalid;
	private var m_johnnyEspionageInitialized : Bool;
	private var m_isEspionageUnlocked : Bool;
	private var m_lastHoveredAttribute : PerkMenuAttribute;
	private var m_cyberwarePerkDetailsPopupToken : inkGameNotificationToken;
	private var m_perksScreenIntroAnimProxy : inkAnimProxy;
	private var m_perksScreenOutroAnimProxy : inkAnimProxy;
	private var m_perksScreenDirection : NewPerkTabsArrowDirection;
	private var m_currentTooltipData : PerkHoverEventTooltipData;
	private var m_uiSystem : UISystem;
	private var m_currentCursorPos : Vector2;
	private var m_perkUserData : PerkUserData;
	private var m_vendorUserData : VendorUserData;
	private var m_skillsOpenData : OpenSkillsMenuData;
	private var m_resetConfirmationToken : inkGameNotificationToken;
	private var m_userData : IScriptable;
	private var m_isPlayerInCombat : Bool;
	private var m_screenDisplayContext : ScreenDisplayContext;

	protected event OnInitialize()
	{
		AsyncSpawnFromLocal( inkWidgetRef.Get( m_tabsContainer ), 'Tabs', this, 'OnTabsSpawned' );
		AsyncSpawnFromLocal( inkWidgetRef.Get( m_perksScreenContainer ), 'PerksScreen', this, 'OnPerksScreenSpawned' );
		AsyncSpawnFromLocal( inkWidgetRef.Get( m_espionageScreenContainer ), 'Spy_PerksScreen', this, 'OnEspionageScreenSpawned' );
		AsyncSpawnFromLocal( inkWidgetRef.Get( m_skillsScreenContainer ), 'SkillsScreen', this, 'OnSkillsScreenSpawned' );
		m_pointsDisplayController = ( ( PerksPointsDisplayController )( inkWidgetRef.GetController( m_pointsDisplay ) ) );
		m_dataManager = new PlayerDevelopmentDataManager;
		m_dataManager.Initialize( ( ( PlayerPuppet )( GameInstance.GetPlayerSystem( GetPlayerControlledObject().GetGame() ).GetLocalPlayerMainGameObject() ) ), this );
		m_perksScreenDirection = NewPerkTabsArrowDirection.Invalid;
		UpdateScreen();
		m_playerStatsBlackboard = GetBlackboardSystem().Get( GetAllBlackboardDefs().UI_PlayerStats );
		m_characterLevelListener = m_playerStatsBlackboard.RegisterDelayedListenerInt( GetAllBlackboardDefs().UI_PlayerStats.Level, this, 'OnCharacterLevelUpdated', true );
		m_buttonHintsController = ( ( weak< weak< ButtonHints > > )( SpawnFromExternal( inkWidgetRef.Get( m_buttonHintsManagerRef ), R"base\gameplay\gui\common\buttonhints.inkwidget", 'Root' ).GetController() ) );
		m_buttonHintsController.AddButtonHint( 'back', GetLocalizedText( "Common-Access-Close" ) );
		m_tooltipsManager = ( ( gameuiTooltipsManager )( inkWidgetRef.GetControllerByType( m_tooltipsManagerRef, 'gameuiTooltipsManager' ) ) );
		m_tooltipsManager.Setup( ETooltipsStyle.Menus );
		inkWidgetRef.RegisterToCallback( m_resetAttributesButton, 'OnRelease', this, 'OnResetAttributesButtonClick' );
		inkWidgetRef.RegisterToCallback( m_resetAttributesButton, 'OnHoverOver', this, 'OnResetAttributesButtonHoverOver' );
		inkWidgetRef.RegisterToCallback( m_resetAttributesButton, 'OnHoverOut', this, 'OnResetAttributesButtonHoverOut' );
		inkWidgetRef.RegisterToCallback( m_skillsScreenButton, 'OnRelease', this, 'OnSkillScreenButtonClick' );
		inkWidgetRef.RegisterToCallback( m_skillsScreenButton, 'OnHoverOver', this, 'OnSkillScreenButtonHoverOver' );
		inkWidgetRef.RegisterToCallback( m_skillsScreenButton, 'OnHoverOut', this, 'OnSkillScreenButtonHoverOut' );
		RegisterToGlobalInputCallback( 'OnPostOnRelative', this, 'OnRelativeInput' );
		RegisterToGlobalInputCallback( 'OnPostOnAxis', this, 'OnAxisInput' );
		HandleEventQueue();
	}

	protected event OnUninitialize()
	{
		m_menuEventDispatcher.UnregisterFromEvent( 'OnBack', this, 'OnBack' );
		m_menuEventDispatcher.UnregisterFromEvent( 'OnBeforeLeaveScenario', this, 'OnBeforeLeaveScenario' );
		m_menuEventDispatcher.UnregisterFromEvent( 'OnSetScreenDisplayContext', this, 'OnSetScreenDisplayContext' );
		inkWidgetRef.UnregisterFromCallback( m_resetAttributesButton, 'OnRelease', this, 'OnResetAttributesButtonClick' );
		inkWidgetRef.UnregisterFromCallback( m_resetAttributesButton, 'OnHoverOver', this, 'OnResetAttributesButtonHoverOver' );
		inkWidgetRef.UnregisterFromCallback( m_resetAttributesButton, 'OnHoverOut', this, 'OnResetAttributesButtonHoverOut' );
		inkWidgetRef.UnregisterFromCallback( m_skillsScreenButton, 'OnRelease', this, 'OnSkillScreenButtonClick' );
		inkWidgetRef.UnregisterFromCallback( m_skillsScreenButton, 'OnHoverOver', this, 'OnSkillScreenButtonHoverOver' );
		inkWidgetRef.UnregisterFromCallback( m_skillsScreenButton, 'OnHoverOut', this, 'OnSkillScreenButtonHoverOut' );
		UnregisterFromGlobalInputCallback( 'OnPostOnRelative', this, 'OnRelativeInput' );
		UnregisterFromGlobalInputCallback( 'OnPostOnAxis', this, 'OnAxisInput' );
		m_skillScreenController.UnregisterData();
		StopRumbleLoop( RumbleStrength.SuperLight );
	}

	protected event OnPlayerAttach( playerPuppet : GameObject )
	{
		var psmBlackboard : IBlackboard;
		m_player = ( ( weak< weak< PlayerPuppet > > )( playerPuppet ) );
		m_questSystem = GameInstance.GetQuestsSystem( m_player.GetGame() );
		ResolveResetAttributesButtonVisibility();
		m_isEspionageUnlocked = GetFact( m_player.GetGame(), 'ep1_tree_unlocked' ) > 0;
		psmBlackboard = m_player.GetPlayerStateMachineBlackboard();
		m_isPlayerInCombat = psmBlackboard.GetInt( GetAllBlackboardDefs().PlayerStateMachine.Combat ) == ( ( Int32 )( gamePSMCombat.InCombat ) );
		UpdateJohnnyEspionageAttribute();
		m_uiSystem = GameInstance.GetUISystem( m_player.GetGame() );
	}

	protected event OnSetMenuEventDispatcher( menuEventDispatcher : weak< inkMenuEventDispatcher > )
	{
		m_menuEventDispatcher = menuEventDispatcher;
		m_menuEventDispatcher.RegisterToEvent( 'OnBack', this, 'OnBack' );
		m_menuEventDispatcher.RegisterToEvent( 'OnBeforeLeaveScenario', this, 'OnBeforeLeaveScenario' );
		m_menuEventDispatcher.RegisterToEvent( 'OnSetScreenDisplayContext', this, 'OnSetScreenDisplayContext' );
	}

	protected event OnBack( userData : IScriptable )
	{
		if( m_currentScreen == NewPeksActiveScreen.Categories && m_perkUserData.cyberwareScreenType == CyberwareScreenType.Ripperdoc )
		{
			m_menuEventDispatcher.SpawnEvent( 'OnRefreshCurrentTab' );
		}
		else if( m_currentScreen == NewPeksActiveScreen.Categories && m_vendorUserData )
		{
			CloseVendor();
		}
		else if( m_currentScreen != NewPeksActiveScreen.Categories )
		{
			CloseActiveScreen();
		}
		else
		{
			m_menuEventDispatcher.SpawnEvent( 'OnCloseHubMenu' );
		}
	}

	protected event OnSetScreenDisplayContext( userData : IScriptable )
	{
		var displayContext : ScreenDisplayContextData;
		displayContext = ( ( ScreenDisplayContextData )( userData ) );
		if( displayContext )
		{
			m_screenDisplayContext = displayContext.Context;
		}
	}

	protected event OnBeforeLeaveScenario( userData : IScriptable )
	{
		if( m_screenDisplayContext == ScreenDisplayContext.Vendor )
		{
			MenuUIUtils.RequestAutoSave( m_player, 1.0 );
		}
	}

	private function CloseVendor()
	{
		var menuEvent : inkMenuInstance_SpawnEvent;
		menuEvent = new inkMenuInstance_SpawnEvent;
		menuEvent.Init( 'OnVendorClose' );
		QueueEvent( menuEvent );
	}

	protected event OnPerksScreenSpawned( widget : inkWidget, userData : IScriptable )
	{
		m_perksScreenController = ( ( weak< weak< NewPerksScreenLogicController > > )( widget.GetController() ) );
		m_perksScreenController.SetGameController( this );
		if( m_perkUserData && m_perkUserData.statType != gamedataStatType.Invalid )
		{
			OpenPerksScreen( m_perkUserData.statType, NewPerkTabsArrowDirection.Invalid );
			m_perksScreenController.SetCursorOverPerk( m_perkUserData.perkType, false );
		}
	}

	protected event OnEspionageScreenSpawned( widget : inkWidget, userData : IScriptable )
	{
		m_espionageScreenController = ( ( weak< weak< NewPerksScreenLogicController > > )( widget.GetController() ) );
	}

	protected event OnSkillsScreenSpawned( widget : inkWidget, userData : IScriptable )
	{
		m_skillScreenController = ( ( weak< weak< NewPerkSkillsLogicController > > )( widget.GetController() ) );
		m_skillsOpenData = ( ( OpenSkillsMenuData )( m_userData ) );
		if( m_skillsOpenData.openSkills == true )
		{
			OpenSkillsScreen();
		}
	}

	protected event OnTabsSpawned( widget : inkWidget, userData : IScriptable )
	{
		m_tabsController = ( ( weak< weak< NewPerkTabsController > > )( widget.GetController() ) );
	}

	protected event OnSetUserData( userData : IScriptable )
	{
		m_userData = userData;
		m_perkUserData = ( ( PerkUserData )( userData ) );
		m_vendorUserData = ( ( VendorUserData )( userData ) );
	}

	private function OpenPerksScreen( statType : gamedataStatType, direction : NewPerkTabsArrowDirection )
	{
		m_perksScreenDirection = direction;
		m_currentStatScreen = statType;
		if( direction != NewPerkTabsArrowDirection.Invalid )
		{
			m_perksScreenOutroAnimProxy = PlayScreenOutro();
			if( m_perksScreenOutroAnimProxy )
			{
				HideTooltip();
				m_perksScreenOutroAnimProxy.RegisterToCallback( inkanimEventType.OnFinish, this, 'OnPerksScreenOutroFinished' );
			}
			else
			{
				InitializePerkScreen();
			}
		}
		else
		{
			InitializePerkScreen();
		}
		ForceResetCursorType();
	}

	protected event OnScreenIntroFinished( proxy : inkAnimProxy )
	{
		if( m_currentTooltipData.isShown )
		{
			ShowTooltip( m_currentTooltipData );
		}
		if( m_currentScreen == NewPeksActiveScreen.Perks )
		{
			m_perksScreenController.SetIntroFinished( true );
			m_perksScreenController.FireDelayedDimming();
			UpdatePerkScreenHighlights( m_currentCursorPos );
		}
		else if( m_currentScreen == NewPeksActiveScreen.Espionage )
		{
			m_espionageScreenController.SetIntroFinished( true );
			m_espionageScreenController.FireDelayedDimming();
		}
		m_perksScreenController.RefreshCursorOverPerk();
	}

	protected event OnPerksScreenOutroFinished( proxy : inkAnimProxy )
	{
		InitializePerkScreen();
	}

	private function InitializePerkScreen()
	{
		var initData : NewPerksScreenInitData;
		initData = new NewPerksScreenInitData;
		initData.stat = m_currentStatScreen;
		initData.attribute = PerkAttributeHelper.BaseStatToAttribute( initData.stat );
		initData.attributeData = TweakDBInterface.GetAttributeDataRecord( initData.attribute );
		initData.perkMenuAttribute = PerkAttributeHelper.BaseStatToPerkMenuAttribute( initData.stat );
		initData.isPlayerInCombat = m_isPlayerInCombat;
		if( m_currentStatScreen == gamedataStatType.Espionage )
		{
			m_espionageScreenController.Initialize( m_dataManager, initData, m_buttonHintsController );
			m_currentScreen = NewPeksActiveScreen.Espionage;
		}
		else
		{
			m_perksScreenController.Initialize( m_dataManager, initData, m_buttonHintsController );
			m_currentScreen = NewPeksActiveScreen.Perks;
		}
		m_tabsController.SetData( m_dataManager, initData, m_isEspionageUnlocked );
		UpdateScreen();
	}

	private function OpenSkillsScreen()
	{
		m_currentScreen = NewPeksActiveScreen.Skills;
		m_skillScreenController.Initialize( m_dataManager );
		m_perksScreenDirection = NewPerkTabsArrowDirection.Invalid;
		UpdateScreen();
	}

	protected event OnCharacterLevelUpdated( value : Int32 )
	{
		inkTextRef.SetText( m_playerLevel, IntToString( value ) );
	}

	private function CloseActiveScreen()
	{
		if( m_currentScreen == NewPeksActiveScreen.Perks || m_currentScreen == NewPeksActiveScreen.Espionage )
		{
			StopPerkScreenAnims();
		}
		m_currentScreen = NewPeksActiveScreen.Categories;
		m_perksScreenDirection = NewPerkTabsArrowDirection.Invalid;
		UpdateScreen();
	}

	private function UpdateScreen()
	{
		var hasSwipeAnims : Bool;
		UpdateData();
		UpdateScreensVisibility();
		hasSwipeAnims = m_currentScreen == NewPeksActiveScreen.Perks || m_currentScreen == NewPeksActiveScreen.Espionage;
		if( m_previousScreen != m_currentScreen || hasSwipeAnims )
		{
			m_perksScreenIntroAnimProxy = PlayScreenIntro();
			m_perksScreenIntroAnimProxy.RegisterToCallback( inkanimEventType.OnFinish, this, 'OnScreenIntroFinished' );
			m_previousScreen = m_currentScreen;
		}
	}

	private function IsPerkScreenAnimPLaying() : Bool
	{
		return ( m_perksScreenIntroAnimProxy && m_perksScreenIntroAnimProxy.IsPlaying() ) || ( m_perksScreenOutroAnimProxy && m_perksScreenOutroAnimProxy.IsPlaying() );
	}

	private function StopPerkScreenAnims()
	{
		if( m_perksScreenIntroAnimProxy && m_perksScreenIntroAnimProxy.IsPlaying() )
		{
			m_perksScreenIntroAnimProxy.UnregisterFromAllCallbacks( inkanimEventType.OnFinish );
			m_perksScreenIntroAnimProxy.GotoEndAndStop();
		}
		if( m_perksScreenOutroAnimProxy && m_perksScreenOutroAnimProxy.IsPlaying() )
		{
			m_perksScreenOutroAnimProxy.UnregisterFromAllCallbacks( inkanimEventType.OnFinish );
			m_perksScreenOutroAnimProxy.GotoStartAndStop();
		}
	}

	private function PlayScreenIntro() : inkAnimProxy
	{
		var animName : CName;
		StopPerkScreenAnims();
		if( m_perksScreenDirection == NewPerkTabsArrowDirection.Invalid )
		{
			animName = ( ( m_currentScreen == NewPeksActiveScreen.Espionage ) ? ( 'panel_perks_espionage_intro' ) : ( 'panel_perks_intro' ) );
		}
		else
		{
			if( m_currentScreen == NewPeksActiveScreen.Espionage )
			{
				animName = ( ( m_perksScreenDirection == NewPerkTabsArrowDirection.Left ) ? ( 'swipe_right_2_espionage_screen' ) : ( 'swipe_left_2_espionage_screen' ) );
			}
			else
			{
				animName = ( ( m_perksScreenDirection == NewPerkTabsArrowDirection.Left ) ? ( 'swipe_right_2_perks_screen' ) : ( 'swipe_left_2_perks_screen' ) );
			}
		}
		switch( m_currentScreen )
		{
			case NewPeksActiveScreen.Categories:
				return PlayLibraryAnimation( 'panel_categories_intro' );
			case NewPeksActiveScreen.Perks:
				return PlayLibraryAnimationOnAutoSelectedTargets( animName, m_perksScreenController.GetRootWidget() );
			case NewPeksActiveScreen.Espionage:
				return PlayLibraryAnimationOnAutoSelectedTargets( animName, m_espionageScreenController.GetRootWidget() );
			case NewPeksActiveScreen.Skills:
				return PlayLibraryAnimationOnAutoSelectedTargets( 'panel_skills_intro', m_skillScreenController.GetRootWidget() );
			default:
				return NULL;
		}
	}

	private function PlayScreenOutro() : inkAnimProxy
	{
		var animName : CName;
		StopPerkScreenAnims();
		if( m_perksScreenDirection == NewPerkTabsArrowDirection.Invalid )
		{
			return NULL;
		}
		else
		{
			if( m_currentScreen == NewPeksActiveScreen.Espionage )
			{
				animName = ( ( m_perksScreenDirection == NewPerkTabsArrowDirection.Left ) ? ( 'swipe_right_1_espionage_screen' ) : ( 'swipe_left_1_espionage_screen' ) );
			}
			else
			{
				animName = ( ( m_perksScreenDirection == NewPerkTabsArrowDirection.Left ) ? ( 'swipe_right_1_perks_screen' ) : ( 'swipe_left_1_perks_screen' ) );
			}
		}
		switch( m_currentScreen )
		{
			case NewPeksActiveScreen.Perks:
				return PlayLibraryAnimationOnAutoSelectedTargets( animName, m_perksScreenController.GetRootWidget() );
			case NewPeksActiveScreen.Espionage:
				return PlayLibraryAnimationOnAutoSelectedTargets( animName, m_espionageScreenController.GetRootWidget() );
			default:
				return NULL;
		}
	}

	private function UpdateData()
	{
		m_perksScreenController.SetValues();
		m_pointsDisplayController.SetValues( m_dataManager.GetAttributePoints(), m_dataManager.GetPerkPoints(), m_dataManager.GetSpyPerkPoints() );
		m_tabsController.SetValues( m_dataManager.GetAttributePoints(), m_dataManager.GetPerkPoints(), m_dataManager.GetSpyPerkPoints() );
	}

	private function UpdateScreensVisibility()
	{
		inkWidgetRef.SetVisible( m_perksCategoriesContainer, m_currentScreen == NewPeksActiveScreen.Categories );
		inkWidgetRef.SetVisible( m_tabsContainer, m_currentScreen == NewPeksActiveScreen.Perks || m_currentScreen == NewPeksActiveScreen.Espionage );
		inkWidgetRef.SetVisible( m_perksScreenContainer, m_currentScreen == NewPeksActiveScreen.Perks );
		inkWidgetRef.SetVisible( m_espionageScreenContainer, m_currentScreen == NewPeksActiveScreen.Espionage );
		inkWidgetRef.SetVisible( m_skillsScreenContainer, m_currentScreen == NewPeksActiveScreen.Skills );
		m_perksScreenController.SetActive( m_currentScreen == NewPeksActiveScreen.Perks );
		m_espionageScreenController.SetActive( m_currentScreen == NewPeksActiveScreen.Espionage );
		m_skillScreenController.SetActive( m_currentScreen == NewPeksActiveScreen.Skills );
	}

	protected event OnPerksMenuAttributeItemCreated( evt : PerksMenuAttributeItemCreated )
	{
		var perkMenuAttribute : PerkMenuAttribute;
		if( m_dataManager )
		{
			evt.perksMenuAttributeItem.Setup( m_dataManager );
			m_attributesControllersList.PushBack( evt.perksMenuAttributeItem );
			perkMenuAttribute = evt.perksMenuAttributeItem.GetAttributeType();
			if( perkMenuAttribute == PerkMenuAttribute.Johnny || perkMenuAttribute == PerkMenuAttribute.Espionage )
			{
				UpdateJohnnyEspionageAttribute();
			}
		}
		else
		{
			m_perksMenuItemCreatedQueue.PushBack( evt );
		}
	}

	protected function HandleEventQueue()
	{
		var i : Int32;
		for( i = 0; i < m_perksMenuItemCreatedQueue.Size(); i += 1 )
		{
			OnPerksMenuAttributeItemCreated( m_perksMenuItemCreatedQueue[ i ] );
		}
		UpdateJohnnyEspionageAttribute();
	}

	private function UpdateJohnnyEspionageAttribute()
	{
		var i, limit, bothReady : Int32;
		var isJohnnyUnlocked : Bool;
		if( !( m_johnnyEspionageInitialized ) )
		{
			if( ( m_player != NULL ) && ( m_questSystem != NULL ) )
			{
				isJohnnyUnlocked = m_questSystem.GetFact( 'q005_johnny_chip_acquired' ) == 1;
				inkWidgetRef.SetVisible( m_espionagePointsRef, m_isEspionageUnlocked );
				for( i = 0, limit = m_attributesControllersList.Size(); i < limit; i += 1 )
				{
					if( m_attributesControllersList[ i ].GetAttributeType() == PerkMenuAttribute.Johnny )
					{
						m_attributesControllersList[ i ].GetRootWidget().SetVisible( !( m_isEspionageUnlocked ) && isJohnnyUnlocked );
						bothReady += 1;
					}
					else if( m_attributesControllersList[ i ].GetAttributeType() == PerkMenuAttribute.Espionage )
					{
						m_attributesControllersList[ i ].GetRootWidget().SetVisible( m_isEspionageUnlocked );
						bothReady += 1;
					}
				}
				if( bothReady > 1 )
				{
					m_johnnyEspionageInitialized = true;
				}
			}
		}
	}

	protected event OnCyberwarePerkDetailsPopup( data : inkGameNotificationData )
	{
		m_cyberwarePerkDetailsPopupToken = NULL;
	}

	protected event OnTabMenuArrowClicked( evt : NewPerksTabArrowClickedEvent )
	{
		var currentPerkMenuAttributeInt, itemsCount : Int32;
		currentPerkMenuAttributeInt = ( ( Int32 )( PerkAttributeHelper.BaseStatToPerkMenuAttribute( m_currentStatScreen ) ) );
		currentPerkMenuAttributeInt += ( ( evt.direction == NewPerkTabsArrowDirection.Left ) ? ( -1 ) : ( 1 ) );
		itemsCount = ( ( m_isEspionageUnlocked ) ? ( ( ( Int32 )( PerkMenuAttribute.Johnny ) ) ) : ( ( ( Int32 )( PerkMenuAttribute.Espionage ) ) ) );
		currentPerkMenuAttributeInt += itemsCount;
		currentPerkMenuAttributeInt = currentPerkMenuAttributeInt % itemsCount;
		PlaySound( 'Button', 'OnPress' );
		OpenPerksScreen( PerkAttributeHelper.PerkMenuAttributeToStat( ( ( PerkMenuAttribute )( currentPerkMenuAttributeInt ) ) ), evt.direction );
	}

	protected event OnPlayerDevUpdateData( evt : PlayerDevUpdateDataEvent )
	{
		var i, j : Int32;
		var attributes : array< AttributeData >;
		UpdateData();
		attributes = m_dataManager.GetAttributes();
		for( i = 0; i < attributes.Size(); i += 1 )
		{
			for( j = 0; j < m_attributesControllersList.Size(); j += 1 )
			{
				if( m_attributesControllersList[ j ].GetStatType() == attributes[ i ].type )
				{
					m_attributesControllersList[ j ].UpdateData( attributes[ i ] );
					break;
				}
			}
		}
		m_tooltipsManager.RefreshTooltip( 0 );
		m_tooltipsManager.RefreshTooltip( 'perkTooltip' );
	}

	private function UpdatePerkScreenHighlights( cursorPos : Vector2 )
	{
		var i : Int32;
		var scale : Float;
		var blackBarsSizes : Vector2;
		var tierHighlights : array< PerkTierHighlight >;
		var position, height : Float;
		scale = 1.0 / m_uiSystem.GetInverseUIScale();
		blackBarsSizes = m_uiSystem.GetBlackBarsSizes();
		tierHighlights = m_perksScreenController.GetHighlightData();
		for( i = 0; i < tierHighlights.Size(); i += 1 )
		{
			position = ( tierHighlights[ i ].position * scale ) + blackBarsSizes.Y;
			height = tierHighlights[ i ].height * scale;
			if( ( cursorPos.Y >= position ) && ( cursorPos.Y <= ( position + height ) ) )
			{
				m_perksScreenController.SetTierHighlightHover( i );
				return;
			}
		}
		m_perksScreenController.SetTierHighlightHover( -1 );
	}

	protected event OnRelativeInput( evt : inkPointerEvent )
	{
		if( !( evt.IsAction( 'mouse_y' ) ) )
		{
			return 0;
		}
		m_currentCursorPos = evt.GetScreenSpacePosition();
		if( m_currentScreen == NewPeksActiveScreen.Perks )
		{
			UpdatePerkScreenHighlights( m_currentCursorPos );
		}
	}

	protected event OnAxisInput( evt : inkPointerEvent )
	{
		if( !( evt.IsAction( 'left_stick_y' ) ) )
		{
			return 0;
		}
		m_currentCursorPos = evt.GetScreenSpacePosition();
		if( m_currentScreen == NewPeksActiveScreen.Perks )
		{
			UpdatePerkScreenHighlights( m_currentCursorPos );
		}
	}

	private function ResolveResetAttributesButtonVisibility()
	{
		if( !( m_questSystem ) )
		{
			inkWidgetRef.SetVisible( m_resetAttributesButton, false );
			return;
		}
		if( m_questSystem.GetFact( 'ResetAttributeDisabled' ) == 0 )
		{
			inkWidgetRef.SetVisible( m_resetAttributesButton, true );
		}
		else
		{
			inkWidgetRef.SetVisible( m_resetAttributesButton, false );
		}
	}

	protected event OnResetConfirmed( data : inkGameNotificationData )
	{
		var resultData : GenericMessageNotificationCloseData;
		resultData = ( ( GenericMessageNotificationCloseData )( data ) );
		m_resetConfirmationToken = NULL;
		if( resultData && resultData.result == GenericMessageNotificationResult.Yes )
		{
			PlayerDevelopmentSystem.GetData( m_player ).ResetNewPerks();
			PlayerDevelopmentSystem.GetData( m_player ).ResetAttributes();
			QueueEvent( new PlayerDevUpdateDataEvent );
			m_questSystem.SetFact( 'ResetAttributeDisabled', 1 );
			inkWidgetRef.SetVisible( m_resetAttributesButton, false );
			PlaySound( 'Attributes', 'OnDone' );
			PlayLibraryAnimation( 'panel_categories_reset_attributes' );
		}
	}

	protected event OnResetFailed( data : inkGameNotificationData )
	{
		m_resetConfirmationToken = NULL;
	}

	protected event OnResetAttributesButtonClick( evt : inkPointerEvent )
	{
		var vendorNotification : UIMenuNotificationEvent;
		var result : CanSellNewPerkResult;
		if( evt.IsAction( 'click' ) )
		{
			if( m_player.IsInCombat() )
			{
				vendorNotification = new UIMenuNotificationEvent;
				vendorNotification.m_notificationType = UIMenuNotificationType.InventoryActionBlocked;
				QueueEvent( vendorNotification );
			}
			else if( m_questSystem.GetFact( 'ResetAttributeDisabled' ) == 1 )
			{
				inkWidgetRef.SetVisible( m_resetAttributesButton, false );
			}
			else
			{
				result = PlayerDevelopmentSystem.CanSellNewPerks( m_player );
				if( !( result.success ) )
				{
					if( result.message == "UI-Notifications-RespecCyberwareCapacityBlocked" )
					{
						OpenPerksScreen( gamedataStatType.TechnicalAbility, NewPerkTabsArrowDirection.Invalid );
						m_perksScreenController.SetCursorOverPerk( result.perkType, false );
					}
					m_resetConfirmationToken = GenericMessageNotification.Show( this, result.title, result.message, GenericMessageNotificationType.Confirm );
					m_resetConfirmationToken.RegisterListener( this, 'OnResetFailed' );
					return false;
				}
				m_resetConfirmationToken = GenericMessageNotification.Show( this, "UI-Menus-Perks-ResetAttributes", "UI-Menus-Perks-ResetAttributesConfirmation", GenericMessageNotificationType.YesNo );
				m_resetConfirmationToken.RegisterListener( this, 'OnResetConfirmed' );
			}
		}
	}

	protected event OnResetAttributesButtonHoverOver( evt : inkPointerEvent )
	{
		inkWidgetRef.SetState( m_resetAttributesButton, 'Hover' );
	}

	protected event OnResetAttributesButtonHoverOut( evt : inkPointerEvent )
	{
		inkWidgetRef.SetState( m_resetAttributesButton, 'Default' );
	}

	protected event OnSkillScreenButtonHoverOver( evt : inkPointerEvent )
	{
		inkWidgetRef.SetState( m_skillsScreenButton, 'Hover' );
	}

	protected event OnSkillScreenButtonHoverOut( evt : inkPointerEvent )
	{
		inkWidgetRef.SetState( m_skillsScreenButton, 'Default' );
	}

	protected event OnSkillScreenButtonClick( evt : inkPointerEvent )
	{
		if( evt.IsAction( 'click' ) )
		{
			OpenSkillsScreen();
		}
	}

	protected event OnAttributeHoldStart( evt : PerksMenuAttributeItemHoldStart )
	{
		if( evt.actionName.IsAction( 'upgrade_attribute' ) && m_dataManager.IsAttributeUpgradeable( evt.attributeType ) )
		{
			PlayRumbleLoop( RumbleStrength.SuperLight );
			PlaySound( 'Attributes', 'OnStart' );
		}
	}

	protected event OnAttributeInvestHoldFinished( evt : NewPerksTabAttributeInvestHoldFinished )
	{
		var currentLevel : Int32;
		if( m_dataManager.IsAttributeUpgradeable( evt.attribute ) )
		{
			StopSoundByName( 'ui_menu_attributes_progress_bar_start' );
			currentLevel = m_dataManager.GetAttributeLevel( evt.attribute );
			if( m_perksScreenController.IsThresholdExceeded( currentLevel, currentLevel + 1 ) )
			{
				PlaySoundByName( 'ui_menu_perk_unlock_level', true );
			}
			else
			{
				PlaySound( 'Attributes', 'OnDone' );
			}
		}
		m_dataManager.UpgradeAttribute( PerkAttributeHelper.PerkMenuAttributeToStat( evt.attribute ) );
		QueueEvent( new UpdatePlayerDevelopmentData );
	}

	protected event OnAttributeClicked( evt : PerksMenuAttributeItemClicked )
	{
		if( evt.attributeType != PerkMenuAttribute.Johnny && !( evt.isHeld ) )
		{
			PlaySound( 'Button', 'OnPress' );
			OpenPerksScreen( PerkAttributeHelper.PerkMenuAttributeToStat( evt.attributeType ), NewPerkTabsArrowDirection.Invalid );
		}
	}

	protected event OnAttributeReleased( evt : PerksMenuAttributeItemReleased )
	{
		StopSoundByName( 'ui_menu_attributes_progress_bar_start' );
		StopRumbleLoop( RumbleStrength.SuperLight );
	}

	protected event OnAttributePurchaseRequest( evt : AttributeUpgradePurchased )
	{
		if( m_dataManager.IsAttributeUpgradeable( evt.attributeType ) )
		{
			StopSoundByName( 'ui_menu_attributes_progress_bar_start' );
			StopRumbleLoop( RumbleStrength.SuperLight );
			PlaySound( 'Attributes', 'OnDone' );
		}
		m_dataManager.UpgradeAttribute( evt.attributeData );
		QueueEvent( new UpdatePlayerDevelopmentData );
	}

	protected event OnAttributeHoverOver( evt : PerksMenuAttributeItemHoverOver )
	{
		PlayHoverAnimation( true );
		SetAttributeBuyButtonHintHoverOver( evt.attributeType );
		m_currentTooltipData.widget = inkWidgetRef.Get( m_attributeTooltipHolderRight );
		m_currentTooltipData.data = evt.attributeData;
		m_currentTooltipData.placement = gameuiETooltipPlacement.RightCenter;
		m_currentTooltipData.isShown = true;
		if( evt.attributeType != PerkMenuAttribute.Johnny )
		{
			ShowTooltip( inkWidgetRef.Get( m_attributeTooltipHolderRight ), evt.attributeData, gameuiETooltipPlacement.RightCenter );
		}
		else
		{
			HideTooltip();
		}
		m_lastHoveredAttribute = evt.attributeType;
	}

	protected event OnAttributeHoverOut( evt : PerksMenuAttributeItemHoverOut )
	{
		if( m_lastHoveredAttribute == evt.attributeType )
		{
			if( m_currentTooltipData.data.IsA( 'AttributeData' ) )
			{
				m_currentTooltipData.isShown = false;
			}
			PlayHoverAnimation( false );
			SetAttributeBuyButtonHintHoverOut();
			HideTooltip();
			StopRumbleLoop( RumbleStrength.SuperLight );
		}
	}

	private function RefreshAttributeTooltip()
	{
		ShowTooltip( m_currentTooltipData.widget, m_currentTooltipData.data, m_currentTooltipData.placement );
	}

	protected function PlayHoverAnimation( value : Bool )
	{
		var i : Int32;
		var transparencyAnimation : inkAnimDef;
		var transparencyInterpolator : inkAnimTransparency;
		transparencyAnimation = new inkAnimDef;
		transparencyInterpolator = new inkAnimTransparency;
		transparencyInterpolator.SetDuration( 0.34999999 );
		transparencyInterpolator.SetDirection( inkanimInterpolationDirection.To );
		transparencyInterpolator.SetType( inkanimInterpolationType.Linear );
		transparencyInterpolator.SetMode( inkanimInterpolationMode.EasyIn );
		transparencyInterpolator.SetEndTransparency( ( ( value ) ? ( 1.0 ) : ( 0.002 ) ) );
		transparencyAnimation.AddInterpolator( transparencyInterpolator );
		for( i = 0; i < m_centerHiglightParts.Size(); i += 1 )
		{
			inkWidgetRef.PlayAnimation( m_centerHiglightParts[ i ], transparencyAnimation );
		}
	}

	private function SetAttributeBuyButtonHintHoverOver( attribute : PerkMenuAttribute )
	{
		var cursorData : MenuCursorUserData;
		cursorData = new MenuCursorUserData;
		cursorData.SetAnimationOverride( 'hoverOnHoldToComplete' );
		cursorData.AddAction( 'upgrade_attribute' );
		if( attribute != PerkMenuAttribute.Espionage && m_dataManager.IsAttributeUpgradeable( attribute ) )
		{
			m_buttonHintsController.AddButtonHint( 'upgrade_perk', GetLocalizedText( "LocKey#49715" ) );
			SetCursorContext( 'Hover', cursorData );
		}
		else
		{
			SetCursorContext( 'Hover' );
		}
	}

	private function ReevaluateAttributeBuyButtonHintHoverOver( attribute : PerkMenuAttribute )
	{
		if( !( m_dataManager.IsAttributeUpgradeable( attribute ) ) )
		{
			m_buttonHintsController.RemoveButtonHint( 'upgrade_perk' );
			SetCursorContext( 'Hover' );
		}
	}

	private function SetAttributeBuyButtonHintHoverOut()
	{
		SetCursorContext( 'Default' );
		m_buttonHintsController.RemoveButtonHint( 'upgrade_perk' );
	}

	protected event OnPerkHoverOver( evt : NewPerkHoverOverEvent )
	{
		m_perksScreenController.OnPerkHoverOver( evt );
		m_espionageScreenController.OnPerkHoverOver( evt );
		m_currentTooltipData.widget = evt.evt.GetTarget();
		m_currentTooltipData.data = evt.perkData;
		m_currentTooltipData.placement = gameuiETooltipPlacement.RightCenter;
		m_currentTooltipData.isShown = true;
		ShowTooltip( m_currentTooltipData );
	}

	protected event OnPerkHoverOut( evt : NewPerkHoverOutEvent )
	{
		if( m_currentTooltipData.data.IsA( 'NewPerkDisplayData' ) )
		{
			m_currentTooltipData.isShown = false;
		}
		HideTooltip();
	}

	protected event OnRefreshPerkTooltipEvent( evt : RefreshPerkTooltipEvent )
	{
		m_currentTooltipData.widget = evt.target;
		m_currentTooltipData.data = evt.perkData;
		m_currentTooltipData.placement = gameuiETooltipPlacement.RightCenter;
		m_currentTooltipData.isShown = true;
		ShowTooltip( m_currentTooltipData );
	}

	protected event OnSkillHoverOver( evt : SkillHoverOver )
	{
		var tooltipData : MessageTooltipData;
		tooltipData = new MessageTooltipData;
		tooltipData.Title = GetLocalizedText( evt.title );
		tooltipData.Description = GetLocalizedText( evt.description );
		m_tooltipsManager.ShowTooltipAtWidget( 'descriptionTooltip', evt.widget, tooltipData, gameuiETooltipPlacement.RightCenter, , inkMargin( 40.0, 0.0, 0.0, 0.0 ) );
	}

	protected event OnSkillHoverOut( evt : SkillHoverOut )
	{
		HideTooltip();
	}

	protected event OnSkillRewardHoverOver( evt : SkillRewardHoverOver )
	{
		var tooltipData : MessageTooltipData;
		tooltipData = new MessageTooltipData;
		tooltipData.Title = GetLocalizedText( evt.data.description );
		tooltipData.TitleLocalizationPackage = evt.data.locPackage;
		m_tooltipsManager.ShowTooltipAtWidget( 'descriptionTooltip', evt.widget, tooltipData, gameuiETooltipPlacement.RightCenter, , inkMargin( 40.0, 0.0, 0.0, 0.0 ) );
	}

	protected event OnSkillRewardHoverOut( evt : SkillRewardHoverOut )
	{
		HideTooltip();
	}

	protected event OnUpdatePlayerDevelopmentData( evt : UpdatePlayerDevelopmentData )
	{
		UpdateData();
		if( m_currentScreen == NewPeksActiveScreen.Categories )
		{
			ReevaluateAttributeBuyButtonHintHoverOver( m_lastHoveredAttribute );
			RefreshAttributeTooltip();
		}
	}

	protected event OnPlayNewPerksSoundEvent( evt : PlayNewPerksSoundEvent )
	{
		PlaySoundByName( evt.soundName, evt.stopIfPlaying );
		PlayRumble( evt.rumbleStrength, RumbleType.Pulse, RumblePosition.Both );
	}

	public function PlaySoundByName( soundName : CName, stopIfPlaying : Bool )
	{
		if( stopIfPlaying )
		{
			GameInstance.GetAudioSystem( GetPlayerControlledObject().GetGame() ).Stop( soundName );
		}
		GameInstance.GetAudioSystem( GetPlayerControlledObject().GetGame() ).Play( soundName );
	}

	public function StopSoundByName( soundName : CName )
	{
		GameInstance.GetAudioSystem( GetPlayerControlledObject().GetGame() ).Stop( soundName );
	}

	private function ShowTooltip( data : PerkHoverEventTooltipData )
	{
		ShowTooltip( data.widget, data.data, data.placement );
	}

	private function ShowTooltip( widget : weak< inkWidget >, data : IDisplayData, optional placement : gameuiETooltipPlacement )
	{
		var tooltipData : BasePerksMenuTooltipData;
		var widgetPosition, holderPosition, widgetSize, boundries : Vector2;
		var perkTooltipName : CName;
		var marginAdjustment : Vector2;
		marginAdjustment = Vector2( 0.0, 0.0 );
		if( IsPerkScreenAnimPLaying() )
		{
			return;
		}
		tooltipData = data.CreateTooltipData( m_dataManager );
		if( tooltipData == NULL )
		{
			return;
		}
		if( ( data.IsA( 'PerkDisplayData' ) || data.IsA( 'TraitDisplayData' ) ) || data.IsA( 'NewPerkDisplayData' ) )
		{
			perkTooltipName = 'perkTooltip';
			if( data.IsA( 'NewPerkDisplayData' ) && ( ( NewPerkDisplayData )( data ) ).m_type == gamedataNewPerkType.Espionage_Central_Milestone_1 )
			{
				perkTooltipName = 'espionageCentralDetails';
			}
			widgetPosition = WidgetUtils.LocalToGlobal( widget );
			boundries = m_perksScreenController.GetHighligtedPerksHorizontalBoundries();
			if( perkTooltipName != 'espionageCentralDetails' )
			{
				holderPosition = WidgetUtils.LocalToGlobal( inkWidgetRef.Get( m_perkTooltipPlacementRight ) );
				widgetSize = widget.GetSize();
				marginAdjustment.Y = ( widgetPosition.Y - holderPosition.Y ) + ( ( widgetSize.Y * ( 1.0 / m_uiSystem.GetInverseUIScale() ) ) / 2.0 );
				marginAdjustment.Y *= m_uiSystem.GetInverseUIScale();
			}
			if( widgetPosition.X < AdjustValueToScaleAndBlackBars( 1400.0 ) )
			{
				if( m_currentScreen != NewPeksActiveScreen.Espionage )
				{
					holderPosition = WidgetUtils.LocalToGlobal( inkWidgetRef.Get( m_perkTooltipPlacementRight ) );
					marginAdjustment.X = ( boundries.Y - holderPosition.X ) + ( 50.0 * ( 1.0 / m_uiSystem.GetInverseUIScale() ) );
					marginAdjustment.X *= m_uiSystem.GetInverseUIScale();
				}
				m_tooltipsManager.ShowTooltipAtWidget( perkTooltipName, inkWidgetRef.Get( m_perkTooltipPlacementRight ), tooltipData, gameuiETooltipPlacement.RightCenter, true, inkMargin( marginAdjustment.X, marginAdjustment.Y, 0.0, 0.0 ) );
				ShowTooltipBackground( m_perkTooltipBgRight );
			}
			else
			{
				if( m_currentScreen != NewPeksActiveScreen.Espionage )
				{
					holderPosition = WidgetUtils.LocalToGlobal( inkWidgetRef.Get( m_perkTooltipPlacementLeft ) );
					marginAdjustment.X = ( boundries.X - holderPosition.X ) - ( 100.0 * ( 1.0 / m_uiSystem.GetInverseUIScale() ) );
					marginAdjustment.X *= m_uiSystem.GetInverseUIScale();
				}
				m_tooltipsManager.ShowTooltipAtWidget( perkTooltipName, inkWidgetRef.Get( m_perkTooltipPlacementLeft ), tooltipData, gameuiETooltipPlacement.LeftCenter, true, inkMargin( marginAdjustment.X, marginAdjustment.Y, 0.0, 0.0 ) );
				ShowTooltipBackground( m_perkTooltipBgLeft );
			}
		}
		else
		{
			m_tooltipsManager.ShowTooltipAtWidget( 'attributeTooltip', widget, tooltipData, placement );
		}
	}

	private function AdjustValueToScaleAndBlackBars( value : Float ) : Float
	{
		var scale : Float;
		var blackBarsSizes : Vector2;
		scale = 1.0 / m_uiSystem.GetInverseUIScale();
		blackBarsSizes = m_uiSystem.GetBlackBarsSizes();
		return ( value * scale ) + blackBarsSizes.X;
	}

	private function ShowTooltipBackground( bgWidget : ref< inkWidgetRef > )
	{
		var animation : inkAnimDef;
		var interpolator : inkAnimTransparency;
		if( inkWidgetRef.IsVisible( bgWidget ) )
		{
			return;
		}
		animation = new inkAnimDef;
		interpolator = new inkAnimTransparency;
		interpolator.SetMode( inkanimInterpolationMode.EasyIn );
		interpolator.SetType( inkanimInterpolationType.Sinusoidal );
		interpolator.SetDirection( inkanimInterpolationDirection.FromTo );
		interpolator.SetDuration( 0.2 );
		interpolator.SetStartDelay( 0.05 );
		interpolator.SetStartTransparency( 0.0 );
		interpolator.SetEndTransparency( 1.0 );
		animation.AddInterpolator( interpolator );
		inkWidgetRef.SetVisible( bgWidget, true );
		m_perkTooltipBgAnimProxy = inkWidgetRef.PlayAnimation( bgWidget, animation );
	}

	private function HideTooltip()
	{
		m_tooltipsManager.HideTooltips();
		inkWidgetRef.SetVisible( m_perkTooltipBgRight, false );
		inkWidgetRef.SetVisible( m_perkTooltipBgLeft, false );
	}

	protected event OnPlayRelicIntroAnimationEvent( evt : PlayRelicIntroAnimationEvent )
	{
		PlayRelicIntroAnim();
	}

	private function PlayRelicIntroAnim()
	{
		var i : Int32;
		var target : inkWidgetsSet;
		PlayLibraryAnimation( 'root_relic_intro' );
		for( i = 0; i < m_attributesControllersList.Size(); i += 1 )
		{
			if( m_attributesControllersList[ i ].GetAttributeType() == PerkMenuAttribute.Johnny )
			{
				m_attributesControllersList[ i ].GetRootWidget().SetVisible( true );
				target = new inkWidgetsSet;
				target.Select( m_attributesControllersList[ i ].GetRootWidget() );
				PlayLibraryAnimationOnTargets( 'base_relic_intro_button', target );
			}
			else if( m_attributesControllersList[ i ].GetAttributeType() == PerkMenuAttribute.Espionage )
			{
				m_attributesControllersList[ i ].GetRootWidget().SetVisible( true );
				target = new inkWidgetsSet;
				target.Select( m_attributesControllersList[ i ].GetRootWidget() );
				PlayLibraryAnimationOnTargets( 'ep1_relic_intro_button', target );
			}
		}
	}

}

enum NewPeksActiveScreen
{
	Categories = 0,
	Perks = 1,
	Espionage = 2,
	Skills = 3,
	Count = 4,
	Invalid = -1,
}

struct PerkHoverEventTooltipData
{
	var widget : weak< inkWidget >;
	var data : IDisplayData;
	var placement : gameuiETooltipPlacement;
	var isShown : Bool;
}

class UpdatePlayerDevelopmentData extends Event
{
}

class PlayNewPerksSoundEvent extends Event
{
	var soundName : CName;
	var rumbleStrength : RumbleStrength;
	var stopIfPlaying : Bool;
	default stopIfPlaying = true;
}

class PlayRelicIntroAnimationEvent extends Event
{
}

class PerkUserData extends IScriptable
{
	var statType : gamedataStatType;
	var cyberwareScreenType : CyberwareScreenType;
	var perkType : gamedataNewPerkType;
}

