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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "FpsPlayer.generated.h"

class APickupItem;
class UAnimMontage;
class UCameraComponent;
class UCharacterAppearanceComponent;
class UCraftingComponent;
class UFootstepComponent;
class UHeadBobComponent;
class UHeldItemComponent;
class UInputAction;
class UInputMappingContext;
class UInteractionComponent;
class UInventoryComponent;
class UItemDataAsset;
class USurvivalStatsComponent;
struct FInputActionValue;

/**
 * Ce que vaut la réception qui vient d'avoir lieu.
 *
 * Produit par le PAWN, qui est le seul à connaître les deux seuils, et republié
 * tel quel par UFpsArmsAnimInstance. Un enum plutôt que deux booléens parce que
 * les trois cas s'excluent par construction : la machine à états compare, elle
 * n'a aucune condition à recomposer, et il n'existe aucune combinaison où deux
 * transitions seraient valides en même temps — c'est-à-dire prises au hasard.
 *
 * Remis à None dès l'entrée en chute (voir Tick), donc il vaut None pendant tout
 * le vol : une transition n'a jamais à vérifier bIsFalling en plus.
 */
UENUM(BlueprintType)
enum class ELandingImpact : uint8
{
	/** En l'air, ou chute trop faible pour mériter une animation. */
	None,

	/** Réception normale (JumpLanding). */
	Soft,

	/**
	 * Réception encaissée (HardLanding) — c'est-à-dire, très exactement, une
	 * chute qui a coûté de la vie. Le seuil n'est pas un réglage d'animation,
	 * c'est `FallDamageMinSpeed`.
	 */
	Hard
};

/**
 * Ressort amorti à une dimension, partagé par les effets de caméra.
 *
 * Pourquoi un ressort plutôt qu'une courbe paramétrée par le temps : un ressort
 * n'a pas d'animation « en cours » qu'il faudrait annuler pour en jouer une
 * autre. Une nouvelle impulsion s'ajoute simplement à l'état courant, position
 * et vitesse restent continues, donc le mouvement est interruptible à n'importe
 * quel instant sans jamais produire de saut. C'est exactement ce que le crouch
 * demande quand le joueur martèle la touche.
 */
struct FCameraSpring
{
	/** Déplacement courant, dans l'unité de ce que le ressort pilote. */
	float Offset = 0.f;

	/** Vitesse courante, par seconde. */
	float Velocity = 0.f;

	/** Impulsion pas encore versée, étalée sur RampTime pour éviter l'à-coup. */
	float PendingImpulse = 0.f;

	void AddImpulse(float Strength) { PendingImpulse += Strength; }

	/** Remet le ressort au repos, sans transition (respawn, téléportation). */
	void Reset()
	{
		Offset = 0.f;
		Velocity = 0.f;
		PendingImpulse = 0.f;
	}

	/**
	 * Intègre le ressort d'un pas de temps et renvoie le nouvel Offset.
	 *
	 * @param Frequency fréquence propre en Hz : vitesse de retour au repos.
	 * @param Damping   1 = retour net sans rebond, en dessous ça oscille.
	 * @param RampTime  durée sur laquelle une impulsion est versée (0 = d'un coup).
	 * @param MaxOffset garde-fou d'amplitude.
	 */
	float Update(float Step, float Frequency, float Damping, float RampTime, float MaxOffset);
};

/**
 * Personnage joueur en vue première personne.
 * Les assets d'input (Input Mapping Context + Input Actions) se branchent
 * dans le Blueprint enfant (BP_FpsPlayer), catégorie "Input".
 */
UCLASS()
class EMBERWILD_API AFpsPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AFpsPlayer();

	// Called every frame
	virtual void Tick(float DeltaTime) override;

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	/** Vrai tant que le joueur sprinte (utile pour l'anim ou la stamina plus tard). */
	UFUNCTION(BlueprintPure, Category = "Mouvement")
	bool IsSprinting() const { return bIsSprinting; }

	UFUNCTION(BlueprintPure, Category = "Camera")
	UCameraComponent* GetFirstPersonCamera() const { return FirstPersonCamera; }

	UFUNCTION(BlueprintPure, Category = "Camera")
	UHeadBobComponent* GetHeadBob() const { return HeadBob; }

	/** Les bras vus par le joueur local. C'est ici que viendra l'objet en main. */
	UFUNCTION(BlueprintPure, Category = "Animation")
	USkeletalMeshComponent* GetFirstPersonArms() const { return FirstPersonArms; }

	/** La réception en cours, ou None en l'air. Lue par les AnimInstance. */
	UFUNCTION(BlueprintPure, Category = "Animation")
	ELandingImpact GetLandingImpact() const { return LandingImpact; }

	/**
	 * Joue un montage sur les bras du joueur LOCAL, et seulement lui.
	 *
	 * Rien n'est replique et c'est voulu : les bras sont en OnlyOwnerSee, donc
	 * les animer chez le serveur ou chez un ami ferait tourner un squelette que
	 * personne ne rend. Le geste vu par les autres joueurs viendra du mesh 3e
	 * personne, avec ses propres animations, le jour ou l'animateur les livrera.
	 *
	 * @return la duree du montage en secondes, 0 s'il n'a pas pu etre joue.
	 */
	UFUNCTION(BlueprintCallable, Category = "Animation")
	float PlayArmsMontage(UAnimMontage* Montage, float PlayRate = 1.f);

	UFUNCTION(BlueprintPure, Category = "Interaction")
	UInteractionComponent* GetInteractionComponent() const { return InteractionComponent; }

	UFUNCTION(BlueprintPure, Category = "Inventaire")
	UInventoryComponent* GetInventoryComponent() const { return InventoryComponent; }

	UFUNCTION(BlueprintPure, Category = "Crafting")
	UCraftingComponent* GetCraftingComponent() const { return CraftingComponent; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	USurvivalStatsComponent* GetSurvivalStats() const { return SurvivalStats; }

	UFUNCTION(BlueprintPure, Category = "Apparence")
	UCharacterAppearanceComponent* GetAppearanceComponent() const { return AppearanceComponent; }

	UFUNCTION(BlueprintPure, Category = "Inventaire")
	UHeldItemComponent* GetHeldItemComponent() const { return HeldItemComponent; }

	UFUNCTION(BlueprintPure, Category = "Audio")
	UFootstepComponent* GetFootstepComponent() const { return FootstepComponent; }

	// ------------------------------------------------------------------
	// Commandes console d'apparence
	//
	// Elles vivent ICI et non sur UCharacterAppearanceComponent, ou leur place
	// serait pourtant plus naturelle : la chaine de routage des commandes
	// (UPlayer::Exec, dans Player.cpp) interroge le monde, le PlayerInput, le
	// controller, le PAWN, le HUD, le GameMode, le CheatManager, le GameState
	// et le camera manager -- jamais les composants, AActor ne surchargeant pas
	// ProcessConsoleExec pour les parcourir. Une UFUNCTION(Exec) posee sur un
	// composant est silencieusement ignoree, sans le moindre message.
	//
	// Le pawn est le maillon le plus proche du composant dans cette chaine,
	// d'ou ces trois delegations d'une ligne. Elles disparaitront le jour ou
	// l'ecran de customisation existera.
	// ------------------------------------------------------------------

	/** `EmberPart hair 3` -- body, head, hair, eyebrows, beard, mustache, eyes. */
	UFUNCTION(Exec, Category = "Apparence")
	void EmberPart(const FString& Part, int32 Index);

	/** `EmberColor skin 2` -- skin, hair, eye, underwear. Index dans la palette. */
	UFUNCTION(Exec, Category = "Apparence")
	void EmberColor(const FString& Part, int32 ColorIndex);

	/** Ecrit l'apparence courante et le nombre d'options de chaque categorie. */
	UFUNCTION(Exec, Category = "Apparence")
	void EmberAppearanceDump();

private:
	/**
	 * Persiste l'apparence du composant et l'annonce au serveur, via le
	 * controller. Appelee apres chaque commande console, pour que le
	 * changement se voie chez les autres et survive au relancement.
	 */
	void SubmitCurrentAppearance();

public:

	/** Sert a l'UI pour afficher la vraie touche mappee, plutot qu'un "E" ecrit en dur. */
	UFUNCTION(BlueprintPure, Category = "Input")
	const UInputAction* GetInteractAction() const { return InteractAction; }

	/**
	 * Coupe les etats maintenus avant de desactiver les inputs du pawn.
	 *
	 * Sprint et saut sont declenches par Started et arretes par Completed.
	 * Si on desactive l'input pendant que la touche est enfoncee, le Completed
	 * n'a plus personne pour l'ecouter et l'etat reste bloque : on rouvrirait
	 * le jeu en sprintant sans toucher a Maj.
	 */
	UFUNCTION(BlueprintCallable, Category = "Input")
	void ClearTransientInputStates();

	/**
	 * Retire des objets d'un slot et les fait apparaitre au sol devant le joueur.
	 * @param Quantity nombre d'exemplaires ; passe un grand nombre pour tout jeter.
	 * @return true si quelque chose est reellement sorti de l'inventaire.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inventaire")
	bool DropSlot(int32 SlotIndex, int32 Quantity);

	/**
	 * Meme chose, depuis un inventaire quelconque : celui d'un coffre ouvert,
	 * typiquement.
	 *
	 * C'est le PAWN qui pose au sol et pas le composant, pour la meme raison
	 * que le surplus de fabrication : un UInventoryComponent ne sait pas
	 * engendrer un acteur, et un coffre qui deposerait lui-meme laisserait le
	 * butin dans sa propre caisse. L'objet part donc toujours devant les yeux
	 * du joueur, quel que soit le contenant d'ou il sort.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inventaire")
	bool DropFromInventory(UInventoryComponent* Inventory, int32 SlotIndex, int32 Quantity);

	/** Vide l'inventaire entier en un tas autour du personnage. */
	UFUNCTION(BlueprintCallable, Category = "Inventaire")
	void DropAllItems();

	UFUNCTION(BlueprintPure, Category = "Survie")
	bool IsDead() const { return bIsDead; }

	/**
	 * Secoue la caméra verticalement. Strength est la vitesse initiale en cm/s
	 * donnée à la caméra ; positif = vers le haut, négatif = vers le bas.
	 * Réutilisable pour une explosion, un recul d'arme, un coup reçu.
	 */
	UFUNCTION(BlueprintCallable, Category = "Camera")
	void AddCameraPunch(float Strength);

	/**
	 * Recopie les reglages du joueur dans le pawn.
	 *
	 * Appelee au BeginPlay ET a chaque fois que USurvivalUserSettings diffuse :
	 * apres un respawn le pawn est un NOUVEL acteur, une valeur poussee une
	 * seule fois serait perdue avec le cadavre. UFUNCTION obligatoire, c'est ce
	 * qui permet le AddDynamic sur le delegue.
	 */
	UFUNCTION()
	void ApplyUserSettings();

	/**
	 * Pose la transform de la camera juste avant que la vue soit lue.
	 *
	 * Le gestionnaire de camera passe par ici pour construire le point de vue :
	 * c'est le seul endroit ou l'on est certain de lire la position de l'acteur
	 * et la demi-hauteur de la capsule au MEME instant. Le Tick du pawn, lui,
	 * n'a aucune dependance de tick vis-a-vis du CharacterMovement (verifie dans
	 * Character.cpp : seul le Mesh en recoit une), donc la frame ou la capsule
	 * change de taille pouvait etre calculee avec l'ancienne demi-hauteur --
	 * soit un decalage de 28 cm sur une frame, a chaque accroupissement.
	 * Bonus : le regard n'a plus une frame de retard sur la souris.
	 */
	virtual void CalcCamera(float DeltaTime, FMinimalViewInfo& OutResult) override;

	/**
	 * L'autre cote de la course avec le PlayerState.
	 *
	 * Chez un client, le pawn et le PlayerState arrivent par deux chemins de
	 * replication independants : tantot l'apparence est la avant le corps --
	 * ASurvivalPlayerState::OnRep_CharacterAppearance la pose alors lui-meme --
	 * tantot c'est l'inverse, et c'est ici qu'on va la chercher. Ni l'un ni
	 * l'autre ne peut supposer avoir gagne, donc les deux appellent la meme
	 * fonction idempotente.
	 */
	virtual void OnRep_PlayerState() override;

	/**
	 * Les deux moities de « le controller est arrive ».
	 *
	 * UHeldItemComponent ne peut lire la case active QUE chez le joueur local,
	 * et son BeginPlay peut tomber avant que le pawn soit possede -- chez un
	 * client, le pawn et son controller arrivent par deux chemins independants,
	 * exactement comme le PlayerState. Un joueur qui reapparait avec une hache
	 * deja en case 1 aurait alors la main vide jusqu'a ce qu'il touche a son
	 * inventaire. PossessedBy couvre le serveur, OnRep_Controller le client, et
	 * RefreshFromInventory() est idempotente.
	 */
	virtual void PossessedBy(AController* NewController) override;
	virtual void OnRep_Controller() override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	/** Lit l'apparence sur le PlayerState et la monte, s'il est deja arrive. */
	void RefreshAppearanceFromPlayerState();

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
	virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
	virtual void OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;

	// Réception au sol et décollage
	virtual void Landed(const FHitResult& Hit) override;
	virtual void OnJumped_Implementation() override;

	// ------------------------------------------------------------------
	// Composants
	// ------------------------------------------------------------------

	/** Caméra placée à hauteur des yeux, suit la rotation du controller. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	TObjectPtr<UCameraComponent> FirstPersonCamera;

	/**
	 * Bras vue première personne, visibles du seul propriétaire.
	 *
	 * Attachés à la CAMÉRA et non à la capsule : ils doivent suivre le tangage du
	 * regard, sinon lever les yeux les laisserait à l'horizontale. Bénéfice
	 * gratuit au passage — ils héritent du ressort d'impact et de la bascule
	 * d'accroupissement, donc les bras encaissent l'atterrissage avec la caméra
	 * sans une ligne de code de plus.
	 *
	 * Sa transform relative se règle À L'ŒIL dans BP_FpsPlayer : les valeurs du
	 * constructeur ne sont qu'un point de départ, elles dépendent entièrement de
	 * l'origine du FBX livré par l'animateur.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
	TObjectPtr<USkeletalMeshComponent> FirstPersonArms;

	/**
	 * Montage joué sur les bras au ramassage d'un objet (MTG_GrabItem).
	 * Vide = aucun geste, le ramassage fonctionne quand même.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> GrabMontage;

	// ------------------------------------------------------------------
	// Dégâts de chute
	//
	// C'est ce bloc qui définit la réception encaissée, et pas l'inverse :
	// `ELandingImpact::Hard` signifie exactement « cet atterrissage a coûté de la
	// vie ». Un seuil d'animation réglé de son côté finirait forcément par
	// diverger de celui des dégâts, et le joueur verrait son personnage encaisser
	// sans rien perdre — ou pire, perdre de la vie en se réceptionnant
	// souplement. Une animation qui ment sur l'état du jeu est pire que pas
	// d'animation du tout.
	//
	// L'atterrissage est un ÉTAT de la machine et non un montage : les trois
	// animations livrées (JumpStart, JumpFallLoop, JumpLanding) forment une
	// chaîne dont le milieu est une BOUCLE, et une boucle ne se joue pas en
	// montage.
	// ------------------------------------------------------------------

	/**
	 * Vitesse de chute (cm/s) à partir de laquelle on prend des dégâts.
	 *
	 * Doit rester nettement au-dessus de la retombée d'un saut sur place
	 * (~JumpZVelocity, 450 par défaut), sinon sauter coûte de la vie et le
	 * déplacement devient une punition.
	 *
	 * À 800 cm/s, c'est une chute d'environ 3,2 m (v = racine(2·g·h), g = 980).
	 * Ne pas raisonner en hauteur pour régler cette valeur : la hauteur ne se
	 * mesure nulle part dans le moteur, alors que la vitesse d'impact est
	 * exactement ce que le CharacterMovement a sous la main — et elle tient
	 * compte, gratuitement, d'un saut fait en tombant.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
	float FallDamageMinSpeed = 800.f;

	/** Vitesse (cm/s) au-delà de laquelle les dégâts plafonnent (~20 m de chute). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "1"))
	float FallDamageMaxSpeed = 2000.f;

	/**
	 * Dégâts pris pile au seuil. Volontairement non nul : franchir la limite
	 * doit se sentir, sinon le seuil n'existe que dans le code.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
	float FallDamageMin = 15.f;

	/** Dégâts pris à FallDamageMaxSpeed et au-delà. À 100, la chute est mortelle. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
	float FallDamageMax = 100.f;

	/** Les dégâts de la chute qui vient d'avoir lieu, 0 sous le seuil. */
	UFUNCTION(BlueprintPure, Category = "Fall Damage")
	float ComputeFallDamage(float FallSpeed) const;

	/**
	 * Balancement de tête procédural : respiration à l'arrêt, pas à la marche,
	 * foulée au sprint. Tous ses réglages sont dans le Details panel du composant,
	 * pas ici : cinq profils de cinq valeurs noieraient la catégorie "Camera".
	 *
	 * Il ne touche pas la caméra lui-même, il expose deux décalages que
	 * ApplyCameraTransform additionne — cette fonction reste le seul écrivain.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	TObjectPtr<UHeadBobComponent> HeadBob;

	/** Détecte et déclenche les interactions avec le monde. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Interaction")
	TObjectPtr<UInteractionComponent> InteractionComponent;

	/**
	 * Inventaire du joueur. Sur le pawn et non sur le controller :
	 * un cadavre qui garde son butin fait partie du gameplay de survie.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire")
	TObjectPtr<UInventoryComponent> InventoryComponent;

	/**
	 * Fabrication. Sur le pawn comme l'inventaire : c'est ce corps qui fabrique,
	 * et il lit l'inventaire du meme acteur.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Crafting")
	TObjectPtr<UCraftingComponent> CraftingComponent;

	/** Vie, faim, soif. Sur le pawn : c'est ce corps qui a faim. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Survie")
	TObjectPtr<USurvivalStatsComponent> SurvivalStats;

	/**
	 * Monte la tete, les cheveux, la barbe et les couleurs sur le mesh 3e
	 * personne.
	 *
	 * Sur le pawn et non sur le controller, alors que l'apparence, elle, vivra
	 * sur le PlayerState : ce composant ne STOCKE rien, il assemble un corps.
	 * Or c'est le corps qui meurt et reapparait, et un nouveau pawn doit se
	 * remonter tout seul.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Apparence")
	TObjectPtr<UCharacterAppearanceComponent> AppearanceComponent;

	/**
	 * Montre dans la main l'objet de la case active de la barre rapide.
	 *
	 * Sur le pawn : ce sont ces bras-la et ce corps-la qui tiennent l'objet, et
	 * un cadavre ne tient plus rien. Il lit l'inventaire du meme acteur.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire")
	TObjectPtr<UHeldItemComponent> HeldItemComponent;

	/**
	 * Bruits de pas.
	 *
	 * Sur le pawn et sur TOUS les pawns, pas seulement celui du joueur local :
	 * le composant ne replique rien, chaque machine calcule les pas de tous les
	 * corps qu'elle connait a partir de leur vitesse deja synchronisee. Le
	 * mettre sur le controller n'aurait donc donne les pas que d'un joueur.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Audio")
	TObjectPtr<UFootstepComponent> FootstepComponent;

	// ------------------------------------------------------------------
	// Enhanced Input : assets à assigner dans le Blueprint
	// ------------------------------------------------------------------

	/** Contexte ajouté au joueur au BeginPlay (IMC_Default). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputMappingContext> DefaultMappingContext;

	/** Déplacement, valeur Axis2D (X = droite/gauche, Y = avant/arrière). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> MoveAction;

	/** Regard souris / stick droit, valeur Axis2D. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> LookAction;

	/** Saut, valeur Digital (bool). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> JumpAction;

	/** Sprint, valeur Digital (bool) — maintenu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> SprintAction;

	/** Accroupi, valeur Digital (bool) — maintenu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> CrouchAction;

	/** Interaction, valeur Digital (bool). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> InteractAction;

	/**
	 * Utilise l'objet actif de la barre rapide, valeur Digital (bool).
	 * A mapper sur le clic gauche.
	 *
	 * Bindee sur le PAWN volontairement : quand l'inventaire est ouvert, les
	 * inputs du pawn sont coupes, donc le clic gauche sert au glisser-deposer
	 * de l'UI sans qu'on ait le moindre test a ecrire.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> UseItemAction;

	// ------------------------------------------------------------------
	// Réglages : mouvement
	// ------------------------------------------------------------------

	/** Vitesse de marche normale (cm/s). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
	float WalkSpeed = 400.f;

	/** Vitesse en sprint (cm/s). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
	float SprintSpeed = 650.f;

	/** Vitesse accroupi (cm/s). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
	float CrouchSpeed = 200.f;

	// ------------------------------------------------------------------
	// Réglages : objets jetés au sol
	// ------------------------------------------------------------------

	/**
	 * Classe engendrée quand on jette un objet. Assigne ton BP_Pickup_Test :
	 * son ItemData est écrasé au spawn, un seul Blueprint suffit pour tout.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inventaire|Drop")
	TSubclassOf<APickupItem> DroppedPickupClass;

	/** Distance devant les yeux où l'objet est déposé (cm). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire|Drop", meta = (ClampMin = "10"))
	float DropDistance = 120.f;

	/** Hauteur au-dessus du sol trouvé, pour éviter que l'objet s'y encastre. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire|Drop")
	float DropGroundOffset = 5.f;

	// ------------------------------------------------------------------
	// Réglages : regard
	// ------------------------------------------------------------------

	/**
	 * Sensibilité horizontale : degrés de rotation par unité de souris.
	 * 1.0 = brut (beaucoup trop rapide), ~0.4 est un bon point de départ.
	 *
	 * ATTENTION : ces trois valeurs, comme BaseFOV, sont ECRASEES au BeginPlay
	 * par USurvivalUserSettings. Elles ne servent plus que de repli si l'objet
	 * de réglages est absent ; pour changer la sensibilité, passe par le menu.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
	float LookSensitivityX = 0.4f;

	/** Sensibilité verticale. Souvent réglée un peu plus basse que l'horizontale. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
	float LookSensitivityY = 0.4f;

	/** Inverse l'axe vertical de la souris. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
	bool bInvertLookY = false;

	/**
	 * Hauteur de l'oeil AU-DESSUS DES PIEDS, debout (cm).
	 * 160 cm correspond a un personnage d'environ 1m76.
	 * Mesure depuis le sol et non depuis la capsule : c'est ce qui permet
	 * d'annuler le saut instantane de la capsule quand elle retrecit.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
	float EyeHeightStanding = 160.f;

	/** Hauteur de l'oeil au-dessus des pieds, accroupi (cm). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
	float EyeHeightCrouched = 104.f;

	// ------------------------------------------------------------------
	// Réglages : animation d'accroupissement
	// ------------------------------------------------------------------

	/** Durée en secondes de la transition debout <-> accroupi. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.01"))
	float CrouchTransitionDuration = 0.4f;

	/**
	 * Tangage (degrés) au pic de la bascule. Positif = le regard plonge.
	 *
	 * Le tilt n'est plus une courbe indexée sur l'avancement de la transition
	 * mais un ressort relancé à chaque changement d'état : c'est ce qui permet
	 * de s'accroupir et de se relever coup sur coup sans que la caméra saute.
	 * L'impulsion est normalisée pour que le pic tombe bien sur cette valeur.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi")
	float CrouchTiltPitch = 3.f;

	/**
	 * Roulis (degrés) au pic de la bascule : bascule sur le côté.
	 * A 0 par defaut, le mouvement reste purement vertical.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi")
	float CrouchTiltRoll = 0.f;

	/**
	 * Fréquence du ressort de bascule (Hz). Le pic est atteint après environ
	 * 1/(2*PI*f) secondes : 0.9 Hz place le sommet vers 0.18 s, soit à peu près
	 * au milieu d'une transition de 0.4 s. Plus haut = plus sec et plus court.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.1"))
	float CrouchTiltFrequency = 0.9f;

	/** Amortissement de la bascule. 1 = aucun rebond ; en dessous, la tête oscille. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.05", ClampMax = "2.0"))
	float CrouchTiltDamping = 1.f;

	/**
	 * Durée (s) sur laquelle l'impulsion de bascule est versée dans le ressort.
	 * A 0 la rotation démarre à pleine vitesse angulaire en une frame, ce qui
	 * se lit comme un claquement ; quelques centièmes suffisent à l'adoucir.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0"))
	float CrouchTiltRampTime = 0.05f;

	// ------------------------------------------------------------------
	// Réglages : impacts caméra (saut / atterrissage)
	// ------------------------------------------------------------------

	/**
	 * Fréquence du ressort de caméra en Hz : à quelle vitesse elle revient
	 * en place après un choc. Plus haut = plus sec.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0.1"))
	float PunchFrequency = 3.f;

	/**
	 * Amortissement du ressort. 1.0 = retour net sans rebond,
	 * 0.5 = un rebond visible, en dessous ça oscille longtemps.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0.05", ClampMax = "2.0"))
	float PunchDamping = 0.85f;

	/**
	 * Durée (s) sur laquelle une impulsion est injectée dans le ressort.
	 * A 0 elle est appliquée d'un coup : la caméra part à pleine vitesse en
	 * une frame, ce qui se lit comme un a-coup. Quelques centiemes suffisent
	 * a rendre l'entree en mouvement progressive.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
	float PunchImpulseRampTime = 0.06f;

	/** Degrés de tangage ajoutés par cm de déplacement vertical de la caméra. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact")
	float PunchPitchPerCm = 0.35f;

	/** Amplitude maximale du déplacement vertical (cm), garde-fou. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
	float PunchMaxOffset = 50.f;

	/**
	 * Impulsion (cm/s) à l'atterrissage, quand l'impact est au maximum.
	 * Ordre de grandeur : le deplacement en cm de la camera vaut environ
	 * cette valeur x 0.018 (avec les reglages de ressort par defaut).
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
	float LandPunchStrength = 2000.f;

	/** En dessous de cette vitesse de chute (cm/s), aucun effet à l'atterrissage. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
	float LandPunchMinFallSpeed = 150.f;

	/**
	 * Vitesse de chute (cm/s) à partir de laquelle l'effet est au maximum.
	 * Un saut sur place retombe a peu pres a JumpZVelocity (450), donc cette
	 * valeur doit rester proche pour qu'un saut normal se sente deja bien.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "1"))
	float LandPunchMaxFallSpeed = 900.f;

	/**
	 * Impulsion (cm/s) au décollage du saut. Positif = la caméra s'enfonce
	 * un instant (compression des jambes) ; mets une valeur négative pour
	 * qu'elle parte vers le haut à la place.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact")
	float JumpPunchStrength = 550.f;

	// ------------------------------------------------------------------
	// Réglages : champ de vision
	// ------------------------------------------------------------------

	/** FOV au repos, en degrés. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV", meta = (ClampMin = "30", ClampMax = "150"))
	float BaseFOV = 90.f;

	/** Degrés de FOV ajoutés en sprint. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV")
	float SprintFOVOffset = 8.f;

	/** Vitesse de transition du FOV. Plus haut = plus brusque. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV", meta = (ClampMin = "0.1"))
	float FOVInterpSpeed = 8.f;

	// ------------------------------------------------------------------
	// Handlers d'input
	// ------------------------------------------------------------------

	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
	void StartSprint();
	void StopSprint();
	void StartCrouch();
	void StopCrouch();
	void Interact();

	/**
	 * Relachement de la touche de sprint / d'accroupissement.
	 *
	 * Separes de StopSprint et StopCrouch, qui restent des ordres inconditionnels :
	 * en mode bascule le relachement ne doit rien faire, alors que la mort ou
	 * l'ouverture de l'inventaire doivent bel et bien couper le sprint.
	 */
	void HandleSprintReleased();
	void HandleCrouchReleased();

	/**
	 * Clic gauche : lance le GESTE de l'objet actif de la barre rapide.
	 *
	 * Elle ne produit plus l'effet elle-même — c'est le marqueur
	 * `UAnimNotify_ItemAction` posé dans le montage qui le fait, au moment où le
	 * geste frappe. Sans montage à jouer, elle retombe sur l'effet immédiat :
	 * un objet qui n'a pas d'animation n'aura jamais de marqueur, et le priver
	 * d'effet serait un objet cassé plutôt qu'un objet sans animation.
	 */
	void UseSelectedItem();

public:
	/**
	 * L'objet en main agit MAINTENANT. Appelée par l'AnimNotify du montage.
	 *
	 * Publique et `BlueprintCallable` pour cette seule raison : c'est le point
	 * d'entrée de l'animation vers le gameplay, y compris depuis un notify
	 * Blueprint. Elle ne fait rien d'autre que ce que faisait la seconde moitié
	 * de `UseSelectedItem` : router vers l'autorité.
	 */
	UFUNCTION(BlueprintCallable, Category = "Items")
	void TriggerHeldItemAction();

private:
	/**
	 * La case visée par le clic, retenue jusqu'à l'impact.
	 *
	 * Relire la case active au moment du marqueur serait faux : trois dixièmes
	 * de seconde séparent l'appui de l'impact, et la molette peut passer sur un
	 * autre objet entre-temps — le coup de hache partirait alors avec la pomme
	 * qu'on vient de sélectionner. Changer d'objet coupe bien le montage en
	 * cours (`PlayEquipMontage` ne saute pas), mais seulement quand le nouvel
	 * objet a un montage d'équipement : ce n'est pas une garantie sur laquelle
	 * s'appuyer.
	 */
	int32 PendingUseSlotIndex = INDEX_NONE;

	// ------------------------------------------------------------------
	// Réseau
	//
	// Jeter et utiliser engendrent ou détruisent des choses dans le monde :
	// ce sont des actions d'autorité. Le routage vit dans les fonctions
	// publiques elles-mêmes, pour que ni l'UI ni l'input n'aient à savoir
	// s'ils tournent chez l'hôte ou chez un client.
	// ------------------------------------------------------------------

	UFUNCTION(Server, Reliable)
	void Server_DropFromInventory(UInventoryComponent* Inventory, int32 SlotIndex, int32 Quantity);

	/**
	 * Le slot est passé explicitement : SelectedHotbarIndex n'est PAS répliqué,
	 * c'est une intention purement locale. Le serveur ne peut donc pas deviner
	 * ce que le joueur avait en main.
	 */
	UFUNCTION(Server, Reliable)
	void Server_UseItem(int32 SlotIndex);

	/** L'utilisation réelle, côté autorité. */
	void PerformUseItem(int32 SlotIndex);

	/**
	 * Point d'entrée unique du sprint : applique en local ET prévient le serveur.
	 *
	 * Sans le second, le serveur croirait le client à `WalkSpeed` et corrigerait
	 * sa position à chaque pas — c'est le rubber-banding classique, et il ne se
	 * voit que sur une machine distante, jamais chez l'hôte.
	 */
	void SetSprinting(bool bNewSprinting);

	UFUNCTION(Server, Reliable)
	void Server_SetSprinting(bool bNewSprinting);

	/** La vitesse suit l'état, y compris sur les pawns des autres joueurs. */
	UFUNCTION()
	void OnRep_IsSprinting();

	void ApplySprintSpeed();

	/** Fait avancer toutes les animations de caméra (intégration uniquement). */
	void UpdateCameraEffects(float DeltaTime);

	/**
	 * Recopie l'état des animations dans la transform de la caméra.
	 * Séparée de l'intégration : appelée à la fois en fin de Tick et depuis
	 * CalcCamera, elle doit rester idempotente et sans notion de temps.
	 */
	void ApplyCameraTransform();

	/** Relance la bascule de tête. Direction : +1 on s'accroupit, -1 on se relève. */
	void AddCrouchTilt(float Direction);

	/** Branche sur OnDied du composant de survie. */
	UFUNCTION()
	void HandleDeath();

	/**
	 * Le resultat d'une fabrication n'est pas entre dans l'inventaire : on le
	 * pose aux pieds du joueur plutot que de le laisser disparaitre.
	 *
	 * C'est le pawn qui fait ca et non UCraftingComponent : le composant n'a pas
	 * a savoir comment on engendre un objet dans le monde, et un etabli qui
	 * porterait le meme composant deposerait son surplus sur sa propre table.
	 */
	UFUNCTION()
	void HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses);

	/** Fait apparaitre un pickup deja configure a un endroit donne. */
	APickupItem* SpawnPickup(const struct FInventorySlot& Slot, const FVector& Location);

	/** Descend une position jusqu'au sol, pour ne pas laisser d'objet en l'air. */
	FVector PlaceOnGround(const FVector& Start) const;

	bool bIsDead = false;

	/** Progression de l'animation d'accroupissement : 0 = debout, 1 = accroupi. */
	float CrouchAlpha = 0.f;

	/** Valeur visée par CrouchAlpha. */
	float CrouchTargetAlpha = 0.f;

	/** Déplacement vertical de la caméra (cm) : sauts, atterrissages, explosions. */
	FCameraSpring PunchSpring;

	/**
	 * Bascule de tête pendant l'accroupissement, en unités normalisées :
	 * son pic vaut 1, ce sont CrouchTiltPitch et CrouchTiltRoll qui le
	 * convertissent en degrés.
	 */
	FCameraSpring CrouchTiltSpring;

	/** Vitesse verticale mémorisée pendant la chute, pour calibrer l'atterrissage. */
	float LastFallSpeed = 0.f;

	/**
	 * La réception en cours, lue par les AnimInstance.
	 *
	 * Écrite à deux endroits et deux seulement : remise à None dans Tick tant
	 * qu'on est en l'air, posée à sa vraie valeur dans Landed(). C'est ce qui
	 * garantit qu'elle vaut None pendant tout le vol — sans quoi la machine à
	 * états prendrait sa transition de réception avec la valeur du saut
	 * précédent, dès la première image de chute.
	 */
	ELandingImpact LandingImpact = ELandingImpact::None;

	/** Répliqué pour que les autres joueurs voient la bonne vitesse et la bonne anim. */
	UPROPERTY(ReplicatedUsing = OnRep_IsSprinting)
	bool bIsSprinting = false;

	/** Évite de spammer le log si IA_Look est mal configurée. */
	bool bLoggedLookTypeWarning = false;

	// ------------------------------------------------------------------
	// Copies locales des réglages joueur, rafraîchies par ApplyUserSettings
	// ------------------------------------------------------------------

	/** 0 = caméra parfaitement fixe, 1 = amplitude d'origine. */
	float CameraShakeMultiplier = 1.f;

	bool bSprintToggleMode = false;

	bool bCrouchToggleMode = false;
};
