Files
Naked-Desire/Source/NakedDesire/Stats/StatsManager.cpp
T
2026-06-01 00:27:56 +03:00

158 lines
4.1 KiB
C++

// © 2025 Naked People Team. All Rights Reserved.
#include "StatsManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
namespace
{
// Max raw exposure one observer can read at once: Boobs + Ass + Genitals fully exposed, equal
// weights (GDD §7.1 — per-part weighting is uniform for now). Normalizes per-observer exposure
// to [0,1] so EmbarrassmentGainRate reads as "gain/sec at full exposure, single close observer".
constexpr float MaxObservedExposure = 3.0f;
}
UStatsManager::UStatsManager()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickInterval = 1.0f;
}
void UStatsManager::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ANakedDesireCharacter>(GetOwner());
EmbarrassmentUpdate.Broadcast(Embarrassment, MaxEmbarrassment);
StaminaUpdate.Broadcast(Stamina, MaxStamina);
EnergyUpdate.Broadcast(Energy, MaxEnergy);
}
void UStatsManager::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
DecreaseEnergy(0.9f);
const float ExposureRate = ComputeObservedExposureRate();
if (ExposureRate > 0.0f)
IncreaseEmbarrassment(EmbarrassmentGainRate * ExposureRate * DeltaTime);
else
DecreaseEmbarrassment(EmbarrassmentDecayRate * DeltaTime);
}
float UStatsManager::ComputeObservedExposureRate()
{
if (!OwnerCharacter)
return 0.0f;
// Sum each active observer's normalized exposure, re-traced live so redressing / exposing
// updates the rate immediately even while the observer set is unchanged.
float SumNormalizedExposure = 0.0f;
int32 ActiveObservers = 0;
for (int32 i = Observers.Num() - 1; i >= 0; --i)
{
AActor* Observer = Observers[i].Get();
if (!Observer)
{
Observers.RemoveAtSwap(i);
continue;
}
const float Exposure = OwnerCharacter->GetObservedExposureFrom(Observer->GetActorLocation(), Observer);
if (Exposure > 0.0f)
{
SumNormalizedExposure += Exposure / MaxObservedExposure;
++ActiveObservers;
}
}
if (ActiveObservers == 0)
return 0.0f;
// Mean per-observer exposure, then a saturating crowd multiplier (diminishing returns).
const float MeanExposure = SumNormalizedExposure / ActiveObservers;
const float DensityMultiplier = 1.0f + ObserverDensityScale * FMath::Loge(static_cast<float>(ActiveObservers));
return MeanExposure * DensityMultiplier;
}
void UStatsManager::Init(UClothingManager* InClothingManager)
{
ClothingManager = InClothingManager;
}
void UStatsManager::SetObserved(const bool bObserved, AActor* Observer)
{
if (!Observer)
return;
bool bChanged = false;
if (bObserved)
{
if (!Observers.Contains(Observer))
{
Observers.Add(Observer);
bChanged = true;
}
}
else
{
bChanged = Observers.Remove(Observer) > 0;
}
if (bChanged)
OnObserversChanged.Broadcast();
}
int32 UStatsManager::GetObserverCount() const
{
int32 Count = 0;
for (const TWeakObjectPtr<AActor>& Observer : Observers)
{
if (Observer.IsValid())
++Count;
}
return Count;
}
void UStatsManager::IncreaseEmbarrassment(const float Amount)
{
Embarrassment = FMath::Clamp(Embarrassment + Amount, 0, MaxEmbarrassment);
// Embarrassment-max session loss is handled by USessionManagerSubsystem, which
// subscribes to EmbarrassmentUpdate (GDD §4.4). Broadcast is the integration point.
EmbarrassmentUpdate.Broadcast(Embarrassment, MaxEmbarrassment);
}
void UStatsManager::DecreaseEmbarrassment(const float Amount)
{
Embarrassment = FMath::Clamp(Embarrassment - Amount, 0, MaxEmbarrassment);
EmbarrassmentUpdate.Broadcast(Embarrassment, MaxEmbarrassment);
}
void UStatsManager::DecreaseStamina(const float Amount)
{
Stamina = FMath::Clamp(Stamina - Amount, 0, MaxStamina);
StaminaUpdate.Broadcast(Stamina, MaxStamina);
}
void UStatsManager::IncreaseStamina(const float Amount)
{
Stamina = FMath::Clamp(Stamina + Amount, 0, MaxStamina);
StaminaUpdate.Broadcast(Stamina, MaxStamina);
}
void UStatsManager::DecreaseEnergy(const float Amount)
{
Energy = FMath::Clamp(Energy - Amount, 0, MaxEnergy);
EnergyUpdate.Broadcast(Energy, MaxEnergy);
}
void UStatsManager::RestoreEnergy()
{
Energy = MaxEnergy;
EnergyUpdate.Broadcast(Energy, MaxEnergy);
}