Files
Naked-Desire/Source/NakedDesire/Global/SessionLossResolver.cpp
T
2026-06-03 15:16:58 +03:00

132 lines
3.6 KiB
C++

// © 2025 Naked People Team. All Rights Reserved.
#include "SessionLossResolver.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Interactables/ItemPickup.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"
void USessionLossResolver::OnWorldBeginPlay(UWorld& InWorld)
{
Super::OnWorldBeginPlay(InWorld);
if (USessionManagerSubsystem* SessionManager = InWorld.GetSubsystem<USessionManagerSubsystem>())
{
SessionManager->OnSessionEnd.AddDynamic(this, &USessionLossResolver::ResolveLoss);
}
}
void USessionLossResolver::ResolveLoss(ESessionLossCause Cause)
{
// §4.4 precedence: if a chase is in progress, the loss always resolves as
// police capture — the chase wins regardless of what actually fired.
if (const USessionManagerSubsystem* SessionManager = GetWorld()->GetSubsystem<USessionManagerSubsystem>())
{
if (SessionManager->IsPoliceChaseActive())
{
Cause = ESessionLossCause::PoliceCapture;
}
}
// §4.4: equipped clothing is never forcibly removed — the resolver simply never
// touches equipped items.
// TODO(§6.4): when bags exist, a bag placed in the world is lost with its
// contents — mark its world record for deletion here.
bool bWentToHoldingCell = false;
switch (Cause)
{
case ESessionLossCause::SafeReturn:
// Normal session end; nothing is lost. Autosave below.
break;
case ESessionLossCause::EmbarrassmentMax:
// Fade to apartment with no extra cost — no time skip, money, or rep hit.
break;
case ESessionLossCause::EnergyZero:
// Forced sleep cycle: everything left outside the apartment is guaranteed lost.
LoseAllWorldClothing();
break;
case ESessionLossCause::PoliceCapture:
{
UGlobalSaveGameData* Save = GetSave();
if (Save && Save->Money >= PoliceCaptureMoneyPenalty)
{
// Can pay: deduct the penalty and go home.
Save->Money -= PoliceCaptureMoneyPenalty;
}
else
{
// Can't pay: a night in the holding cell settles the debt; no money owed.
bWentToHoldingCell = true;
}
ClearWanted();
}
break;
}
Autosave();
OnSessionLossResolved.Broadcast(Cause, bWentToHoldingCell);
}
void USessionLossResolver::LoseAllWorldClothing()
{
UGlobalSaveGameData* Save = GetSave();
// Loose clothing in the world is represented by AItemPickup actors. Dropping only
// happens outside, and the apartment stores items in the wardrobe (not as pickups),
// so every pickup currently counts as "outside the apartment".
// TODO: once an apartment volume query exists, spare pickups that sit inside it.
TArray<AActor*> Pickups;
UGameplayStatics::GetAllActorsOfClass(this, AItemPickup::StaticClass(), Pickups);
for (AActor* Actor : Pickups)
{
AItemPickup* Pickup = Cast<AItemPickup>(Actor);
if (!Pickup)
continue;
if (Save)
{
if (UClothingItemInstance* Item = Pickup->GetItem())
{
Save->RemoveWorldItem(Item);
}
}
Pickup->Destroy();
}
}
void USessionLossResolver::ClearWanted()
{
// TODO(§7.7 / Phase 6): clear the `wanted` tag once the Wanted attribute exists.
}
void USessionLossResolver::Autosave() const
{
if (const UGameInstance* GameInstance = GetWorld()->GetGameInstance())
{
if (USaveSubsystem* SaveSubsystem = GameInstance->GetSubsystem<USaveSubsystem>())
{
SaveSubsystem->SaveGame();
}
}
}
UGlobalSaveGameData* USessionLossResolver::GetSave() const
{
if (const UGameInstance* GameInstance = GetWorld()->GetGameInstance())
{
if (USaveSubsystem* SaveSubsystem = GameInstance->GetSubsystem<USaveSubsystem>())
{
return SaveSubsystem->GetCurrentSave();
}
}
return nullptr;
}