Setup player preview for equipment panel

This commit is contained in:
2026-05-31 15:33:43 +03:00
parent e70b1d757c
commit f59fa8d948
42 changed files with 823 additions and 318 deletions
+117
View File
@@ -0,0 +1,117 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "Bed.h"
#include "Components/BoxComponent.h"
#include "Components/WidgetComponent.h"
#include "NakedDesire/Global/SessionLossResolver.h"
#include "NakedDesire/Global/SessionManagerSubsystem.h"
#include "NakedDesire/Global/TimeOfDaySubsystem.h"
#define LOCTEXT_NAMESPACE "Bed"
ABed::ABed()
{
ColliderComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("Collider"));
SetRootComponent(ColliderComponent);
// Trace-only: detected by the interaction LOS/focus line traces (ECC_Visibility),
// transparent to movement and physics.
ColliderComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
ColliderComponent->SetCollisionObjectType(ECC_WorldStatic);
ColliderComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
ColliderComponent->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
MeshComponent->SetupAttachment(RootComponent);
// Movement blocker only: stops the character capsule (ECC_Pawn), but is invisible
// to line traces so it never occludes the interaction LOS check.
MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
MeshComponent->SetCollisionObjectType(ECC_WorldStatic);
MeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
MeshComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
InteractionHint = CreateDefaultSubobject<UWidgetComponent>(TEXT("Interaction Hint"));
InteractionHint->SetupAttachment(RootComponent);
}
void ABed::Interact_Implementation(ANakedDesireCharacter* Player)
{
// Cosmetic transition first (BP), then resolve the sleep. The skip is instantaneous,
// so a BP fade simply overlaps it and clears on the new time.
PlaySleepTransition();
DoSleep();
}
bool ABed::CanInteract_Implementation(ANakedDesireCharacter* Player) const
{
// The bed only exists in the apartment, so reaching it means the session has ended.
// Guard defensively against sleeping while a session is still flagged active.
if (const UWorld* World = GetWorld())
{
if (const USessionManagerSubsystem* Session = World->GetSubsystem<USessionManagerSubsystem>())
{
return !Session->IsSessionActive();
}
}
return true;
}
FText ABed::GetInteractionPrompt_Implementation() const
{
return LOCTEXT("SleepPrompt", "Sleep");
}
void ABed::DoSleep()
{
UWorld* World = GetWorld();
if (!World)
return;
// §4.4 step 2: clothing left outside the apartment is guaranteed lost on sleep. The
// loss is owned by USessionLossResolver (all "what gets lost" logic lives there).
if (USessionLossResolver* Resolver = World->GetSubsystem<USessionLossResolver>())
{
Resolver->ResolveSleepLoss();
}
// Time skip + energy restore + phone charge + autosave (§2.4 / §9.8 / §11.19). This
// is the single save for the whole sleep flow — it also persists the loss above.
if (UTimeOfDaySubsystem* Time = World->GetSubsystem<UTimeOfDaySubsystem>())
{
Time->Sleep();
}
}
void ABed::HideInteractionHint_Implementation()
{
ApplyOutline(false, 0);
InteractionHint->SetVisibility(false);
}
void ABed::ShowInteractionFocusHint_Implementation()
{
ApplyOutline(true, 2);
InteractionHint->SetVisibility(true);
}
void ABed::ShowInteractionProximityHint_Implementation()
{
ApplyOutline(true, 1);
InteractionHint->SetVisibility(false);
}
void ABed::BeginPlay()
{
Super::BeginPlay();
InteractionHint->SetVisibility(false);
}
void ABed::ApplyOutline(bool bEnabled, int32 StencilValue)
{
MeshComponent->SetRenderCustomDepth(bEnabled);
MeshComponent->SetCustomDepthStencilValue(StencilValue);
}
#undef LOCTEXT_NAMESPACE
+57
View File
@@ -0,0 +1,57 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "NakedDesire/Interaction/Interactable.h"
#include "Bed.generated.h"
class UBoxComponent;
class UWidgetComponent;
/**
* The apartment bed (GDD §2.4, §4.4 step 2, §11.19). Interacting sleeps: fast-forwards
* 8 hours, restores energy, charges the phone, autosaves (all via UTimeOfDaySubsystem),
* and loses any clothing left outside the apartment. Sleep is the only place the player
* sleeps — there is no sleeping outside.
*/
UCLASS(Blueprintable)
class NAKEDDESIRE_API ABed : public AActor, public IInteractable
{
GENERATED_BODY()
public:
ABed();
virtual void Interact_Implementation(ANakedDesireCharacter* Player) override;
virtual bool CanInteract_Implementation(ANakedDesireCharacter* Player) const override;
virtual FText GetInteractionPrompt_Implementation() const override;
virtual void HideInteractionHint_Implementation() override;
virtual void ShowInteractionFocusHint_Implementation() override;
virtual void ShowInteractionProximityHint_Implementation() override;
protected:
virtual void BeginPlay() override;
// Cosmetic fade / SFX over the (instantaneous) time jump — authored in BP. Fired just
// before the sleep is resolved so the screen can be covered as time advances.
UFUNCTION(BlueprintImplementableEvent, Category = "Sleep")
void PlaySleepTransition();
private:
// Performs the actual sleep transaction (time skip + energy + loss). Split out so a BP
// fade timeline can drive it at the black midpoint if desired.
void DoSleep();
void ApplyOutline(bool bEnabled, int32 StencilValue);
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UStaticMeshComponent> MeshComponent;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UBoxComponent> ColliderComponent;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UWidgetComponent> InteractionHint;
};