// Copyright Epic Games, Inc. All Rights Reserved. #include "GameFeatureAction_AddInputContextMapping.h" #include "GameFeaturesSubsystem.h" #include "GameFeaturesSubsystemSettings.h" #include "Components/GameFrameworkComponentManager.h" #include "GameFramework/PlayerController.h" #include "Engine/LocalPlayer.h" #include "EnhancedInputSubsystems.h" #include "InputMappingContext.h" #include "Character/LyraHeroComponent.h" #define LOCTEXT_NAMESPACE "GameFeatures" ////////////////////////////////////////////////////////////////////// // UGameFeatureAction_AddInputContextMapping void UGameFeatureAction_AddInputContextMapping::OnGameFeatureActivating(FGameFeatureActivatingContext& Context) { FPerContextData& ActiveData = ContextData.FindOrAdd(Context); if (!ensure(ActiveData.ExtensionRequestHandles.IsEmpty()) || !ensure(ActiveData.ControllersAddedTo.IsEmpty())) { Reset(ActiveData); } Super::OnGameFeatureActivating(Context); } void UGameFeatureAction_AddInputContextMapping::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) { Super::OnGameFeatureDeactivating(Context); FPerContextData* ActiveData = ContextData.Find(Context); if (ensure(ActiveData)) { Reset(*ActiveData); } } #if WITH_EDITOR EDataValidationResult UGameFeatureAction_AddInputContextMapping::IsDataValid(TArray& ValidationErrors) { EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid); int32 Index = 0; for (const FInputMappingContextAndPriority& Entry : InputMappings) { if (Entry.InputMapping.IsNull()) { Result = EDataValidationResult::Invalid; ValidationErrors.Add(FText::Format(LOCTEXT("NullInputMapping", "Null InputMapping at index {0}."), Index)); } ++Index; } return Result; } #endif void UGameFeatureAction_AddInputContextMapping::AddToWorld(const FWorldContext& WorldContext, const FGameFeatureStateChangeContext& ChangeContext) { UWorld* World = WorldContext.World(); UGameInstance* GameInstance = WorldContext.OwningGameInstance; FPerContextData& ActiveData = ContextData.FindOrAdd(ChangeContext); if ((GameInstance != nullptr) && (World != nullptr) && World->IsGameWorld()) { if (UGameFrameworkComponentManager* ComponentManager = UGameInstance::GetSubsystem(GameInstance)) { UGameFrameworkComponentManager::FExtensionHandlerDelegate AddAbilitiesDelegate = UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(this, &ThisClass::HandleControllerExtension, ChangeContext); TSharedPtr ExtensionRequestHandle = ComponentManager->AddExtensionHandler(APlayerController::StaticClass(), AddAbilitiesDelegate); ActiveData.ExtensionRequestHandles.Add(ExtensionRequestHandle); } } } void UGameFeatureAction_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 UGameFeatureAction_AddInputContextMapping::HandleControllerExtension(AActor* Actor, FName EventName, FGameFeatureStateChangeContext ChangeContext) { APlayerController* AsController = 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(AsController, ActiveData); } else if ((EventName == UGameFrameworkComponentManager::NAME_ExtensionAdded) || (EventName == ULyraHeroComponent::NAME_BindInputsNow)) { AddInputMappingForPlayer(AsController->GetLocalPlayer(), ActiveData); } } void UGameFeatureAction_AddInputContextMapping::AddInputMappingForPlayer(UPlayer* Player, FPerContextData& ActiveData) { if (ULocalPlayer* LocalPlayer = Cast(Player)) { if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem()) { for (const FInputMappingContextAndPriority& Entry : InputMappings) { if (const UInputMappingContext* IMC = Entry.InputMapping.Get()) { InputSystem->AddMappingContext(IMC, Entry.Priority); } } } else { UE_LOG(LogGameFeatures, 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 UGameFeatureAction_AddInputContextMapping::RemoveInputMapping(APlayerController* PlayerController, FPerContextData& ActiveData) { if (ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer()) { if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem()) { for (const FInputMappingContextAndPriority& Entry : InputMappings) { if (const UInputMappingContext* IMC = Entry.InputMapping.Get()) { InputSystem->RemoveMappingContext(IMC); } } } } ActiveData.ControllersAddedTo.Remove(PlayerController); } #undef LOCTEXT_NAMESPACE