// © 2025 Naked People Team. All Rights Reserved. #include "ClothingManager.h" #include "ClothingItemDefinition.h" #include "ClothingItemInstance.h" #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "NakedDesire/Interactables/ItemPickup.h" #include "NakedDesire/Player/NakedDesireCharacter.h" #include "NakedDesire/SaveGame/GlobalSaveGameData.h" #include "NakedDesire/SaveGame/ItemSaveRecord.h" #include "NakedDesire/SaveGame/SaveSubsystem.h" UClothingManager::UClothingManager() { PrimaryComponentTick.bCanEverTick = false; } void UClothingManager::BeginPlay() { Super::BeginPlay(); HydrateClothing(); } void UClothingManager::SetClothingSlotItem(const EClothingSlotType ClothingSlotType, UClothingItemInstance* ClothingItemInstance) { if (ClothingItemInstance) EquippedClothing.Add(ClothingSlotType, ClothingItemInstance); else EquippedClothing.Remove(ClothingSlotType); } TArray UClothingManager::GetEquippedClothing() const { TArray> Items; EquippedClothing.GenerateValueArray(Items); return Items; } UClothingItemInstance* UClothingManager::GetSlotClothing(const EClothingSlotType SlotType) { if (EquippedClothing.Contains(SlotType)) return EquippedClothing[SlotType]; return nullptr; } bool UClothingManager::IsBodyPartExposed(const EBodyPart BodyPart) { for (const auto& [Key, Value] : EquippedClothing) { if (Value->GetClothingItemDefinition()->HiddenBodyParts.Contains(BodyPart)) return false; } return true; } float UClothingManager::GetEffectiveCoverage(const EBodyPart BodyPart) { // GDD §6.3.2: effective coverage of a part = max() across garments that include it. // A garment that doesn't cover the part contributes nothing; nothing covering it = 0. float MaxCoverage = 0.0f; for (const auto& [Slot, Instance] : EquippedClothing) { if (!Instance) continue; const UClothingItemDefinition* Definition = Instance->GetClothingItemDefinition(); if (!Definition) continue; for (const FBodyPartCoverage& Coverage : Definition->CoveredBodyParts) { if (Coverage.BodyPart == BodyPart) MaxCoverage = FMath::Max(MaxCoverage, Coverage.Coverage); } } return MaxCoverage; } float UClothingManager::GetBoobsSupport() { const UClothingItemInstance* UnderwearTop = GetSlotClothing(EClothingSlotType::UnderwearTop); const UClothingItemInstance* Top = GetSlotClothing(EClothingSlotType::Top); const UClothingItemInstance* Bodysuit = GetSlotClothing(EClothingSlotType::Bodysuit); TArray TotalSupports; if (UnderwearTop) TotalSupports.Push(UnderwearTop->GetClothingItemDefinition()->BoobsSupport); if (Top) TotalSupports.Push(Top->GetClothingItemDefinition()->BoobsSupport); if (Bodysuit) TotalSupports.Push(Bodysuit->GetClothingItemDefinition()->BoobsSupport); return FMath::Max(TotalSupports); } void UClothingManager::HydrateClothing() { USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem(); for (const FItemSaveRecord& ItemSaveRecord : SaveSubsystem->GetCurrentSave()->GetEquippedItems()) { UClothingItemInstance* ClothingItemInstance = Cast(UItemInstance::CreateFromRecord(this, ItemSaveRecord)); if (!ClothingItemInstance) continue; PutOnClothing(ClothingItemInstance); } } float UClothingManager::GetHeelHeight() { if (!EquippedClothing.Contains(EClothingSlotType::Footwear)) return 0; const UClothingItemInstance* Footwear = EquippedClothing[EClothingSlotType::Footwear]; return Footwear->GetClothingItemDefinition()->ShoesOffset; } void UClothingManager::SpawnClothingPickup(UClothingItemInstance* ItemInstance) { if (!ItemPickupActor) { UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup ItemPickupActor is not set")); return; } AItemPickup* NewItemPickup = GetWorld()->SpawnActor(ItemPickupActor, GetOwner()->GetActorTransform()); if (!NewItemPickup) { UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup NewItemPickup == nullptr")); return; } USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem(); SaveSubsystem->GetCurrentSave()->AddWorldItem(ItemInstance, NewItemPickup->GetActorTransform()); NewItemPickup->SetItem(ItemInstance); } void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance) { if (!ClothingItemInstance) return; const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType; SetClothingSlotItem(ClothingSlotType, ClothingItemInstance); const UClothingItemDefinition* ClothingItem = ClothingItemInstance->GetClothingItemDefinition(); if (ClothingItem->SlotType == EClothingSlotType::Bodysuit) { DropClothing(EClothingSlotType::Top); DropClothing(EClothingSlotType::Bottom); DropClothing(EClothingSlotType::UnderwearTop); DropClothing(EClothingSlotType::UnderwearBottom); } else if (ClothingItem->SlotType == EClothingSlotType::Top || ClothingItem->SlotType == EClothingSlotType::Bottom || ClothingItem->SlotType == EClothingSlotType::UnderwearTop || ClothingItem->SlotType == EClothingSlotType::UnderwearBottom) { DropClothing(EClothingSlotType::Bodysuit); } OnClothingEquip.Broadcast(ClothingItemInstance); } void UClothingManager::TakeClothing(UClothingItemInstance* ClothingItemInstance) { const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType; if (EquippedClothing.Contains(SlotType)) { DropClothing(SlotType); } SetClothingSlotItem(SlotType, ClothingItemInstance); USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem(); SaveSubsystem->GetCurrentSave()->AddEquippedItem(ClothingItemInstance); PutOnClothing(ClothingItemInstance); } UClothingItemInstance* UClothingManager::RemoveClothing(const EClothingSlotType ClothingSlotType) { if (!EquippedClothing.Contains(ClothingSlotType)) return nullptr; UClothingItemInstance* ExistingItem = EquippedClothing[ClothingSlotType]; SetClothingSlotItem(ClothingSlotType, nullptr); OnClothingUnequip.Broadcast(ExistingItem); USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem(); SaveSubsystem->GetCurrentSave()->RemoveEquippedItem(ExistingItem); return ExistingItem; } void UClothingManager::DropClothing(const EClothingSlotType ClothingType) { UClothingItemInstance* ClothingItemInstance = RemoveClothing(ClothingType); if (!ClothingItemInstance) return; SpawnClothingPickup(ClothingItemInstance); OnClothingDropped.Broadcast(ClothingItemInstance); } bool UClothingManager::IsClothingTypeOn(const EClothingSlotType ClothingType) { return EquippedClothing.Contains(ClothingType); }