From 2c4a71b34311071bf1133506a35d1c41a5a3183e Mon Sep 17 00:00:00 2001 From: LongLy Date: Fri, 17 Jan 2025 15:14:51 -0700 Subject: [PATCH] Added GameFeatureActions, HeroComponent, and AbilityCost. --- .../Abilities/OLSAbilityCost.cpp | 28 + .../OLSAbilitySystemComponent.cpp | 2 +- .../Private/Components/OLSHeroComponent.cpp | 517 ++++++++++++++++++ .../Components/OLSPawnExtensionComponent.cpp | 10 + ...meFeatureAction_AddInputContextMapping.cpp | 308 +++++++++++ .../OLSGameFeatureAction_WorldActionBase.cpp | 46 ++ .../AbilitySystem/Abilities/OLSAbilityCost.h | 61 +++ .../Abilities/OLSGameplayAbility.h | 100 +++- .../ols/Public/Components/OLSHeroComponent.h | 94 ++++ .../Components/OLSPawnExtensionComponent.h | 4 + ...GameFeatureAction_AddInputContextMapping.h | 98 ++++ .../OLSGameFeatureAction_WorldActionBase.h | 38 ++ Source/ols/ols.Build.cs | 6 +- 13 files changed, 1308 insertions(+), 4 deletions(-) create mode 100644 Source/ols/Private/AbilitySystem/Abilities/OLSAbilityCost.cpp create mode 100644 Source/ols/Private/Components/OLSHeroComponent.cpp create mode 100644 Source/ols/Private/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.cpp create mode 100644 Source/ols/Private/GameFeatures/OLSGameFeatureAction_WorldActionBase.cpp create mode 100644 Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h create mode 100644 Source/ols/Public/Components/OLSHeroComponent.h create mode 100644 Source/ols/Public/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h create mode 100644 Source/ols/Public/GameFeatures/OLSGameFeatureAction_WorldActionBase.h diff --git a/Source/ols/Private/AbilitySystem/Abilities/OLSAbilityCost.cpp b/Source/ols/Private/AbilitySystem/Abilities/OLSAbilityCost.cpp new file mode 100644 index 0000000..c2247b8 --- /dev/null +++ b/Source/ols/Private/AbilitySystem/Abilities/OLSAbilityCost.cpp @@ -0,0 +1,28 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + + +#include "AbilitySystem/Abilities/OLSAbilityCost.h" + +UOLSAbilityCost::UOLSAbilityCost() +{ +} + +bool UOLSAbilityCost::CheckCost(const UOLSGameplayAbility* ability, + const FGameplayAbilitySpecHandle handle, + const FGameplayAbilityActorInfo* actorInfo, + FGameplayTagContainer* optionalRelevantTags) const +{ + return true; +} + +void UOLSAbilityCost::ApplyCost(const UOLSGameplayAbility* ability, + const FGameplayAbilitySpecHandle handle, + const FGameplayAbilityActorInfo* actorInfo, + const FGameplayAbilityActivationInfo activationInfo) +{ +} + +bool UOLSAbilityCost::ShouldOnlyApplyCostOnHit() const +{ + return bShouldOnlyApplyCostOnHit; +} diff --git a/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp b/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp index bd759a0..4fb8f5b 100644 --- a/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp +++ b/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp @@ -695,7 +695,7 @@ void UOLSAbilitySystemComponent::RemoveDynamicTagGameplayEffect(const FGameplayT { OLS_LOG(LogOLSAbilitySystemComponent, Warning, TEXT("RemoveDynamicTagGameplayEffect: Unable to find gameplay effect [%s]."), - UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName()); + *UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName()); return; } diff --git a/Source/ols/Private/Components/OLSHeroComponent.cpp b/Source/ols/Private/Components/OLSHeroComponent.cpp new file mode 100644 index 0000000..08f324d --- /dev/null +++ b/Source/ols/Private/Components/OLSHeroComponent.cpp @@ -0,0 +1,517 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + + +#include "Components/OLSHeroComponent.h" + +#include "EnhancedInputSubsystems.h" +#include "OLSLog.h" +#include "AbilitySystem/OLSAbilitySystemComponent.h" +#include "Characters/OLSCharacter.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Components/OLSPawnExtensionComponent.h" +#include "DataAssets/OLSPawnDataAsset.h" +#include "GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h" +#include "Player/OLSPlayerController.h" +#include "Player/OLSPlayerState.h" + +#if WITH_EDITOR +#include "Misc/UObjectToken.h" +#endif // WITH_EDITOR + +DEFINE_LOG_CATEGORY(LogOLSHeroComponent); + +namespace OLSHero +{ + static const float LookYawRate = 300.0f; + static const float LookPitchRate = 165.0f; +}; + +const FName UOLSHeroComponent::NAME_BindInputsNow("BindInputsNow"); +const FName UOLSHeroComponent::NAME_ActorFeatureName("Hero"); + +UOLSHeroComponent::UOLSHeroComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer) +{ + //@TODO: implement UOLSCameraMode. + // AbilityCameraMode = nullptr; + bIsReadyToBindInputs = false; +} + +FName UOLSHeroComponent::GetFeatureName() const +{ + // Don't call Super since it does not fit in this. + + return NAME_ActorFeatureName; +} + +bool UOLSHeroComponent::CanChangeInitState(UGameFrameworkComponentManager* manager, + FGameplayTag currentState, + FGameplayTag desiredState) const +{ + // Don't call Super since it does not fit into this. + + check(manager); + + APawn* pawn = GetPawn(); + + //@TODO: implement LyraGameplayTags::InitState_Spawned. + //@TODO: implement LyraGameplayTags::InitState_DataAvailable. + //@TODO: implement LyraGameplayTags::InitState_DataInitialized. + //@TODO: implement LyraGameplayTags::InitState_GameplayReady. + // if (!currentState.IsValid() && desiredState == LyraGameplayTags::InitState_Spawned) + // { + // // As long as we have a real pawn, let us transition + // if (pawn) + // { + // return true; + // } + // } + // else if (currentState == LyraGameplayTags::InitState_Spawned && desiredState == LyraGameplayTags::InitState_DataAvailable) + // { + // // The player state is required. + // if (!GetPlayerState()) + // { + // return false; + // } + // + // // If we're authority or autonomous, we need to wait for a controller with registered ownership of the player state. + // if (pawn->GetLocalRole() != ROLE_SimulatedProxy) + // { + // AController* controller = GetController(); + // + // const bool hasControllerPairedWithPS = (controller != nullptr) && \ + // (controller->PlayerState != nullptr) && \ + // (controller->PlayerState->GetOwner() == controller); + // + // if (!hasControllerPairedWithPS) + // { + // return false; + // } + // } + // + // const bool isLocallyControlled = pawn->IsLocallyControlled(); + // const bool isBot = pawn->IsBotControlled(); + // + // if (isLocallyControlled && !isBot) + // { + // AOLSPlayerController* playerController = GetController(); + // + // // The input component and local player is required when locally controlled. + // if (!pawn->InputComponent || !playerController || !playerController->GetLocalPlayer()) + // { + // return false; + // } + // } + // + // return true; + // } + // else if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized) + // { + // // Wait for player state and extension component + // AOLSPlayerState* playerState = GetPlayerState(); + // + // return playerState && manager->HasFeatureReachedInitState(pawn, UOLSPawnExtensionComponent::NAME_ActorFeatureName, LyraGameplayTags::InitState_DataInitialized); + // } + // else if (currentState == LyraGameplayTags::InitState_DataInitialized && desiredState == LyraGameplayTags::InitState_GameplayReady) + // { + // // TODO add ability initialization checks? + // return true; + // } + + return false; +} + +void UOLSHeroComponent::HandleChangeInitState(UGameFrameworkComponentManager* manager, + FGameplayTag currentState, + FGameplayTag desiredState) +{ + //@TODO: implement LyraGameplayTags::InitState_DataAvailable. + //@TODO: implement LyraGameplayTags::InitState_DataInitialized. + // if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized) + // { + // APawn* pawn = GetPawn(); + // AOLSPlayerState* playerState = GetPlayerState(); + // if (!ensure(pawn && playerState)) + // { + // return; + // } + // + // const UOLSPawnDataAsset* pawnData = nullptr; + // + // if (UOLSPawnExtensionComponent* PawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn)) + // { + // pawnData = PawnExtComp->GetPawnData(); + // + // // The player state holds the persistent data for this player (state that persists across deaths and multiple pawns). + // // The ability system component and attribute sets live on the player state. + // PawnExtComp->InitializeAbilitySystem(playerState->GetOLSAbilitySystemComponent(), playerState); + // } + // + // if (AOLSPlayerController* playerController = GetController()) + // { + // if (pawn->InputComponent) + // { + // InitializePlayerInput(pawn->InputComponent); + // } + // } + // + // //@TODO: implement UOLSCameraMode. + // // Hook up the delegate for all pawns, in case we spectate later + // // if (pawnData) + // // { + // // if (ULyraCameraComponent* CameraComponent = ULyraCameraComponent::FindCameraComponent(pawn)) + // // { + // // CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode); + // // } + // // } + // } +} + +void UOLSHeroComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& params) +{ + // if (params.FeatureName == UOLSPawnExtensionComponent::NAME_ActorFeatureName) + // { + // //@TODO: implement LyraGameplayTags::InitState_DataAvailable. + // if (params.FeatureState == LyraGameplayTags::InitState_DataInitialized) + // { + // // If the extension component says all other components are initialized, try to progress to next state + // CheckDefaultInitialization(); + // } + // } +} + +void UOLSHeroComponent::CheckDefaultInitialization() +{ + //@TODO: implement LyraGameplayTags::InitState_Spawned. + //@TODO: implement LyraGameplayTags::InitState_DataAvailable. + //@TODO: implement LyraGameplayTags::InitState_DataInitialized. + //@TODO: implement LyraGameplayTags::InitState_GameplayReady. + // static const TArray stateChain = { + // LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable, + // LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady + // }; + // + // // This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready + // ContinueInitStateChain(stateChain); +} + +void UOLSHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& owningSpecHandle) +{ + if (AbilityCameraModeOwningSpecHandle == owningSpecHandle) + { + //@TODO: implement UOLSCameraMode. + // AbilityCameraMode = nullptr; + AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle(); + } +} + +void UOLSHeroComponent::AddAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig) +{ + TArray bindHandles; + + const APawn* pawn = GetPawn(); + if (!pawn) + { + return; + } + + const APlayerController* playerController = GetController(); + check(playerController); + + const ULocalPlayer* localPlayer = playerController->GetLocalPlayer(); + check(localPlayer); + + UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem(); + check(subsystem); + + if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn)) + { + //@TODO: Implement UOLSInputComponent. + // UOLSInputComponent* inputComponent = pawn->FindComponentByClass(); + // if (ensureMsgf(inputComponent, + // TEXT( + // "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it." + // ))) + // { + // inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed, + // &ThisClass::Input_AbilityInputTagReleased, /*out*/ bindHandles); + // } + } +} + +void UOLSHeroComponent::RemoveAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig) +{ + //@TODO: Implement me! +} + +bool UOLSHeroComponent::IsReadyToBindInputs() const +{ + return bIsReadyToBindInputs; +} + +void UOLSHeroComponent::OnRegister() +{ + Super::OnRegister(); + + if (!GetPawn()) + { + OLS_LOG(LogOLSHeroComponent, Error, + TEXT( + "This component has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint." + )); + +#if WITH_EDITOR + if (GIsEditor) + { + static const FText message = NSLOCTEXT("OLSHeroComponent", "NotOnPawnError", + "has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint. This will cause a crash if you PIE!"); + static const FName heroMessageLogName = TEXT("OLSHeroComponent"); + + FMessageLog(heroMessageLogName).Error() + ->AddToken(FUObjectToken::Create(this, FText::FromString(GetNameSafe(this)))) + ->AddToken(FTextToken::Create(message)); + + FMessageLog(heroMessageLogName).Open(); + } +#endif + } + else + { + // Register with the init state system early, this will only work if this is a game world + RegisterInitStateFeature(); + } +} + +void UOLSHeroComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Listen for when the pawn extension component changes init state + BindOnActorInitStateChanged(UOLSPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false); + + // Notifies that we are done spawning, then try the rest of initialization + //@TODO: implement LyraGameplayTags::InitState_Spawned. + // ensure(TryToChangeInitState(LyraGameplayTags::InitState_Spawned)); + CheckDefaultInitialization(); +} + +void UOLSHeroComponent::EndPlay(const EEndPlayReason::Type endPlayReason) +{ + UnregisterInitStateFeature(); + + Super::EndPlay(endPlayReason); +} + +void UOLSHeroComponent::InitializePlayerInput(UInputComponent* playerInputComponent) +{ + check(playerInputComponent); + + const APawn* Pawn = GetPawn(); + if (!Pawn) + { + return; + } + + const APlayerController* playerController = GetController(); + check(playerController); + + //@TODO implement UOLSLocalPlayer. + // const UOLSLocalPlayer* localPlayer = Cast(playerController->GetLocalPlayer()); + // check(localPlayer); + + // UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem(); + // check(subsystem); + // + // subsystem->ClearAllMappings(); + + // if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + // { + // if (const UOLSPawnDataAsset* pawnData = pawnExtComp->GetPawnData()) + // { + // //@TODO: Implement UOLSInputConfig + // if (const UOLSInputConfig* inputConfig = pawnData->InputConfig) + // { + // for (const FOLSInputMappingContextAndPriority& mapping : DefaultInputMappings) + // { + // if (UInputMappingContext* imc = mapping.InputMapping.Get()) + // { + // if (mapping.bShouldRegisterWithSettings) + // { + // if (UEnhancedInputUserSettings* settings = subsystem->GetUserSettings()) + // { + // settings->RegisterInputMappingContext(imc); + // } + // + // FModifyContextOptions options = {}; + // options.bIgnoreAllPressedKeysUntilRelease = false; + // // Actually add the config to the local player + // subsystem->AddMappingContext(imc, mapping.Priority, options); + // } + // } + // } + // + // // The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action. + // // If you want this functionality but still want to change your input component class, make it a subclass + // // of the ULyraInputComponent or modify this component accordingly. + // //@TODO: Implement UOLSInputComponent. + // // UOLSInputComponent* inputComponent = Cast(playerInputComponent); + // if (ensureMsgf(inputComponent, + // TEXT( + // "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it." + // ))) + // { + // // Add the key mappings that may have been set by the player + // inputComponent->AddInputMappings(inputConfig, subsystem); + // + // // This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will + // // be triggered directly by these input actions Triggered events. + // TArray BindHandles; + // inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles); + // + // inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false); + // inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false); + // inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false); + // inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false); + // inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false); + // } + // } + // } + // } + + if (ensure(!bIsReadyToBindInputs)) + { + bIsReadyToBindInputs = true; + } + + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(playerController), NAME_BindInputsNow); + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(Pawn), NAME_BindInputsNow); +} + +void UOLSHeroComponent::Input_AbilityInputTagPressed(FGameplayTag inputTag) +{ + if (const APawn* pawn = GetPawn()) + { + if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn)) + { + if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent()) + { + asc->AbilityInputTagPressed(inputTag); + } + } + } +} + +void UOLSHeroComponent::Input_AbilityInputTagReleased(FGameplayTag inputTag) +{ + const APawn* pawn = GetPawn(); + if (!pawn) + { + return; + } + + if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn)) + { + if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent()) + { + asc->AbilityInputTagReleased(inputTag); + } + } +} + +void UOLSHeroComponent::Input_Move(const FInputActionValue& inputActionValue) +{ + APawn* pawn = GetPawn(); + AController* controller = pawn ? pawn->GetController() : nullptr; + + // If the player has attempted to move again then cancel auto running + if (AOLSPlayerController* playerController = Cast(controller)) + { + //@TODO: Should we have this? + // playerController->SetIsAutoRunning(false); + } + + if (controller) + { + const FVector2D value = inputActionValue.Get(); + const FRotator movementRotation(0.0f, controller->GetControlRotation().Yaw, 0.0f); + + if (value.X != 0.0f) + { + const FVector movementDirection = movementRotation.RotateVector(FVector::RightVector); + pawn->AddMovementInput(movementDirection, value.X); + } + + if (value.Y != 0.0f) + { + const FVector movementDirection = movementRotation.RotateVector(FVector::ForwardVector); + pawn->AddMovementInput(movementDirection, value.Y); + } + } +} + +void UOLSHeroComponent::Input_LookMouse(const FInputActionValue& inputActionValue) +{ + APawn* pawn = GetPawn(); + + if (!pawn) + { + return; + } + + const FVector2D value = inputActionValue.Get(); + + if (value.X != 0.0f) + { + pawn->AddControllerYawInput(value.X); + } + + if (value.Y != 0.0f) + { + pawn->AddControllerPitchInput(value.Y); + } +} + +void UOLSHeroComponent::Input_LookStick(const FInputActionValue& inputActionValue) +{ + APawn* pawn = GetPawn(); + + if (!pawn) + { + return; + } + + const FVector2D value = inputActionValue.Get(); + + const UWorld* world = GetWorld(); + check(world); + + if (value.X != 0.0f) + { + pawn->AddControllerYawInput(value.X * OLSHero::LookYawRate * world->GetDeltaSeconds()); + } + + if (value.Y != 0.0f) + { + pawn->AddControllerPitchInput(value.Y * OLSHero::LookPitchRate * world->GetDeltaSeconds()); + } +} + +void UOLSHeroComponent::Input_Crouch(const FInputActionValue& inputActionValue) +{ + if (AOLSCharacter* character = GetPawn()) + { + character->ToggleCrouch(); + } +} + +void UOLSHeroComponent::Input_AutoRun(const FInputActionValue& inputActionValue) +{ + if (APawn* Pawp = GetPawn()) + { + if (AOLSPlayerController* controller = Cast(Pawp->GetController())) + { + //@TODO: Should we have this? + // Toggle auto running + // controller->SetIsAutoRunning(!controller->GetIsAutoRunning()); + } + } +} diff --git a/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp b/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp index f4fb91a..a2b4012 100644 --- a/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp +++ b/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp @@ -7,9 +7,12 @@ #include "AbilitySystem/OLSAbilitySystemComponent.h" #include "Components/GameFrameworkComponentManager.h" #include "DataAssets/OLSPawnDataAsset.h" +#include "Net/UnrealNetwork.h" DEFINE_LOG_CATEGORY(LogOLSPawnExtensionComponent); +const FName UOLSPawnExtensionComponent::NAME_ActorFeatureName("PawnExtension"); + UOLSPawnExtensionComponent::UOLSPawnExtensionComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer) { @@ -22,6 +25,13 @@ UOLSPawnExtensionComponent::UOLSPawnExtensionComponent(const FObjectInitializer& AbilitySystemComponent = nullptr; } +void UOLSPawnExtensionComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UOLSPawnExtensionComponent, PawnData); +} + FName UOLSPawnExtensionComponent::GetFeatureName() const { return NAME_ActorFeatureName; diff --git a/Source/ols/Private/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.cpp b/Source/ols/Private/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.cpp new file mode 100644 index 0000000..d242e78 --- /dev/null +++ b/Source/ols/Private/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.cpp @@ -0,0 +1,308 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + + +#include "GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h" + +#include "UserSettings/EnhancedInputUserSettings.h" +#include "EnhancedInputSubsystems.h" +#include "Systems/OLSAssetManager.h" +#include "Engine/LocalPlayer.h" + +#if WITH_EDITOR +#include "Misc/DataValidation.h" +#endif + +#include "InputMappingContext.h" +#include "OLSLog.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Components/OLSHeroComponent.h" + +DEFINE_LOG_CATEGORY(LogOLSGameFA_AddInputContextMapping); + +#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameFeatureAction_AddInputContextMapping) + +#define LOCTEXT_NAMESPACE "GameFeatures" + +void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureRegistering() +{ + Super::OnGameFeatureRegistering(); + + RegisterInputMappingContexts(); +} + +void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureActivating(FGameFeatureActivatingContext& context) +{ + FPerContextData& activeData = ContextData.FindOrAdd(context); + if (!ensure(activeData.ExtensionRequestHandles.IsEmpty()) || + !ensure(activeData.ControllersAddedTo.IsEmpty())) + { + Reset(activeData); + } + + Super::OnGameFeatureActivating(context); +} + +void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context) +{ + Super::OnGameFeatureDeactivating(context); + + FPerContextData* activeData = ContextData.Find(context); + if (ensure(activeData)) + { + Reset(*activeData); + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureUnregistering() +{ + Super::OnGameFeatureUnregistering(); + + UnregisterInputMappingContexts(); +} + +#if WITH_EDITOR +EDataValidationResult UOLSGameFeatureAction_AddInputContextMapping::IsDataValid(FDataValidationContext& context) const +{ + // Don't call Super since it does not fit in this. + + EDataValidationResult result = CombineDataValidationResults(Super::IsDataValid(context), EDataValidationResult::Valid); + + int32 index = 0; + + for (const FOLSInputMappingContextAndPriority& entry : InputMappings) + { + if (entry.InputMapping.IsNull()) + { + result = EDataValidationResult::Invalid; + context.AddError(FText::Format(LOCTEXT("NullInputMapping", "Null InputMapping at index {0}."), index)); + } + ++index; + } + + return result; +} +#endif + +void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContexts() +{ + RegisterInputContextMappingsForGameInstanceHandle = FWorldDelegates::OnStartGameInstance.AddUObject( + this, &UOLSGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance); + + const TIndirectArray& worldContexts = GEngine->GetWorldContexts(); + for (TIndirectArray::TConstIterator worldContextIterator = worldContexts.CreateConstIterator(); + worldContextIterator; ++worldContextIterator) + { + RegisterInputContextMappingsForGameInstance(worldContextIterator->OwningGameInstance); + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance( + UGameInstance* gameInstance) +{ + if (gameInstance != nullptr && !gameInstance->OnLocalPlayerAddedEvent.IsBoundToObject(this)) + { + gameInstance->OnLocalPlayerAddedEvent.AddUObject( + this, &UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer); + gameInstance->OnLocalPlayerRemovedEvent.AddUObject( + this, &UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer); + + for (TArray::TConstIterator localPlayerIterator = gameInstance->GetLocalPlayerIterator(); + localPlayerIterator; ++localPlayerIterator) + { + RegisterInputMappingContextsForLocalPlayer(*localPlayerIterator); + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer(ULocalPlayer* localPlayer) +{ + if (ensure(localPlayer)) + { + UOLSAssetManager& assetManager = UOLSAssetManager::Get(); + + if (UEnhancedInputLocalPlayerSubsystem* eiSubsystem = ULocalPlayer::GetSubsystem(localPlayer)) + { + if (UEnhancedInputUserSettings* settings = eiSubsystem->GetUserSettings()) + { + for (const FOLSInputMappingContextAndPriority& entry : InputMappings) + { + // Skip entries that don't want to be registered + if (!entry.bShouldRegisterWithSettings) + { + continue; + } + + // Register this IMC with the settings! + if (UInputMappingContext* imc = assetManager.GetAsset(entry.InputMapping)) + { + settings->RegisterInputMappingContext(imc); + } + } + } + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContexts() +{ + FWorldDelegates::OnStartGameInstance.Remove(RegisterInputContextMappingsForGameInstanceHandle); + RegisterInputContextMappingsForGameInstanceHandle.Reset(); + + const TIndirectArray& worldContexts = GEngine->GetWorldContexts(); + for (TIndirectArray::TConstIterator worldContextIterator = worldContexts.CreateConstIterator(); + worldContextIterator; ++worldContextIterator) + { + UnregisterInputContextMappingsForGameInstance(worldContextIterator->OwningGameInstance); + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputContextMappingsForGameInstance( + UGameInstance* gameInstance) +{ + if (gameInstance) + { + gameInstance->OnLocalPlayerAddedEvent.RemoveAll(this); + gameInstance->OnLocalPlayerRemovedEvent.RemoveAll(this); + + for (TArray::TConstIterator localPlayerIterator = gameInstance->GetLocalPlayerIterator(); + localPlayerIterator; ++localPlayerIterator) + { + UnregisterInputMappingContextsForLocalPlayer(*localPlayerIterator); + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer( + ULocalPlayer* localPlayer) +{ + if (ensure(localPlayer)) + { + if (UEnhancedInputLocalPlayerSubsystem* eiSubsystem = ULocalPlayer::GetSubsystem(localPlayer)) + { + if (UEnhancedInputUserSettings* settings = eiSubsystem->GetUserSettings()) + { + for (const FOLSInputMappingContextAndPriority& entry : InputMappings) + { + // Skip entries that don't want to be registered + if (!entry.bShouldRegisterWithSettings) + { + continue; + } + + // Register this IMC with the settings! + if (UInputMappingContext* imc = entry.InputMapping.Get()) + { + settings->UnregisterInputMappingContext(imc); + } + } + } + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::AddToWorld(const FWorldContext& worldContext, + const FGameFeatureStateChangeContext& changeContext) +{ + UWorld* world = worldContext.World(); + UGameInstance* gameInstance = worldContext.OwningGameInstance; + FPerContextData& activeData = ContextData.FindOrAdd(changeContext); + + if (gameInstance && world && world->IsGameWorld()) + { + if (UGameFrameworkComponentManager* componentManager = UGameInstance::GetSubsystem< + UGameFrameworkComponentManager>(gameInstance)) + { + UGameFrameworkComponentManager::FExtensionHandlerDelegate addAbilitiesDelegate = + UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject( + this, &ThisClass::HandleControllerExtension, changeContext); + TSharedPtr extensionRequestHandle = + componentManager->AddExtensionHandler(APlayerController::StaticClass(), addAbilitiesDelegate); + + activeData.ExtensionRequestHandles.Add(extensionRequestHandle); + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::Reset(FPerContextData& activeData) +{ + activeData.ExtensionRequestHandles.Empty(); + + while (!activeData.ControllersAddedTo.IsEmpty()) + { + TWeakObjectPtr controllerPtr = activeData.ControllersAddedTo.Top(); + if (controllerPtr.IsValid()) + { + RemoveInputMapping(controllerPtr.Get(), activeData); + } + else + { + activeData.ControllersAddedTo.Pop(); + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::HandleControllerExtension(AActor* actor, + FName eventName, + FGameFeatureStateChangeContext changeContext) +{ + APlayerController* playerController = CastChecked(actor); + FPerContextData& activeData = ContextData.FindOrAdd(changeContext); + + // TODO Why does this code mix and match controllers and local players? ControllersAddedTo is never modified + if ((eventName == UGameFrameworkComponentManager::NAME_ExtensionRemoved) || (eventName == UGameFrameworkComponentManager::NAME_ReceiverRemoved)) + { + RemoveInputMapping(playerController, activeData); + } + else if ((eventName == UGameFrameworkComponentManager::NAME_ExtensionAdded) || (eventName == UOLSHeroComponent::NAME_BindInputsNow)) + { + AddInputMappingForPlayer(playerController->GetLocalPlayer(), activeData); + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::AddInputMappingForPlayer(UPlayer* player, + FPerContextData& activeData) +{ + if (ULocalPlayer* localPlayer = Cast(player)) + { + if (UEnhancedInputLocalPlayerSubsystem* inputSystem = localPlayer->GetSubsystem< + UEnhancedInputLocalPlayerSubsystem>()) + { + for (const FOLSInputMappingContextAndPriority& entry : InputMappings) + { + if (const UInputMappingContext* imc = entry.InputMapping.Get()) + { + inputSystem->AddMappingContext(imc, entry.Priority); + } + } + } + else + { + OLS_LOG(LogOLSGameFA_AddInputContextMapping, Error, + TEXT( + "Failed to find `UEnhancedInputLocalPlayerSubsystem` for local player. Input mappings will not be added. Make sure you're set to use the EnhancedInput system via config file." + )); + } + } +} + +void UOLSGameFeatureAction_AddInputContextMapping::RemoveInputMapping(APlayerController* playerController, + FPerContextData& activeData) +{ + if (ULocalPlayer* localPlayer = playerController->GetLocalPlayer()) + { + if (UEnhancedInputLocalPlayerSubsystem* inputSystem = localPlayer->GetSubsystem()) + { + for (const FOLSInputMappingContextAndPriority& entry : InputMappings) + { + if (const UInputMappingContext* imc = entry.InputMapping.Get()) + { + inputSystem->RemoveMappingContext(imc); + } + } + } + } + + activeData.ControllersAddedTo.Remove(playerController); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ols/Private/GameFeatures/OLSGameFeatureAction_WorldActionBase.cpp b/Source/ols/Private/GameFeatures/OLSGameFeatureAction_WorldActionBase.cpp new file mode 100644 index 0000000..abc5e6b --- /dev/null +++ b/Source/ols/Private/GameFeatures/OLSGameFeatureAction_WorldActionBase.cpp @@ -0,0 +1,46 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + + +#include "GameFeatures/OLSGameFeatureAction_WorldActionBase.h" + +#include "GameFeaturesSubsystem.h" + +void UOLSGameFeatureAction_WorldActionBase::OnGameFeatureActivating(FGameFeatureActivatingContext& context) +{ + // Don't call Super since we don't need it. + + GameInstanceStartHandles.FindOrAdd(context) = FWorldDelegates::OnStartGameInstance.AddUObject(this, + &UOLSGameFeatureAction_WorldActionBase::HandleGameInstanceStart, FGameFeatureStateChangeContext(context)); + + // Add to any worlds with associated game instances that have already been initialized + for (const FWorldContext& worldContext : GEngine->GetWorldContexts()) + { + if (context.ShouldApplyToWorldContext(worldContext)) + { + AddToWorld(worldContext, context); + } + } +} + +void UOLSGameFeatureAction_WorldActionBase::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context) +{ + // Don't call Super since we don't need it. + + FDelegateHandle* foundHandle = GameInstanceStartHandles.Find(context); + if (ensure(foundHandle)) + { + FWorldDelegates::OnStartGameInstance.Remove(*foundHandle); + } +} + +void UOLSGameFeatureAction_WorldActionBase::HandleGameInstanceStart(UGameInstance* gameInstance, + FGameFeatureStateChangeContext changeContext) +{ + if (FWorldContext* worldContext = gameInstance->GetWorldContext()) + { + if (changeContext.ShouldApplyToWorldContext(*worldContext)) + { + AddToWorld(*worldContext, changeContext); + } + } +} diff --git a/Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h b/Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h new file mode 100644 index 0000000..b7a7da3 --- /dev/null +++ b/Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h @@ -0,0 +1,61 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trad`emark is strictly prohibited and may result in legal action. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayAbilitySpec.h" +#include "GameplayTagContainer.h" +#include "Abilities/GameplayAbilityTypes.h" +#include "OLSAbilityCost.generated.h" + + +/** + * UOLSAbilityCost + * + * Base class for costs that a LyraGameplayAbility has (e.g., ammo or charges) + */ +UCLASS(DefaultToInstanced, EditInlineNew, Abstract) +class OLS_API UOLSAbilityCost : public UObject +{ + GENERATED_BODY() + +public: + + UOLSAbilityCost(); + + /** + * Checks if we can afford this cost. + * + * A failure reason tag can be added to OptionalRelevantTags (if non-null), which can be queried + * elsewhere to determine how to provide user feedback (e.g., a clicking noise if a weapon is out of ammo) + * + * Ability and ActorInfo are guaranteed to be non-null on entry, but OptionalRelevantTags can be nullptr. + * + * @return true if we can pay for the ability, false otherwise. + */ + virtual bool CheckCost(const class UOLSGameplayAbility* ability, + const FGameplayAbilitySpecHandle handle, + const FGameplayAbilityActorInfo* actorInfo, + FGameplayTagContainer* optionalRelevantTags) const; + + /** + * Applies the ability's cost to the target + * + * Notes: + * - Your implementation don't need to check ShouldOnlyApplyCostOnHit(), the caller does that for you. + * - Ability and ActorInfo are guaranteed to be non-null on entry. + */ + virtual void ApplyCost(const class UOLSGameplayAbility* ability, + const FGameplayAbilitySpecHandle handle, + const FGameplayAbilityActorInfo* actorInfo, + const FGameplayAbilityActivationInfo activationInfo); + + /** If true, this cost should only be applied if this ability hits successfully */ + bool ShouldOnlyApplyCostOnHit() const; + +protected: + + /** If true, this cost should only be applied if this ability hits successfully */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OLS|Costs") + uint8 bShouldOnlyApplyCostOnHit : 1 = false; +}; diff --git a/Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h b/Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h index 60da1a5..afbdc5f 100644 --- a/Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h +++ b/Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h @@ -7,10 +7,106 @@ #include "OLSGameplayAbility.generated.h" /** - * + * ELyraAbilityActivationPolicy + * + * Defines how an ability is meant to activate. */ -UCLASS() +UENUM(BlueprintType) +enum class EOLSAbilityActivationPolicy : uint8 +{ + // Try to activate the ability when the input is triggered. + OnInputTriggered, + + // Continually try to activate the ability while the input is active. + WhileInputActive, + + // Try to activate the ability when an avatar is assigned. + OnSpawn +}; + +/** + * ELyraAbilityActivationGroup + * + * Defines how an ability activates in relation to other abilities. + */ +UENUM(BlueprintType) +enum class EOLSAbilityActivationGroup : uint8 +{ + // Ability runs independently of all other abilities. + Independent, + + // Ability is canceled and replaced by other exclusive abilities. + Exclusive_Replaceable, + + // Ability blocks all other exclusive abilities from activating. + Exclusive_Blocking, + + MAX UMETA(Hidden) +}; + +/** Failure reason that can be used to play an animation montage when a failure occurs */ +USTRUCT(BlueprintType) +struct FOLSAbilityMontageFailureMessage +{ + GENERATED_BODY() + +public: + // Player controller that failed to activate the ability, if the AbilitySystemComponent was player owned + UPROPERTY(BlueprintReadWrite) + TObjectPtr PlayerController = nullptr; + + // Avatar actor that failed to activate the ability + UPROPERTY(BlueprintReadWrite) + TObjectPtr AvatarActor = nullptr; + + // All the reasons why this ability has failed + UPROPERTY(BlueprintReadWrite) + FGameplayTagContainer FailureTags; + + UPROPERTY(BlueprintReadWrite) + TObjectPtr FailureMontage = nullptr; +}; + +/** + * UOLSGameplayAbility + * + * The base gameplay ability class used by this project. + */ +UCLASS(Abstract, HideCategories = Input, Meta = (ShortTooltip = "The base gameplay ability class used by this project.")) class OLS_API UOLSGameplayAbility : public UGameplayAbility { GENERATED_BODY() + +public: + + + +protected: + + // Defines how this ability is meant to activate. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Ability Activation") + EOLSAbilityActivationPolicy ActivationPolicy; + + // Defines the relationship between this ability activating and other abilities activating. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Ability Activation") + EOLSAbilityActivationGroup ActivationGroup; + + // Additional costs that must be paid to activate this ability + UPROPERTY(EditDefaultsOnly, Instanced, Category = Costs) + TArray> AdditionalCosts; + + // Map of failure tags to simple error messages + UPROPERTY(EditDefaultsOnly, Category = "Advanced") + TMap FailureTagToUserFacingMessages; + + // Map of failure tags to anim montages that should be played with them + UPROPERTY(EditDefaultsOnly, Category = "Advanced") + TMap> FailureTagToAnimMontage; + + // If true, extra information should be logged when this ability is canceled. This is temporary, used for tracking a bug. + UPROPERTY(EditDefaultsOnly, Category = "Advanced") + uint8 bShouldLogCancellation : 1 = false; + + // Current camera mode set by the ability. + // TSubclassOf ActiveCameraMode; }; diff --git a/Source/ols/Public/Components/OLSHeroComponent.h b/Source/ols/Public/Components/OLSHeroComponent.h new file mode 100644 index 0000000..ea32913 --- /dev/null +++ b/Source/ols/Public/Components/OLSHeroComponent.h @@ -0,0 +1,94 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayAbilitySpecHandle.h" +#include "Components/GameFrameworkInitStateInterface.h" +#include "Components/PawnComponent.h" +#include "OLSHeroComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOLSHeroComponent, Verbose, All); + +namespace EEndPlayReason { enum Type : int; } + +/** + * Component that sets up input and camera handling for player controlled pawns (or bots that simulate players). + * This depends on a PawnExtensionComponent to coordinate initialization. + */ +UCLASS(Blueprintable, Meta=(BlueprintSpawnableComponent)) +class OLS_API UOLSHeroComponent : public UPawnComponent, public IGameFrameworkInitStateInterface +{ + GENERATED_BODY() + +public: + + UOLSHeroComponent(const FObjectInitializer& objectInitializer); + + //~ Begin IGameFrameworkInitStateInterface interface + virtual FName GetFeatureName() const override; + virtual bool CanChangeInitState(UGameFrameworkComponentManager* manager, FGameplayTag currentState, FGameplayTag desiredState) const override; + virtual void HandleChangeInitState(UGameFrameworkComponentManager* manager, FGameplayTag currentState, FGameplayTag desiredState) override; + virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& params) override; + virtual void CheckDefaultInitialization() override; + //~ End IGameFrameworkInitStateInterface interface + + /** The name of the extension event sent via UGameFrameworkComponentManager when ability inputs are ready to bind */ + static const FName NAME_BindInputsNow; + + /** The name of this component-implemented feature */ + static const FName NAME_ActorFeatureName; + + /** Overrides the camera from an active gameplay ability */ + //@TODO: implement UOLSCameraMode. + // void SetAbilityCameraMode(TSubclassOf CameraMode, const FGameplayAbilitySpecHandle& OwningSpecHandle); + + /** Clears the camera override if it is set */ + void ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& owningSpecHandle); + + /** Adds mode-specific input config */ + void AddAdditionalInputConfig(const class UOLSInputConfigDataAsset* inputConfig); + + /** Removes a mode-specific input config if it has been added */ + void RemoveAdditionalInputConfig(const class UOLSInputConfigDataAsset* inputConfig); + + /** True if this is controlled by a real player and has progressed far enough in initialization where additional input bindings can be added */ + bool IsReadyToBindInputs() const; + +protected: + + //~ Begin UActorComponent interface. + virtual void OnRegister() override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type endPlayReason) override; + //~ End UActorComponent interface. + + virtual void InitializePlayerInput(UInputComponent* playerInputComponent); + + void Input_AbilityInputTagPressed(FGameplayTag inputTag); + void Input_AbilityInputTagReleased(FGameplayTag inputTag); + + void Input_Move(const struct FInputActionValue& inputActionValue); + void Input_LookMouse(const struct FInputActionValue& inputActionValue); + void Input_LookStick(const struct FInputActionValue& inputActionValue); + void Input_Crouch(const struct FInputActionValue& InputActionValue); + void Input_AutoRun(const struct FInputActionValue& inputActionValue); + + //@TODO: implement UOLSCameraMode. + // TSubclassOf DetermineCameraMode() const; + +protected: + + UPROPERTY(EditAnywhere) + TArray DefaultInputMappings; + + /** Camera mode set by an ability. */ + // UPROPERTY() + // TSubclassOf AbilityCameraMode; + + /** Spec handle for the last ability to set a camera mode. */ + FGameplayAbilitySpecHandle AbilityCameraModeOwningSpecHandle; + + /** True when player input bindings have been applied, will never be true for non - players */ + bool bIsReadyToBindInputs; +}; diff --git a/Source/ols/Public/Components/OLSPawnExtensionComponent.h b/Source/ols/Public/Components/OLSPawnExtensionComponent.h index cfeca69..fe8e615 100644 --- a/Source/ols/Public/Components/OLSPawnExtensionComponent.h +++ b/Source/ols/Public/Components/OLSPawnExtensionComponent.h @@ -28,6 +28,10 @@ public: /** The name of this overall feature, this one depends on the other named component features */ static const FName NAME_ActorFeatureName; + //~ Begin UActorComponent interface + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + //~ End UActorComponent interface + //~ Begin IGameFrameworkInitStateInterface interface virtual FName GetFeatureName() const override; virtual bool CanChangeInitState(UGameFrameworkComponentManager* manager, FGameplayTag currentState, FGameplayTag desiredState) const override; diff --git a/Source/ols/Public/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h b/Source/ols/Public/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h new file mode 100644 index 0000000..bc22dfa --- /dev/null +++ b/Source/ols/Public/GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h @@ -0,0 +1,98 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + +#pragma once + +#include "CoreMinimal.h" +#include "OLSGameFeatureAction_WorldActionBase.h" +#include "OLSGameFeatureAction_AddInputContextMapping.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOLSGameFA_AddInputContextMapping, Verbose, All); + +USTRUCT() +struct FOLSInputMappingContextAndPriority +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category="Input", meta=(AssetBundles="Client,Server")) + TSoftObjectPtr InputMapping; + + // Higher priority input mappings will be prioritized over mappings with a lower priority. + UPROPERTY(EditAnywhere, Category="Input") + int32 Priority = 0; + + /** If true, then this mapping context will be registered with the settings when this game feature action is registered. */ + UPROPERTY(EditAnywhere, Category="Input") + uint8 bShouldRegisterWithSettings : 1 = true; +}; + +/** + * Adds InputMappingContext to local players' EnhancedInput system. + * Expects that local players are set up to use the EnhancedInput system. + */ +UCLASS(MinimalAPI, meta = (DisplayName = "Add Input Mapping")) +class UOLSGameFeatureAction_AddInputContextMapping : public UOLSGameFeatureAction_WorldActionBase +{ + GENERATED_BODY() + +public: + + //~ Begin UGameFeatureAction interface + virtual void OnGameFeatureRegistering() override; + virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& context) override; + virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context) override; + virtual void OnGameFeatureUnregistering() override; + //~ End UGameFeatureAction interface + + //~ Begin UObject interface +#if WITH_EDITOR + virtual EDataValidationResult IsDataValid(class FDataValidationContext& context) const override; +#endif + //~ End UObject interface + +private: + + /** Registers owned Input Mapping Contexts to the Input Registry Subsystem. Also binds onto the start of GameInstances and the adding/removal of Local Players. */ + void RegisterInputMappingContexts(); + + /** Registers owned Input Mapping Contexts to the Input Registry Subsystem for a specified GameInstance. This also gets called by a GameInstance Start. */ + void RegisterInputContextMappingsForGameInstance(class UGameInstance* gameInstance); + + /** Registers owned Input Mapping Contexts to the Input Registry Subsystem for a specified Local Player. This also gets called when a Local Player is added. */ + void RegisterInputMappingContextsForLocalPlayer(class ULocalPlayer* localPlayer); + + /** Unregisters owned Input Mapping Contexts from the Input Registry Subsystem. Also unbinds from the start of GameInstances and the adding/removal of Local Players. */ + void UnregisterInputMappingContexts(); + + /** Unregisters owned Input Mapping Contexts from the Input Registry Subsystem for a specified GameInstance. */ + void UnregisterInputContextMappingsForGameInstance(class UGameInstance* gameInstance); + + /** Unregisters owned Input Mapping Contexts from the Input Registry Subsystem for a specified Local Player. This also gets called when a Local Player is removed. */ + void UnregisterInputMappingContextsForLocalPlayer(class ULocalPlayer* localPlayer); + + //~UGameFeatureAction_WorldActionBase interface + virtual void AddToWorld(const FWorldContext& worldContext, const struct FGameFeatureStateChangeContext& changeContext) override; + //~End of UGameFeatureAction_WorldActionBase interface + + struct FPerContextData + { + TArray> ExtensionRequestHandles; + TArray> ControllersAddedTo; + }; + + void Reset(FPerContextData& activeData); + void HandleControllerExtension(class AActor* actor, FName eventName, struct FGameFeatureStateChangeContext changeContext); + void AddInputMappingForPlayer(class UPlayer* player, struct FPerContextData& activeData); + void RemoveInputMapping(class APlayerController* playerController, FPerContextData& activeData); + +public: + + UPROPERTY(EditAnywhere, Category="Input") + TArray InputMappings; + +private: + + TMap ContextData; + + /** Delegate for when the game instance is changed to register IMC's */ + FDelegateHandle RegisterInputContextMappingsForGameInstanceHandle; +}; diff --git a/Source/ols/Public/GameFeatures/OLSGameFeatureAction_WorldActionBase.h b/Source/ols/Public/GameFeatures/OLSGameFeatureAction_WorldActionBase.h new file mode 100644 index 0000000..32fd7c7 --- /dev/null +++ b/Source/ols/Public/GameFeatures/OLSGameFeatureAction_WorldActionBase.h @@ -0,0 +1,38 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFeatureAction.h" +#include "GameFeaturesSubsystem.h" + +#include "OLSGameFeatureAction_WorldActionBase.generated.h" + +/** + * Base class for GameFeatureActions that wish to do something world specific. + */ +UCLASS(Abstract) +class OLS_API UOLSGameFeatureAction_WorldActionBase : public UGameFeatureAction +{ + GENERATED_BODY() + +public: + + //~ Begin UGameFeatureAction interface + virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& context) override; + virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context) override; + //~ End UGameFeatureAction interface + +private: + + void HandleGameInstanceStart(UGameInstance* gameInstance, FGameFeatureStateChangeContext changeContext); + + /** Override with the action-specific logic */ + virtual void AddToWorld(const FWorldContext& worldContext, + const FGameFeatureStateChangeContext& changeContext) PURE_VIRTUAL( + UOLSGameFeatureAction_WorldActionBase::AddToWorld); + +private: + + TMap GameInstanceStartHandles; +}; diff --git a/Source/ols/ols.Build.cs b/Source/ols/ols.Build.cs index f916abe..0e067f6 100644 --- a/Source/ols/ols.Build.cs +++ b/Source/ols/ols.Build.cs @@ -27,7 +27,8 @@ public class ols : ModuleRules "InputCore", "AnimGraphRuntime", "GameplayMessageRuntime", - "NetCore" + "NetCore", + "EnhancedInput", }); // Header files path @@ -43,5 +44,8 @@ public class ols : ModuleRules // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + + + SetupGameplayDebuggerSupport(Target); } }