181 lines
6.4 KiB
C++
181 lines
6.4 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GameFeatures/GameFeatureAction_AddInputConfig.h"
|
|
#include "Components/GameFrameworkComponentManager.h"
|
|
#include "EnhancedInputSubsystems.h"
|
|
#include "Character/LyraHeroComponent.h" // for NAME_BindInputsNow
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "PlayerMappableInputConfig.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "GameFeatures_AddInputConfig"
|
|
|
|
void UGameFeatureAction_AddInputConfig::OnGameFeatureRegistering()
|
|
{
|
|
Super::OnGameFeatureRegistering();
|
|
|
|
// Register the input configs with the local settings, this way the data inside them is available all the time
|
|
// and not just when this game feature is active. This is necessary for displaying key binding options
|
|
// on the main menu, or other times when the game feature may not be active.
|
|
for (const FMappableConfigPair& Pair : InputConfigs)
|
|
{
|
|
FMappableConfigPair::RegisterPair(Pair);
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::OnGameFeatureUnregistering()
|
|
{
|
|
Super::OnGameFeatureUnregistering();
|
|
|
|
for (const FMappableConfigPair& Pair : InputConfigs)
|
|
{
|
|
FMappableConfigPair::UnregisterPair(Pair);
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
|
|
{
|
|
FPerContextData& ActiveData = ContextData.FindOrAdd(Context);
|
|
if (!ensure(ActiveData.ExtensionRequestHandles.IsEmpty()) ||
|
|
!ensure(ActiveData.PawnsAddedTo.IsEmpty()))
|
|
{
|
|
Reset(ActiveData);
|
|
}
|
|
|
|
// Call super after the above logic so that we have our context before being added to the world
|
|
Super::OnGameFeatureActivating(Context);
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
|
|
{
|
|
Super::OnGameFeatureDeactivating(Context);
|
|
FPerContextData* ActiveData = ContextData.Find(Context);
|
|
|
|
if (ensure(ActiveData))
|
|
{
|
|
Reset(*ActiveData);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
EDataValidationResult UGameFeatureAction_AddInputConfig::IsDataValid(TArray<FText>& ValidationErrors)
|
|
{
|
|
EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid);
|
|
|
|
int32 EntryIndex = 0;
|
|
for (const FMappableConfigPair& Pair : InputConfigs)
|
|
{
|
|
if (Pair.Config.IsNull())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("NullConfigPointer", "Null Config pointer at index {0} in Pair list"), FText::AsNumber(EntryIndex)));
|
|
}
|
|
|
|
++EntryIndex;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
void UGameFeatureAction_AddInputConfig::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* ComponentMan = UGameInstance::GetSubsystem<UGameFrameworkComponentManager>(GameInstance))
|
|
{
|
|
UGameFrameworkComponentManager::FExtensionHandlerDelegate AddConfigDelegate =
|
|
UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(this, &ThisClass::HandlePawnExtension, ChangeContext);
|
|
|
|
TSharedPtr<FComponentRequestHandle> ExtensionRequestHandle = ComponentMan->AddExtensionHandler(APawn::StaticClass(), AddConfigDelegate);
|
|
ActiveData.ExtensionRequestHandles.Add(ExtensionRequestHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::Reset(FPerContextData& ActiveData)
|
|
{
|
|
ActiveData.ExtensionRequestHandles.Empty();
|
|
|
|
while (!ActiveData.PawnsAddedTo.IsEmpty())
|
|
{
|
|
TWeakObjectPtr<APawn> PawnPtr = ActiveData.PawnsAddedTo.Top();
|
|
if (PawnPtr.IsValid())
|
|
{
|
|
RemoveInputConfig(PawnPtr.Get(), ActiveData);
|
|
}
|
|
else
|
|
{
|
|
ActiveData.PawnsAddedTo.Pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::HandlePawnExtension(AActor* Actor, FName EventName, FGameFeatureStateChangeContext ChangeContext)
|
|
{
|
|
APawn* AsPawn = CastChecked<APawn>(Actor);
|
|
FPerContextData& ActiveData = ContextData.FindOrAdd(ChangeContext);
|
|
|
|
if (EventName == UGameFrameworkComponentManager::NAME_ExtensionAdded || EventName == ULyraHeroComponent::NAME_BindInputsNow)
|
|
{
|
|
AddInputConfig(AsPawn, ActiveData);
|
|
}
|
|
else if (EventName == UGameFrameworkComponentManager::NAME_ExtensionRemoved || EventName == UGameFrameworkComponentManager::NAME_ReceiverRemoved)
|
|
{
|
|
RemoveInputConfig(AsPawn, ActiveData);
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::AddInputConfig(APawn* Pawn, FPerContextData& ActiveData)
|
|
{
|
|
APlayerController* PlayerController = Cast<APlayerController>(Pawn->GetController());
|
|
|
|
if (ULocalPlayer* LP = PlayerController ? PlayerController->GetLocalPlayer() : nullptr)
|
|
{
|
|
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
|
{
|
|
// We don't want to ignore keys that were "Down" when we add the mapping context
|
|
// This allows you to die holding a movement key, keep holding while waiting for respawn,
|
|
// and have it be applied after you respawn immediately. Leaving bIgnoreAllPressedKeysUntilRelease
|
|
// to it's default "true" state would require the player to release the movement key,
|
|
// and press it again when they respawn
|
|
FModifyContextOptions Options = {};
|
|
Options.bIgnoreAllPressedKeysUntilRelease = false;
|
|
|
|
// Add the input mappings
|
|
for (const FMappableConfigPair& Pair : InputConfigs)
|
|
{
|
|
if (Pair.bShouldActivateAutomatically && Pair.CanBeActivated())
|
|
{
|
|
Subsystem->AddPlayerMappableConfig(Pair.Config.LoadSynchronous(), Options);
|
|
}
|
|
}
|
|
ActiveData.PawnsAddedTo.AddUnique(Pawn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddInputConfig::RemoveInputConfig(APawn* Pawn, FPerContextData& ActiveData)
|
|
{
|
|
APlayerController* PlayerController = Cast<APlayerController>(Pawn->GetController());
|
|
|
|
if (ULocalPlayer* LP = PlayerController ? PlayerController->GetLocalPlayer() : nullptr)
|
|
{
|
|
// If this is called during the shutdown of the game then there isn't a strict guarantee that the input subsystem is valid
|
|
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
|
{
|
|
// Remove the input mappings
|
|
for (const FMappableConfigPair& Pair : InputConfigs)
|
|
{
|
|
Subsystem->RemovePlayerMappableConfig(Pair.Config.LoadSynchronous());
|
|
}
|
|
}
|
|
}
|
|
ActiveData.PawnsAddedTo.Remove(Pawn);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |