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
@@ -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 "ClothingSlotType.h"
#include "Engine/DataAsset.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Progression/ProgressionPath.h"
#include "ClothingItem.generated.h"
#include "ClothingItemDefinition.generated.h"
USTRUCT(BlueprintType)
struct NAKEDDESIRE_API FBodyPartCoverage
@@ -68,17 +69,13 @@ struct NAKEDDESIRE_API FGarmentContainerSlot
};
UCLASS(BlueprintType)
class NAKEDDESIRE_API UClothingItem : public UPrimaryDataAsset
class NAKEDDESIRE_API UClothingItemDefinition : public UItemDefinition
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText Name;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UTexture2D> Icon;
virtual TSubclassOf<UItemInstance> GetInstanceClass() const override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EClothingSlotType SlotType;
@@ -88,9 +85,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TMap<FName, UMaterialInstance*> Materials;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UStaticMesh* StaticMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int BasePrice;
@@ -109,9 +103,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<EBodyPart> CanExpose;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Coverage = 1.0f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FGarmentContainerSlot> ContainerSlots;
@@ -1,27 +1,24 @@
#include "ClothingItemInstance.h"
#include "ClothingItem.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "ClothingItemDefinition.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,
const float InCondition, const FGuid InInstanceId)
void UClothingItemInstance::CaptureState(FInstancedStruct& OutState) const
{
this->ClothingItem = InClothingItem;
this->StoredItems = InStoredItems;
this->Condition = InCondition;
this->InstanceId = InInstanceId;
FClothingInstanceState ClothingState;
ClothingState.Condition = Condition;
OutState.InitializeAs<FClothingInstanceState>(ClothingState);
}
UClothingItemInstance* UClothingItemInstance::CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord)
void UClothingItemInstance::ApplyState(const FInstancedStruct& InState)
{
UClothingItemInstance* NewItemInstance = NewObject<UClothingItemInstance>(Outer);
NewItemInstance->Setup(ItemSaveRecord.Definition.Get(), {}, ItemSaveRecord.Condition, ItemSaveRecord.InstanceId);
return NewItemInstance;
}
if (const FClothingInstanceState* ClothingState = InState.GetPtr<FClothingInstanceState>())
{
Condition = ClothingState->Condition;
}
}
@@ -1,32 +1,39 @@
#pragma once
#include "CoreMinimal.h"
#include "ClothingItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "ClothingItemInstance.generated.h"
struct FItemSaveRecord;
class UClothingItem;
class UClothingItemDefinition;
/** Per-instance mutable state for clothing. */
USTRUCT()
struct FClothingInstanceState : public FItemInstanceState
{
GENERATED_BODY()
UPROPERTY(SaveGame)
float Condition = 1.0f;
};
UCLASS(BlueprintType)
class NAKEDDESIRE_API UClothingItemInstance : public UItemInstance
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
float Condition = 1.0f;
UClothingItem* GetClothingItem() const { return ClothingItem; }
void Init(UClothingItem* InClothingItem);
void Setup(UClothingItem* InClothingItem, const TArray<UItemInstance*>& InStoredItems, float InCondition, FGuid InInstanceId);
static UClothingItemInstance* CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord);
UClothingItemDefinition* GetClothingItemDefinition() const { return Cast<UClothingItemDefinition>(ItemDefinition); }
void Init(UClothingItemDefinition* InClothingItem);
protected:
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
TObjectPtr<UClothingItem> ClothingItem;
virtual void CaptureState(FInstancedStruct& OutState) const override;
virtual void ApplyState(const FInstancedStruct& InState) override;
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
TArray<TObjectPtr<UItemInstance>> StoredItems;
};
};
+15 -12
View File
@@ -2,7 +2,7 @@
#include "ClothingManager.h"
#include "ClothingItem.h"
#include "ClothingItemDefinition.h"
#include "ClothingItemInstance.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
@@ -51,7 +51,7 @@ bool UClothingManager::IsBodyPartExposed(const EBodyPart BodyPart)
{
for (const auto& [Key, Value] : EquippedClothing)
{
if (Value->GetClothingItem()->HiddenBodyParts.Contains(BodyPart))
if (Value->GetClothingItemDefinition()->HiddenBodyParts.Contains(BodyPart))
return false;
}
@@ -63,7 +63,10 @@ void UClothingManager::HydrateClothing()
USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
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);
}
}
@@ -75,7 +78,7 @@ float UClothingManager::GetHeelHeight()
const UClothingItemInstance* Footwear = EquippedClothing[EClothingSlotType::Footwear];
return Footwear->GetClothingItem()->ShoesOffset;
return Footwear->GetClothingItemDefinition()->ShoesOffset;
}
USkeletalMeshComponent* UClothingManager::GetMeshComponent(const EClothingSlotType SlotType) const
@@ -135,25 +138,25 @@ void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance
if (!ClothingItemInstance)
return;
const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItem()->SlotType;
const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh);
if (!ClothingItemInstance->GetClothingItem()->Materials.IsEmpty())
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
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);
}
}
SetClothingSlotItem(ClothingSlotType, ClothingItemInstance);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose)
if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{
MeshComponent->SetLeaderPoseComponent(Cast<ACharacter>(GetOwner())->GetMesh());
}
const UClothingItem* ClothingItem = ClothingItemInstance->GetClothingItem();
const UClothingItemDefinition* ClothingItem = ClothingItemInstance->GetClothingItemDefinition();
if (ClothingItem->SlotType == EClothingSlotType::Bodysuit)
{
DropClothing(EClothingSlotType::Top);
@@ -174,7 +177,7 @@ void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance
void UClothingManager::TakeClothing(UClothingItemInstance* ClothingItemInstance)
{
const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItem()->SlotType;
const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
if (EquippedClothing.Contains(SlotType))
{
DropClothing(SlotType);
@@ -200,7 +203,7 @@ UClothingItemInstance* UClothingManager::RemoveClothing(const EClothingSlotType
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
MeshComponent->SetSkeletalMesh(nullptr);
if (ExistingItem->GetClothingItem()->UseLeaderPose)
if (ExistingItem->GetClothingItemDefinition()->UseLeaderPose)
{
MeshComponent->SetLeaderPoseComponent(nullptr);
}
@@ -2,7 +2,7 @@
#include "NakedDesireGameMode.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Interactables/ItemPickup.h"
#include "UObject/ConstructorHelpers.h"
@@ -34,10 +34,10 @@ void ANakedDesireGameMode::BuyItem(UClothingItemInstance* ClothingItemInstance)
return;
}
if (SaveGame->Money < ClothingItemInstance->GetClothingItem()->BasePrice)
if (SaveGame->Money < ClothingItemInstance->GetClothingItemDefinition()->BasePrice)
return;
SaveGame->Money -= ClothingItemInstance->GetClothingItem()->BasePrice;
SaveGame->Money -= ClothingItemInstance->GetClothingItemDefinition()->BasePrice;
Wardrobe->AddItem(ClothingItemInstance);
}
@@ -71,12 +71,12 @@ void ANakedDesireGameMode::BeginPlay()
}
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);
NewItemInstance->Init(Item.Definition.Get());
NewItemInstance->Condition = Item.Condition;
NewItemInstance->SetInstanceId(Item.InstanceId);
UClothingItemInstance* NewItemInstance = Cast<UClothingItemInstance>(UItemInstance::CreateFromRecord(this, Item));
if (!NewItemInstance)
continue;
AItemPickup* NewItemPickup = GetWorld()->SpawnActor<AItemPickup>(ItemPickupClass, Item.WorldTransform);
NewItemPickup->SetItem(NewItemInstance);
}
@@ -5,7 +5,7 @@
#include "Components/BoxComponent.h"
#include "Components/WidgetComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
@@ -71,7 +71,14 @@ void AItemPickup::SetItem(UClothingItemInstance* 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)
@@ -25,7 +25,6 @@ public:
virtual void ShowInteractionFocusHint_Implementation() override;
virtual void ShowInteractionProximityHint_Implementation() override;
void SetItem(UClothingItemInstance* InItem);
UClothingItemInstance* GetItem() const { return ClothingItemInstance; }
@@ -3,6 +3,7 @@
#include "Wardrobe.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"
@@ -29,7 +30,10 @@ void AWardrobe::BeginPlay()
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);
}
}
@@ -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 "ItemDefinition.h"
#include "StructUtils/InstancedStruct.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
void UItemInstance::PostInitProperties()
{
Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject | RF_NeedLoad))
return;
if (!InstanceId.IsValid())
@@ -20,3 +24,37 @@ void UItemInstance::SetInstanceId(FGuid 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 "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)
class NAKEDDESIRE_API UItemInstance : public UObject
{
GENERATED_BODY()
public:
virtual void PostInitProperties() override;
virtual void PostDuplicate(EDuplicateMode::Type DuplicateMode) override;
FGuid GetInstanceId() const { return InstanceId; }
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:
/** 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"))
FGuid InstanceId;
};
UPROPERTY()
TObjectPtr<UItemDefinition> ItemDefinition;
};
@@ -1 +1,20 @@
#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 "ItemInstance.h"
#include "SexToyItem.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()
class NAKEDDESIRE_API USexToyInstance : public UItemInstance
{
GENERATED_BODY()
public:
USexToyItem* GetSexToyItem() const { return SexToyItem; }
private:
UPROPERTY()
TObjectPtr<USexToyItem> SexToyItem;
};
USexToyItem* GetSexToyItem() const { return Cast<USexToyItem>(ItemDefinition); }
protected:
virtual void CaptureState(FInstancedStruct& OutState) const override;
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 "SexToyInstance.h"
TSubclassOf<UItemInstance> USexToyItem::GetInstanceClass() const
{
return USexToyInstance::StaticClass();
}
+5 -3
View File
@@ -1,16 +1,18 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "NakedDesire/Clothing/ClothingSlotType.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "SexToyItem.generated.h"
UCLASS()
class NAKEDDESIRE_API USexToyItem : public UPrimaryDataAsset
class NAKEDDESIRE_API USexToyItem : public UItemDefinition
{
GENERATED_BODY()
public:
virtual TSubclassOf<UItemInstance> GetInstanceClass() const override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sex Toy")
float LustModifier = 0.0f;
@@ -2,7 +2,7 @@
#include "EquipClothingRestriction.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
@@ -65,9 +65,9 @@ FText UEquipClothingRestriction::GetDescription() const
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;
if (IsTargetClothing)
{
@@ -77,9 +77,9 @@ void UEquipClothingRestriction::OnClothingEquipped(UClothingItemInstance* Clothi
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;
if (IsTargetClothing)
{
@@ -7,7 +7,7 @@
#include "EquipClothingRestriction.generated.h"
class UClothingItemInstance;
class UClothingItem;
class UClothingItemDefinition;
UCLASS(EditInlineNew)
class NAKEDDESIRE_API UEquipClothingRestriction : public UGoalRestriction
@@ -16,7 +16,7 @@ class NAKEDDESIRE_API UEquipClothingRestriction : public UGoalRestriction
UPROPERTY(EditDefaultsOnly, meta = (ToolTip =
"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:
virtual void Init(ANakedDesireCharacter* PlayerCharacter) override;
@@ -3,7 +3,7 @@
#include "ExposeBodyPartRestriction.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/Clothing/ClothingManager.h"
+1 -1
View File
@@ -11,7 +11,7 @@ public class NakedDesire : ModuleRules
PublicDependencyModuleNames.AddRange(new string[]
{
"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 "Kismet/GameplayStatics.h"
#include "Internationalization/Text.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Global/NakedDesireHUD.h"
@@ -227,15 +227,15 @@ void ANakedDesireCharacter::LogTest()
void ANakedDesireCharacter::OnClothingEquip(UClothingItemInstance* ClothingItemInstance)
{
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Ass))
if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Ass))
{
AnalCensorship->SetVisibility(false);
}
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Genitals))
if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Genitals))
{
VaginaCensorship->SetVisibility(false);
}
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Boobs))
if (ClothingItemInstance->GetClothingItemDefinition()->HiddenBodyParts.Contains(EBodyPart::Boobs))
{
BoobLCensorship->SetVisibility(false);
BoobRCensorship->SetVisibility(false);
@@ -14,7 +14,7 @@
class UInteractionComponent;
class ANakedDesireHUD;
class UClothingItem;
class UClothingItemDefinition;
class UClothingItemInstance;
class UClothingSlotsData;
class UAIPerceptionStimuliSourceComponent;
@@ -1,7 +1,7 @@
#include "PlayerCinematic.h"
#include "NakedDesireCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
@@ -109,16 +109,16 @@ USkeletalMeshComponent* APlayerCinematic::GetMeshByType(const EClothingSlotType
void APlayerCinematic::EquipClothing(const UClothingItemInstance* ClothingItemInstance)
{
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose)
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{
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);
}
@@ -127,7 +127,7 @@ void APlayerCinematic::EquipClothing(const UClothingItemInstance* ClothingItemIn
void APlayerCinematic::UnequipClothing(const UClothingItemInstance* ClothingItemInstance)
{
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType);
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(nullptr);
MeshComponent->SetLeaderPoseComponent(nullptr);
}
+7 -7
View File
@@ -1,7 +1,7 @@
#include "PlayerImpostor.h"
#include "NakedDesireCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
@@ -114,16 +114,16 @@ USkeletalMeshComponent* APlayerImpostor::GetMeshByType(const EClothingSlotType S
void APlayerImpostor::EquipClothing(const UClothingItemInstance* ClothingItemInstance)
{
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItem()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItem()->UseLeaderPose)
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
{
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);
}
@@ -132,7 +132,7 @@ void APlayerImpostor::EquipClothing(const UClothingItemInstance* ClothingItemIns
void APlayerImpostor::UnequipClothing(const UClothingItemInstance* ClothingItemInstance)
{
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItem()->SlotType);
USkeletalMeshComponent* MeshComponent = GetMeshByType(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
MeshComponent->SetSkeletalMesh(nullptr);
MeshComponent->SetLeaderPoseComponent(nullptr);
}
@@ -5,6 +5,7 @@
#include "ItemSaveRecord.h"
#include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "Kismet/GameplayStatics.h"
UGlobalSaveGameData* UGlobalSaveGameData::CreateNewSaveGame()
@@ -33,100 +34,102 @@ bool UGlobalSaveGameData::SaveGame(UGlobalSaveGameData* SaveGameData, const FStr
return UGameplayStatics::SaveGameToSlot(SaveGameData, SlotName, SLOT_PLAYER);
}
FItemSaveRecord UGlobalSaveGameData::AddWardrobeItem(const UClothingItemInstance* ItemInstance)
FItemSaveRecord UGlobalSaveGameData::AddWardrobeItem(const UItemInstance* ItemInstance)
{
FItemSaveRecord NewSaveRecord;
NewSaveRecord.Init(ItemInstance);
FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
WardrobeItems.Push(NewSaveRecord);
return NewSaveRecord;
}
bool UGlobalSaveGameData::UpdateWardrobeItem(UClothingItemInstance* ItemInstance)
bool UGlobalSaveGameData::UpdateWardrobeItem(UItemInstance* ItemInstance)
{
for (auto& ItemSaveRecord : WardrobeItems)
{
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{
ItemSaveRecord.Init(ItemInstance);
ItemSaveRecord = ItemInstance->ToSaveRecord();
return true;
}
}
return false;
}
bool UGlobalSaveGameData::RemoveWardrobeItem(UClothingItemInstance* ItemInstance)
bool UGlobalSaveGameData::RemoveWardrobeItem(UItemInstance* ItemInstance)
{
const int32 RemovedElementsCount = WardrobeItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{
return Item.InstanceId == ItemInstance->GetInstanceId();
});
return RemovedElementsCount > 0;
}
FItemSaveRecord UGlobalSaveGameData::AddEquippedItem(const UClothingItemInstance* ItemInstance)
FItemSaveRecord UGlobalSaveGameData::AddEquippedItem(const UItemInstance* ItemInstance)
{
FItemSaveRecord NewSaveRecord;
NewSaveRecord.Init(ItemInstance);
FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
EquippedItems.Push(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)
{
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{
ItemSaveRecord.Init(ItemInstance);
ItemSaveRecord = ItemInstance->ToSaveRecord();
return true;
}
}
return false;
}
bool UGlobalSaveGameData::RemoveEquippedItem(UClothingItemInstance* ItemInstance)
bool UGlobalSaveGameData::RemoveEquippedItem(UItemInstance* ItemInstance)
{
const int32 RemovedElementsCount = EquippedItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{
return Item.InstanceId == ItemInstance->GetInstanceId();
});
return RemovedElementsCount > 0;
}
FItemSaveRecord UGlobalSaveGameData::AddWorldItem(const UClothingItemInstance* ItemInstance, FTransform Transform)
FItemSaveRecord UGlobalSaveGameData::AddWorldItem(const UItemInstance* ItemInstance, FTransform Transform)
{
FItemSaveRecord NewSaveRecord;
NewSaveRecord.Init(ItemInstance);
FItemSaveRecord NewSaveRecord = ItemInstance->ToSaveRecord();
NewSaveRecord.WorldTransform = Transform;
WorldItems.Push(NewSaveRecord);
return NewSaveRecord;
}
bool UGlobalSaveGameData::UpdateWorldItem(UClothingItemInstance* ItemInstance, FTransform Transform)
bool UGlobalSaveGameData::UpdateWorldItem(UItemInstance* ItemInstance, FTransform Transform)
{
for (auto& ItemSaveRecord : WorldItems)
{
if (ItemSaveRecord.InstanceId == ItemInstance->GetInstanceId())
{
ItemSaveRecord.Init(ItemInstance);
ItemSaveRecord = ItemInstance->ToSaveRecord();
ItemSaveRecord.WorldTransform = Transform;
return true;
}
}
return false;
}
bool UGlobalSaveGameData::RemoveWorldItem(UClothingItemInstance* ItemInstance)
bool UGlobalSaveGameData::RemoveWorldItem(UItemInstance* ItemInstance)
{
const int32 RemovedElementsCount = WorldItems.RemoveAll([ItemInstance](const FItemSaveRecord& Item)
{
return Item.InstanceId == ItemInstance->GetInstanceId();
});
return RemovedElementsCount > 0;
}
}
@@ -8,7 +8,7 @@
#include "ItemSaveRecord.h"
#include "GlobalSaveGameData.generated.h"
class UClothingItemInstance;
class UItemInstance;
UCLASS()
class NAKEDDESIRE_API UGlobalSaveGameData : public USaveGame
@@ -26,19 +26,20 @@ public:
UPROPERTY(SaveGame)
float Money = 0;
FItemSaveRecord AddWardrobeItem(const UClothingItemInstance* ItemInstance);
bool UpdateWardrobeItem(UClothingItemInstance* ItemInstance);
bool RemoveWardrobeItem(UClothingItemInstance* ItemInstance);
FItemSaveRecord AddWardrobeItem(const UItemInstance* ItemInstance);
bool UpdateWardrobeItem(UItemInstance* ItemInstance);
bool RemoveWardrobeItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetWardrobeItems() const { return WardrobeItems; }
FItemSaveRecord AddEquippedItem(const UClothingItemInstance* ItemInstance);
bool UpdateEquippedItem(UClothingItemInstance* ItemInstance);
bool RemoveEquippedItem(UClothingItemInstance* ItemInstance);
FItemSaveRecord AddEquippedItem(const UItemInstance* ItemInstance);
void AddEquippedItem(const FItemSaveRecord& ItemRecord);
bool UpdateEquippedItem(UItemInstance* ItemInstance);
bool RemoveEquippedItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetEquippedItems() const { return EquippedItems; }
FItemSaveRecord AddWorldItem(const UClothingItemInstance* ItemInstance, FTransform Transform);
bool UpdateWorldItem(UClothingItemInstance* ItemInstance, FTransform Transform);
bool RemoveWorldItem(UClothingItemInstance* ItemInstance);
FItemSaveRecord AddWorldItem(const UItemInstance* ItemInstance, FTransform Transform);
bool UpdateWorldItem(UItemInstance* ItemInstance, FTransform Transform);
bool RemoveWorldItem(UItemInstance* ItemInstance);
TArray<FItemSaveRecord> GetWorldItems() const { return WorldItems; }
UPROPERTY(SaveGame)
@@ -50,7 +51,7 @@ public:
private:
UPROPERTY(SaveGame)
TArray<FItemSaveRecord> WardrobeItems;
UPROPERTY(SaveGame)
TArray<FItemSaveRecord> EquippedItems;
+16 -19
View File
@@ -1,12 +1,18 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "StructUtils/InstancedStruct.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()
struct NAKEDDESIRE_API FItemSaveRecord
{
@@ -14,25 +20,16 @@ struct NAKEDDESIRE_API FItemSaveRecord
UPROPERTY(SaveGame)
FGuid InstanceId;
UPROPERTY(SaveGame)
TSoftObjectPtr<UClothingItem> Definition;
TSoftObjectPtr<UItemDefinition> Definition;
UPROPERTY(SaveGame)
float Condition = 1.0f;
FInstancedStruct State;
UPROPERTY(SaveGame)
FGuid ParentId;
UPROPERTY(SaveGame)
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 "ItemSaveRecord.h"
#include "StartingSaveData.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "NakedDesire/Global/NakedDesireGameInstance.h"
void USaveSubsystem::LoadGame(const FString& SlotName)
@@ -62,14 +62,15 @@ void USaveSubsystem::PopulateStartingData(UGlobalSaveGameData* Save) const
if (!GameInstance || !GameInstance->StartingSaveData)
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;
// TODO: Refactor. Skip converting to ClothingItemInstance
UClothingItemInstance* Instance = NewObject<UClothingItemInstance>(Save);
Instance->Init(ClothingDef);
Save->AddEquippedItem(Instance);
}
}
@@ -6,7 +6,7 @@
#include "Engine/DataAsset.h"
#include "StartingSaveData.generated.h"
class UClothingItem;
class UItemDefinition;
UCLASS()
class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset
@@ -15,5 +15,5 @@ class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset
public:
UPROPERTY(EditDefaultsOnly, Category = "Starting State")
TArray<TObjectPtr<UClothingItem>> StartingClothing;
TArray<TObjectPtr<UItemDefinition>> StartingItems;
};
@@ -3,7 +3,7 @@
#include "EquipmentSlotWidget.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Clothing/ClothingSlotsData.h"
@@ -13,7 +13,7 @@ void UEquipmentSlotWidget::SetItem(UClothingItemInstance* InItem)
{
ClothingItemInstance = InItem;
IconImage->SetBrushFromTexture(InItem->GetClothingItem()->Icon);
IconImage->SetBrushFromTexture(InItem->GetClothingItemDefinition()->Icon);
IconImage->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
PlaceholderImage->SetVisibility(ESlateVisibility::Hidden);
SetIsEnabled(true);
@@ -45,7 +45,7 @@ void UEquipmentSlotWidget::NativePreConstruct()
void UEquipmentSlotWidget::OnClothingEquip(UClothingItemInstance* InClothingItemInstance)
{
if (InClothingItemInstance->GetClothingItem()->SlotType != SlotType)
if (InClothingItemInstance->GetClothingItemDefinition()->SlotType != SlotType)
return;
SetItem(InClothingItemInstance);
@@ -53,7 +53,7 @@ void UEquipmentSlotWidget::OnClothingEquip(UClothingItemInstance* InClothingItem
void UEquipmentSlotWidget::OnClothingUnequip(UClothingItemInstance* InClothingItemInstance)
{
if (InClothingItemInstance->GetClothingItem()->SlotType != SlotType)
if (InClothingItemInstance->GetClothingItemDefinition()->SlotType != SlotType)
return;
ClearItem();