Clothing system refactor

This commit is contained in:
2026-05-29 22:13:09 +03:00
parent c6eff4d076
commit cfecd1f4c6
59 changed files with 417 additions and 227 deletions
Binary file not shown.
+4
View File
@@ -42,6 +42,10 @@
"Win64" "Win64"
], ],
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2" "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2"
},
{
"Name": "StructUtils",
"Enabled": true
} }
] ]
} }
@@ -1,4 +0,0 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "ClothingItem.h"
@@ -0,0 +1,11 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "ClothingItemDefinition.h"
#include "ClothingItemInstance.h"
TSubclassOf<UItemInstance> UClothingItemDefinition::GetInstanceClass() const
{
return UClothingItemInstance::StaticClass();
}
@@ -6,8 +6,9 @@
#include "BodyPart.h" #include "BodyPart.h"
#include "ClothingSlotType.h" #include "ClothingSlotType.h"
#include "Engine/DataAsset.h" #include "Engine/DataAsset.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Progression/ProgressionPath.h" #include "NakedDesire/Progression/ProgressionPath.h"
#include "ClothingItem.generated.h" #include "ClothingItemDefinition.generated.h"
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct NAKEDDESIRE_API FBodyPartCoverage struct NAKEDDESIRE_API FBodyPartCoverage
@@ -68,17 +69,13 @@ struct NAKEDDESIRE_API FGarmentContainerSlot
}; };
UCLASS(BlueprintType) UCLASS(BlueprintType)
class NAKEDDESIRE_API UClothingItem : public UPrimaryDataAsset class NAKEDDESIRE_API UClothingItemDefinition : public UItemDefinition
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) virtual TSubclassOf<UItemInstance> GetInstanceClass() const override;
FText Name;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UTexture2D> Icon;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EClothingSlotType SlotType; EClothingSlotType SlotType;
@@ -88,9 +85,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TMap<FName, UMaterialInstance*> Materials; TMap<FName, UMaterialInstance*> Materials;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UStaticMesh* StaticMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int BasePrice; int BasePrice;
@@ -109,9 +103,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<EBodyPart> CanExpose; TArray<EBodyPart> CanExpose;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Coverage = 1.0f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FGarmentContainerSlot> ContainerSlots; TArray<FGarmentContainerSlot> ContainerSlots;
@@ -1,27 +1,24 @@
#include "ClothingItemInstance.h" #include "ClothingItemInstance.h"
#include "ClothingItem.h" #include "ClothingItemDefinition.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h" #include "StructUtils/InstancedStruct.h"
void UClothingItemInstance::Init(UClothingItem* InClothingItem) void UClothingItemInstance::Init(UClothingItemDefinition* InClothingItem)
{ {
ClothingItem = InClothingItem; ItemDefinition = InClothingItem;
} }
void UClothingItemInstance::Setup(UClothingItem* InClothingItem, const TArray<UItemInstance*>& InStoredItems, void UClothingItemInstance::CaptureState(FInstancedStruct& OutState) const
const float InCondition, const FGuid InInstanceId)
{ {
this->ClothingItem = InClothingItem; FClothingInstanceState ClothingState;
this->StoredItems = InStoredItems; ClothingState.Condition = Condition;
this->Condition = InCondition; OutState.InitializeAs<FClothingInstanceState>(ClothingState);
this->InstanceId = InInstanceId;
} }
UClothingItemInstance* UClothingItemInstance::CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord) void UClothingItemInstance::ApplyState(const FInstancedStruct& InState)
{ {
UClothingItemInstance* NewItemInstance = NewObject<UClothingItemInstance>(Outer); if (const FClothingInstanceState* ClothingState = InState.GetPtr<FClothingInstanceState>())
{
NewItemInstance->Setup(ItemSaveRecord.Definition.Get(), {}, ItemSaveRecord.Condition, ItemSaveRecord.InstanceId); Condition = ClothingState->Condition;
}
return NewItemInstance; }
}
@@ -1,32 +1,39 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "ClothingItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h" #include "NakedDesire/Items/ItemInstance.h"
#include "ClothingItemInstance.generated.h" #include "ClothingItemInstance.generated.h"
struct FItemSaveRecord; class UClothingItemDefinition;
class UClothingItem;
/** Per-instance mutable state for clothing. */
USTRUCT()
struct FClothingInstanceState : public FItemInstanceState
{
GENERATED_BODY()
UPROPERTY(SaveGame)
float Condition = 1.0f;
};
UCLASS(BlueprintType) UCLASS(BlueprintType)
class NAKEDDESIRE_API UClothingItemInstance : public UItemInstance class NAKEDDESIRE_API UClothingItemInstance : public UItemInstance
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item") UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
float Condition = 1.0f; float Condition = 1.0f;
UClothingItem* GetClothingItem() const { return ClothingItem; } UClothingItemDefinition* GetClothingItemDefinition() const { return Cast<UClothingItemDefinition>(ItemDefinition); }
void Init(UClothingItem* InClothingItem); void Init(UClothingItemDefinition* InClothingItem);
void Setup(UClothingItem* InClothingItem, const TArray<UItemInstance*>& InStoredItems, float InCondition, FGuid InInstanceId);
static UClothingItemInstance* CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord);
protected: protected:
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item") virtual void CaptureState(FInstancedStruct& OutState) const override;
TObjectPtr<UClothingItem> ClothingItem; virtual void ApplyState(const FInstancedStruct& InState) override;
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item") UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
TArray<TObjectPtr<UItemInstance>> StoredItems; TArray<TObjectPtr<UItemInstance>> StoredItems;
}; };
+15 -12
View File
@@ -2,7 +2,7 @@
#include "ClothingManager.h" #include "ClothingManager.h"
#include "ClothingItem.h" #include "ClothingItemDefinition.h"
#include "ClothingItemInstance.h" #include "ClothingItemInstance.h"
#include "GameFramework/Character.h" #include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
@@ -51,7 +51,7 @@ bool UClothingManager::IsBodyPartExposed(const EBodyPart BodyPart)
{ {
for (const auto& [Key, Value] : EquippedClothing) for (const auto& [Key, Value] : EquippedClothing)
{ {
if (Value->GetClothingItem()->HiddenBodyParts.Contains(BodyPart)) if (Value->GetClothingItemDefinition()->HiddenBodyParts.Contains(BodyPart))
return false; return false;
} }
@@ -63,7 +63,10 @@ void UClothingManager::HydrateClothing()
USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>(); USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
for (const FItemSaveRecord& ItemSaveRecord : SaveSubsystem->GetCurrentSave()->GetEquippedItems()) for (const FItemSaveRecord& ItemSaveRecord : SaveSubsystem->GetCurrentSave()->GetEquippedItems())
{ {
UClothingItemInstance* ClothingItemInstance = UClothingItemInstance::CreateFromSave(this, ItemSaveRecord); UClothingItemInstance* ClothingItemInstance = Cast<UClothingItemInstance>(UItemInstance::CreateFromRecord(this, ItemSaveRecord));
if (!ClothingItemInstance)
continue;
PutOnClothing(ClothingItemInstance); PutOnClothing(ClothingItemInstance);
} }
} }
@@ -75,7 +78,7 @@ float UClothingManager::GetHeelHeight()
const UClothingItemInstance* Footwear = EquippedClothing[EClothingSlotType::Footwear]; const UClothingItemInstance* Footwear = EquippedClothing[EClothingSlotType::Footwear];
return Footwear->GetClothingItem()->ShoesOffset; return Footwear->GetClothingItemDefinition()->ShoesOffset;
} }
USkeletalMeshComponent* UClothingManager::GetMeshComponent(const EClothingSlotType SlotType) const USkeletalMeshComponent* UClothingManager::GetMeshComponent(const EClothingSlotType SlotType) const
@@ -135,25 +138,25 @@ void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance
if (!ClothingItemInstance) if (!ClothingItemInstance)
return; return;
const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItem()->SlotType; const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType); USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh); MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
if (!ClothingItemInstance->GetClothingItem()->Materials.IsEmpty()) if (!ClothingItemInstance->GetClothingItemDefinition()->Materials.IsEmpty())
{ {
for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItem()->Materials) for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItemDefinition()->Materials)
{ {
MeshComponent->SetMaterialByName(Material.Key, Material.Value); MeshComponent->SetMaterialByName(Material.Key, Material.Value);
} }
} }
SetClothingSlotItem(ClothingSlotType, ClothingItemInstance); SetClothingSlotItem(ClothingSlotType, ClothingItemInstance);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose) if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{ {
MeshComponent->SetLeaderPoseComponent(Cast<ACharacter>(GetOwner())->GetMesh()); MeshComponent->SetLeaderPoseComponent(Cast<ACharacter>(GetOwner())->GetMesh());
} }
const UClothingItem* ClothingItem = ClothingItemInstance->GetClothingItem(); const UClothingItemDefinition* ClothingItem = ClothingItemInstance->GetClothingItemDefinition();
if (ClothingItem->SlotType == EClothingSlotType::Bodysuit) if (ClothingItem->SlotType == EClothingSlotType::Bodysuit)
{ {
DropClothing(EClothingSlotType::Top); DropClothing(EClothingSlotType::Top);
@@ -174,7 +177,7 @@ void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance
void UClothingManager::TakeClothing(UClothingItemInstance* ClothingItemInstance) void UClothingManager::TakeClothing(UClothingItemInstance* ClothingItemInstance)
{ {
const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItem()->SlotType; const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
if (EquippedClothing.Contains(SlotType)) if (EquippedClothing.Contains(SlotType))
{ {
DropClothing(SlotType); DropClothing(SlotType);
@@ -200,7 +203,7 @@ UClothingItemInstance* UClothingManager::RemoveClothing(const EClothingSlotType
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType); USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
MeshComponent->SetSkeletalMesh(nullptr); MeshComponent->SetSkeletalMesh(nullptr);
if (ExistingItem->GetClothingItem()->UseLeaderPose) if (ExistingItem->GetClothingItemDefinition()->UseLeaderPose)
{ {
MeshComponent->SetLeaderPoseComponent(nullptr); MeshComponent->SetLeaderPoseComponent(nullptr);
} }
@@ -2,7 +2,7 @@
#include "NakedDesireGameMode.h" #include "NakedDesireGameMode.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Interactables/ItemPickup.h" #include "NakedDesire/Interactables/ItemPickup.h"
#include "UObject/ConstructorHelpers.h" #include "UObject/ConstructorHelpers.h"
@@ -34,10 +34,10 @@ void ANakedDesireGameMode::BuyItem(UClothingItemInstance* ClothingItemInstance)
return; return;
} }
if (SaveGame->Money < ClothingItemInstance->GetClothingItem()->BasePrice) if (SaveGame->Money < ClothingItemInstance->GetClothingItemDefinition()->BasePrice)
return; return;
SaveGame->Money -= ClothingItemInstance->GetClothingItem()->BasePrice; SaveGame->Money -= ClothingItemInstance->GetClothingItemDefinition()->BasePrice;
Wardrobe->AddItem(ClothingItemInstance); Wardrobe->AddItem(ClothingItemInstance);
} }
@@ -71,12 +71,12 @@ void ANakedDesireGameMode::BeginPlay()
} }
USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>(); USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
for (const auto& Item : SaveSubsystem->GetCurrentSave()->GetWorldItems()) for (const FItemSaveRecord& Item : SaveSubsystem->GetCurrentSave()->GetWorldItems())
{ {
UClothingItemInstance* NewItemInstance = NewObject<UClothingItemInstance>(this); UClothingItemInstance* NewItemInstance = Cast<UClothingItemInstance>(UItemInstance::CreateFromRecord(this, Item));
NewItemInstance->Init(Item.Definition.Get()); if (!NewItemInstance)
NewItemInstance->Condition = Item.Condition; continue;
NewItemInstance->SetInstanceId(Item.InstanceId);
AItemPickup* NewItemPickup = GetWorld()->SpawnActor<AItemPickup>(ItemPickupClass, Item.WorldTransform); AItemPickup* NewItemPickup = GetWorld()->SpawnActor<AItemPickup>(ItemPickupClass, Item.WorldTransform);
NewItemPickup->SetItem(NewItemInstance); NewItemPickup->SetItem(NewItemInstance);
} }
@@ -5,7 +5,7 @@
#include "Components/BoxComponent.h" #include "Components/BoxComponent.h"
#include "Components/WidgetComponent.h" #include "Components/WidgetComponent.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h" #include "NakedDesire/Player/NakedDesireCharacter.h"
@@ -71,7 +71,14 @@ void AItemPickup::SetItem(UClothingItemInstance* InItem)
{ {
ClothingItemInstance = InItem; ClothingItemInstance = InItem;
Mesh->SetStaticMesh(InItem->GetClothingItem()->StaticMesh); Mesh->SetStaticMesh(InItem->GetClothingItemDefinition()->StaticMesh);
if (!InItem->GetClothingItemDefinition()->Materials.IsEmpty())
{
for (const TPair<FName, UMaterialInstance*>& Material : InItem->GetClothingItemDefinition()->Materials)
{
Mesh->SetMaterialByName(Material.Key, Material.Value);
}
}
} }
void AItemPickup::ApplyOutline(UStaticMeshComponent* InMesh, bool bEnabled, int32 StencilValue) void AItemPickup::ApplyOutline(UStaticMeshComponent* InMesh, bool bEnabled, int32 StencilValue)
@@ -25,7 +25,6 @@ public:
virtual void ShowInteractionFocusHint_Implementation() override; virtual void ShowInteractionFocusHint_Implementation() override;
virtual void ShowInteractionProximityHint_Implementation() override; virtual void ShowInteractionProximityHint_Implementation() override;
void SetItem(UClothingItemInstance* InItem); void SetItem(UClothingItemInstance* InItem);
UClothingItemInstance* GetItem() const { return ClothingItemInstance; } UClothingItemInstance* GetItem() const { return ClothingItemInstance; }
@@ -3,6 +3,7 @@
#include "Wardrobe.h" #include "Wardrobe.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h" #include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h" #include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h" #include "NakedDesire/SaveGame/SaveSubsystem.h"
@@ -29,7 +30,10 @@ void AWardrobe::BeginPlay()
for (const FItemSaveRecord& ItemSaveRecord : SaveGame->GetWardrobeItems()) for (const FItemSaveRecord& ItemSaveRecord : SaveGame->GetWardrobeItems())
{ {
UClothingItemInstance* NewItemInstance = UClothingItemInstance::CreateFromSave(this, ItemSaveRecord); UClothingItemInstance* NewItemInstance = Cast<UClothingItemInstance>(UItemInstance::CreateFromRecord(this, ItemSaveRecord));
if (!NewItemInstance)
continue;
ClothingItems.Push(NewItemInstance); ClothingItems.Push(NewItemInstance);
} }
} }
@@ -0,0 +1,17 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "ItemDefinition.h"
#include "ItemInstance.h"
UItemInstance* UItemDefinition::CreateInstance(UObject* Outer) const
{
const TSubclassOf<UItemInstance> InstanceClass = GetInstanceClass();
if (!InstanceClass)
return nullptr;
UItemInstance* Instance = NewObject<UItemInstance>(Outer, InstanceClass);
Instance->SetItemDefinition(const_cast<UItemDefinition*>(this));
return Instance;
}
+32
View File
@@ -0,0 +1,32 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Templates/SubclassOf.h"
#include "ItemDefinition.generated.h"
class UItemInstance;
UCLASS(Abstract)
class NAKEDDESIRE_API UItemDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
/** The UItemInstance subclass that runtime instances of this definition use. */
virtual TSubclassOf<UItemInstance> GetInstanceClass() const PURE_VIRTUAL(UItemDefinition::GetInstanceClass, return nullptr;);
/** Mint a fresh runtime instance of this definition (new GUID, default per-type state). */
UItemInstance* CreateInstance(UObject* Outer) const;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText Name;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UTexture2D> Icon;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UStaticMesh* StaticMesh;
};
+39 -1
View File
@@ -1,9 +1,13 @@
#include "ItemInstance.h" #include "ItemInstance.h"
#include "ItemDefinition.h"
#include "StructUtils/InstancedStruct.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
void UItemInstance::PostInitProperties() void UItemInstance::PostInitProperties()
{ {
Super::PostInitProperties(); Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject | RF_NeedLoad)) if (HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject | RF_NeedLoad))
return; return;
if (!InstanceId.IsValid()) if (!InstanceId.IsValid())
@@ -20,3 +24,37 @@ void UItemInstance::SetInstanceId(FGuid InId)
{ {
InstanceId = InId; InstanceId = InId;
} }
FItemSaveRecord UItemInstance::ToSaveRecord() const
{
FItemSaveRecord Record;
Record.InstanceId = InstanceId;
Record.Definition = ItemDefinition;
CaptureState(Record.State);
return Record;
}
UItemInstance* UItemInstance::CreateFromRecord(UObject* Outer, const FItemSaveRecord& Record)
{
UItemDefinition* Definition = Record.Definition.LoadSynchronous();
if (!Definition)
{
UE_LOG(LogTemp, Warning, TEXT("UItemInstance::CreateFromRecord: failed to load definition for instance %s"),
*Record.InstanceId.ToString());
return nullptr;
}
const TSubclassOf<UItemInstance> InstanceClass = Definition->GetInstanceClass();
if (!InstanceClass)
{
UE_LOG(LogTemp, Warning, TEXT("UItemInstance::CreateFromRecord: %s returned no instance class"),
*Definition->GetName());
return nullptr;
}
UItemInstance* Instance = NewObject<UItemInstance>(Outer, InstanceClass);
Instance->ItemDefinition = Definition;
Instance->InstanceId = Record.InstanceId.IsValid() ? Record.InstanceId : FGuid::NewGuid();
Instance->ApplyState(Record.State);
return Instance;
}
+39 -3
View File
@@ -4,19 +4,55 @@
#include "UObject/Object.h" #include "UObject/Object.h"
#include "ItemInstance.generated.h" #include "ItemInstance.generated.h"
class UItemDefinition;
struct FItemSaveRecord;
struct FInstancedStruct;
/**
* Base for per-instance mutable state carried inside FItemSaveRecord::State.
* Subclass per item type (see FClothingInstanceState, FSexToyInstanceState).
*/
USTRUCT()
struct NAKEDDESIRE_API FItemInstanceState
{
GENERATED_BODY()
};
UCLASS(Abstract) UCLASS(Abstract)
class NAKEDDESIRE_API UItemInstance : public UObject class NAKEDDESIRE_API UItemInstance : public UObject
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual void PostInitProperties() override; virtual void PostInitProperties() override;
virtual void PostDuplicate(EDuplicateMode::Type DuplicateMode) override; virtual void PostDuplicate(EDuplicateMode::Type DuplicateMode) override;
FGuid GetInstanceId() const { return InstanceId; } FGuid GetInstanceId() const { return InstanceId; }
void SetInstanceId(FGuid InId); void SetInstanceId(FGuid InId);
UItemDefinition* GetItemDefinition() const { return ItemDefinition; }
void SetItemDefinition(UItemDefinition* InDefinition) { ItemDefinition = InDefinition; }
/** Serialize identity + definition + per-type state into a record. */
FItemSaveRecord ToSaveRecord() const;
/**
* Reconstruct the correct UItemInstance subclass from a saved record,
* driven by the definition's GetInstanceClass(). Returns nullptr if the
* definition fails to load.
*/
static UItemInstance* CreateFromRecord(UObject* Outer, const FItemSaveRecord& Record);
protected: protected:
/** Override to pack this type's mutable state into OutState. */
virtual void CaptureState(FInstancedStruct& OutState) const {}
/** Override to restore this type's mutable state from InState. */
virtual void ApplyState(const FInstancedStruct& InState) {}
UPROPERTY(VisibleAnywhere, SaveGame, BlueprintReadOnly, Category = "Item", meta = (AllowPrivateAccess = "true")) UPROPERTY(VisibleAnywhere, SaveGame, BlueprintReadOnly, Category = "Item", meta = (AllowPrivateAccess = "true"))
FGuid InstanceId; FGuid InstanceId;
};
UPROPERTY()
TObjectPtr<UItemDefinition> ItemDefinition;
};
@@ -1 +1,20 @@
#include "SexToyInstance.h" #include "SexToyInstance.h"
#include "StructUtils/InstancedStruct.h"
void USexToyInstance::CaptureState(FInstancedStruct& OutState) const
{
FSexToyInstanceState State;
State.bActive = bActive;
State.Battery = Battery;
OutState.InitializeAs<FSexToyInstanceState>(State);
}
void USexToyInstance::ApplyState(const FInstancedStruct& InState)
{
if (const FSexToyInstanceState* State = InState.GetPtr<FSexToyInstanceState>())
{
bActive = State->bActive;
Battery = State->Battery;
}
}
+26 -8
View File
@@ -2,19 +2,37 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "ItemInstance.h" #include "ItemInstance.h"
#include "SexToyItem.h"
#include "SexToyInstance.generated.h" #include "SexToyInstance.generated.h"
class USexToyItem; /** Per-instance mutable state for sex toys. */
USTRUCT()
struct FSexToyInstanceState : public FItemInstanceState
{
GENERATED_BODY()
UPROPERTY(SaveGame)
bool bActive = false;
UPROPERTY(SaveGame)
float Battery = 1.0f;
};
UCLASS() UCLASS()
class NAKEDDESIRE_API USexToyInstance : public UItemInstance class NAKEDDESIRE_API USexToyInstance : public UItemInstance
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
USexToyItem* GetSexToyItem() const { return SexToyItem; } USexToyItem* GetSexToyItem() const { return Cast<USexToyItem>(ItemDefinition); }
private: protected:
UPROPERTY() virtual void CaptureState(FInstancedStruct& OutState) const override;
TObjectPtr<USexToyItem> SexToyItem; virtual void ApplyState(const FInstancedStruct& InState) override;
};
UPROPERTY(BlueprintReadOnly, Category = "Sex Toy")
bool bActive = false;
UPROPERTY(BlueprintReadOnly, Category = "Sex Toy")
float Battery = 1.0f;
};
+7
View File
@@ -1 +1,8 @@
#include "SexToyItem.h" #include "SexToyItem.h"
#include "SexToyInstance.h"
TSubclassOf<UItemInstance> USexToyItem::GetInstanceClass() const
{
return USexToyInstance::StaticClass();
}
+5 -3
View File
@@ -1,16 +1,18 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "NakedDesire/Clothing/ClothingSlotType.h" #include "NakedDesire/Clothing/ClothingSlotType.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "SexToyItem.generated.h" #include "SexToyItem.generated.h"
UCLASS() UCLASS()
class NAKEDDESIRE_API USexToyItem : public UPrimaryDataAsset class NAKEDDESIRE_API USexToyItem : public UItemDefinition
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual TSubclassOf<UItemInstance> GetInstanceClass() const override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sex Toy") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sex Toy")
float LustModifier = 0.0f; float LustModifier = 0.0f;
@@ -2,7 +2,7 @@
#include "EquipClothingRestriction.h" #include "EquipClothingRestriction.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h" #include "NakedDesire/Player/NakedDesireCharacter.h"
@@ -65,9 +65,9 @@ FText UEquipClothingRestriction::GetDescription() const
void UEquipClothingRestriction::OnClothingEquipped(UClothingItemInstance* ClothingItemInstance) void UEquipClothingRestriction::OnClothingEquipped(UClothingItemInstance* ClothingItemInstance)
{ {
const bool IsTargetClothing = ClothingItems.FindByPredicate([&ClothingItemInstance](const UClothingItem* Item) const bool IsTargetClothing = ClothingItems.FindByPredicate([&ClothingItemInstance](const UClothingItemDefinition* Item)
{ {
return Item && Item->Name.EqualTo(ClothingItemInstance->GetClothingItem()->Name); return Item && Item->Name.EqualTo(ClothingItemInstance->GetClothingItemDefinition()->Name);
}) != nullptr; }) != nullptr;
if (IsTargetClothing) if (IsTargetClothing)
{ {
@@ -77,9 +77,9 @@ void UEquipClothingRestriction::OnClothingEquipped(UClothingItemInstance* Clothi
void UEquipClothingRestriction::OnClothingUnequipped(UClothingItemInstance* ClothingItemInstance) void UEquipClothingRestriction::OnClothingUnequipped(UClothingItemInstance* ClothingItemInstance)
{ {
const bool IsTargetClothing = ClothingItems.FindByPredicate([&ClothingItemInstance](const UClothingItem* Item) const bool IsTargetClothing = ClothingItems.FindByPredicate([&ClothingItemInstance](const UClothingItemDefinition* Item)
{ {
return Item && Item->Name.EqualTo(ClothingItemInstance->GetClothingItem()->Name); return Item && Item->Name.EqualTo(ClothingItemInstance->GetClothingItemDefinition()->Name);
}) != nullptr; }) != nullptr;
if (IsTargetClothing) if (IsTargetClothing)
{ {
@@ -7,7 +7,7 @@
#include "EquipClothingRestriction.generated.h" #include "EquipClothingRestriction.generated.h"
class UClothingItemInstance; class UClothingItemInstance;
class UClothingItem; class UClothingItemDefinition;
UCLASS(EditInlineNew) UCLASS(EditInlineNew)
class NAKEDDESIRE_API UEquipClothingRestriction : public UGoalRestriction class NAKEDDESIRE_API UEquipClothingRestriction : public UGoalRestriction
@@ -16,7 +16,7 @@ class NAKEDDESIRE_API UEquipClothingRestriction : public UGoalRestriction
UPROPERTY(EditDefaultsOnly, meta = (ToolTip = UPROPERTY(EditDefaultsOnly, meta = (ToolTip =
"One of provided clothing items should be equipped by player. If multiple clothing items required provide multiple restrictions")) "One of provided clothing items should be equipped by player. If multiple clothing items required provide multiple restrictions"))
TArray<UClothingItem*> ClothingItems; TArray<UClothingItemDefinition*> ClothingItems;
public: public:
virtual void Init(ANakedDesireCharacter* PlayerCharacter) override; virtual void Init(ANakedDesireCharacter* PlayerCharacter) override;
@@ -3,7 +3,7 @@
#include "ExposeBodyPartRestriction.h" #include "ExposeBodyPartRestriction.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Player/NakedDesireCharacter.h" #include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
+1 -1
View File
@@ -11,7 +11,7 @@ public class NakedDesire : ModuleRules
PublicDependencyModuleNames.AddRange(new string[] PublicDependencyModuleNames.AddRange(new string[]
{ {
"Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "UMG", "CommonUI", "NavigationSystem", "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "UMG", "CommonUI", "NavigationSystem",
"AIModule", "GameplayTags", "Slate", "SlateCore" "AIModule", "GameplayTags", "Slate", "SlateCore", "StructUtils"
}); });
} }
} }
@@ -9,7 +9,7 @@
#include "EnhancedInputSubsystems.h" #include "EnhancedInputSubsystems.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Internationalization/Text.h" #include "Internationalization/Text.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Global/Constants.h" #include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Global/NakedDesireHUD.h" #include "NakedDesire/Global/NakedDesireHUD.h"
@@ -227,15 +227,15 @@ void ANakedDesireCharacter::LogTest()
void ANakedDesireCharacter::OnClothingEquip(UClothingItemInstance* ClothingItemInstance) void ANakedDesireCharacter::OnClothingEquip(UClothingItemInstance* ClothingItemInstance)
{ {
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Ass)) if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Ass))
{ {
AnalCensorship->SetVisibility(false); AnalCensorship->SetVisibility(false);
} }
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Genitals)) if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Genitals))
{ {
VaginaCensorship->SetVisibility(false); VaginaCensorship->SetVisibility(false);
} }
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Boobs)) if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Boobs))
{ {
BoobLCensorship->SetVisibility(false); BoobLCensorship->SetVisibility(false);
BoobRCensorship->SetVisibility(false); BoobRCensorship->SetVisibility(false);
@@ -14,7 +14,7 @@
class UInteractionComponent; class UInteractionComponent;
class ANakedDesireHUD; class ANakedDesireHUD;
class UClothingItem; class UClothingItemDefinition;
class UClothingItemInstance; class UClothingItemInstance;
class UClothingSlotsData; class UClothingSlotsData;
class UAIPerceptionStimuliSourceComponent; class UAIPerceptionStimuliSourceComponent;
@@ -1,7 +1,7 @@
#include "PlayerCinematic.h" #include "PlayerCinematic.h"
#include "NakedDesireCharacter.h" #include "NakedDesireCharacter.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
@@ -109,16 +109,16 @@ USkeletalMeshComponent* APlayerCinematic::GetMeshByType(const EClothingSlotType
void APlayerCinematic::EquipClothing(const UClothingItemInstance* ClothingItemInstance) void APlayerCinematic::EquipClothing(const UClothingItemInstance* ClothingItemInstance)
{ {
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType); USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh); MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose) if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{ {
MeshComponent->SetLeaderPoseComponent(GetMesh()); MeshComponent->SetLeaderPoseComponent(GetMesh());
} }
if (!ClothingItemInstance->GetClothingItem()->Materials.IsEmpty()) if (!ClothingItemInstance->GetClothingItemDefinition()->Materials.IsEmpty())
{ {
for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItem()->Materials) for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItemDefinition()->Materials)
{ {
MeshComponent->SetMaterialByName(Material.Key, Material.Value); MeshComponent->SetMaterialByName(Material.Key, Material.Value);
} }
@@ -127,7 +127,7 @@ void APlayerCinematic::EquipClothing(const UClothingItemInstance* ClothingItemIn
void APlayerCinematic::UnequipClothing(const UClothingItemInstance* ClothingItemInstance) void APlayerCinematic::UnequipClothing(const UClothingItemInstance* ClothingItemInstance)
{ {
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType); USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(nullptr); MeshComponent->SetSkeletalMesh(nullptr);
MeshComponent->SetLeaderPoseComponent(nullptr); MeshComponent->SetLeaderPoseComponent(nullptr);
} }
+7 -7
View File
@@ -1,7 +1,7 @@
#include "PlayerImpostor.h" #include "PlayerImpostor.h"
#include "NakedDesireCharacter.h" #include "NakedDesireCharacter.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
@@ -114,16 +114,16 @@ USkeletalMeshComponent* APlayerImpostor::GetMeshByType(const EClothingSlotType S
void APlayerImpostor::EquipClothing(const UClothingItemInstance* ClothingItemInstance) void APlayerImpostor::EquipClothing(const UClothingItemInstance* ClothingItemInstance)
{ {
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType); USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh); MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose) if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{ {
MeshComponent->SetLeaderPoseComponent(GetMesh()); MeshComponent->SetLeaderPoseComponent(GetMesh());
} }
if (!ClothingItemInstance->GetClothingItem()->Materials.IsEmpty()) if (!ClothingItemInstance->GetClothingItemDefinition()->Materials.IsEmpty())
{ {
for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItem()->Materials) for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItemDefinition()->Materials)
{ {
MeshComponent->SetMaterialByName(Material.Key, Material.Value); MeshComponent->SetMaterialByName(Material.Key, Material.Value);
} }
@@ -132,7 +132,7 @@ void APlayerImpostor::EquipClothing(const UClothingItemInstance* ClothingItemIns
void APlayerImpostor::UnequipClothing(const UClothingItemInstance* ClothingItemInstance) void APlayerImpostor::UnequipClothing(const UClothingItemInstance* ClothingItemInstance)
{ {
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType); USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(nullptr); MeshComponent->SetSkeletalMesh(nullptr);
MeshComponent->SetLeaderPoseComponent(nullptr); MeshComponent->SetLeaderPoseComponent(nullptr);
} }
@@ -5,6 +5,7 @@
#include "ItemSaveRecord.h" #include "ItemSaveRecord.h"
#include "NakedDesire/Global/Constants.h" #include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
UGlobalSaveGameData* UGlobalSaveGameData::CreateNewSaveGame() UGlobalSaveGameData* UGlobalSaveGameData::CreateNewSaveGame()
@@ -33,100 +34,102 @@ bool UGlobalSaveGameData::SaveGame(UGlobalSaveGameData* SaveGameData, const FStr
return UGameplayStatics::SaveGameToSlot(SaveGameData, SlotName, SLOT_PLAYER); return UGameplayStatics::SaveGameToSlot(SaveGameData, SlotName, SLOT_PLAYER);
} }
FItemSaveRecord UGlobalSaveGameData::AddWardrobeItem(const UClothingItemInstance* ItemInstance) FItemSaveRecord UGlobalSaveGameData::AddWardrobeItem(const UItemInstance* ItemInstance)
{ {
FItemSaveRecord NewSaveRecord; FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
NewSaveRecord.Init(ItemInstance);
WardrobeItems.Push(NewSaveRecord); WardrobeItems.Push(NewSaveRecord);
return NewSaveRecord; return NewSaveRecord;
} }
bool UGlobalSaveGameData::UpdateWardrobeItem(UClothingItemInstance* ItemInstance) bool UGlobalSaveGameData::UpdateWardrobeItem(UItemInstance* ItemInstance)
{ {
for (auto& ItemSaveRecord : WardrobeItems) for (auto& ItemSaveRecord : WardrobeItems)
{ {
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId()) if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{ {
ItemSaveRecord.Init(ItemInstance); ItemSaveRecord = ItemInstance->ToSaveRecord();
return true; return true;
} }
} }
return false; return false;
} }
bool UGlobalSaveGameData::RemoveWardrobeItem(UClothingItemInstance* ItemInstance) bool UGlobalSaveGameData::RemoveWardrobeItem(UItemInstance* ItemInstance)
{ {
const int32 RemovedElementsCount = WardrobeItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item) const int32 RemovedElementsCount = WardrobeItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{ {
return Item.InstanceId == ItemInstance->GetInstanceId(); return Item.InstanceId == ItemInstance->GetInstanceId();
}); });
return RemovedElementsCount > 0; return RemovedElementsCount > 0;
} }
FItemSaveRecord UGlobalSaveGameData::AddEquippedItem(const UClothingItemInstance* ItemInstance) FItemSaveRecord UGlobalSaveGameData::AddEquippedItem(const UItemInstance* ItemInstance)
{ {
FItemSaveRecord NewSaveRecord; FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
NewSaveRecord.Init(ItemInstance);
EquippedItems.Push(NewSaveRecord); EquippedItems.Push(NewSaveRecord);
return NewSaveRecord; return NewSaveRecord;
} }
bool UGlobalSaveGameData::UpdateEquippedItem(UClothingItemInstance* ItemInstance) void UGlobalSaveGameData::AddEquippedItem(const FItemSaveRecord& ItemRecord)
{
EquippedItems.Push(ItemRecord);
}
bool UGlobalSaveGameData::UpdateEquippedItem(UItemInstance* ItemInstance)
{ {
for (auto& ItemSaveRecord : EquippedItems) for (auto& ItemSaveRecord : EquippedItems)
{ {
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId()) if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{ {
ItemSaveRecord.Init(ItemInstance); ItemSaveRecord = ItemInstance->ToSaveRecord();
return true; return true;
} }
} }
return false; return false;
} }
bool UGlobalSaveGameData::RemoveEquippedItem(UClothingItemInstance* ItemInstance) bool UGlobalSaveGameData::RemoveEquippedItem(UItemInstance* ItemInstance)
{ {
const int32 RemovedElementsCount = EquippedItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item) const int32 RemovedElementsCount = EquippedItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{ {
return Item.InstanceId == ItemInstance->GetInstanceId(); return Item.InstanceId == ItemInstance->GetInstanceId();
}); });
return RemovedElementsCount > 0; return RemovedElementsCount > 0;
} }
FItemSaveRecord UGlobalSaveGameData::AddWorldItem(const UClothingItemInstance* ItemInstance, FTransform Transform) FItemSaveRecord UGlobalSaveGameData::AddWorldItem(const UItemInstance* ItemInstance, FTransform Transform)
{ {
FItemSaveRecord NewSaveRecord; FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
NewSaveRecord.Init(ItemInstance);
NewSaveRecord.WorldTransform = Transform; NewSaveRecord.WorldTransform = Transform;
WorldItems.Push(NewSaveRecord); WorldItems.Push(NewSaveRecord);
return NewSaveRecord; return NewSaveRecord;
} }
bool UGlobalSaveGameData::UpdateWorldItem(UClothingItemInstance* ItemInstance, FTransform Transform) bool UGlobalSaveGameData::UpdateWorldItem(UItemInstance* ItemInstance, FTransform Transform)
{ {
for (auto& ItemSaveRecord : WorldItems) for (auto& ItemSaveRecord : WorldItems)
{ {
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId()) if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{ {
ItemSaveRecord.Init(ItemInstance); ItemSaveRecord = ItemInstance->ToSaveRecord();
ItemSaveRecord.WorldTransform = Transform; ItemSaveRecord.WorldTransform = Transform;
return true; return true;
} }
} }
return false; return false;
} }
bool UGlobalSaveGameData::RemoveWorldItem(UClothingItemInstance* ItemInstance) bool UGlobalSaveGameData::RemoveWorldItem(UItemInstance* ItemInstance)
{ {
const int32 RemovedElementsCount = WorldItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item) const int32 RemovedElementsCount = WorldItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{ {
return Item.InstanceId == ItemInstance->GetInstanceId(); return Item.InstanceId == ItemInstance->GetInstanceId();
}); });
return RemovedElementsCount > 0; return RemovedElementsCount > 0;
} }
@@ -8,7 +8,7 @@
#include "ItemSaveRecord.h" #include "ItemSaveRecord.h"
#include "GlobalSaveGameData.generated.h" #include "GlobalSaveGameData.generated.h"
class UClothingItemInstance; class UItemInstance;
UCLASS() UCLASS()
class NAKEDDESIRE_API UGlobalSaveGameData : public USaveGame class NAKEDDESIRE_API UGlobalSaveGameData : public USaveGame
@@ -26,19 +26,20 @@ public:
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
float Money = 0; float Money = 0;
FItemSaveRecord AddWardrobeItem(const UClothingItemInstance* ItemInstance); FItemSaveRecord AddWardrobeItem(const UItemInstance* ItemInstance);
bool UpdateWardrobeItem(UClothingItemInstance* ItemInstance); bool UpdateWardrobeItem(UItemInstance* ItemInstance);
bool RemoveWardrobeItem(UClothingItemInstance* ItemInstance); bool RemoveWardrobeItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetWardrobeItems() const { return WardrobeItems; } TArray<FItemSaveRecord> GetWardrobeItems() const { return WardrobeItems; }
FItemSaveRecord AddEquippedItem(const UClothingItemInstance* ItemInstance); FItemSaveRecord AddEquippedItem(const UItemInstance* ItemInstance);
bool UpdateEquippedItem(UClothingItemInstance* ItemInstance); void AddEquippedItem(const FItemSaveRecord& ItemRecord);
bool RemoveEquippedItem(UClothingItemInstance* ItemInstance); bool UpdateEquippedItem(UItemInstance* ItemInstance);
bool RemoveEquippedItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetEquippedItems() const { return EquippedItems; } TArray<FItemSaveRecord> GetEquippedItems() const { return EquippedItems; }
FItemSaveRecord AddWorldItem(const UClothingItemInstance* ItemInstance, FTransform Transform); FItemSaveRecord AddWorldItem(const UItemInstance* ItemInstance, FTransform Transform);
bool UpdateWorldItem(UClothingItemInstance* ItemInstance, FTransform Transform); bool UpdateWorldItem(UItemInstance* ItemInstance, FTransform Transform);
bool RemoveWorldItem(UClothingItemInstance* ItemInstance); bool RemoveWorldItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetWorldItems() const { return WorldItems; } TArray<FItemSaveRecord> GetWorldItems() const { return WorldItems; }
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
@@ -50,7 +51,7 @@ public:
private: private:
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
TArray<FItemSaveRecord> WardrobeItems; TArray<FItemSaveRecord> WardrobeItems;
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
TArray<FItemSaveRecord> EquippedItems; TArray<FItemSaveRecord> EquippedItems;
+16 -19
View File
@@ -1,12 +1,18 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "StructUtils/InstancedStruct.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "ItemSaveRecord.generated.h" #include "ItemSaveRecord.generated.h"
class UClothingItemInstance; class UItemDefinition;
/**
* Type-agnostic save record for any UItemInstance.
* Per-type mutable state lives in State as an FItemInstanceState subclass
* (e.g. FClothingInstanceState), so new item types add no fields here.
*/
USTRUCT() USTRUCT()
struct NAKEDDESIRE_API FItemSaveRecord struct NAKEDDESIRE_API FItemSaveRecord
{ {
@@ -14,25 +20,16 @@ struct NAKEDDESIRE_API FItemSaveRecord
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
FGuid InstanceId; FGuid InstanceId;
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
TSoftObjectPtr<UClothingItem> Definition; TSoftObjectPtr<UItemDefinition> Definition;
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
float Condition = 1.0f; FInstancedStruct State;
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
FGuid ParentId; FGuid ParentId;
UPROPERTY(SaveGame) UPROPERTY(SaveGame)
FTransform WorldTransform; FTransform WorldTransform;
};
void Init(const UClothingItemInstance* ClothingItemInstance);
};
inline void FItemSaveRecord::Init(const UClothingItemInstance* ClothingItemInstance)
{
InstanceId = ClothingItemInstance->GetInstanceId();
Definition = ClothingItemInstance->GetClothingItem();
Condition = ClothingItemInstance->Condition;
}
@@ -6,8 +6,8 @@
#include "GlobalSaveGameData.h" #include "GlobalSaveGameData.h"
#include "ItemSaveRecord.h" #include "ItemSaveRecord.h"
#include "StartingSaveData.h" #include "StartingSaveData.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Items/ItemInstance.h"
#include "NakedDesire/Global/NakedDesireGameInstance.h" #include "NakedDesire/Global/NakedDesireGameInstance.h"
void USaveSubsystem::LoadGame(const FString& SlotName) void USaveSubsystem::LoadGame(const FString& SlotName)
@@ -62,14 +62,15 @@ void USaveSubsystem::PopulateStartingData(UGlobalSaveGameData* Save) const
if (!GameInstance || !GameInstance->StartingSaveData) if (!GameInstance || !GameInstance->StartingSaveData)
return; return;
for (UClothingItem* ClothingDef : GameInstance->StartingSaveData->StartingClothing) for (UItemDefinition* Definition : GameInstance->StartingSaveData->StartingItems)
{ {
if (!ClothingDef) if (!Definition)
continue;
UItemInstance* Instance = Definition->CreateInstance(Save);
if (!Instance)
continue; continue;
// TODO: Refactor. Skip converting to ClothingItemInstance
UClothingItemInstance* Instance = NewObject<UClothingItemInstance>(Save);
Instance->Init(ClothingDef);
Save->AddEquippedItem(Instance); Save->AddEquippedItem(Instance);
} }
} }
@@ -6,7 +6,7 @@
#include "Engine/DataAsset.h" #include "Engine/DataAsset.h"
#include "StartingSaveData.generated.h" #include "StartingSaveData.generated.h"
class UClothingItem; class UItemDefinition;
UCLASS() UCLASS()
class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset
@@ -15,5 +15,5 @@ class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset
public: public:
UPROPERTY(EditDefaultsOnly, Category = "Starting State") UPROPERTY(EditDefaultsOnly, Category = "Starting State")
TArray<TObjectPtr<UClothingItem>> StartingClothing; TArray<TObjectPtr<UItemDefinition>> StartingItems;
}; };
@@ -3,7 +3,7 @@
#include "EquipmentSlotWidget.h" #include "EquipmentSlotWidget.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Clothing/ClothingSlotsData.h" #include "NakedDesire/Clothing/ClothingSlotsData.h"
@@ -13,7 +13,7 @@ void UEquipmentSlotWidget::SetItem(UClothingItemInstance* InItem)
{ {
ClothingItemInstance = InItem; ClothingItemInstance = InItem;
IconImage->SetBrushFromTexture(InItem->GetClothingItem()->Icon); IconImage->SetBrushFromTexture(InItem->GetClothingItemDefinition()->Icon);
IconImage->SetVisibility(ESlateVisibility::SelfHitTestInvisible); IconImage->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
PlaceholderImage->SetVisibility(ESlateVisibility::Hidden); PlaceholderImage->SetVisibility(ESlateVisibility::Hidden);
SetIsEnabled(true); SetIsEnabled(true);
@@ -45,7 +45,7 @@ void UEquipmentSlotWidget::NativePreConstruct()
void UEquipmentSlotWidget::OnClothingEquip(UClothingItemInstance* InClothingItemInstance) void UEquipmentSlotWidget::OnClothingEquip(UClothingItemInstance* InClothingItemInstance)
{ {
if (InClothingItemInstance->GetClothingItem()->SlotType != SlotType) if (InClothingItemInstance->GetClothingItemDefinition()->SlotType != SlotType)
return; return;
SetItem(InClothingItemInstance); SetItem(InClothingItemInstance);
@@ -53,7 +53,7 @@ void UEquipmentSlotWidget::OnClothingEquip(UClothingItemInstance* InClothingItem
void UEquipmentSlotWidget::OnClothingUnequip(UClothingItemInstance* InClothingItemInstance) void UEquipmentSlotWidget::OnClothingUnequip(UClothingItemInstance* InClothingItemInstance)
{ {
if (InClothingItemInstance->GetClothingItem()->SlotType != SlotType) if (InClothingItemInstance->GetClothingItemDefinition()->SlotType != SlotType)
return; return;
ClearItem(); ClearItem();