(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
+100 -6
View File
@@ -17,6 +17,7 @@
#include "InteractionComponent.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "Net/UnrealNetwork.h"
#include "PickupItem.h"
#include "SurvivalStatsComponent.h"
#include "SurvivalUserSettings.h"
@@ -448,14 +449,58 @@ void AFpsPlayer::StartSprint()
return;
}
bIsSprinting = true;
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
SetSprinting(true);
}
void AFpsPlayer::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// COND_SkipOwner : celui qui sprinte le sait deja, il vient de l'appliquer
// en local. Le renvoyer ecraserait son etat courant par une valeur qui a un
// aller-retour de retard -- exactement ce qui fait "sauter" un sprint qu'on
// relance vite.
DOREPLIFETIME_CONDITION(AFpsPlayer, bIsSprinting, COND_SkipOwner);
}
void AFpsPlayer::StopSprint()
{
bIsSprinting = false;
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
SetSprinting(false);
}
void AFpsPlayer::SetSprinting(bool bNewSprinting)
{
if (bIsSprinting == bNewSprinting)
{
return;
}
bIsSprinting = bNewSprinting;
ApplySprintSpeed();
// Applique en local d'abord pour que la vitesse soit instantanee sous les
// doigts, puis previent le serveur. Le sens inverse -- attendre la reponse --
// donnerait un sprint qui demarre avec un temps de retard.
if (!HasAuthority())
{
Server_SetSprinting(bNewSprinting);
}
}
void AFpsPlayer::Server_SetSprinting_Implementation(bool bNewSprinting)
{
bIsSprinting = bNewSprinting;
ApplySprintSpeed();
}
void AFpsPlayer::OnRep_IsSprinting()
{
ApplySprintSpeed();
}
void AFpsPlayer::ApplySprintSpeed()
{
GetCharacterMovement()->MaxWalkSpeed = bIsSprinting ? SprintSpeed : WalkSpeed;
}
void AFpsPlayer::HandleSprintReleased()
@@ -471,7 +516,33 @@ void AFpsPlayer::HandleSprintReleased()
void AFpsPlayer::UseSelectedItem()
{
const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
const int32 SelectedIndex = InventoryComponent->GetSelectedHotbarIndex();
// L'hote agit tout de suite, le client demande. On envoie meme si le slot
// parait vide chez nous : c'est le serveur qui fait foi, et son inventaire
// peut avoir une frame d'avance sur le notre.
if (!HasAuthority())
{
Server_UseItem(SelectedIndex);
return;
}
PerformUseItem(SelectedIndex);
}
void AFpsPlayer::Server_UseItem_Implementation(int32 SlotIndex)
{
PerformUseItem(SlotIndex);
}
void AFpsPlayer::PerformUseItem(int32 SlotIndex)
{
if (!HasAuthority() || !InventoryComponent->GetSlots().IsValidIndex(SlotIndex))
{
return;
}
const FInventorySlot Selected = InventoryComponent->GetSlots()[SlotIndex];
if (Selected.IsEmpty())
{
return;
@@ -493,7 +564,7 @@ void AFpsPlayer::UseSelectedItem()
// usages multiples (une charge en moins, l'objet reste tant qu'il en a).
// On vise le slot actif precisement, et pas RemoveItem qui piocherait
// n'importe quelle pile du meme objet, y compris dans le sac.
InventoryComponent->ConsumeUse(InventoryComponent->GetSelectedHotbarIndex());
InventoryComponent->ConsumeUse(SlotIndex);
}
FVector AFpsPlayer::PlaceOnGround(const FVector& Start) const
@@ -543,6 +614,11 @@ bool AFpsPlayer::DropSlot(int32 SlotIndex, int32 Quantity)
return DropFromInventory(InventoryComponent, SlotIndex, Quantity);
}
void AFpsPlayer::Server_DropFromInventory_Implementation(UInventoryComponent* Inventory, int32 SlotIndex, int32 Quantity)
{
DropFromInventory(Inventory, SlotIndex, Quantity);
}
bool AFpsPlayer::DropFromInventory(UInventoryComponent* Inventory, int32 SlotIndex, int32 Quantity)
{
if (!Inventory)
@@ -550,6 +626,16 @@ bool AFpsPlayer::DropFromInventory(UInventoryComponent* Inventory, int32 SlotInd
return false;
}
// Jeter engendre un acteur dans le monde : c'est une action d'autorite.
// La position de depot est calculee cote SERVEUR a partir de la camera
// repliquee du pawn -- a quelques centimetres pres du point vise par le
// client, ce qui n'a aucune importance pour un objet pose au sol.
if (!HasAuthority())
{
Server_DropFromInventory(Inventory, SlotIndex, Quantity);
return true;
}
if (!DroppedPickupClass)
{
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, impossible de jeter un objet."));
@@ -618,6 +704,14 @@ void AFpsPlayer::HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32
void AFpsPlayer::DropAllItems()
{
// Declenche par la mort, qui sera arbitree par le serveur. La garde est la
// pour le jour ou un client l'appellerait : il engendrerait des pickups
// visibles de lui seul, qui disparaitraient au premier nettoyage reseau.
if (!HasAuthority())
{
return;
}
if (!DroppedPickupClass)
{
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, le butin est perdu."));