132 lines
3.6 KiB
C++
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;
|
|
} |