// 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(TEXT("Root")); SetRootComponent(Root); Mesh = CreateDefaultSubobject(TEXT("Mesh")); Mesh->SetupAttachment(Root); Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); Mesh->SetCollisionResponseToAllChannels(ECR_Block); Mesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); Interactable = CreateDefaultSubobject(TEXT("Interactable")); } void APickupItemActor::GetLifetimeReplicatedProps(TArray& 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 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(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; } }