diff --git a/Content/Blueprints/Data/StartingSaveData.uasset b/Content/Blueprints/Data/StartingSaveData.uasset index db5e08c5..c300aeea 100644 --- a/Content/Blueprints/Data/StartingSaveData.uasset +++ b/Content/Blueprints/Data/StartingSaveData.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c18d2c02f4024ad31c51405e78aa1c125df8ffd39b14df8a4db61cb7cde835d -size 1589 +oid sha256:f921a7c1136774aada952d197ca39c9c39605906f37d22852c64bfc203fe0ca9 +size 1783 diff --git a/Content/Characters/Yumi/Clothing/Casual/Panties/SKM_Casual_Panties.uasset b/Content/Characters/Yumi/Clothing/Casual/Panties/SKM_Casual_Panties.uasset index d5b161af..adda6536 100644 --- a/Content/Characters/Yumi/Clothing/Casual/Panties/SKM_Casual_Panties.uasset +++ b/Content/Characters/Yumi/Clothing/Casual/Panties/SKM_Casual_Panties.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:445c7b939c1fc885ee9996fa6cf65eed780e3329e3feca68d1d6426715de5147 -size 188590 +oid sha256:c8dc7306880ca4b51de424d4761fe86822d34e42b8946388273b534671729e78 +size 190321 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Black.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Black.uasset new file mode 100644 index 00000000..6b1de831 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Black.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e68ff482214b027f06f6136e34facf0cd8214cdef15e9ea9ba3b24603c69ca07 +size 3685 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Blue.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Blue.uasset new file mode 100644 index 00000000..0b82beae --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Blue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c9cca3def24519e3ad64847d031704e5dc52413909d25366faa581faa5f8b14 +size 3673 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Green.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Green.uasset new file mode 100644 index 00000000..bf122891 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:056fd4512393cf73ba3ae3981e36682e2f1bd052e6c5899eb30feabc03d4d1d7 +size 3685 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Red.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Red.uasset new file mode 100644 index 00000000..579f8541 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Red.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2b63f9cef5bc060e1f8c56b1f787d3296dff7603b77c9da8bbbf7d7ba017f05 +size 3661 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Violet.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Violet.uasset new file mode 100644 index 00000000..5cf12996 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_Violet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40b623ebedd598375a2c2550891f1a3bab4500482de99533241e3e474f68c764 +size 3697 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_White.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_White.uasset new file mode 100644 index 00000000..6d3fc1d6 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/Casual_Bra_White.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7172b6d77a7270bdd5d4cbe549d8850f35743a4a755c2b5ad574c0020abb399 +size 3685 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Black.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Black.uasset deleted file mode 100644 index d80f5f2d..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Black.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0f3cd78f72e224a2e2b793629f73db244323a6ee45efce9d66b4bc9b3514589 -size 3237 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Blue.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Blue.uasset deleted file mode 100644 index 243be78b..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Blue.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af87c222f0d4c349bbd0695074514a5baea78c4aad333ae858e3b7605245c18e -size 3368 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Green.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Green.uasset deleted file mode 100644 index ac7da857..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Green.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff9f1ac0132df2e4746e48a433ef6918f64260650f229ecf3e5f93a15f949dc0 -size 3379 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Red.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Red.uasset deleted file mode 100644 index c4ce19ec..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Red.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b71fa8a5b6b1374498c47bdd0cf19c66b6a6c39d47317a794b70c7b62e7e6e7 -size 3357 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Violet.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Violet.uasset deleted file mode 100644 index 5945d034..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_Violet.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89af0f263b2f032355e300c26560b2019461a5089c97a98e3a091cec9a04d40f -size 3390 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_White.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_White.uasset deleted file mode 100644 index e8307ba9..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Bra/DA_Casual_Bra_White.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b03d0899bdec458a95a898ffe18f69f0826adec669ecaebb9f0ddaff4829a148 -size 3375 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Black.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Black.uasset new file mode 100644 index 00000000..d41065d8 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Black.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70629453c17f676893c580df7d5d036b92d0b6d49b76f7ae4f82a8fddb65776a +size 3908 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Blue.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Blue.uasset new file mode 100644 index 00000000..d9a6818d --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Blue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3291648b2951c976918915153fe78543cac93e83aaceeb6b7141240fd6439863 +size 3896 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Green.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Green.uasset new file mode 100644 index 00000000..ef180b88 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49d5fc1945723a1848a74916470cffee9fdb8023bc0fae9bceec3ba460dbf479 +size 3908 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Red.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Red.uasset new file mode 100644 index 00000000..132795b9 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Red.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f63f2d010f21709b5b32fca05a4fd448875616fa70f86b2f2415aff0b787d4f +size 3884 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Violet.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Violet.uasset new file mode 100644 index 00000000..b5c42e03 --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_Violet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a7b5605ba99206d5015db158d0f9c8b7698d9b9df9d4513a9c5375b8d37a040 +size 3920 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_White.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_White.uasset new file mode 100644 index 00000000..587b43ee --- /dev/null +++ b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/Casual_Panties_White.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7078520aa9422e2299141aada48cd4bcc263e5903ce9a1feceb346dd411c946f +size 3908 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Black.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Black.uasset deleted file mode 100644 index 32c3d289..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Black.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79637120c4b7aac418d4c6c48448f8d4306c399274d8fbd044307bd5c2f5dfaa -size 3509 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Blue.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Blue.uasset deleted file mode 100644 index 20e53125..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Blue.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0ca277d947d03e52c0b04e1a4f4950ef3b6f1cf580456c68602948145364b04 -size 3498 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Green.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Green.uasset deleted file mode 100644 index 88d2edb6..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Green.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04df551198c0a6cd15de5b78c67bd5cb525d84a4fa19a6de6cce227c9de24cc4 -size 3509 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Red.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Red.uasset deleted file mode 100644 index 6cfeb8be..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Red.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc996ed2ad20500fd06992f7bc31e876b528fa82e480158d650027ea9736893f -size 3487 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Violet.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Violet.uasset deleted file mode 100644 index e294fd34..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_Violet.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90987914f1802a05db202c0e4ef3746778fdec5a84cf30b56fea2bdd362741cd -size 3520 diff --git a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_White.uasset b/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_White.uasset deleted file mode 100644 index 7571d40f..00000000 --- a/Content/Characters/Yumi/Clothing/Data/Casual/Panties/DA_Casual_Panties_White.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62e7bd3442c733f07ca5c5d151d90bced70743de6635be4fd54e0cfb0384d8d2 -size 3509 diff --git a/NakedDesire.uproject b/NakedDesire.uproject index 10fd0761..b2d996bf 100644 --- a/NakedDesire.uproject +++ b/NakedDesire.uproject @@ -42,6 +42,10 @@ "Win64" ], "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2" + }, + { + "Name": "StructUtils", + "Enabled": true } ] } \ No newline at end of file diff --git a/Source/NakedDesire/Clothing/ClothingItem.cpp b/Source/NakedDesire/Clothing/ClothingItem.cpp deleted file mode 100644 index 48e0e258..00000000 --- a/Source/NakedDesire/Clothing/ClothingItem.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// © 2025 Naked People Team. All Rights Reserved. - - -#include "ClothingItem.h" diff --git a/Source/NakedDesire/Clothing/ClothingItemDefinition.cpp b/Source/NakedDesire/Clothing/ClothingItemDefinition.cpp new file mode 100644 index 00000000..d0eb1406 --- /dev/null +++ b/Source/NakedDesire/Clothing/ClothingItemDefinition.cpp @@ -0,0 +1,11 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "ClothingItemDefinition.h" + +#include "ClothingItemInstance.h" + +TSubclassOf UClothingItemDefinition::GetInstanceClass() const +{ + return UClothingItemInstance::StaticClass(); +} \ No newline at end of file diff --git a/Source/NakedDesire/Clothing/ClothingItem.h b/Source/NakedDesire/Clothing/ClothingItemDefinition.h similarity index 86% rename from Source/NakedDesire/Clothing/ClothingItem.h rename to Source/NakedDesire/Clothing/ClothingItemDefinition.h index 6a6d99fc..aaf43d11 100644 --- a/Source/NakedDesire/Clothing/ClothingItem.h +++ b/Source/NakedDesire/Clothing/ClothingItemDefinition.h @@ -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 Icon; - + virtual TSubclassOf GetInstanceClass() const override; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) EClothingSlotType SlotType; @@ -88,9 +85,6 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) TMap Materials; - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) - UStaticMesh* StaticMesh; - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) int BasePrice; @@ -109,9 +103,6 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) TArray CanExpose; - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) - float Coverage = 1.0f; - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) TArray ContainerSlots; diff --git a/Source/NakedDesire/Clothing/ClothingItemInstance.cpp b/Source/NakedDesire/Clothing/ClothingItemInstance.cpp index e6388fa2..58101b2b 100644 --- a/Source/NakedDesire/Clothing/ClothingItemInstance.cpp +++ b/Source/NakedDesire/Clothing/ClothingItemInstance.cpp @@ -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& 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(ClothingState); } -UClothingItemInstance* UClothingItemInstance::CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord) +void UClothingItemInstance::ApplyState(const FInstancedStruct& InState) { - UClothingItemInstance* NewItemInstance = NewObject(Outer); - - NewItemInstance->Setup(ItemSaveRecord.Definition.Get(), {}, ItemSaveRecord.Condition, ItemSaveRecord.InstanceId); - - return NewItemInstance; -} + if (const FClothingInstanceState* ClothingState = InState.GetPtr()) + { + Condition = ClothingState->Condition; + } +} \ No newline at end of file diff --git a/Source/NakedDesire/Clothing/ClothingItemInstance.h b/Source/NakedDesire/Clothing/ClothingItemInstance.h index 66bb02df..2c2e485a 100644 --- a/Source/NakedDesire/Clothing/ClothingItemInstance.h +++ b/Source/NakedDesire/Clothing/ClothingItemInstance.h @@ -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& InStoredItems, float InCondition, FGuid InInstanceId); - - static UClothingItemInstance* CreateFromSave(UObject* Outer, const FItemSaveRecord& ItemSaveRecord); - + + UClothingItemDefinition* GetClothingItemDefinition() const { return Cast(ItemDefinition); } + + void Init(UClothingItemDefinition* InClothingItem); + protected: - UPROPERTY(BlueprintReadOnly, Category = "Clothing Item") - TObjectPtr ClothingItem; - + virtual void CaptureState(FInstancedStruct& OutState) const override; + virtual void ApplyState(const FInstancedStruct& InState) override; + UPROPERTY(BlueprintReadOnly, Category = "Clothing Item") TArray> StoredItems; -}; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Clothing/ClothingManager.cpp b/Source/NakedDesire/Clothing/ClothingManager.cpp index cb7ecbee..1b462e5a 100644 --- a/Source/NakedDesire/Clothing/ClothingManager.cpp +++ b/Source/NakedDesire/Clothing/ClothingManager.cpp @@ -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(); for (const FItemSaveRecord& ItemSaveRecord : SaveSubsystem->GetCurrentSave()->GetEquippedItems()) { - UClothingItemInstance* ClothingItemInstance = UClothingItemInstance::CreateFromSave(this, ItemSaveRecord); + UClothingItemInstance* ClothingItemInstance = Cast(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& Material : ClothingItemInstance->GetClothingItem()->Materials) + for (const TPair& Material : ClothingItemInstance->GetClothingItemDefinition()->Materials) { MeshComponent->SetMaterialByName(Material.Key, Material.Value); } } SetClothingSlotItem(ClothingSlotType, ClothingItemInstance); - if (ClothingItemInstance->GetClothingItem()->UseLeaderPose) + if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose) { MeshComponent->SetLeaderPoseComponent(Cast(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); } diff --git a/Source/NakedDesire/Global/NakedDesireGameMode.cpp b/Source/NakedDesire/Global/NakedDesireGameMode.cpp index 1201d45c..31f0885d 100644 --- a/Source/NakedDesire/Global/NakedDesireGameMode.cpp +++ b/Source/NakedDesire/Global/NakedDesireGameMode.cpp @@ -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(); - for (const auto& Item : SaveSubsystem->GetCurrentSave()->GetWorldItems()) + for (const FItemSaveRecord& Item : SaveSubsystem->GetCurrentSave()->GetWorldItems()) { - UClothingItemInstance* NewItemInstance = NewObject(this); - NewItemInstance->Init(Item.Definition.Get()); - NewItemInstance->Condition = Item.Condition; - NewItemInstance->SetInstanceId(Item.InstanceId); + UClothingItemInstance* NewItemInstance = Cast(UItemInstance::CreateFromRecord(this, Item)); + if (!NewItemInstance) + continue; + AItemPickup* NewItemPickup = GetWorld()->SpawnActor(ItemPickupClass, Item.WorldTransform); NewItemPickup->SetItem(NewItemInstance); } diff --git a/Source/NakedDesire/Interactables/ItemPickup.cpp b/Source/NakedDesire/Interactables/ItemPickup.cpp index dce2150b..a0ead4fd 100644 --- a/Source/NakedDesire/Interactables/ItemPickup.cpp +++ b/Source/NakedDesire/Interactables/ItemPickup.cpp @@ -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& Material : InItem->GetClothingItemDefinition()->Materials) + { + Mesh->SetMaterialByName(Material.Key, Material.Value); + } + } } void AItemPickup::ApplyOutline(UStaticMeshComponent* InMesh, bool bEnabled, int32 StencilValue) diff --git a/Source/NakedDesire/Interactables/ItemPickup.h b/Source/NakedDesire/Interactables/ItemPickup.h index fc673e4d..ca11727e 100644 --- a/Source/NakedDesire/Interactables/ItemPickup.h +++ b/Source/NakedDesire/Interactables/ItemPickup.h @@ -25,7 +25,6 @@ public: virtual void ShowInteractionFocusHint_Implementation() override; virtual void ShowInteractionProximityHint_Implementation() override; - void SetItem(UClothingItemInstance* InItem); UClothingItemInstance* GetItem() const { return ClothingItemInstance; } diff --git a/Source/NakedDesire/Interactables/Wardrobe.cpp b/Source/NakedDesire/Interactables/Wardrobe.cpp index cd70ee8b..07c17e20 100644 --- a/Source/NakedDesire/Interactables/Wardrobe.cpp +++ b/Source/NakedDesire/Interactables/Wardrobe.cpp @@ -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(UItemInstance::CreateFromRecord(this, ItemSaveRecord)); + if (!NewItemInstance) + continue; + ClothingItems.Push(NewItemInstance); } } diff --git a/Source/NakedDesire/Items/ItemDefinition.cpp b/Source/NakedDesire/Items/ItemDefinition.cpp new file mode 100644 index 00000000..e64b4b8d --- /dev/null +++ b/Source/NakedDesire/Items/ItemDefinition.cpp @@ -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 InstanceClass = GetInstanceClass(); + if (!InstanceClass) + return nullptr; + + UItemInstance* Instance = NewObject(Outer, InstanceClass); + Instance->SetItemDefinition(const_cast(this)); + return Instance; +} \ No newline at end of file diff --git a/Source/NakedDesire/Items/ItemDefinition.h b/Source/NakedDesire/Items/ItemDefinition.h new file mode 100644 index 00000000..e821721b --- /dev/null +++ b/Source/NakedDesire/Items/ItemDefinition.h @@ -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 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 Icon; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + UStaticMesh* StaticMesh; +}; diff --git a/Source/NakedDesire/Items/ItemInstance.cpp b/Source/NakedDesire/Items/ItemInstance.cpp index 3d16d609..1da48431 100644 --- a/Source/NakedDesire/Items/ItemInstance.cpp +++ b/Source/NakedDesire/Items/ItemInstance.cpp @@ -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 InstanceClass = Definition->GetInstanceClass(); + if (!InstanceClass) + { + UE_LOG(LogTemp, Warning, TEXT("UItemInstance::CreateFromRecord: %s returned no instance class"), + *Definition->GetName()); + return nullptr; + } + + UItemInstance* Instance = NewObject(Outer, InstanceClass); + Instance->ItemDefinition = Definition; + Instance->InstanceId = Record.InstanceId.IsValid() ? Record.InstanceId : FGuid::NewGuid(); + Instance->ApplyState(Record.State); + return Instance; +} \ No newline at end of file diff --git a/Source/NakedDesire/Items/ItemInstance.h b/Source/NakedDesire/Items/ItemInstance.h index fb335cb3..993cb1ad 100644 --- a/Source/NakedDesire/Items/ItemInstance.h +++ b/Source/NakedDesire/Items/ItemInstance.h @@ -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 ItemDefinition; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Items/SexToyInstance.cpp b/Source/NakedDesire/Items/SexToyInstance.cpp index 13cbfdc9..00e248a3 100644 --- a/Source/NakedDesire/Items/SexToyInstance.cpp +++ b/Source/NakedDesire/Items/SexToyInstance.cpp @@ -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(State); +} + +void USexToyInstance::ApplyState(const FInstancedStruct& InState) +{ + if (const FSexToyInstanceState* State = InState.GetPtr()) + { + bActive = State->bActive; + Battery = State->Battery; + } +} \ No newline at end of file diff --git a/Source/NakedDesire/Items/SexToyInstance.h b/Source/NakedDesire/Items/SexToyInstance.h index b1e25307..a4717b1c 100644 --- a/Source/NakedDesire/Items/SexToyInstance.h +++ b/Source/NakedDesire/Items/SexToyInstance.h @@ -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 SexToyItem; -}; + USexToyItem* GetSexToyItem() const { return Cast(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; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Items/SexToyItem.cpp b/Source/NakedDesire/Items/SexToyItem.cpp index 3356d339..dfe00584 100644 --- a/Source/NakedDesire/Items/SexToyItem.cpp +++ b/Source/NakedDesire/Items/SexToyItem.cpp @@ -1 +1,8 @@ #include "SexToyItem.h" + +#include "SexToyInstance.h" + +TSubclassOf USexToyItem::GetInstanceClass() const +{ + return USexToyInstance::StaticClass(); +} \ No newline at end of file diff --git a/Source/NakedDesire/Items/SexToyItem.h b/Source/NakedDesire/Items/SexToyItem.h index ad006c0f..1d02ff6d 100644 --- a/Source/NakedDesire/Items/SexToyItem.h +++ b/Source/NakedDesire/Items/SexToyItem.h @@ -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 GetInstanceClass() const override; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sex Toy") float LustModifier = 0.0f; diff --git a/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.cpp b/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.cpp index 300f9d54..92045b0c 100644 --- a/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.cpp +++ b/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.cpp @@ -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) { diff --git a/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.h b/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.h index c7805ab2..5516ac79 100644 --- a/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.h +++ b/Source/NakedDesire/MissionBuilder/Restrictions/EquipClothingRestriction.h @@ -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 ClothingItems; + TArray ClothingItems; public: virtual void Init(ANakedDesireCharacter* PlayerCharacter) override; diff --git a/Source/NakedDesire/MissionBuilder/Restrictions/ExposeBodyPartRestriction.cpp b/Source/NakedDesire/MissionBuilder/Restrictions/ExposeBodyPartRestriction.cpp index 2c293b93..7cf31e97 100644 --- a/Source/NakedDesire/MissionBuilder/Restrictions/ExposeBodyPartRestriction.cpp +++ b/Source/NakedDesire/MissionBuilder/Restrictions/ExposeBodyPartRestriction.cpp @@ -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" diff --git a/Source/NakedDesire/NakedDesire.Build.cs b/Source/NakedDesire/NakedDesire.Build.cs index 1347bf46..1c182888 100644 --- a/Source/NakedDesire/NakedDesire.Build.cs +++ b/Source/NakedDesire/NakedDesire.Build.cs @@ -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" }); } } \ No newline at end of file diff --git a/Source/NakedDesire/Player/NakedDesireCharacter.cpp b/Source/NakedDesire/Player/NakedDesireCharacter.cpp index 439fa50e..75c2dbd7 100644 --- a/Source/NakedDesire/Player/NakedDesireCharacter.cpp +++ b/Source/NakedDesire/Player/NakedDesireCharacter.cpp @@ -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); diff --git a/Source/NakedDesire/Player/NakedDesireCharacter.h b/Source/NakedDesire/Player/NakedDesireCharacter.h index c0f879f4..2412beb1 100644 --- a/Source/NakedDesire/Player/NakedDesireCharacter.h +++ b/Source/NakedDesire/Player/NakedDesireCharacter.h @@ -14,7 +14,7 @@ class UInteractionComponent; class ANakedDesireHUD; -class UClothingItem; +class UClothingItemDefinition; class UClothingItemInstance; class UClothingSlotsData; class UAIPerceptionStimuliSourceComponent; diff --git a/Source/NakedDesire/Player/PlayerCinematic.cpp b/Source/NakedDesire/Player/PlayerCinematic.cpp index 7cf8ca91..d0b5b968 100644 --- a/Source/NakedDesire/Player/PlayerCinematic.cpp +++ b/Source/NakedDesire/Player/PlayerCinematic.cpp @@ -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& Material : ClothingItemInstance->GetClothingItem()->Materials) + for (const TPair& 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); } diff --git a/Source/NakedDesire/Player/PlayerImpostor.cpp b/Source/NakedDesire/Player/PlayerImpostor.cpp index 90d45b53..04337d77 100644 --- a/Source/NakedDesire/Player/PlayerImpostor.cpp +++ b/Source/NakedDesire/Player/PlayerImpostor.cpp @@ -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& Material : ClothingItemInstance->GetClothingItem()->Materials) + for (const TPair& 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); } diff --git a/Source/NakedDesire/SaveGame/GlobalSaveGameData.cpp b/Source/NakedDesire/SaveGame/GlobalSaveGameData.cpp index dffbf9c2..e4022bde 100644 --- a/Source/NakedDesire/SaveGame/GlobalSaveGameData.cpp +++ b/Source/NakedDesire/SaveGame/GlobalSaveGameData.cpp @@ -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; -} +} \ No newline at end of file diff --git a/Source/NakedDesire/SaveGame/GlobalSaveGameData.h b/Source/NakedDesire/SaveGame/GlobalSaveGameData.h index ede10cc1..770f2d7c 100644 --- a/Source/NakedDesire/SaveGame/GlobalSaveGameData.h +++ b/Source/NakedDesire/SaveGame/GlobalSaveGameData.h @@ -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 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 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 GetWorldItems() const { return WorldItems; } UPROPERTY(SaveGame) @@ -50,7 +51,7 @@ public: private: UPROPERTY(SaveGame) TArray WardrobeItems; - + UPROPERTY(SaveGame) TArray EquippedItems; diff --git a/Source/NakedDesire/SaveGame/ItemSaveRecord.h b/Source/NakedDesire/SaveGame/ItemSaveRecord.h index 8a0bbe54..91ee85f4 100644 --- a/Source/NakedDesire/SaveGame/ItemSaveRecord.h +++ b/Source/NakedDesire/SaveGame/ItemSaveRecord.h @@ -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 Definition; - + TSoftObjectPtr 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; -} +}; \ No newline at end of file diff --git a/Source/NakedDesire/SaveGame/SaveSubsystem.cpp b/Source/NakedDesire/SaveGame/SaveSubsystem.cpp index bb3d641f..6e9b0314 100644 --- a/Source/NakedDesire/SaveGame/SaveSubsystem.cpp +++ b/Source/NakedDesire/SaveGame/SaveSubsystem.cpp @@ -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(Save); - Instance->Init(ClothingDef); Save->AddEquippedItem(Instance); } } diff --git a/Source/NakedDesire/SaveGame/StartingSaveData.h b/Source/NakedDesire/SaveGame/StartingSaveData.h index 396ad185..bfb6c46d 100644 --- a/Source/NakedDesire/SaveGame/StartingSaveData.h +++ b/Source/NakedDesire/SaveGame/StartingSaveData.h @@ -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> StartingClothing; + TArray> StartingItems; }; \ No newline at end of file diff --git a/Source/NakedDesire/UI/Inventory/EquipmentSlotWidget.cpp b/Source/NakedDesire/UI/Inventory/EquipmentSlotWidget.cpp index e96e6e26..460667c8 100644 --- a/Source/NakedDesire/UI/Inventory/EquipmentSlotWidget.cpp +++ b/Source/NakedDesire/UI/Inventory/EquipmentSlotWidget.cpp @@ -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();