// Copyright Epic Games, Inc. All Rights Reserved. // UE5 Modular Game Framework — BPC_StateManager (130) // Central State Authority. Single source of truth for "what can the player do right now?" // Manages exclusive action states, upper-body overlay states, action gating, // vital signs (heart rate), and the force-stack pattern for nested overrides. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "GameplayTagContainer.h" #include "BPC_StateManager.generated.h" // Forward declarations class UDA_StateGatingTable; class UBPC_HealthSystem; class UBPC_StressSystem; class UBPC_StaminaSystem; class UBPC_MovementStateSystem; // ============================================================================ // Delegates // ============================================================================ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActionStateChanged, FGameplayTag, NewState, FGameplayTag, OldState); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnOverlayStateChanged, FGameplayTag, NewOverlay, FGameplayTag, OldOverlay); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnVitalSignChanged, FGameplayTag, VitalTag, float, NewValue); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnForceStackPushed, FGameplayTag, ForceState); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnForceStackPopped, FGameplayTag, RestoredState); /** * Result codes for state change requests. * Mirrors the Blueprint E_ActionRequestResult enum. */ UENUM(BlueprintType) enum class EActionRequestResult : uint8 { Granted UMETA(DisplayName = "Granted"), Denied UMETA(DisplayName = "Denied — Gated"), BlockedByForce UMETA(DisplayName = "Blocked — Force Stack Override"), AlreadyActive UMETA(DisplayName = "Already Active"), InvalidState UMETA(DisplayName = "Invalid State Tag"), RequesterNotFound UMETA(DisplayName = "Requester Not Found"), CooldownActive UMETA(DisplayName = "Cooldown Active"), VitalThreshold UMETA(DisplayName = "Vital Threshold Not Met"), }; /** * Heart rate tier for vital sign tracking. */ UENUM(BlueprintType) enum class EHeartRateTier : uint8 { Resting UMETA(DisplayName = "Resting (60-80 BPM)"), Elevated UMETA(DisplayName = "Elevated (80-100 BPM)"), Stressed UMETA(DisplayName = "Stressed (100-130 BPM)"), Panic UMETA(DisplayName = "Panic (130-160 BPM)"), Critical UMETA(DisplayName = "Critical (160+ BPM)"), }; /** * BPC_StateManager — Central State Authority. * * Every system queries IsActionPermitted(Tag) instead of checking other systems * directly. Gating rules are defined in DA_StateGatingTable (37 rules). * In C++, the Chooser Table iteration is native-speed — no BP interpretive overhead. */ UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent)) class FRAMEWORK_API UBPC_StateManager : public UActorComponent { GENERATED_BODY() public: UBPC_StateManager(); // ======================================================================== // Configuration // ======================================================================== /** The gating rules Data Asset. Contains all 37 action rules. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config") TObjectPtr GatingTable; /** Default action state on BeginPlay. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config") FGameplayTag DefaultActionState; /** Default overlay state on BeginPlay. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config") FGameplayTag DefaultOverlayState; // ======================================================================== // Core Query — Hot Path // ======================================================================== /** * Central query: "Can the player perform this action right now?" * Called by EVERY gameplay system per-frame. C++ makes this fast. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|State") bool IsActionPermitted(FGameplayTag ActionTag) const; /** * Request a state change. Returns the result code. * Server-authoritative. */ UFUNCTION(BlueprintCallable, Category = "Framework|State") EActionRequestResult RequestStateChange(FGameplayTag NewState, AActor* Requester); // ======================================================================== // Current State (Read-Only) // ======================================================================== /** Currently active exclusive action state (only one at a time). */ UPROPERTY(BlueprintReadOnly, Category = "Framework|State") FGameplayTag CurrentActionState; /** Currently active upper-body overlay state. */ UPROPERTY(BlueprintReadOnly, Category = "Framework|State") FGameplayTag CurrentOverlayState; // ======================================================================== // Force Stack Pattern (Death, Cutscenes, Void Space) // ======================================================================== /** * Pushes a forced state onto the stack. Overrides all gating. * Example: death overrides everything. */ UFUNCTION(BlueprintCallable, Category = "Framework|State") void ForceStateChange(FGameplayTag ForceState, FString Reason); /** * Pops the top forced state and restores the previous state. * Example: respawn restores the pre-death state. */ UFUNCTION(BlueprintCallable, Category = "Framework|State") void RestorePreviousState(); /** Returns the number of states currently on the force stack. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|State") int32 GetForceStackDepth() const { return ForceStack.Num(); } // ======================================================================== // Vital Signs — Heart Rate // ======================================================================== /** Current heart rate in BPM (smoothed). */ UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals") float HeartRateBPM = 70.0f; /** Current heart rate tier. */ UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals") EHeartRateTier HeartRateTier = EHeartRateTier::Resting; /** Target heart rate (set by stress, stamina, combat). Interpolated toward each tick. */ UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals") float TargetHeartRate = 70.0f; /** Smoothing speed for heart rate interpolation. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Vitals") float HeartRateSmoothSpeed = 2.0f; // ======================================================================== // Event Dispatchers // ======================================================================== UPROPERTY(BlueprintAssignable, Category = "Framework|Events") FOnActionStateChanged OnActionStateChanged; UPROPERTY(BlueprintAssignable, Category = "Framework|Events") FOnOverlayStateChanged OnOverlayStateChanged; UPROPERTY(BlueprintAssignable, Category = "Framework|Events") FOnVitalSignChanged OnVitalSignChanged; UPROPERTY(BlueprintAssignable, Category = "Framework|Events") FOnForceStackPushed OnForceStackPushed; UPROPERTY(BlueprintAssignable, Category = "Framework|Events") FOnForceStackPopped OnForceStackPopped; // ======================================================================== // Overrides // ======================================================================== virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; protected: // ======================================================================== // Internal State // ======================================================================== /** Force stack — array of (State, Reason) pairs. Most recent is active. */ struct FForceStackEntry { FGameplayTag State; FString Reason; }; TArray ForceStack; /** Previous action state before force override (for restore). */ FGameplayTag PreForceActionState; /** Previous overlay state before force override (for restore). */ FGameplayTag PreForceOverlayState; // ======================================================================== // Gating Logic // ======================================================================== /** Check gating rules for a tag against current state. */ bool EvaluateGatingRules(FGameplayTag ActionTag) const; /** Check if any force stack entry blocks this action. */ bool IsBlockedByForceStack(FGameplayTag ActionTag) const; // ======================================================================== // Vital Sign Calculation // ======================================================================== /** Recalculates target heart rate based on stress tier + stamina exhaustion + combat. */ void RecalculateTargetHeartRate(); /** Determines heart rate tier from current BPM. */ static EHeartRateTier GetHeartRateTier(float BPM); // ======================================================================== // Binding References (cached in BeginPlay) // ======================================================================== UPROPERTY() TObjectPtr CachedHealthSystem; UPROPERTY() TObjectPtr CachedStressSystem; UPROPERTY() TObjectPtr CachedStaminaSystem; UPROPERTY() TObjectPtr CachedMovementSystem; };