class ServerNode extends InteractiveDevice
{
	default m_controllerTypeName = 'ServerNodeController';
	[ category = "Tech weapon charge properties" ][ tooltip = "minimal charge value to damage the node when it's closed" ]
	private instanceeditable var m_minWeaponCharge : Float;
	[ category = "Tech weapon charge properties" ][ tooltip = "above the max value the node will explode (set it above 1 if you don't want any explosion)" ]
	private instanceeditable var m_maxWeaponCharge : Float;
	private var m_livePinMeshes : array< MeshComponent >;
	private var m_deadPinMeshes : array< MeshComponent >;
	private var m_closedFrontPlates : array< MeshComponent >;
	private var m_animatedFrontPlates : array< MeshComponent >;
	private var m_numOfPins : Int32;
	default m_numOfPins = 12;
	private var m_alivePins : Int32;
	default m_alivePins = 12;
	protected mutable instanceeditable var m_pinIndices : array< Int32 >;
	protected editable var m_nodesDestroyedInTotalQuestFactName : CName;
	default m_nodesDestroyedInTotalQuestFactName = 'q303_03_server_nodes_destroyed';
	private var m_animFeatureServer : AnimFeatureServer;
	private var m_statPoolSystem : StatPoolsSystem;
	private var m_healthListener : ServerNodeHealthChangeListener;
	private const var m_ventingFX : CName;
	default m_ventingFX = 'venting';
	private const var m_damagedFX : CName;
	default m_damagedFX = 'damage_state_01';
	protected var m_destroyedMesh : PhysicalMeshComponent;

	protected event OnGameAttached()
	{
		super.OnGameAttached();
		m_healthListener = ServerNodeHealthChangeListener.Create( this );
		GameInstance.GetStatPoolsSystem( GetGame() ).RequestRegisteringListener( GetEntityID(), gamedataStatPoolType.Health, m_healthListener );
	}

	protected event OnDetach()
	{
		GameInstance.GetStatPoolsSystem( GetGame() ).RequestUnregisteringListener( GetEntityID(), gamedataStatPoolType.Health, m_healthListener );
		super.OnDetach();
	}

	protected override function ResolveGameplayState()
	{
		var i : Int32;
		var numberOfDestroyedPins : Int32;
		m_animFeatureServer = new AnimFeatureServer;
		for( i = 0; i < m_numOfPins; i += 1 )
		{
			if( GetDevicePS().IsPinDestroyed( i ) )
			{
				numberOfDestroyedPins += 1;
				m_livePinMeshes[ i ].Toggle( false );
				m_deadPinMeshes[ i ].Toggle( true );
			}
		}
		if( numberOfDestroyedPins == m_numOfPins )
		{
			GetDevicePS().SetServerState( ServerState.Destroyed );
		}
		UpdateVisuals();
		super.ResolveGameplayState();
	}

	protected event OnRequestComponents( ri : EntityRequestComponentsInterface )
	{
		var i : Int32;
		var pinComponentName : String;
		for( i = 1; i <= m_numOfPins; i += 1 )
		{
			pinComponentName = "q303_server_core_a" + i;
			EntityRequestComponentsInterface.RequestComponent( ri, StringToName( pinComponentName ), 'MeshComponent', true );
			pinComponentName = "q303_server_core_destroyed_" + i;
			EntityRequestComponentsInterface.RequestComponent( ri, StringToName( pinComponentName ), 'MeshComponent', true );
		}
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_a0', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_b0', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_c0', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_d0', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_a', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_b', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_c', 'MeshComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'q303_server_cores_shutter_d', 'MeshComponent', true );
		super.OnRequestComponents( ri );
	}

	protected event OnTakeControl( ri : EntityResolveComponentsInterface )
	{
		var i : Int32;
		var pinComponentName : String;
		for( i = 1; i <= m_numOfPins; i += 1 )
		{
			pinComponentName = "q303_server_core_a" + i;
			m_livePinMeshes.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, StringToName( pinComponentName ) ) ) ) );
			pinComponentName = "q303_server_core_destroyed_" + i;
			m_deadPinMeshes.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, StringToName( pinComponentName ) ) ) ) );
		}
		m_closedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_a0' ) ) ) );
		m_closedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_b0' ) ) ) );
		m_closedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_c0' ) ) ) );
		m_closedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_d0' ) ) ) );
		m_animatedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_a' ) ) ) );
		m_animatedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_b' ) ) ) );
		m_animatedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_c' ) ) ) );
		m_animatedFrontPlates.PushBack( ( ( MeshComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'q303_server_cores_shutter_d' ) ) ) );
		super.OnTakeControl( ri );
		m_controller = ( ( ServerNodeController )( EntityResolveComponentsInterface.GetComponent( ri, 'controller' ) ) );
	}

	protected override function UpdateDeviceState( optional isDelayed : Bool ) : Bool
	{
		return super.UpdateDeviceState( isDelayed );
	}

	private function UpdateAnimation()
	{
		m_animFeatureServer.m_coverState = ( ( Int32 )( GetDevicePS().GetCoverState() ) );
		m_animFeatureServer.m_serverState = ( ( Int32 )( GetDevicePS().GetServerState() ) );
		AnimationControllerComponent.ApplyFeature( this, 'server', m_animFeatureServer );
	}

	private function UpdateFX()
	{
		var serverState : ServerState;
		serverState = GetDevicePS().GetServerState();
		if( serverState == ServerState.Active )
		{
			StartHackingFX();
		}
		else if( serverState == ServerState.Destroyed )
		{
			DestroyFX();
		}
		else if( serverState == ServerState.Damaged )
		{
			DamageFX();
		}
		else if( serverState == ServerState.Inactive )
		{
			StopHackingFX();
		}
	}

	private function StartHackingFX()
	{
		SetLineVisibleState( true );
		GameObjectEffectHelper.StartEffectEvent( this, m_ventingFX );
	}

	private function SetLineVisibleState( isVisible : Bool )
	{
		GetDevicePS().DrawBetweenEntities( isVisible, false, GetFxResourceByKey( 'pingNetworkLink' ), GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject().GetEntityID(), GetEntityID(), false, false, false, false, true );
	}

	private function StopHackingFX()
	{
		SetLineVisibleState( false );
		GameObjectEffectHelper.BreakEffectLoopEvent( this, m_ventingFX );
	}

	private function DamageFX()
	{
		GameObjectEffectHelper.StartEffectEvent( this, m_damagedFX );
	}

	private function DestroyFX()
	{
		var coverState : CoverState;
		coverState = GetDevicePS().GetCoverState();
		if( coverState == CoverState.Closed )
		{
			DestroyedClosed();
		}
		SetLineVisibleState( false );
		GameObjectEffectHelper.BreakEffectLoopEvent( this, m_damagedFX );
		GameObjectEffectHelper.BreakEffectLoopEvent( this, m_ventingFX );
		GameObjectEffectHelper.StartEffectEvent( this, 'destroyed' );
	}

	private function DestroyedClosed()
	{
		var i : Int32;
		for( i = 0; i < 4; i += 1 )
		{
			m_animatedFrontPlates[ i ].Toggle( false );
			m_closedFrontPlates[ i ].Toggle( true );
		}
	}

	private function UpdateVisuals()
	{
		UpdateAnimation();
		UpdateFX();
	}

	protected event OnQuestOpen( evt : QuestOpen )
	{
		UpdateAnimation();
	}

	protected event OnQuestStartHacking( evt : QuestStartHacking )
	{
		UpdateVisuals();
	}

	protected event OnQuestClose( evt : QuestClose )
	{
		UpdateVisuals();
	}

	protected event OnQuestStopHacking( evt : QuestStopHacking )
	{
		UpdateVisuals();
	}

	protected event OnOverloadDevice( evt : OverloadDevice )
	{
		Explode();
	}

	protected event OnQuestExplode( evt : QuestExplode )
	{
		Explode();
	}

	protected event OnDeath( evt : gameDeathEvent )
	{
		if( GetDevicePS().GetServerState() != ServerState.Destroyed )
		{
			GetDevicePS().TryExplode();
			UpdateVisuals();
			SetPinMeshes( 0 );
			SetFactValue( GetGame(), m_nodesDestroyedInTotalQuestFactName, GetFact( GetGame(), m_nodesDestroyedInTotalQuestFactName ) + 1 );
			super.OnDeath( evt );
		}
	}

	private function Explode()
	{
		var deathEvent : gameDeathEvent;
		deathEvent = new gameDeathEvent;
		QueueEvent( deathEvent );
	}

	protected override function ProcessDamagePipeline( evt : gameHitEvent )
	{
		var isPiercingHit : Bool;
		if( GetDevicePS().GetServerState() == ServerState.Destroyed || !( IsPlayerHitting( evt.attackData ) ) )
		{
			return;
		}
		isPiercingHit = IsPiercingHit( evt.attackData );
		if( isPiercingHit && IsExplosionChargeMet( evt.attackData ) )
		{
			Explode();
		}
		else if( ( ( GetDevicePS().GetCoverState() == CoverState.Open || GetDevicePS().GetServerState() == ServerState.Active ) || GetDevicePS().GetServerState() == ServerState.Damaged ) || ( isPiercingHit && IsPiercingChargeMet( evt.attackData ) ) )
		{
			super.ProcessDamagePipeline( evt );
		}
	}

	private function IsPlayerHitting( attackData : AttackData ) : Bool
	{
		var instigator : weak< GameObject >;
		instigator = attackData.GetInstigator();
		return instigator.GetEntityID() == GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject().GetEntityID();
	}

	private function IsPiercingHit( attackData : AttackData ) : Bool
	{
		var weapon : weak< WeaponObject >;
		var instigator : weak< GameObject >;
		weapon = attackData.GetWeapon();
		instigator = attackData.GetInstigator();
		return RPGManager.GetWeaponEvolution( weapon.GetItemID() ) == gamedataWeaponEvolution.Tech || RPGManager.IsTechPierceEnabled( instigator.GetGame(), instigator, weapon.GetItemID() );
	}

	private function IsExplosionChargeMet( attackData : AttackData ) : Bool
	{
		var chargeValue : Float;
		chargeValue = attackData.GetWeaponCharge();
		return chargeValue >= m_maxWeaponCharge;
	}

	private function IsPiercingChargeMet( attackData : AttackData ) : Bool
	{
		var chargeValue : Float;
		chargeValue = attackData.GetWeaponCharge();
		return ( chargeValue >= m_minWeaponCharge ) && ( chargeValue < m_maxWeaponCharge );
	}

	private function SetPinMeshes( numOfPinsToKeepOn : Int32 )
	{
		var i : Int32;
		var pinToDisable : Int32;
		var random : Int32;
		random = SetRandom();
		if( numOfPinsToKeepOn < m_alivePins )
		{
			pinToDisable = m_alivePins - numOfPinsToKeepOn;
			for( i = 0; i < pinToDisable; i += 1 )
			{
				m_livePinMeshes[ m_pinIndices[ random ] ].Toggle( false );
				m_deadPinMeshes[ m_pinIndices[ random ] ].Toggle( true );
				m_pinIndices.Erase( random );
				m_alivePins -= 1;
				GetDevicePS().SetDestroyedPin( i );
				random = SetRandom();
			}
		}
	}

	private function SetRandom() : Int32
	{
		if( m_pinIndices.Size() > 1 )
		{
			return RandRange( 0, m_pinIndices.Size() );
		}
		return 0;
	}

	public function OnHealthChanged( currentHealthPercentage : Float )
	{
		var numOfPins : Float;
		numOfPins = ( ( Float )( m_numOfPins ) ) - 1.0;
		SetPinMeshes( CeilF( ( numOfPins * currentHealthPercentage ) / 100.0 ) );
		if( currentHealthPercentage < 70.0 )
		{
			GetDevicePS().SetServerState( ServerState.Damaged );
			UpdateFX();
		}
	}

	public const override function GetDevicePS() : ServerNodeControllerPS
	{
		return GetController().GetPS();
	}

	private const override function GetController() : ServerNodeController
	{
		return ( ( ServerNodeController )( m_controller ) );
	}

}

class AnimFeatureServer extends AnimFeature
{
	editable var m_coverState : Int32;
	editable var m_serverState : Int32;
}

