Added wardrobe

This commit is contained in:
2026-05-31 21:00:55 +03:00
parent 3f51679553
commit 80be766e2c
39 changed files with 787 additions and 111 deletions
@@ -0,0 +1,48 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "EquipmentSlotMenuWidget.h"
#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Inventory/InventorySubsystem.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
void UEquipmentSlotMenuWidget::Init(const EClothingSlotType InSlotType, const bool bInAtWardrobe)
{
SlotType = InSlotType;
bAtWardrobe = bInAtWardrobe;
}
void UEquipmentSlotMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
Player = Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
RemoveClothingBtn->OnClicked.AddUniqueDynamic(this, &UEquipmentSlotMenuWidget::OnRemoveClothingClicked);
}
void UEquipmentSlotMenuWidget::OnRemoveClothingClicked()
{
if (Player && Player->ClothingManager)
{
if (bAtWardrobe)
{
// At the wardrobe the garment is stored, not dropped on the ground.
if (UClothingItemInstance* Equipped = Player->ClothingManager->GetSlotClothing(SlotType))
{
if (UInventorySubsystem* Inventory = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<UInventorySubsystem>())
Inventory->UnequipToWardrobe(Equipped);
}
}
else
{
Player->ClothingManager->DropClothing(SlotType);
}
}
OnActionPerformed.Broadcast();
}
@@ -14,9 +14,10 @@ UCLASS(Abstract)
class NAKEDDESIRE_API UEquipmentSlotMenuWidget : public UCommonUserWidget
{
GENERATED_BODY()
public:
void Init(EClothingSlotType InSlotType);
// bInAtWardrobe makes Remove store the garment in the wardrobe instead of dropping it (§6.5).
void Init(EClothingSlotType InSlotType, bool bInAtWardrobe = false);
EClothingSlotType GetSlotType() const { return SlotType; }
@@ -28,7 +29,7 @@ protected:
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> DropClothingBtn;
TObjectPtr<UButton> RemoveClothingBtn;
UPROPERTY()
TObjectPtr<ANakedDesireCharacter> Player;
@@ -36,6 +37,8 @@ private:
UPROPERTY()
EClothingSlotType SlotType;
bool bAtWardrobe = false;
UFUNCTION()
void OnDropClothingClicked();
};
void OnRemoveClothingClicked();
};
@@ -1,29 +0,0 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "EquipmentSlotMenuWidget.h"
#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
void UEquipmentSlotMenuWidget::Init(const EClothingSlotType InSlotType)
{
SlotType = InSlotType;
}
void UEquipmentSlotMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
Player = Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
DropClothingBtn->OnClicked.AddUniqueDynamic(this, &UEquipmentSlotMenuWidget::OnDropClothingClicked);
}
void UEquipmentSlotMenuWidget::OnDropClothingClicked()
{
Player->ClothingManager->DropClothing(SlotType);
OnActionPerformed.Broadcast();
}
@@ -3,9 +3,9 @@
#include "Components/Button.h"
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "EquipmentPanelWidget.h"
#include "EquipmentSlotMenuWidget.h"
#include "EquipmentSlotWidget.h"
#include "Equipment/EquipmentPanelWidget.h"
#include "Equipment/EquipmentSlotMenuWidget.h"
#include "Equipment/EquipmentSlotWidget.h"
#include "NakedDesire/Global/PlayerPreviewCaptureSubsystem.h"
void UInventoryScreenWidget::NativeOnActivated()
@@ -0,0 +1,71 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "WardrobeInventoryWidget.h"
#include "Components/VerticalBox.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Inventory/InventorySubsystem.h"
#include "WardrobeItemWidget.h"
void UWardrobeInventoryWidget::Init()
{
if (UInventorySubsystem* Inventory = GetInventory())
Inventory->OnWardrobeChanged.AddUniqueDynamic(this, &UWardrobeInventoryWidget::HandleWardrobeChanged);
RenderItems();
}
void UWardrobeInventoryWidget::NativeDestruct()
{
if (UInventorySubsystem* Inventory = GetInventory())
Inventory->OnWardrobeChanged.RemoveDynamic(this, &UWardrobeInventoryWidget::HandleWardrobeChanged);
Super::NativeDestruct();
}
UInventorySubsystem* UWardrobeInventoryWidget::GetInventory() const
{
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(GetWorld());
return GameInstance ? GameInstance->GetSubsystem<UInventorySubsystem>() : nullptr;
}
void UWardrobeInventoryWidget::HandleWardrobeChanged()
{
RenderItems();
}
void UWardrobeInventoryWidget::HandleItemClicked(UWardrobeItemWidget* ItemWidget)
{
if (!ItemWidget)
return;
if (UInventorySubsystem* Inventory = GetInventory())
Inventory->EquipFromWardrobe(ItemWidget->GetClothingItemInstance());
}
void UWardrobeInventoryWidget::RenderItems()
{
if (!ItemsList || !WardrobeItemWidgetClass)
return;
UInventorySubsystem* Inventory = GetInventory();
if (!Inventory)
return;
ItemsList->ClearChildren();
for (UItemInstance* Item : Inventory->GetWardrobeItems())
{
// Wardrobe holds any UItemInstance (phones / toys land here too, §6.5 / §9.9); the
// clothing widget only renders clothing for now — other types get their own widgets later.
UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
if (!Clothing)
continue;
UWardrobeItemWidget* NewItemWidget = CreateWidget<UWardrobeItemWidget>(this, WardrobeItemWidgetClass);
NewItemWidget->SetClothingItemInstance(Clothing);
NewItemWidget->OnItemClicked.BindUObject(this, &UWardrobeInventoryWidget::HandleItemClicked);
ItemsList->AddChild(NewItemWidget);
}
}
@@ -0,0 +1,38 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "WardrobeInventoryWidget.generated.h"
class UVerticalBox;
class UWardrobeItemWidget;
class UInventorySubsystem;
UCLASS(Abstract)
class NAKEDDESIRE_API UWardrobeInventoryWidget : public UCommonUserWidget
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UWardrobeItemWidget> WardrobeItemWidgetClass;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UVerticalBox> ItemsList;
public:
void Init();
protected:
virtual void NativeDestruct() override;
private:
UInventorySubsystem* GetInventory() const;
UFUNCTION()
void HandleWardrobeChanged();
void HandleItemClicked(UWardrobeItemWidget* ItemWidget);
void RenderItems();
};
@@ -0,0 +1,30 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "WardrobeItemWidget.h"
#include "CommonTextBlock.h"
#include "Components/Image.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
void UWardrobeItemWidget::SetClothingItemInstance(UClothingItemInstance* InItemInstance)
{
ClothingItemInstance = InItemInstance;
UpdateVisuals();
}
void UWardrobeItemWidget::NativeOnClicked()
{
Super::NativeOnClicked();
OnItemClicked.ExecuteIfBound(this);
}
void UWardrobeItemWidget::UpdateVisuals()
{
if (!ClothingItemInstance)
return;
IconImage->SetBrushFromTexture(ClothingItemInstance->GetClothingItemDefinition()->Icon);
NameText->SetText(ClothingItemInstance->GetClothingItemDefinition()->Name);
}
@@ -0,0 +1,39 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonButtonBase.h"
#include "WardrobeItemWidget.generated.h"
class UCommonTextBlock;
class UClothingItemInstance;
class UImage;
UCLASS(Abstract)
class NAKEDDESIRE_API UWardrobeItemWidget : public UCommonButtonBase
{
GENERATED_BODY()
UPROPERTY()
TObjectPtr<UClothingItemInstance> ClothingItemInstance;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> IconImage;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UCommonTextBlock> NameText;
public:
void SetClothingItemInstance(UClothingItemInstance* InItemInstance);
UClothingItemInstance* GetClothingItemInstance() const { return ClothingItemInstance; }
DECLARE_DELEGATE_OneParam(FOnWardrobeItemClickedSignature, UWardrobeItemWidget* ItemWidget)
FOnWardrobeItemClickedSignature OnItemClicked;
protected:
virtual void NativeOnClicked() override;
private:
void UpdateVisuals();
};
@@ -0,0 +1,83 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "WardrobeScreenWidget.h"
#include "Components/Button.h"
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "NakedDesire/UI/Inventory/Equipment/EquipmentPanelWidget.h"
#include "NakedDesire/UI/Inventory/Equipment/EquipmentSlotMenuWidget.h"
#include "NakedDesire/UI/Inventory/Equipment/EquipmentSlotWidget.h"
#include "WardrobeInventoryWidget.h"
void UWardrobeScreenWidget::NativeOnActivated()
{
Super::NativeOnActivated();
if (WardrobeInventory)
WardrobeInventory->Init();
if (!EquipmentSlotMenuWidget && EquipmentSlotMenuWidgetClass)
{
// Host the slot menu in the same canvas as the blocker, matching UInventoryScreenWidget.
if (UCanvasPanel* MenuCanvas = Cast<UCanvasPanel>(MenuBlocker->GetParent()))
{
EquipmentSlotMenuWidget = CreateWidget<UEquipmentSlotMenuWidget>(this, EquipmentSlotMenuWidgetClass);
if (UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(MenuCanvas->AddChild(EquipmentSlotMenuWidget)))
{
CanvasSlot->SetAutoSize(true);
CanvasSlot->SetAnchors(FAnchors(0.f, 0.f));
CanvasSlot->SetZOrder(1);
}
EquipmentSlotMenuWidget->OnActionPerformed.AddUObject(this, &UWardrobeScreenWidget::CloseMenu);
}
if (EquipmentPanel)
EquipmentPanel->OnSlotClicked.BindUObject(this, &UWardrobeScreenWidget::HandleSlotClicked);
if (MenuBlocker)
MenuBlocker->OnClicked.AddUniqueDynamic(this, &UWardrobeScreenWidget::CloseMenu);
}
CloseMenu();
}
void UWardrobeScreenWidget::HandleSlotClicked(UEquipmentSlotWidget* SlotWidget)
{
if (!EquipmentSlotMenuWidget || !SlotWidget)
return;
const bool bMenuOpen = EquipmentSlotMenuWidget->GetVisibility() != ESlateVisibility::Collapsed;
if (bMenuOpen && EquipmentSlotMenuWidget->GetSlotType() == SlotWidget->GetSlotType())
{
CloseMenu();
return;
}
// At the wardrobe the "remove" action stores the garment instead of dropping it on the ground.
EquipmentSlotMenuWidget->Init(SlotWidget->GetSlotType(), /*bAtWardrobe=*/true);
if (UCanvasPanelSlot* MenuSlot = Cast<UCanvasPanelSlot>(EquipmentSlotMenuWidget->Slot))
{
if (UCanvasPanel* MenuCanvas = Cast<UCanvasPanel>(MenuBlocker->GetParent()))
{
const FGeometry& SlotGeom = SlotWidget->GetCachedGeometry();
const FVector2D AbsPos = SlotGeom.GetAbsolutePosition() + FVector2D(SlotGeom.GetAbsoluteSize().X, 0.f);
MenuSlot->SetPosition(MenuCanvas->GetCachedGeometry().AbsoluteToLocal(AbsPos));
}
}
MenuBlocker->SetVisibility(ESlateVisibility::Visible);
EquipmentSlotMenuWidget->SetVisibility(ESlateVisibility::Visible);
}
void UWardrobeScreenWidget::CloseMenu()
{
if (EquipmentSlotMenuWidget)
EquipmentSlotMenuWidget->SetVisibility(ESlateVisibility::Collapsed);
if (MenuBlocker)
MenuBlocker->SetVisibility(ESlateVisibility::Collapsed);
}
@@ -0,0 +1,43 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "WardrobeScreenWidget.generated.h"
class UButton;
class UWardrobeInventoryWidget;
class UEquipmentPanelWidget;
class UEquipmentSlotMenuWidget;
class UEquipmentSlotWidget;
UCLASS(Abstract)
class NAKEDDESIRE_API UWardrobeScreenWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
protected:
virtual void NativeOnActivated() override;
private:
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UEquipmentSlotMenuWidget> EquipmentSlotMenuWidgetClass;
UPROPERTY()
TObjectPtr<UEquipmentSlotMenuWidget> EquipmentSlotMenuWidget;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UEquipmentPanelWidget> EquipmentPanel;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UWardrobeInventoryWidget> WardrobeInventory;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> MenuBlocker;
void HandleSlotClicked(UEquipmentSlotWidget* SlotWidget);
UFUNCTION()
void CloseMenu();
};