// Copyright Epic Games, Inc. All Rights Reserved. #include "GameFeatureAction_AddInputContextMapping.h" #include "Components/GameFrameworkComponentManager.h" #include "Engine/GameInstance.h" #include "GameFramework/PlayerController.h" #include "Engine/LocalPlayer.h" #include "Engine/World.h" #include "EnhancedInputSubsystems.h" #include "GameFeatures/GameFeatureAction_WorldActionBase.h" #include "InputMappingContext.h" #include "Character/LyraHeroComponent.h" #include "UserSettings/EnhancedInputUserSettings.h" #include "System/LyraAssetManager.h" #if WITH_EDITOR #include "Misc/DataValidation.h" #endif #include UE_INLINE_GENERATED_CPP_BY_NAME(GameFeatureAction_AddInputContextMapping) #define LOCTEXT_NAMESPACE "GameFeatures" ////////////////////////////////////////////////////////////////////// // UGameFeatureAction_AddInputContextMapping void UGameFeatureAction_AddInputContextMapping::OnGameFeatureRegistering() { Super::OnGameFeatureRegistering(); RegisterInputMappingContexts(); } 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); } } void UGameFeatureAction_AddInputContextMapping::OnGameFeatureUnregistering() { Super::OnGameFeatureUnregistering(); UnregisterInputMappingContexts(); } void UGameFeatureAction_AddInputContextMapping::RegisterInputMappingContexts() { RegisterInputContextMappingsForGameInstanceHandle = FWorldDelegates::OnStartGameInstance.AddUObject(this, &UGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance); const TIndirectArray& WorldContexts = GEngine->GetWorldContexts(); for (TIndirectArray::TConstIterator WorldContextIterator = WorldContexts.CreateConstIterator(); WorldContextIterator; ++WorldContextIterator) { RegisterInputContextMappingsForGameInstance(WorldContextIterator->OwningGameInstance); } } void UGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance(UGameInstance* GameInstance) { if (GameInstance != nullptr && !GameInstance->OnLocalPlayerAddedEvent.IsBoundToObject(this)) { GameInstance->OnLocalPlayerAddedEvent.AddUObject(this, &UGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer); GameInstance->OnLocalPlayerRemovedEvent.AddUObject(this, &UGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer); for (TArray::TConstIterator LocalPlayerIterator = GameInstance->GetLocalPlayerIterator(); LocalPlayerIterator; ++LocalPlayerIterator) { RegisterInputMappingContextsForLocalPlayer(*LocalPlayerIterator); } } } void UGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer(ULocalPlayer* LocalPlayer) { if (ensure(LocalPlayer)) { ULyraAssetManager& AssetManager = ULyraAssetManager::Get(); if (UEnhancedInputLocalPlayerSubsystem* EISubsystem = ULocalPlayer::GetSubsystem(LocalPlayer)) { if (UEnhancedInputUserSettings* Settings = EISubsystem->GetUserSettings()) { for (const FInputMappingContextAndPriority& Entry : InputMappings) { // Skip entries that don't want to be registered if (!Entry.bRegisterWithSettings) { continue; } // Register this IMC with the settings! if (UInputMappingContext* IMC = AssetManager.GetAsset(Entry.InputMapping)) { Settings->RegisterInputMappingContext(IMC); } } } } } } void UGameFeatureAction_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 UGameFeatureAction_AddInputContextMapping::UnregisterInputContextMappingsForGameInstance(UGameInstance* GameInstance) { if (GameInstance != nullptr) { GameInstance->OnLocalPlayerAddedEvent.RemoveAll(this); GameInstance->OnLocalPlayerRemovedEvent.RemoveAll(this); for (TArray::TConstIterator LocalPlayerIterator = GameInstance->GetLocalPlayerIterator(); LocalPlayerIterator; ++LocalPlayerIterator) { UnregisterInputMappingContextsForLocalPlayer(*LocalPlayerIterator); } } } void UGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer(ULocalPlayer* LocalPlayer) { if (ensure(LocalPlayer)) { if (UEnhancedInputLocalPlayerSubsystem* EISubsystem = ULocalPlayer::GetSubsystem(LocalPlayer)) { if (UEnhancedInputUserSettings* Settings = EISubsystem->GetUserSettings()) { for (const FInputMappingContextAndPriority& Entry : InputMappings) { // Skip entries that don't want to be registered if (!Entry.bRegisterWithSettings) { continue; } // Register this IMC with the settings! if (UInputMappingContext* IMC = Entry.InputMapping.Get()) { Settings->UnregisterInputMappingContext(IMC); } } } } } } #if WITH_EDITOR EDataValidationResult UGameFeatureAction_AddInputContextMapping::IsDataValid(FDataValidationContext& Context) const { EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(Context), EDataValidationResult::Valid); int32 Index = 0; for (const FInputMappingContextAndPriority& 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 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