Files
inventory-system-plugin/Source/ItemWorld/Private/PickupItemActor.cpp
Bonchellon 7f7e043a88 Initial commit: Item Interaction Ecosystem plugin (UE5.7)
Server-authoritative, data-driven item/inventory/interaction/carry/equipment/
world-slot/placement/crafting framework. 8 modules, compiles against UE 5.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:49:56 +03:00

207 lines
5.1 KiB
C++

// Copyright ExByte Studios. Item Interaction Ecosystem.
#include "PickupItemActor.h"
#include "ItemWorldLog.h"
#include "InteractableComponent.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Net/UnrealNetwork.h"
APickupItemActor::APickupItemActor()
{
bReplicates = true;
SetReplicateMovement(true);
PrimaryActorTick.bCanEverTick = false;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(Root);
Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
Mesh->SetCollisionResponseToAllChannels(ECR_Block);
Mesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
Interactable = CreateDefaultSubobject<UInteractableComponent>(TEXT("Interactable"));
}
void APickupItemActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(APickupItemActor, ItemData);
DOREPLIFETIME(APickupItemActor, bIsLocked);
DOREPLIFETIME(APickupItemActor, CurrentHolder);
}
void APickupItemActor::BeginPlay()
{
Super::BeginPlay();
RefreshVisuals();
}
void APickupItemActor::InitializeFromItem(const FItemInstanceData& InItem)
{
if (!HasAuthority())
{
return;
}
ItemData = InItem;
ItemData.EnsureInstanceId();
ItemData.LocationType = EItemLocationType::World;
RefreshVisuals();
}
APickupItemActor* APickupItemActor::SpawnPickup(UObject* WorldContext, FName ItemId, int32 Quantity, const FTransform& Transform)
{
UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
if (!DB || !DB->HasItem(ItemId))
{
UE_LOG(LogItemWorld, Warning, TEXT("SpawnPickup: unknown item '%s'."), *ItemId.ToString());
return nullptr;
}
const FItemInstanceData Item = DB->MakeItemInstance(ItemId, Quantity);
return SpawnPickupFromInstance(WorldContext, Item, Transform);
}
APickupItemActor* APickupItemActor::SpawnPickupFromInstance(UObject* WorldContext, const FItemInstanceData& Item, const FTransform& Transform)
{
UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull) : nullptr;
if (!World)
{
return nullptr;
}
// Prefer the item's authored pickup actor class; fall back to the base class.
TSubclassOf<AActor> SpawnClass = APickupItemActor::StaticClass();
if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext))
{
if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
{
if (!Def->PickupActorClass.IsNull())
{
if (UClass* Loaded = Def->PickupActorClass.LoadSynchronous())
{
if (Loaded->IsChildOf(APickupItemActor::StaticClass()))
{
SpawnClass = Loaded;
}
}
}
}
}
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
APickupItemActor* Pickup = World->SpawnActor<APickupItemActor>(SpawnClass, Transform, Params);
if (Pickup)
{
Pickup->InitializeFromItem(Item);
}
return Pickup;
}
bool APickupItemActor::GetProvidedItem(FItemInstanceData& OutItem) const
{
if (!ItemData.IsValid())
{
return false;
}
OutItem = ItemData;
return true;
}
bool APickupItemActor::IsItemAvailable() const
{
return !bIsLocked && ItemData.IsValid();
}
void APickupItemActor::NotifyItemTaken(AActor* Taker)
{
if (!HasAuthority())
{
return;
}
UE_LOG(LogItemWorld, Verbose, TEXT("Pickup %s taken by %s; destroying."), *GetName(), *GetNameSafe(Taker));
Destroy();
}
void APickupItemActor::UpdateProvidedItem(const FItemInstanceData& RemainingItem)
{
if (!HasAuthority())
{
return;
}
if (RemainingItem.Quantity <= 0)
{
Destroy();
return;
}
ItemData = RemainingItem;
ItemData.LocationType = EItemLocationType::World;
RefreshVisuals();
}
void APickupItemActor::SetCarried(AActor* Holder, bool bCarried)
{
if (!HasAuthority())
{
return;
}
bIsLocked = bCarried;
CurrentHolder = bCarried ? Holder : nullptr;
if (Mesh)
{
Mesh->SetSimulatePhysics(false);
Mesh->SetCollisionEnabled(bCarried ? ECollisionEnabled::NoCollision : ECollisionEnabled::QueryAndPhysics);
}
}
void APickupItemActor::ReleaseToWorld(const FTransform& WorldTransform)
{
if (!HasAuthority())
{
return;
}
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
SetActorTransform(WorldTransform);
bIsLocked = false;
CurrentHolder = nullptr;
if (Mesh)
{
Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
if (ItemData.IsValid())
{
ItemData.LocationType = EItemLocationType::World;
}
}
void APickupItemActor::OnRep_ItemData()
{
RefreshVisuals();
}
void APickupItemActor::RefreshVisuals()
{
const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(ItemData.ItemId) : nullptr;
if (!Def)
{
return;
}
if (Mesh && !Def->WorldMesh.IsNull())
{
if (UStaticMesh* LoadedMesh = Def->WorldMesh.LoadSynchronous())
{
Mesh->SetStaticMesh(LoadedMesh);
}
}
if (Interactable && Interactable->DisplayName.IsEmpty())
{
Interactable->DisplayName = Def->DisplayName;
}
}