Added wardrobe
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
// © 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/ItemInstance.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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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::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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "InventorySubsystem.generated.h"
|
||||
|
||||
class UItemInstance;
|
||||
class UClothingManager;
|
||||
class UGlobalSaveGameData;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWardrobeChangedSignature);
|
||||
|
||||
/**
|
||||
* Runtime owner of the off-body item store (the apartment wardrobe, GDD §6.5 / §10.4).
|
||||
*
|
||||
* Holds live UItemInstance objects mirrored from UGlobalSaveGameData::WardrobeItems and is
|
||||
* the single entry point for moving items between the wardrobe and the body. Equipped items
|
||||
* stay owned by the per-character UClothingManager (it remains the body-state authority and
|
||||
* keeps writing the EquippedItems bucket); this subsystem orchestrates the wardrobe<->equipped
|
||||
* transfer so the two save buckets can never drift. Mutations broadcast OnWardrobeChanged so
|
||||
* the wardrobe UI can refresh without polling.
|
||||
*/
|
||||
UCLASS()
|
||||
class NAKEDDESIRE_API UInventorySubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnWardrobeChangedSignature OnWardrobeChanged;
|
||||
|
||||
/** Live wardrobe instances (off-body, mirrored from the save). */
|
||||
const TArray<TObjectPtr<UItemInstance>>& GetWardrobeItems();
|
||||
|
||||
/** Bring an item into the wardrobe (purchase, world-return). */
|
||||
void AddToWardrobe(UItemInstance* Item);
|
||||
|
||||
/** Remove an item from the wardrobe without equipping it (discard). */
|
||||
void RemoveFromWardrobe(UItemInstance* Item);
|
||||
|
||||
/** Move a stored item onto the body; any displaced garment returns to the wardrobe. */
|
||||
void EquipFromWardrobe(UItemInstance* Item);
|
||||
|
||||
/** Take an equipped garment off the body and store it back in the wardrobe. */
|
||||
void UnequipToWardrobe(UItemInstance* Item);
|
||||
|
||||
private:
|
||||
void EnsureHydrated();
|
||||
void StoreItem(UItemInstance* Item); // live list + save bucket, no broadcast
|
||||
void UnstoreItem(UItemInstance* Item); // live list + save bucket, no broadcast
|
||||
|
||||
UClothingManager* GetPlayerClothingManager() const;
|
||||
UGlobalSaveGameData* GetSave() const;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UItemInstance>> WardrobeItems;
|
||||
|
||||
// The save the live list was built from; re-hydrate when this changes (e.g. load game).
|
||||
TWeakObjectPtr<UGlobalSaveGameData> HydratedSave;
|
||||
};
|
||||
Reference in New Issue
Block a user