struct ChatterKeyValuePair
{
	var Key : CRUID;
	var Value : weak< ChatterLineLogicController >;
	var Owner : weak< GameObject >;
}

class ChattersGameController extends BaseSubtitlesGameController
{
	private var c_DisplayRange : Float;
	default c_DisplayRange = 150;
	private var c_CloseDisplayRange : Float;
	default c_CloseDisplayRange = 20;
	private var c_TimeToUnblockSec : Float;
	default c_TimeToUnblockSec = 5.f;
	private var m_rootWidget : weak< inkCompoundWidget >;
	private var m_AllControllers : array< ChatterKeyValuePair >;
	private var m_targetingSystem : TargetingSystem;
	private var m_broadcastBlockingLines : array< CRUID >;
	private var m_playerInDialogChoice : Bool;
	default m_playerInDialogChoice = false;
	private var m_lastBroadcastBlockingLineTime : EngineTime;
	private var m_lastChoiceTime : EngineTime;
	private var m_bbPSceneTierEventId : CallbackHandle;
	private var m_sceneTier : Int32;
	private var m_OnNameplateEntityChangedCallback : CallbackHandle;
	private var m_OnNameplateOffsetChangedCallback : CallbackHandle;
	private var m_OnNameplateVisibilityChangedCallback : CallbackHandle;
	private var m_OnScannerModeChangedCallback : CallbackHandle;
	private var m_OnOnDialogsDataCallback : CallbackHandle;

	protected event OnInitialize()
	{
		var blackboardSystem : BlackboardSystem;
		var blackboard : IBlackboard;
		var blackboardUI : IBlackboard;
		var blackboardUIInteractions : IBlackboard;
		super.OnInitialize();
		m_rootWidget = ( ( inkCompoundWidget )( GetRootWidget() ) );
		blackboardSystem = GetBlackboardSystem();
		blackboard = blackboardSystem.Get( GetAllBlackboardDefs().UI_NameplateData );
		m_OnNameplateEntityChangedCallback = blackboard.RegisterListenerVariant( GetAllBlackboardDefs().UI_NameplateData.EntityID, this, 'OnNameplateEntityChanged' );
		m_OnNameplateOffsetChangedCallback = blackboard.RegisterListenerFloat( GetAllBlackboardDefs().UI_NameplateData.HeightOffset, this, 'OnNameplateOffsetChanged' );
		m_OnNameplateVisibilityChangedCallback = blackboard.RegisterListenerBool( GetAllBlackboardDefs().UI_NameplateData.IsVisible, this, 'OnNameplateVisibilityChanged' );
		m_targetingSystem = GameInstance.GetTargetingSystem( GetPlayerControlledObject().GetGame() );
		blackboardUI = blackboardSystem.Get( GetAllBlackboardDefs().UI_Scanner );
		m_OnScannerModeChangedCallback = blackboardUI.RegisterListenerVariant( GetAllBlackboardDefs().UI_Scanner.ScannerMode, this, 'OnScannerModeChanged' );
		m_lastBroadcastBlockingLineTime = EngineTime.FromFloat( -( c_TimeToUnblockSec ) );
		m_lastChoiceTime = EngineTime.FromFloat( -( c_TimeToUnblockSec ) );
		blackboardUIInteractions = GameInstance.GetBlackboardSystem( GetGame() ).Get( GetAllBlackboardDefs().UIInteractions );
		m_OnOnDialogsDataCallback = blackboardUIInteractions.RegisterDelayedListenerVariant( GetAllBlackboardDefs().UIInteractions.DialogChoiceHubs, this, 'OnDialogsData' );
	}

	protected event OnPlayerAttach( playerGameObject : GameObject )
	{
		var playerStateMachineBlackboard : IBlackboard;
		var playerSMDef : PlayerStateMachineDef;
		playerSMDef = GetAllBlackboardDefs().PlayerStateMachine;
		if( playerSMDef )
		{
			playerStateMachineBlackboard = GetPSMBlackboard( playerGameObject );
			if( playerStateMachineBlackboard )
			{
				m_bbPSceneTierEventId = playerStateMachineBlackboard.RegisterListenerInt( playerSMDef.SceneTier, this, 'OnSceneTierChange' );
				m_sceneTier = playerStateMachineBlackboard.GetInt( playerSMDef.SceneTier );
			}
		}
	}

	protected event OnPlayerDetach( playerGameObject : GameObject )
	{
		var playerStateMachineBlackboard : IBlackboard;
		var playerSMDef : PlayerStateMachineDef;
		playerSMDef = GetAllBlackboardDefs().PlayerStateMachine;
		if( playerSMDef )
		{
			playerStateMachineBlackboard = GetPSMBlackboard( playerGameObject );
			if( playerStateMachineBlackboard )
			{
				playerStateMachineBlackboard.UnregisterListenerInt( playerSMDef.SceneTier, m_bbPSceneTierEventId );
			}
		}
	}

	protected event OnSceneTierChange( argTier : Int32 )
	{
		m_sceneTier = argTier;
	}

	protected override function GetTextSizeSettigId() : CName
	{
		return 'ChattersTextSize';
	}

	protected override function SetChattersVisibilitySetting( value : Bool )
	{
		DisableBySettings( !( value ) );
	}

	protected override function SetChattersForeignLinesVisibilitySetting( value : Bool )
	{
		ForceForeignLinesBySettings( value );
	}

	protected override function ShowKiroshiSettings( value : Bool )
	{
		var configVar : ConfigVarBool;
		configVar = ( ( ConfigVarBool )( m_settings.GetVar( m_groupPath, 'OverheadsForceKiroshiTexts' ) ) );
		configVar.SetVisible( value );
	}

	protected override function OnSubCreated( controller : weak< BaseSubtitleLineLogicController > )
	{
		var nameplateEntityId : EntityID;
		var isNameplateVisible : Bool;
		var blackboardSystem : BlackboardSystem;
		var blackboard : IBlackboard;
		var tmpVariant : Variant;
		blackboardSystem = GetBlackboardSystem();
		blackboard = blackboardSystem.Get( GetAllBlackboardDefs().UI_NameplateData );
		tmpVariant = blackboard.GetVariant( GetAllBlackboardDefs().UI_NameplateData.EntityID );
		if( tmpVariant.IsValid() )
		{
			nameplateEntityId = ( ( EntityID )tmpVariant );
		}
		isNameplateVisible = blackboard.GetBool( GetAllBlackboardDefs().UI_NameplateData.IsVisible );
		( ( ChatterLineLogicController )( controller ) ).SetNameplateData( isNameplateVisible, nameplateEntityId );
		super.OnSubCreated( controller );
	}

	private function OnNameplateOffsetChanged( vrt : Float )
	{
		var i : Int32;
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			( ( ChatterLineLogicController )( m_AllControllers[ i ].Value ) ).SetNameplateOffsetValue( vrt );
		}
	}

	private function OnNameplateEntityChanged( vrt : Variant )
	{
		var i : Int32;
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			( ( ChatterLineLogicController )( m_AllControllers[ i ].Value ) ).SetNameplateEntity( vrt );
		}
	}

	private function OnNameplateVisibilityChanged( visibility : Bool )
	{
		var i : Int32;
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			( ( ChatterLineLogicController )( m_AllControllers[ i ].Value ) ).SetNameplateVisibility( visibility );
		}
	}

	private function OnNameplateChanged()
	{
		var nameplateEntityId : EntityID;
		var isNameplateVisible : Bool;
		var blackboardSystem : BlackboardSystem;
		var blackboard : IBlackboard;
		blackboardSystem = GetBlackboardSystem();
		blackboard = blackboardSystem.Get( GetAllBlackboardDefs().UI_NameplateData );
		nameplateEntityId = ( ( EntityID )( blackboard.GetVariant( GetAllBlackboardDefs().UI_NameplateData.EntityID ) ) );
		isNameplateVisible = blackboard.GetBool( GetAllBlackboardDefs().UI_NameplateData.IsVisible );
		UpdateChattersNameplateData( nameplateEntityId, isNameplateVisible );
	}

	protected event OnDialogsData( value : Variant )
	{
		var data : DialogChoiceHubs;
		var inDialogChoice : Bool;
		data = ( ( DialogChoiceHubs )value );
		inDialogChoice = data.choiceHubs.Size() > 0;
		if( !( inDialogChoice ) && m_playerInDialogChoice )
		{
			m_lastChoiceTime = GameInstance.GetSimTime( GetGame() );
		}
		m_playerInDialogChoice = inDialogChoice;
	}

	protected event OnUninitialize()
	{
		super.OnUninitialize();
	}

	private function AddBroadcastBlockingLine( const lineData : ref< scnDialogLineData > )
	{
		if( !( m_broadcastBlockingLines.Contains( lineData.id ) ) )
		{
			m_broadcastBlockingLines.PushBack( lineData.id );
		}
	}

	private function IsBroadcastBlockedByMainDialogue() : Bool
	{
		return ( ( m_playerInDialogChoice || ( m_broadcastBlockingLines.Size() > 0 ) ) || ( EngineTime.ToFloat( GameInstance.GetSimTime( GetGame() ) - m_lastBroadcastBlockingLineTime ) < c_TimeToUnblockSec ) ) || ( EngineTime.ToFloat( GameInstance.GetSimTime( GetGame() ) - m_lastChoiceTime ) < c_TimeToUnblockSec );
	}

	private function IsLineTypeConditionFulfilled( const lineData : ref< scnDialogLineData > ) : Bool
	{
		var speakerVehicle : weak< VehicleObject >;
		var player : weak< PlayerPuppet >;
		if( lineData.type == scnDialogLineType.OverHead || lineData.type == scnDialogLineType.OverHeadAlwaysVisible )
		{
			return true;
		}
		else if( lineData.type == scnDialogLineType.Radio )
		{
			player = ( ( PlayerPuppet )( GetPlayerControlledObject() ) );
			speakerVehicle = ( ( VehicleObject )( lineData.speaker ) );
			return ( ( speakerVehicle == NULL ) && ( lineData.speaker != player ) ) && !( IsBroadcastBlockedByMainDialogue() );
		}
		else if( lineData.type == scnDialogLineType.GlobalTV )
		{
			return !( IsBroadcastBlockedByMainDialogue() );
		}
		return false;
	}

	private function IsDistanceConditionFulfilled( const lineData : ref< scnDialogLineData > ) : Bool
	{
		var range : Float;
		range = ( ( m_sceneTier < ( ( Int32 )( GameplayTier.Tier2_StagedGameplay ) ) ) ? ( c_DisplayRange ) : ( c_CloseDisplayRange ) );
		return Vector4.DistanceSquared( GetPlayerControlledObject().GetWorldPosition(), lineData.speaker.GetWorldPosition() ) < ( range * range );
	}

	protected override function ShouldDisplayLine( const lineData : ref< scnDialogLineData > ) : Bool
	{
		if( IsMainDialogLine( lineData ) )
		{
			AddBroadcastBlockingLine( lineData );
			return false;
		}
		if( lineData.type == scnDialogLineType.OverHeadAlwaysVisible )
		{
			return true;
		}
		return ( ( ( lineData.speaker != NULL ) && ( m_sceneTier < ( ( Int32 )( GameplayTier.Tier3_LimitedGameplay ) ) ) ) && IsLineTypeConditionFulfilled( lineData ) ) && IsDistanceConditionFulfilled( lineData );
	}

	private override function OnRemovalFailure( lineId : CRUID )
	{
		if( m_broadcastBlockingLines.Remove( lineId ) && ( m_broadcastBlockingLines.Size() == 0 ) )
		{
			m_lastBroadcastBlockingLineTime = GameInstance.GetSimTime( GetGame() );
		}
	}

	protected override function CreateLine( lineSpawnData : LineSpawnData )
	{
		AsyncSpawnFromLocal( m_rootWidget, 'Line', this, 'OnLineSpawned', lineSpawnData );
	}

	protected override function SetupLine( lineWidget : inkWidget, lineSpawnData : LineSpawnData )
	{
		var currKeyValuePair : ChatterKeyValuePair;
		var gameObject : weak< GameObject >;
		var isDevice : Bool;
		gameObject = ( ( GameObject )( lineSpawnData.m_lineData.speaker ) );
		isDevice = gameObject && gameObject.IsDevice();
		StartScreenProjection( lineWidget, isDevice );
		currKeyValuePair.Key = lineSpawnData.m_lineData.id;
		currKeyValuePair.Value = ( ( ChatterLineLogicController )( lineWidget.GetController() ) );
		currKeyValuePair.Owner = lineSpawnData.m_lineData.speaker;
		m_AllControllers.PushBack( currKeyValuePair );
	}

	protected override function OnHideLineByData( const lineData : ref< subtitleLineMapEntry > )
	{
		var i : Int32;
		super.OnHideLineByData( lineData );
		StopScreenProjection( lineData.widget );
		m_rootWidget.RemoveChild( lineData.widget );
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			if( !( m_AllControllers[ i ].Owner ) || ( lineData.owner && m_AllControllers[ i ].Owner.GetPersistentID() == lineData.owner.GetPersistentID() ) )
			{
				m_AllControllers.Erase( i );
				i -= 1;
			}
		}
	}

	protected override function OnHideLine( const lineData : ref< subtitleLineMapEntry > )
	{
		var i : Int32;
		super.OnHideLine( lineData );
		StopScreenProjection( lineData.widget );
		m_rootWidget.RemoveChild( lineData.widget );
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			if( m_AllControllers[ i ].Key == lineData.id )
			{
				m_AllControllers.Erase( i );
			}
		}
	}

	private function StartScreenProjection( lineWidget : weak< inkWidget >, isDevice : Bool )
	{
		var projection : inkScreenProjection;
		var controller : weak< ChatterLineLogicController >;
		controller = ( ( ChatterLineLogicController )( lineWidget.GetController() ) );
		projection = RegisterScreenProjection( controller.CreateProjectionData( isDevice ) );
		controller.SetProjection( projection );
	}

	private function StopScreenProjection( lineWidget : weak< inkWidget > )
	{
		var controller : weak< ChatterLineLogicController >;
		controller = ( ( ChatterLineLogicController )( lineWidget.GetController() ) );
		UnregisterScreenProjection( controller.GetProjection() );
	}

	protected event OnScreenProjectionUpdate( projections : gameuiScreenProjectionsData )
	{
		var i, count : Int32;
		var controller : weak< ChatterLineLogicController >;
		var projection : inkScreenProjection;
		var target : EntityID;
		var screenSize : Vector2;
		var X : Float;
		var Y : Float;
		var distance : Float;
		var closestDistance : Float;
		closestDistance = 100000.0;
		count = projections.data.Size();
		for( i = 0; i < count; i += 1 )
		{
			projection = projections.data[ i ];
			controller = ( ( ChatterLineLogicController )( projection.GetUserData() ) );
			screenSize = m_rootWidget.GetSize();
			X = projection.currentPosition.X;
			Y = projection.currentPosition.Y;
			X -= ( screenSize.X / 2.0 );
			Y -= ( screenSize.Y / 2.0 );
			distance = SqrtF( ( X * X ) + ( Y * Y ) );
			if( ( distance < closestDistance ) && projection.IsInScreen() )
			{
				closestDistance = distance;
				target = controller.GetOwnerID();
			}
		}
		for( i = 0; i < count; i += 1 )
		{
			projection = projections.data[ i ];
			controller = ( ( ChatterLineLogicController )( projection.GetUserData() ) );
			controller.UpdateProjection( target, this );
		}
	}

	protected event OnNameplateVisibleEvent( evt : NameplateVisibleEvent )
	{
		UpdateChattersNameplateData( evt.entityID, evt.isNameplateVisible );
	}

	private function UpdateChattersNameplateData( entID : EntityID, isVisible : Bool )
	{
		var i : Int32;
		for( i = 0; i < m_AllControllers.Size(); i = i + 1 )
		{
			( ( ChatterLineLogicController )( m_AllControllers[ i ].Value ) ).SetNameplateData( isVisible, entID );
		}
	}

	protected event OnScannerModeChanged( val : Variant )
	{
		var evt : ScanningModeEvent;
		evt = ( ( ScanningModeEvent )val );
		m_rootWidget.SetVisible( evt.mode == gameScanningMode.Inactive );
	}

}

class ChatterLineLogicController extends BaseSubtitleLineLogicController
{
	private editable var m_TextContainer : inkWidgetRef;
	private editable var m_speachBubble : inkWidgetRef;
	private editable var m_background : inkRectangleRef;
	private editable var m_container_normal : inkWidgetRef;
	private editable var m_container_wide : inkWidgetRef;
	private editable var m_text_normal : inkTextRef;
	private editable var m_text_wide : inkTextRef;
	private var m_kiroshiAnimationCtrl_Normal : weak< inkTextKiroshiAnimController >;
	private var m_kiroshiAnimationCtrl_Wide : weak< inkTextKiroshiAnimController >;
	private var m_motherTongueCtrl_Normal : weak< inkTextMotherTongueController >;
	private var m_motherTongueCtrl_Wide : weak< inkTextMotherTongueController >;
	private var m_isNameplateVisible : Bool;
	private var m_nameplateEntityId : EntityID;
	private var m_nameplatHeightOffset : Float;
	private var m_ownerId : EntityID;
	private var c_ExtraWideTextWidth : Int32;
	default c_ExtraWideTextWidth = 110;
	private var m_rootWidget : weak< inkWidget >;
	private var m_projection : inkScreenProjection;
	private var m_subtitlesMaxDistance : Float;
	private var m_bubbleMinDistance : Float;
	private var m_limitSubtitlesDistance : Bool;
	private var m_isOverHead : Bool;

	protected event OnInitialize()
	{
		m_rootWidget = GetRootWidget();
		m_kiroshiAnimationCtrl_Normal = ( ( inkTextKiroshiAnimController )( inkWidgetRef.GetController( m_container_normal ) ) );
		m_kiroshiAnimationCtrl_Wide = ( ( inkTextKiroshiAnimController )( inkWidgetRef.GetController( m_container_wide ) ) );
		m_motherTongueCtrl_Normal = ( ( inkTextMotherTongueController )( inkWidgetRef.GetControllerByType( m_container_normal, 'inkTextMotherTongueController' ) ) );
		m_motherTongueCtrl_Wide = ( ( inkTextMotherTongueController )( inkWidgetRef.GetControllerByType( m_container_wide, 'inkTextMotherTongueController' ) ) );
		inkWidgetRef.SetVisible( m_speachBubble, false );
	}

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

	public override function SetupSettings( textSize : Int32, backgroundOpacity : Float )
	{
		inkTextRef.SetFontSize( m_text_normal, textSize );
		inkTextRef.SetFontSize( m_text_wide, textSize );
		m_kiroshiAnimationCtrl_Normal.SetupFontSettings( textSize );
		m_kiroshiAnimationCtrl_Wide.SetupFontSettings( textSize );
		inkRectangleRef.SetOpacity( m_background, backgroundOpacity / 100.0 );
	}

	public override function ShowBackground( value : Bool )
	{
		inkRectangleRef.SetVisible( m_background, value );
	}

	public override function SetLineData( const lineData : ref< scnDialogLineData > )
	{
		var displayData : scnDialogDisplayString;
		var animCtrl : weak< inkTextKiroshiAnimController >;
		var motherTongueCtrl : weak< inkTextMotherTongueController >;
		var isWide : Bool;
		var gameObject : weak< GameObject >;
		gameObject = ( ( GameObject )( lineData.speaker ) );
		m_isOverHead = lineData.type == scnDialogLineType.OverHead;
		if( gameObject && gameObject.IsDevice() )
		{
			m_rootWidget.SetAnchorPoint( Vector2( 0.5, 0.0 ) );
			m_limitSubtitlesDistance = true;
			m_subtitlesMaxDistance = 10.0;
			m_bubbleMinDistance = 10.0;
		}
		else if( gameObject && m_isOverHead )
		{
			m_rootWidget.SetAnchorPoint( Vector2( 0.5, 1.0 ) );
			m_limitSubtitlesDistance = true;
			m_subtitlesMaxDistance = 30.0;
			m_bubbleMinDistance = 25.0;
		}
		else
		{
			m_rootWidget.SetAnchorPoint( Vector2( 0.5, 1.0 ) );
			m_limitSubtitlesDistance = false;
			m_subtitlesMaxDistance = 0.0;
			m_bubbleMinDistance = 0.0;
		}
		m_projection.SetEntity( lineData.speaker );
		displayData = scnDialogLineData.GetDisplayText( lineData );
		isWide = StrLen( displayData.translation ) >= c_ExtraWideTextWidth;
		m_ownerId = lineData.speaker.GetEntityID();
		if( isWide )
		{
			animCtrl = m_kiroshiAnimationCtrl_Wide;
			motherTongueCtrl = m_motherTongueCtrl_Wide;
		}
		else
		{
			animCtrl = m_kiroshiAnimationCtrl_Normal;
			motherTongueCtrl = m_motherTongueCtrl_Normal;
		}
		inkTextRef.SetVisible( m_text_normal, !( isWide ) );
		inkTextRef.SetVisible( m_text_wide, isWide );
		inkWidgetRef.SetVisible( m_container_normal, !( isWide ) );
		inkWidgetRef.SetVisible( m_container_wide, isWide );
		inkWidgetRef.SetVisible( m_TextContainer, false );
		inkWidgetRef.SetVisible( m_speachBubble, true );
		if( scnDialogLineData.HasKiroshiTag( lineData ) )
		{
			displayData = scnDialogLineData.GetDisplayText( lineData );
			if( IsKiroshiEnabled() )
			{
				animCtrl.SetPreTranslatedText( displayData.preTranslatedText );
				animCtrl.SetNativeText( displayData.text, displayData.language );
				animCtrl.SetTargetText( displayData.translation );
				animCtrl.SetPostTranslatedText( displayData.postTranslatedText );
				SetupAnimation( lineData.duration, animCtrl );
				animCtrl.PlaySetAnimation();
			}
			else
			{
				motherTongueCtrl.SetPreTranslatedText( "" );
				motherTongueCtrl.SetNativeText( displayData.text, displayData.language );
				motherTongueCtrl.SetTranslatedText( "" );
				motherTongueCtrl.SetPostTranslatedText( "" );
				motherTongueCtrl.ApplyTexts();
			}
		}
		else if( scnDialogLineData.HasMothertongueTag( lineData ) )
		{
			displayData = scnDialogLineData.GetDisplayText( lineData );
			motherTongueCtrl.SetPreTranslatedText( displayData.preTranslatedText );
			motherTongueCtrl.SetNativeText( displayData.text, displayData.language );
			motherTongueCtrl.SetTranslatedText( displayData.translation );
			motherTongueCtrl.SetPostTranslatedText( displayData.postTranslatedText );
			motherTongueCtrl.ApplyTexts();
		}
		else
		{
			inkTextRef.SetText( m_text_normal, lineData.text );
			inkTextRef.SetText( m_text_wide, lineData.text );
		}
	}

	public function CreateProjectionData( isDevice : Bool ) : inkScreenProjectionData
	{
		var projectionData : inkScreenProjectionData;
		projectionData.userData = this;
		projectionData.slotComponentName = 'UI_Slots';
		if( isDevice )
		{
			projectionData.fixedWorldOffset = Vector4( 0.0, 0.0, 0.0, 0.0 );
			projectionData.slotName = 'UI_Subtitles';
			projectionData.slotFallbackName = 'UI_Interaction';
		}
		else
		{
			projectionData.fixedWorldOffset = Vector4( 0.0, 0.0, 0.30000001, 0.0 );
			projectionData.slotName = 'UI_Interaction';
			projectionData.slotFallbackName = 'UI_Subtitles';
		}
		return projectionData;
	}

	public function GetProjection() : inkScreenProjection
	{
		return m_projection;
	}

	public function SetProjection( projection : inkScreenProjection )
	{
		m_projection = projection;
	}

	public function IsVisible( targetedObject : EntityID ) : Bool
	{
		var isVisible : Bool;
		if( !( m_limitSubtitlesDistance ) )
		{
			isVisible = true;
		}
		else if( m_isOverHead )
		{
			isVisible = m_projection.distanceToCamera <= m_subtitlesMaxDistance;
		}
		else
		{
			isVisible = m_projection.VoIsPerceptible( targetedObject );
		}
		return isVisible;
	}

	public function IsBubble( targetedObject : EntityID ) : Bool
	{
		var isBubble : Bool;
		if( !( m_limitSubtitlesDistance ) )
		{
			isBubble = targetedObject != m_ownerId;
		}
		else
		{
			isBubble = ( targetedObject != m_ownerId ) || ( ( m_projection.distanceToCamera > m_bubbleMinDistance ) && ( m_projection.distanceToCamera <= m_subtitlesMaxDistance ) );
		}
		return isBubble;
	}

	public function UpdateProjection( targetedObject : EntityID, owner : weak< ChattersGameController > )
	{
		var margin : inkMargin;
		var isVisible : Bool;
		var isBubble : Bool;
		if( m_projection )
		{
			isVisible = IsVisible( targetedObject );
			isBubble = IsBubble( targetedObject );
			margin.left = m_projection.currentPosition.X;
			margin.top = m_projection.currentPosition.Y;
			if( m_isNameplateVisible && ( m_projection.GetEntity().GetEntityID() == m_nameplateEntityId ) )
			{
				margin.top -= 30.0;
			}
			owner.ApplyProjectionMarginOnWidget( m_rootWidget, margin );
			if( m_projection.IsInScreen() )
			{
				inkWidgetRef.SetVisible( m_TextContainer, isVisible && !( isBubble ) );
				inkWidgetRef.SetVisible( m_speachBubble, isVisible && isBubble );
			}
		}
	}

	public function SetNameplateData( argNameplateVisible : Bool, argEntityId : EntityID )
	{
		m_isNameplateVisible = argNameplateVisible;
		m_nameplateEntityId = argEntityId;
	}

	public function SetNameplateOffsetValue( value : Float )
	{
		m_nameplatHeightOffset = value;
	}

	public function SetNameplateEntity( blackboardVariant : Variant )
	{
		m_nameplateEntityId = ( ( EntityID )blackboardVariant );
	}

	public function SetNameplateVisibility( isVisible : Bool )
	{
		m_isNameplateVisible = isVisible;
	}

}

