// © 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(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(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(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()) { 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()) { 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()) { 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