Files
Naked-Desire/Source/NakedDesire/Global/TimeOfDaySubsystem.h
T

143 lines
4.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "TimeOfDaySubsystem.generated.h"
class UGlobalSaveGameData;
/**
* Day vs. night phase (GDD §10.1). Drives NPC density and embarrassment rate.
* Day is 08:0020:00; everything else is night.
*/
UENUM(BlueprintType)
enum class EDayPhase : uint8
{
Day,
Night
};
/**
* Why the 90-day campaign ended (GDD §3.3). Evicted is the rent-failure loss;
* CampaignComplete fires when the player survives to day 90. The ending screen /
* win-threshold logic (§21 open) lives in BP and reacts to OnCampaignEnded.
*/
UENUM(BlueprintType)
enum class ECampaignEndReason : uint8
{
Evicted,
CampaignComplete
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHourChangedSignature, int32, Hour);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDayChangedSignature, int32, NewDay);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPhaseChangedSignature, EDayPhase, NewPhase);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCampaignEndedSignature, ECampaignEndReason, Reason);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPushTimeToSkySignature, const FTimecode&, Timecode);
/**
* The single authoritative clock (GDD §2.4, §10.1). Owns time-of-day and the
* calendar in C++ and pushes the current time to the UltraDynamicSky actor each
* in-game minute via ANakedDesireGameMode::SetCurrentTime — inverting the old
* BP-drives-time flow. Persists to UGlobalSaveGameData (MinuteOfDay / DaysPassed).
*
* The calendar rolls at 04:00 (DAY_ROLL_HOUR); the day phase flips at 08:00 / 20:00.
* Weekly rent is charged every WEEK_LENGTH_DAYS-th roll; follower income deposits
* each roll. Sleep / time-skips funnel through AdvanceClock so boundaries always fire.
*/
UCLASS()
class NAKEDDESIRE_API UTimeOfDaySubsystem : public UTickableWorldSubsystem
{
GENERATED_BODY()
public:
virtual void OnWorldBeginPlay(UWorld& InWorld) override;
// FTickableGameObject
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override;
virtual bool IsTickable() const override { return bBegunPlay && !IsTemplate(); }
// --- Queries ---
UFUNCTION(BlueprintPure, Category = "Time")
int32 GetDay() const;
UFUNCTION(BlueprintPure, Category = "Time")
float GetMinuteOfDay() const;
UFUNCTION(BlueprintPure, Category = "Time")
int32 GetHour() const;
UFUNCTION(BlueprintPure, Category = "Time")
int32 GetMinute() const;
UFUNCTION(BlueprintPure, Category = "Time")
EDayPhase GetPhase() const;
UFUNCTION(BlueprintPure, Category = "Time")
bool IsDay() const { return GetPhase() == EDayPhase::Day; }
// --- Time control ---
// Advance the clock by a number of in-game minutes, firing every boundary crossed.
UFUNCTION(BlueprintCallable, Category = "Time")
void SkipTime(float Minutes);
// Fast-forward to the next 08:00 (used by the §4.4 holding-cell cutscene).
UFUNCTION(BlueprintCallable, Category = "Time")
void SkipToNextMorning();
// §2.4 sleep: fast-forward 8 hours, restore energy, autosave. (The apartment bed
// also routes outside-clothing loss through USessionLossResolver — see §4.4.)
UFUNCTION(BlueprintCallable, Category = "Time")
void Sleep();
// --- Pause (reason-keyed; the clock runs only while the reason set is empty).
// Note §11.17: the holding-cell cutscene deliberately does NOT pause the clock. ---
UFUNCTION(BlueprintCallable, Category = "Time")
void PushPause(FName Reason);
UFUNCTION(BlueprintCallable, Category = "Time")
void PopPause(FName Reason);
UFUNCTION(BlueprintPure, Category = "Time")
bool IsPaused() const { return PauseReasons.Num() > 0; }
// --- Delegates ---
UPROPERTY(BlueprintAssignable, Category = "Time")
FOnHourChangedSignature OnHourChanged;
UPROPERTY(BlueprintAssignable, Category = "Time")
FOnDayChangedSignature OnDayChanged;
UPROPERTY(BlueprintAssignable, Category = "Time")
FOnPhaseChangedSignature OnPhaseChanged;
UPROPERTY(BlueprintAssignable, Category = "Time")
FOnCampaignEndedSignature OnCampaignEnded;
UPROPERTY(BlueprintAssignable, Category = "Time")
FOnPushTimeToSkySignature OnPushTimeToSky;
private:
void AdvanceClock(double DeltaMinutes);
void HandleHourBoundary(int32 HourOfDay); // 023
void SetPhase(EDayPhase NewPhase);
void AdvanceCalendarDay();
void ChargeWeeklyRent();
void DepositDailyFollowerIncome();
void PushTimeToSky();
static EDayPhase ComputePhase(float InMinuteOfDay);
UGlobalSaveGameData* GetSave() const;
void RestorePlayerEnergy() const;
void Autosave() const;
// Last whole in-game minute pushed to the sky, to throttle the push to ~1/min.
int32 LastPushedMinute = -1;
EDayPhase CurrentPhase = EDayPhase::Day;
TSet<FName> PauseReasons;
bool bBegunPlay = false;
};