// Fill out your copyright notice in the Description page of Project Settings. #include "SurvivalStatsComponent.h" #include "Engine/World.h" #include "Net/UnrealNetwork.h" #include "TimerManager.h" USurvivalStatsComponent::USurvivalStatsComponent() { // Pas de Tick : tout passe par un timer a basse frequence. PrimaryComponentTick.bCanEverTick = false; SetIsReplicatedByDefault(true); } void USurvivalStatsComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); // COND_OwnerOnly ici, contrairement a l'inventaire : ces jauges n'existent // que sur un pawn, qui appartient bien a une connexion -- la condition // fonctionne donc. Et la faim d'un coequipier ne regarde personne d'autre // que lui. Le jour ou une barre de vie s'affichera au-dessus des allies, // c'est Health seul qu'il faudra ouvrir a tous. DOREPLIFETIME_CONDITION(USurvivalStatsComponent, Health, COND_OwnerOnly); DOREPLIFETIME_CONDITION(USurvivalStatsComponent, Hunger, COND_OwnerOnly); DOREPLIFETIME_CONDITION(USurvivalStatsComponent, Thirst, COND_OwnerOnly); DOREPLIFETIME_CONDITION(USurvivalStatsComponent, bDead, COND_OwnerOnly); } void USurvivalStatsComponent::OnRep_Stats() { OnStatsChanged.Broadcast(); } void USurvivalStatsComponent::OnRep_Dead() { OnStatsChanged.Broadcast(); // bDead ne repasse jamais a vrai sans un nouveau pawn : apres un respawn // c'est un AUTRE composant qui arrive, plein et vivant. Pas de risque de // rediffuser une mort deja traitee. if (bDead) { OnDied.Broadcast(); } } void USurvivalStatsComponent::BeginPlay() { Super::BeginPlay(); // Un client ne simule rien : il recevra les valeurs par replication. // Sans cette garde, chaque client ferait descendre SA copie des jauges en // plus de celle du serveur, et les barres sauteraient a chaque OnRep -- // un scintillement qu'on chercherait longtemps. const AActor* Owner = GetOwner(); if (!Owner || !Owner->HasAuthority()) { return; } Health = MaxHealth; Hunger = MaxHunger; Thirst = MaxThirst; bDead = false; if (UWorld* World = GetWorld()) { World->GetTimerManager().SetTimer( UpdateTimerHandle, this, &USurvivalStatsComponent::UpdateStats, FMath::Max(UpdateInterval, 0.05f), /*bLoop=*/true); } OnStatsChanged.Broadcast(); } void USurvivalStatsComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { // Un timer laisse en vie apres la destruction du composant rappellerait // une methode sur un objet mort. if (UWorld* World = GetWorld()) { World->GetTimerManager().ClearTimer(UpdateTimerHandle); } Super::EndPlay(EndPlayReason); } void USurvivalStatsComponent::UpdateStats() { if (bDead) { return; } // Les vitesses sont par minute, l'intervalle en secondes. const float Minutes = FMath::Max(UpdateInterval, 0.05f) / 60.f; Hunger = FMath::Max(0.f, Hunger - HungerDecayPerMinute * Minutes); Thirst = FMath::Max(0.f, Thirst - ThirstDecayPerMinute * Minutes); // Faim ET soif a zero cumulent leurs degats : mourir de soif pendant // qu'on meurt de faim doit aller plus vite. float Damage = 0.f; if (Hunger <= 0.f) { Damage += StarvationDamagePerMinute * Minutes; } if (Thirst <= 0.f) { Damage += DehydrationDamagePerMinute * Minutes; } if (Damage > 0.f) { Health = FMath::Max(0.f, Health - Damage); } OnStatsChanged.Broadcast(); if (Health <= 0.f && !bDead) { bDead = true; if (UWorld* World = GetWorld()) { World->GetTimerManager().ClearTimer(UpdateTimerHandle); } UE_LOG(LogTemp, Log, TEXT("USurvivalStatsComponent : %s est mort."), *GetNameSafe(GetOwner())); OnDied.Broadcast(); } } void USurvivalStatsComponent::ApplyRestore(float HealthAmount, float HungerAmount, float ThirstAmount) { // Manger se decide sur le serveur (AFpsPlayer::Server_UseItem). Une // restauration jouee en local remonterait la barre du client, que le // prochain OnRep ferait redescendre : un clignotement pour rien. const AActor* Owner = GetOwner(); if (!Owner || !Owner->HasAuthority()) { return; } if (bDead) { return; } Health = FMath::Clamp(Health + HealthAmount, 0.f, MaxHealth); Hunger = FMath::Clamp(Hunger + HungerAmount, 0.f, MaxHunger); Thirst = FMath::Clamp(Thirst + ThirstAmount, 0.f, MaxThirst); OnStatsChanged.Broadcast(); } void USurvivalStatsComponent::ApplyDamage(float Amount) { // Meme regle que ApplyRestore : les degats appartiennent a l'autorite. const AActor* Owner = GetOwner(); if (!Owner || !Owner->HasAuthority()) { return; } if (bDead || Amount <= 0.f) { return; } Health = FMath::Max(0.f, Health - Amount); OnStatsChanged.Broadcast(); if (Health <= 0.f) { bDead = true; if (UWorld* World = GetWorld()) { World->GetTimerManager().ClearTimer(UpdateTimerHandle); } OnDied.Broadcast(); } }