(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
@@ -4,6 +4,7 @@
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "Net/UnrealNetwork.h"
UInventoryComponent::UInventoryComponent()
{
@@ -11,6 +12,9 @@ UInventoryComponent::UInventoryComponent()
// Necessaire pour que InitializeComponent() soit appele.
bWantsInitializeComponent = true;
// Le contenu appartient au serveur et descend vers les clients.
SetIsReplicatedByDefault(true);
}
void UInventoryComponent::InitializeComponent()
@@ -22,6 +26,30 @@ void UInventoryComponent::InitializeComponent()
SelectedHotbarIndex = FMath::Clamp(SelectedHotbarIndex, 0, FMath::Max(0, GetHotbarSlotCount() - 1));
}
void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, Slots);
}
void UInventoryComponent::OnRep_Slots()
{
// Pas de BroadcastChanged() ici : le regroupement differe ne sert qu'a
// eviter vingt reconstructions d'UI pendant un "Tout ranger" execute en
// local. La replication, elle, arrive deja groupee -- le serveur a fini son
// lot avant d'envoyer quoi que ce soit.
OnInventoryChanged.Broadcast();
}
/**
* SelectedHotbarIndex n'est volontairement PAS replique : le slot actif est une
* intention purement locale, chaque joueur choisit le sien et personne d'autre
* n'a besoin de le connaitre. Le jour ou l'objet en main devra etre visible par
* les autres, ce sera une propriete du PAWN -- ce qu'on tient dans sa main est
* une donnee de corps, pas de sac.
*/
void UInventoryComponent::SelectHotbarSlot(int32 Index)
{
const int32 Count = GetHotbarSlotCount();
@@ -178,8 +206,27 @@ int32 UInventoryComponent::RemoveItem(UItemDataAsset* Item, int32 Quantity)
return Removed;
}
void UInventoryComponent::Server_TransferAllTo_Implementation(UInventoryComponent* From, UInventoryComponent* To, int32 FirstIndex)
{
if (From)
{
From->TransferAllTo(To, FirstIndex);
}
}
void UInventoryComponent::Server_ConsumeUse_Implementation(int32 SlotIndex)
{
ConsumeUse(SlotIndex);
}
bool UInventoryComponent::ConsumeUse(int32 SlotIndex)
{
if (!HasInventoryAuthority())
{
Server_ConsumeUse(SlotIndex);
return true;
}
if (!Slots.IsValidIndex(SlotIndex))
{
return false;
@@ -419,6 +466,28 @@ bool UInventoryComponent::ApplySlotMove(FInventorySlot& From, FInventorySlot& To
return true;
}
bool UInventoryComponent::HasInventoryAuthority() const
{
const AActor* Owner = GetOwner();
return Owner && Owner->HasAuthority();
}
UInventoryComponent* UInventoryComponent::FindNetProxy(UInventoryComponent* A, UInventoryComponent* B)
{
auto IsLocalPawnInventory = [](const UInventoryComponent* Inventory)
{
const APawn* OwnerPawn = Inventory ? Cast<APawn>(Inventory->GetOwner()) : nullptr;
return OwnerPawn && OwnerPawn->IsLocallyControlled();
};
if (IsLocalPawnInventory(A))
{
return A;
}
return IsLocalPawnInventory(B) ? B : nullptr;
}
bool UInventoryComponent::TransferSlot(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To, int32 ToIndex, int32 Quantity)
{
if (!From || !To || Quantity <= 0)
@@ -426,6 +495,43 @@ bool UInventoryComponent::TransferSlot(UInventoryComponent* From, int32 FromInde
return false;
}
// Chez un client : on ne touche a rien, on demande. Le contenu redescendra
// par OnRep_Slots, qui rediffuse OnInventoryChanged et rafraichit l'UI.
if (!From->HasInventoryAuthority())
{
UInventoryComponent* Proxy = FindNetProxy(From, To);
if (!Proxy)
{
UE_LOG(LogTemp, Warning, TEXT("UInventoryComponent::TransferSlot : aucun inventaire de joueur local dans l'echange, demande abandonnee."));
return false;
}
Proxy->Server_TransferSlot(From, FromIndex, To, ToIndex, Quantity);
// True signifie ici "demande partie", pas "contenu modifie". Les
// appelants s'en servent pour savoir si le geste a ete pris en compte,
// ce qui reste exact.
return true;
}
return ApplyTransferSlot(From, FromIndex, To, ToIndex, Quantity);
}
void UInventoryComponent::Server_TransferSlot_Implementation(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To, int32 ToIndex, int32 Quantity)
{
// From et To arrivent par le reseau : ils peuvent etre nuls si l'acteur a
// ete detruit entre l'envoi et l'arrivee -- un coffre casse, un joueur
// deconnecte. ApplyTransferSlot les revalide de toute facon.
ApplyTransferSlot(From, FromIndex, To, ToIndex, Quantity);
}
bool UInventoryComponent::ApplyTransferSlot(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To, int32 ToIndex, int32 Quantity)
{
if (!From || !To || Quantity <= 0)
{
return false;
}
// Meme inventaire et meme case : rien a faire. Le test doit porter sur le
// couple et non sur l'index seul, deux inventaires differents ayant tous
// les deux une case 3.
@@ -491,6 +597,21 @@ int32 UInventoryComponent::QuickTransferSlot(UInventoryComponent* From, int32 Fr
return 0;
}
if (!From->HasInventoryAuthority())
{
if (UInventoryComponent* Proxy = FindNetProxy(From, To))
{
Proxy->Server_QuickTransferSlot(From, FromIndex, To);
// Le nombre reellement deplace n'est connu que du serveur. On rend
// une valeur non nulle pour que l'appelant sache que le clic a ete
// pris en compte ; l'affichage, lui, viendra de OnRep_Slots.
return 1;
}
return 0;
}
const int32 Moved = From->PushSlotInto(FromIndex, *To);
if (Moved > 0)
{
@@ -500,6 +621,11 @@ int32 UInventoryComponent::QuickTransferSlot(UInventoryComponent* From, int32 Fr
return Moved;
}
void UInventoryComponent::Server_QuickTransferSlot_Implementation(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To)
{
QuickTransferSlot(From, FromIndex, To);
}
int32 UInventoryComponent::TransferAllTo(UInventoryComponent* To, int32 FirstIndex)
{
if (!To || To == this)
@@ -507,6 +633,17 @@ int32 UInventoryComponent::TransferAllTo(UInventoryComponent* To, int32 FirstInd
return 0;
}
if (!HasInventoryAuthority())
{
if (UInventoryComponent* Proxy = FindNetProxy(this, To))
{
Proxy->Server_TransferAllTo(this, To, FirstIndex);
return 1;
}
return 0;
}
To->BeginBatch();
int32 Moved = 0;