// © 2025 Naked People Team. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/Object.h" #include "CommissionObjective.generated.h" class ANakedDesireCharacter; class UCommissionObjective; class UCommissionConstraint; DECLARE_MULTICAST_DELEGATE_OneParam(FOnObjectiveStateChangedSignature, UCommissionObjective*); /** * One typed objective step (GDD §13.4). Replaces the old Goal/Restriction split: a step owns its own * condition and, optionally, a sustained-hold requirement (RequiredHoldSeconds). Subclasses override * IsConditionMet() and call NotifyConditionChanged() when their inputs change; count-style objectives * can instead call MarkSatisfied() directly. The hold timer lives here, so "expose for N seconds" and * "expose once" differ only by a data value, not a class. */ UCLASS(Abstract, EditInlineNew, BlueprintType) class NAKEDDESIRE_API UCommissionObjective : public UObject { GENERATED_BODY() public: FOnObjectiveStateChangedSignature OnStateChanged; void Activate(ANakedDesireCharacter* InPlayer); void Deactivate(); bool IsSatisfied() const { return bSatisfied; } UFUNCTION(BlueprintPure) virtual FText GetDescription() const; // 0..1 for UI. Default: 1 when satisfied, partial while a hold timer runs, else 0. UFUNCTION(BlueprintPure) virtual float GetProgress() const; // Combined "while Y" text for every attached constraint (e.g. "while at Beach, during Night"), // or empty when the objective is unconstrained. For UI to show alongside GetDescription(). UFUNCTION(BlueprintPure) FText GetConstraintsDescription() const; protected: UPROPERTY() ANakedDesireCharacter* Player = nullptr; // Continuous seconds the condition must hold to satisfy. 0 = satisfied the instant it is first met. UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 0)) float RequiredHoldSeconds = 0.0f; // "While Y" gates: the objective only counts progress while every constraint reports IsMet(). UPROPERTY(EditDefaultsOnly, Instanced) TArray> Constraints; virtual void OnActivate() {} virtual void OnDeactivate() {} // Sustained objectives override this; the base ANDs it with the constraints to drive the hold timer. virtual bool IsConditionMet() const { return false; } // True when every attached constraint is currently satisfied (count-style subclasses gate on this too). bool AreConstraintsMet() const; // Re-check the condition and (re)arm or cancel the hold timer accordingly. void NotifyConditionChanged(); void MarkSatisfied(); bool bSatisfied = false; private: void HandleConstraintChanged(); void OnHoldElapsed(); void ClearHoldTimer(); FTimerHandle HoldTimerHandle; float HoldStartTime = 0.0f; };