// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerNameplateComponent.h"

#include "Engine/Engine.h"
#include "Engine/World.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "PlayerNameplateWidget.h"

UPlayerNameplateComponent::UPlayerNameplateComponent()
{
	// Espace ecran : l'etiquette fait toujours face a la camera, par
	// construction, et n'est jamais occultee par le decor.
	SetWidgetSpace(EWidgetSpace::Screen);

	// Le widget se dimensionne sur son contenu : un pseudo court n'a pas a
	// trainer une boite de la largeur du plus long.
	SetDrawAtDesiredSize(true);

	// Une etiquette ne se vise pas et n'arrete pas un trace : sans ca, elle
	// bloquerait le canal Visibility et masquerait l'interaction du joueur
	// qu'elle surmonte.
	SetCollisionEnabled(ECollisionEnabled::NoCollision);
	SetGenerateOverlapEvents(false);

	// Au-dessus de la tete. Reglable dans le Blueprint, la capsule et la taille
	// du personnage pouvant changer.
	SetRelativeLocation(FVector(0.f, 0.f, 100.f));

	// Cachee au depart : la premiere passe decidera. Sans ca, une etiquette
	// sans nom clignote pendant la frame qui precede l'arrivee du PlayerState.
	SetVisibility(false);

	PrimaryComponentTick.bCanEverTick = true;
}

void UPlayerNameplateComponent::BeginPlay()
{
	Super::BeginPlay();

	// Un serveur dedie ne rend rien : lui faire calculer des distances pour des
	// etiquettes que personne ne verra serait du gaspillage pur. Sans effet sur
	// notre serveur d'ecoute, qui n'est pas dans ce mode.
	if (GetNetMode() == NM_DedicatedServer)
	{
		SetComponentTickEnabled(false);
		return;
	}

	const APawn* OwnerPawn = Cast<APawn>(GetOwner());

	// Son propre pseudo : on eteint definitivement, tick compris. Ce n'est pas
	// un cas a reevaluer -- un pawn ne change pas de proprietaire en cours de
	// vie, et apres un respawn c'est un NOUVEL acteur qui repassera par ici.
	if (bHideOnOwningPlayer && OwnerPawn && OwnerPawn->IsLocallyControlled())
	{
		SetVisibility(false);
		SetComponentTickEnabled(false);
	}
}

APlayerState* UPlayerNameplateComponent::GetOwnerPlayerState() const
{
	const APawn* OwnerPawn = Cast<APawn>(GetOwner());
	return OwnerPawn ? OwnerPawn->GetPlayerState() : nullptr;
}

void UPlayerNameplateComponent::RefreshName()
{
	// Le PlayerState arrive APRES le pawn chez un client : la premiere passe
	// trouve donc un nom vide, et il faut continuer a regarder.
	const APlayerState* State = GetOwnerPlayerState();
	if (!State)
	{
		return;
	}

	const FString CurrentName = State->GetPlayerName();
	if (CurrentName.IsEmpty() || CurrentName == LastShownName)
	{
		return;
	}

	if (UPlayerNameplateWidget* Nameplate = Cast<UPlayerNameplateWidget>(GetUserWidgetObject()))
	{
		Nameplate->SetPlayerName(FText::FromString(CurrentName));
		LastShownName = CurrentName;
	}
}

void UPlayerNameplateComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// Le point de vue du joueur LOCAL, et non celui du pawn porteur : c'est de
	// notre ecran qu'on parle. GetFirstLocalPlayerController et non
	// GetFirstPlayerController -- en PIE a deux fenetres, le second rendrait le
	// controller de l'autre instance et l'etiquette clignoterait.
	const APlayerController* Viewer = GEngine ? GEngine->GetFirstLocalPlayerController(GetWorld()) : nullptr;
	if (!Viewer || !Viewer->PlayerCameraManager)
	{
		SetVisibility(false);
		return;
	}

	const float Distance = FVector::Dist(
		Viewer->PlayerCameraManager->GetCameraLocation(),
		GetComponentLocation());

	// Au-dela de la portee on eteint le composant lui-meme, pas seulement son
	// opacite : un widget transparent continue d'etre projete et dessine.
	if (Distance > MaxDrawDistance)
	{
		SetVisibility(false);
		return;
	}

	RefreshName();

	// Sans nom a montrer, rien a montrer du tout.
	if (LastShownName.IsEmpty())
	{
		SetVisibility(false);
		return;
	}

	UPlayerNameplateWidget* Nameplate = Cast<UPlayerNameplateWidget>(GetUserWidgetObject());
	if (!Nameplate)
	{
		SetVisibility(false);
		return;
	}

	// Echelle en 1/d : c'est la loi de la perspective, et c'est ce que l'oeil
	// attend. A ReferenceDistance l'etiquette vaut exactement ce qui est dessine
	// dans le WBP ; deux fois plus pres elle double, deux fois plus loin elle
	// est de moitie -- borne des deux cotes.
	const float SafeDistance = FMath::Max(Distance, 1.f);
	const float Scale = FMath::Clamp(ReferenceDistance / SafeDistance, MinScale, MaxScale);
	Nameplate->SetRenderScale(FVector2D(Scale, Scale));

	// Fondu sur la derniere bande avant la portee maximale. FadeLength est
	// borne a MaxDrawDistance : un fondu plus long que la portee elle-meme
	// donnerait une etiquette jamais totalement opaque.
	const float EffectiveFade = FMath::Clamp(FadeLength, 0.f, MaxDrawDistance);
	float Opacity = 1.f;
	if (EffectiveFade > KINDA_SMALL_NUMBER)
	{
		const float FadeStart = MaxDrawDistance - EffectiveFade;
		Opacity = FMath::Clamp(1.f - (Distance - FadeStart) / EffectiveFade, 0.f, 1.f);
	}
	Nameplate->SetRenderOpacity(Opacity);

	SetVisibility(true);
}
