Files
Naked-Desire/Source/NakedDesire/Inventory/InventorySubsystem.cpp
T
2026-06-03 15:17:02 +03:00

220 lines
5.7 KiB
C++

// © 2025 Naked People Team. All Rights Reserved.
#include "InventorySubsystem.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "NakedDesire/Phone/PhoneItemDefinition.h"
#include "NakedDesire/Phone/PhoneItemInstance.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"
const TArray<TObjectPtr<UItemInstance>>& UInventorySubsystem::GetWardrobeItems()
{
EnsureHydrated();
return WardrobeItems;
}
UPhoneItemInstance* UInventorySubsystem::GetEquippedPhone()
{
EnsureHydrated();
return EquippedPhone;
}
void UInventorySubsystem::AddToWardrobe(UItemInstance* Item)
{
EnsureHydrated();
StoreItem(Item);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::RemoveFromWardrobe(UItemInstance* Item)
{
EnsureHydrated();
UnstoreItem(Item);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::EquipFromWardrobe(UItemInstance* Item)
{
EnsureHydrated();
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
{
EquipPhone(Phone);
return;
}
UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
if (!Clothing || !Clothing->GetClothingItemDefinition())
return;
UClothingManager* ClothingManager = GetPlayerClothingManager();
if (!ClothingManager)
return;
const EClothingSlotType SlotType = Clothing->GetClothingItemDefinition()->SlotType;
// Return the current occupant of the target slot to the wardrobe (never drop it to the world).
if (UClothingItemInstance* Occupant = ClothingManager->GetSlotClothing(SlotType))
{
ClothingManager->RemoveClothing(SlotType);
StoreItem(Occupant);
}
// Bodysuit exclusion (§6.5): vacate conflicting slots back into the wardrobe.
for (const EClothingSlotType ExcludedSlot : UClothingManager::GetBodysuitExcludedSlots(SlotType))
{
if (UClothingItemInstance* Excluded = ClothingManager->GetSlotClothing(ExcludedSlot))
{
ClothingManager->RemoveClothing(ExcludedSlot);
StoreItem(Excluded);
}
}
UnstoreItem(Clothing);
ClothingManager->EquipSlot(Clothing);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::UnequipToWardrobe(UItemInstance* Item)
{
EnsureHydrated();
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
{
UnequipPhone(Phone);
return;
}
UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
if (!Clothing || !Clothing->GetClothingItemDefinition())
return;
UClothingManager* ClothingManager = GetPlayerClothingManager();
if (!ClothingManager)
return;
UClothingItemInstance* Removed = ClothingManager->RemoveClothing(Clothing->GetClothingItemDefinition()->SlotType);
if (!Removed)
return;
StoreItem(Removed);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::EquipPhone(UPhoneItemInstance* Phone)
{
if (!Phone || Phone == EquippedPhone)
return;
UGlobalSaveGameData* Save = GetSave();
// Hot-swap: the previously equipped phone returns to the wardrobe (§9.9).
if (EquippedPhone)
{
if (Save)
Save->RemoveEquippedItem(EquippedPhone);
StoreItem(EquippedPhone);
}
UnstoreItem(Phone);
EquippedPhone = Phone;
if (Save)
Save->AddEquippedItem(Phone);
OnPhoneChanged.Broadcast(EquippedPhone);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::UnequipPhone(UPhoneItemInstance* Phone)
{
if (!Phone || EquippedPhone != Phone)
return;
if (UGlobalSaveGameData* Save = GetSave())
Save->RemoveEquippedItem(Phone);
EquippedPhone = nullptr;
StoreItem(Phone);
OnPhoneChanged.Broadcast(nullptr);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::EnsureHydrated()
{
UGlobalSaveGameData* Save = GetSave();
if (!Save)
return;
// Re-hydrate only when the underlying save object changes (fresh game / load game).
if (HydratedSave.Get() == Save)
return;
HydratedSave = Save;
WardrobeItems.Reset();
for (const FItemSaveRecord& Record : Save->GetWardrobeItems())
{
if (UItemInstance* Instance = UItemInstance::CreateFromRecord(this, Record))
WardrobeItems.Add(Instance);
}
// Only the phone is hydrated from the equipped bucket here; equipped clothing is owned and
// hydrated by the per-character UClothingManager. Pre-check the definition type so we don't
// mint throwaway clothing instances.
EquippedPhone = nullptr;
for (const FItemSaveRecord& Record : Save->GetEquippedItems())
{
const UItemDefinition* Definition = Record.Definition.LoadSynchronous();
if (!Definition || !Definition->IsA<UPhoneItemDefinition>())
continue;
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(UItemInstance::CreateFromRecord(this, Record)))
{
EquippedPhone = Phone;
break;
}
}
}
void UInventorySubsystem::StoreItem(UItemInstance* Item)
{
if (!Item || WardrobeItems.Contains(Item))
return;
WardrobeItems.Add(Item);
if (UGlobalSaveGameData* Save = GetSave())
Save->AddWardrobeItem(Item);
}
void UInventorySubsystem::UnstoreItem(UItemInstance* Item)
{
if (!Item)
return;
WardrobeItems.Remove(Item);
if (UGlobalSaveGameData* Save = GetSave())
Save->RemoveWardrobeItem(Item);
}
UClothingManager* UInventorySubsystem::GetPlayerClothingManager() const
{
const ANakedDesireCharacter* Player = Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetGameInstance(), 0));
return Player ? Player->ClothingManager : nullptr;
}
UGlobalSaveGameData* UInventorySubsystem::GetSave() const
{
USaveSubsystem* SaveSubsystem = GetGameInstance() ? GetGameInstance()->GetSubsystem<USaveSubsystem>() : nullptr;
return SaveSubsystem ? SaveSubsystem->GetCurrentSave() : nullptr;
}