167 lines
4.9 KiB
C++
167 lines
4.9 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "PickupItem.h"
|
|
|
|
#include "Components/SphereComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "InventoryComponent.h"
|
|
#include "ItemDataAsset.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "PickupItem"
|
|
|
|
APickupItem::APickupItem()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = false;
|
|
|
|
// Acteur du monde : le serveur fait autorite. Un pickup ramasse doit
|
|
// disparaitre chez les quatre joueurs, pas seulement chez celui qui l'a pris.
|
|
bReplicates = true;
|
|
|
|
// Un pickup ne bouge plus une fois pose. Couper la replication de mouvement
|
|
// evite d'envoyer une transform a chaque joueur qui entre dans sa zone de
|
|
// pertinence, alors que celle du spawn suffit.
|
|
SetReplicateMovement(false);
|
|
|
|
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
|
SetRootComponent(Mesh);
|
|
// Purement decoratif : c'est la sphere qui porte la collision.
|
|
Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
|
|
// Le trace d'interaction passe par le canal Visibility. On ne bloque QUE
|
|
// ce canal : le pickup reste traversable par le joueur et les projectiles.
|
|
InteractionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("InteractionSphere"));
|
|
InteractionSphere->SetupAttachment(Mesh);
|
|
InteractionSphere->InitSphereRadius(50.f);
|
|
InteractionSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
|
InteractionSphere->SetCollisionResponseToAllChannels(ECR_Ignore);
|
|
InteractionSphere->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
|
|
}
|
|
|
|
void APickupItem::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(APickupItem, ItemData);
|
|
DOREPLIFETIME(APickupItem, Quantity);
|
|
DOREPLIFETIME(APickupItem, RemainingUses);
|
|
}
|
|
|
|
void APickupItem::InitPickup(UItemDataAsset* InItemData, int32 InQuantity, int32 InRemainingUses)
|
|
{
|
|
ItemData = InItemData;
|
|
Quantity = FMath::Max(1, InQuantity);
|
|
RemainingUses = InRemainingUses;
|
|
}
|
|
|
|
void APickupItem::ApplyItemVisuals()
|
|
{
|
|
if (bUseMeshFromItemData && ItemData && ItemData->WorldMesh)
|
|
{
|
|
ItemData->ApplyWorldVisualsTo(Mesh);
|
|
}
|
|
}
|
|
|
|
void APickupItem::OnRep_ItemData()
|
|
{
|
|
ApplyItemVisuals();
|
|
}
|
|
|
|
void APickupItem::OnConstruction(const FTransform& Transform)
|
|
{
|
|
Super::OnConstruction(Transform);
|
|
|
|
// Confort d'edition : le mesh suit l'objet choisi, tu vois immediatement
|
|
// dans le viewport ce que tu es en train de poser. Vaut aussi a l'execution
|
|
// pour les objets jetes, puisque OnConstruction s'execute a FinishSpawning.
|
|
//
|
|
// Chez un client, ItemData est encore nul a cet instant : c'est OnRep_ItemData
|
|
// qui reposera le visuel quand la propriete arrivera.
|
|
ApplyItemVisuals();
|
|
}
|
|
|
|
FText APickupItem::GetInteractionPrompt_Implementation(AActor* Interactor) const
|
|
{
|
|
// Interactor ne sert pas ici : ramasser dit la meme chose a tout le monde.
|
|
if (!ItemData)
|
|
{
|
|
return LOCTEXT("PickupUnconfigured", "Unconfigured item");
|
|
}
|
|
|
|
if (Quantity > 1)
|
|
{
|
|
return FText::Format(LOCTEXT("PickupPromptStack", "Pick up {0} x{1}"),
|
|
ItemData->DisplayName, FText::AsNumber(Quantity));
|
|
}
|
|
|
|
return FText::Format(LOCTEXT("PickupPrompt", "Pick up {0}"), ItemData->DisplayName);
|
|
}
|
|
|
|
bool APickupItem::CanInteract_Implementation(AActor* Interactor) const
|
|
{
|
|
// Un pickup sans donnee est une erreur de configuration : on le rend
|
|
// inerte plutot que de laisser le joueur ramasser du vide.
|
|
return ItemData != nullptr;
|
|
}
|
|
|
|
void APickupItem::Interact_Implementation(AActor* Interactor)
|
|
{
|
|
// Filet de securite : UInteractionComponent ne nous appelle deja que cote
|
|
// autorite, mais un Blueprint ou un futur appelant pourrait l'oublier, et
|
|
// un ramassage joue en local donnerait un objet qui reapparait a la
|
|
// prochaine replication -- le pire des symptomes, parce qu'il est
|
|
// intermittent.
|
|
if (!HasAuthority())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UInventoryComponent* Inventory = Interactor ? Interactor->FindComponentByClass<UInventoryComponent>() : nullptr;
|
|
if (!Inventory)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("APickupItem : %s n'a pas d'UInventoryComponent."), *GetNameSafe(Interactor));
|
|
return;
|
|
}
|
|
|
|
const int32 Leftover = Inventory->AddItem(ItemData, Quantity, RemainingUses);
|
|
|
|
// Inventaire plein : on ne detruit surtout pas le tas, on retire seulement
|
|
// ce qui est rentre. Le joueur peut revenir chercher le reste.
|
|
if (Leftover >= Quantity)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Les anciens AddOnScreenDebugMessage ont disparu : en reseau ils
|
|
// s'affichaient sur l'ecran de l'HOTE, puisque c'est lui qui execute ce
|
|
// code, y compris quand c'est un client qui ramasse. L'UI d'inventaire
|
|
// existe desormais et dit la meme chose au bon joueur.
|
|
|
|
if (Leftover > 0)
|
|
{
|
|
Quantity = Leftover;
|
|
return;
|
|
}
|
|
|
|
Destroy();
|
|
}
|
|
|
|
void APickupItem::OnBeginFocus_Implementation()
|
|
{
|
|
if (bHighlightOnFocus)
|
|
{
|
|
Mesh->SetRenderCustomDepth(true);
|
|
}
|
|
}
|
|
|
|
void APickupItem::OnEndFocus_Implementation()
|
|
{
|
|
if (bHighlightOnFocus)
|
|
{
|
|
Mesh->SetRenderCustomDepth(false);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|