(Feat) Add mulitplayer

This commit is contained in:
2026-08-03 10:30:47 +02:00
parent 41d6e6a7f3
commit a4cc59a411
41 changed files with 3418 additions and 125 deletions
@@ -11,6 +11,7 @@
#include "ItemDataAsset.h"
#include "Kismet/GameplayStatics.h"
#include "NiagaraComponent.h"
#include "Net/UnrealNetwork.h"
#include "TimerManager.h"
#define LOCTEXT_NAMESPACE "CookingStation"
@@ -20,6 +21,14 @@ ACookingStation::ACookingStation()
// Tout passe par des timers : la station n'a rien a faire chaque frame.
PrimaryActorTick.bCanEverTick = false;
// Acteur du monde. La cuisson et la combustion ne sont comptees QUE par le
// serveur : les timers ne partent jamais sur le reseau, seuls leurs
// resultats -- l'objet devenu cuit, la buche disparue -- sont repliques.
// Quatre clients qui compteraient chacun leur cuisson divergeraient, et le
// steak serait brule chez l'un et cru chez l'autre.
bReplicates = true;
SetReplicateMovement(false);
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh"));
SetRootComponent(BaseMesh);
// Le foyer est un obstacle physique, contrairement a un ramassable : on
@@ -77,40 +86,83 @@ ACookingStation::ACookingStation()
}
}
void ACookingStation::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ACookingStation, Slots);
DOREPLIFETIME(ACookingStation, Fuel);
DOREPLIFETIME(ACookingStation, bIsLit);
}
void ACookingStation::OnRep_Slots()
{
// Le mesh d'un emplacement suit son contenu : c'est TOUT l'affichage de la
// cuisson, il n'y a aucune interface. Cru, cuit, brule -- le joueur ne lit
// que ca.
for (int32 Index = 0; Index < Slots.Num(); ++Index)
{
RefreshSlotMesh(Index);
}
}
void ACookingStation::OnRep_Fuel()
{
RefreshFuelIndicators();
}
void ACookingStation::OnRep_IsLit()
{
ApplyFireVisuals(bIsLit);
}
void ACookingStation::BeginPlay()
{
Super::BeginPlay();
Slots.SetNum(MaxCookingSlots);
// Recensement des buches AVANT tout : il fixe la capacite, et il est purement
// local -- ce sont des composants du Blueprint, identiques partout.
CacheFuelIndicators();
// Le contenu initial est de l'etat de monde : seul le serveur le pose, les
// clients le recevront par replication. Sans cette garde, chaque client se
// donnerait ses propres buches de depart en plus de celles du serveur.
if (HasAuthority())
{
Slots.SetNum(MaxCookingSlots);
if (StartingFuelItem && StartingFuelItem->bIsFuel)
{
const int32 Count = FMath::Min(StartingFuelCount, GetFuelCapacity());
for (int32 Index = 0; Index < Count; ++Index)
{
FFuelSlotState& NewFuel = Fuel.AddDefaulted_GetRef();
NewFuel.Item = StartingFuelItem;
NewFuel.RemainingSeconds = FMath::Max(1.f, StartingFuelItem->FuelDuration);
}
}
}
// Les meshes de reperage poses dans le Blueprint n'ont plus rien a faire la :
// RefreshSlotMesh remet chaque emplacement sur son contenu reel, donc rien.
for (int32 Index = 0; Index < MaxCookingSlots; ++Index)
// Chez un client Slots est encore vide a cet instant ; OnRep_Slots repassera
// des que le contenu reel arrivera.
for (int32 Index = 0; Index < Slots.Num(); ++Index)
{
RefreshSlotMesh(Index);
}
// Avant tout usage du carburant : c'est ce recensement qui fixe la capacite.
CacheFuelIndicators();
if (StartingFuelItem && StartingFuelItem->bIsFuel)
{
const int32 Count = FMath::Min(StartingFuelCount, GetFuelCapacity());
for (int32 Index = 0; Index < Count; ++Index)
{
FFuelSlotState& NewFuel = Fuel.AddDefaulted_GetRef();
NewFuel.Item = StartingFuelItem;
NewFuel.RemainingSeconds = FMath::Max(1.f, StartingFuelItem->FuelDuration);
}
}
RefreshFuelIndicators();
// Le grill est vide au demarrage, donc le feu reste eteint quoi qu'il
// arrive. On passe quand meme par le point de decision unique plutot que
// d'ecrire un cas particulier de plus.
ApplyFireVisuals(false);
UpdateFireState();
if (HasAuthority())
{
UpdateFireState();
}
}
void ACookingStation::EndPlay(const EEndPlayReason::Type EndPlayReason)
@@ -245,6 +297,14 @@ bool ACookingStation::HasPendingWork() const
void ACookingStation::UpdateFireState()
{
// Point de decision unique du feu, donc point de garde unique : bIsLit et
// les timers de combustion appartiennent au serveur. Chez un client c'est
// OnRep_IsLit qui allume la flamme, sans jamais rien decider.
if (!HasAuthority())
{
return;
}
// Un objet DEJA cuit qui peut encore bruler compte comme du travail : sans
// ca le feu s'arreterait pile a la fin de la cuisson et la sur-cuisson
// n'arriverait jamais. Oublier sa viande doit rester punitif.