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>
This commit is contained in:
206
Source/ItemWorld/Private/PickupItemActor.cpp
Normal file
206
Source/ItemWorld/Private/PickupItemActor.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user