// Fill out your copyright notice in the Description page of Project Settings. #include "PickupItem.h" #include "Components/BoxComponent.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.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(TEXT("Mesh")); SetRootComponent(Mesh); // Purement decoratif : c'est la boite 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. InteractionBox = CreateDefaultSubobject(TEXT("InteractionBox")); InteractionBox->SetupAttachment(Mesh); // Valeur de repli seulement : FitInteractionBox l'ecrase des qu'un mesh est // pose. Elle ne sert qu'aux pickups sans ItemData, et au cas ou // bAutoFitInteractionBox serait decoche sans qu'on regle la boite. InteractionBox->SetBoxExtent(FVector(25.f)); InteractionBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly); InteractionBox->SetCollisionResponseToAllChannels(ECR_Ignore); InteractionBox->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block); } void APickupItem::GetLifetimeReplicatedProps(TArray& 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); } // Hors du if, et apres lui : la boite doit suivre le mesh REELLEMENT pose, // que celui-ci vienne de l'ItemData ou qu'il ait ete impose a la main. if (bAutoFitInteractionBox) { FitInteractionBox(); } } void APickupItem::FitInteractionBox() { const UStaticMesh* CurrentMesh = Mesh->GetStaticMesh(); if (!CurrentMesh) { // Pickup non configure, ou client dont l'ItemData n'est pas encore arrive : // on garde la boite du Blueprint plutot que de l'ecraser par un volume nul. // OnRep_ItemData repassera ici quand la donnee sera la. return; } // La bounding box de l'ASSET, jamais Mesh->Bounds : ces dernieres sont en // espace monde et deja multipliees par l'echelle de l'acteur. Or SetBoxExtent // recoit une extension LOCALE que le moteur remultiplie a son tour -- une // instance posee a l'echelle 2 aurait une boite quatre fois trop grande. const FBox LocalBounds = CurrentMesh->GetBoundingBox(); // Le pivot d'un mesh d'asset pack tombe rarement au centre de sa geometrie : // il est presque toujours au sol, parfois franchement decale. Une boite // laissee a l'origine deborderait d'un cote et laisserait l'autre invisable. InteractionBox->SetRelativeLocation(LocalBounds.GetCenter()); // ComponentMax et non un Max sur la norme : le plancher s'applique axe par // axe, sinon relever l'epaisseur d'une branche rallongerait aussi la boite // dans le sens ou elle etait deja bien assez grande. const FVector Fitted = LocalBounds.GetExtent() + FVector(InteractionBoxPadding); // bUpdateOverlaps a false : cette boite ne bloque que le canal Visibility et // ne genere aucun evenement d'overlap. Le defaut (true) declencherait une // requete physique par pickup a chaque spawn, pour rien. InteractionBox->SetBoxExtent( Fitted.ComponentMax(FVector(MinInteractionExtent)), /*bUpdateOverlaps=*/ false); } 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() : 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