ComboInput/Source/ComboInput/Private/Components/InputBufferComponent.cpp

251 lines
8.4 KiB
C++

// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Components/InputBufferComponent.h"
#include "ComboInputAssets.h"
#include "EnhancedInputComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Components/ComboManagerComponent.h"
#include "GlobalSettings/InputBufferGlobalSettings.h"
DEFINE_LOG_CATEGORY(LogInputBufferComponent);
UInputBufferComponent::UInputBufferComponent()
{
this->bWantsInitializeComponent = true;
}
void UInputBufferComponent::InitializeComponent()
{
Super::InitializeComponent();
if (APlayerController *PlayerController = UGameplayStatics::GetPlayerController(this, 0))
{
if (UComboManagerComponent *ComboManager = PlayerController->GetComponentByClass<UComboManagerComponent>())
{
// Get the player character and try to connect to its combo manager.
this->OnNewComboInput.BindUObject(ComboManager, &UComboManagerComponent::HandleComboInput);
const TSet<const UComboInputAsset *> &ComboInputs = ComboManager->FindAllUsedInputs();
// Get all unique EnhancedInput actions bound to combo input actions.
TSet<const UInputAction *> InputActionsToBind;
for (const UComboInputAsset *ComboInput : ComboInputs)
{
if (ComboInput)
{
this->ComboInputList.Emplace(ComboInput);
for (const UInputAction *InputAction : ComboInput->ActionGroup)
{
InputActionsToBind.Add(InputAction);
}
}
else
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Invalid combo input found"));
}
}
for (const UInputAction *InputAction : InputActionsToBind)
{
this->BindAction(InputAction, ETriggerEvent::Started, this, &UInputBufferComponent::AddActionToBuffer, InputAction);
this->BindAction(InputAction, ETriggerEvent::Completed, this, &UInputBufferComponent::ExpireAction, InputAction);
}
}
}
}
void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
{
this->MultiPressActions.Add(Action);
this->ExpiringActions.Remove(Action);
// Find any combo input that matches this action, plus buffered actions.
bool bComboInputFound = false;
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{
if (ComboInput->MatchesInputActions(this->MultiPressActions))
{
this->ActivateComboInput(ComboInput.Get());
bComboInputFound = true;
break;
}
}
// If we haven't found any matching inputs, check if anything was somehow unhandled.
// This can happen if, for example, a jump input can combine with an attack input to
// create a combo action, but jump by itself has no action on its own.
if (!bComboInputFound)
{
if (!this->UnhandledActions.IsEmpty())
{
TSet<const UInputAction*> HandledActions = this->MultiPressActions;
for (const UInputAction *UnhandledAction : this->UnhandledActions)
{
HandledActions.Remove(UnhandledAction);
}
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{
if (ComboInput->MatchesInputActions(HandledActions))
{
this->ActivateComboInput(ComboInput);
bComboInputFound = true;
break;
}
}
}
this->UnhandledActions.Add(Action);
}
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, Settings->MultiPressTimerLength);
}
void UInputBufferComponent::ClearMultiPresses()
{
#if WITH_EDITOR
TArray<FString> ActionNames;
for (const UInputAction *Action : this->MultiPressActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
#endif
this->MultiPressActions.Empty();
this->UnhandledActions.Empty();
}
void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput)
{
checkf(ComboInput, TEXT("Invalid %s."), *UComboInputAsset::StaticClass()->GetName());
const bool bNewInputLocked = this->LockedComboInputs.Contains(ComboInput);
const bool bNewSupercedesActive = (this->InputBufferActive && this->InputBufferActive->ContainsOneOf(ComboInput->ActionGroup) && !bNewInputLocked);
// Make this combo input active if it isn't being locked, or if we are
// overwriting a previous combo input with a multi-press combo input.
if (bNewSupercedesActive || !bNewInputLocked)
{
// Set the combo input as active.
this->InputBufferActive = ComboInput;
this->InputBufferHold = nullptr;
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
for (const TObjectPtr<const UComboInputAsset> &LockedAsset : this->ComboInputList)
{
this->LockComboInput(LockedAsset);
}
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Activated);
// If the incoming combo input contains an expiring action,
// we know it's been released, so send that signal too.
if (ComboInput->ContainsOneOf(this->ExpiringActions))
{
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Released);
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active."), *ComboInput->ComboInputName.ToString());
}
else
{
this->HoldComboInput(ComboInput);
}
}
void UInputBufferComponent::HoldComboInput(const UComboInputAsset *ComboInput)
{
this->InputBufferHold = ComboInput;
UE_SUPPRESS(LogInputBufferComponent, Verbose,
{
if (this->LockedComboInputs.Contains(ComboInput))
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is locked and won't be activated yet."), *ComboInput->ComboInputName.ToString());
}
else
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s added to buffer."), *ComboInput->ComboInputName.ToString());
}
}
);
}
void UInputBufferComponent::LockComboInput(const UComboInputAsset *Input)
{
this->LockedComboInputs.Emplace(Input);
}
void UInputBufferComponent::UnlockComboInput(const UComboInputAsset *Unlocked)
{
// Remove the newly-unlocked asset from the locked combo inputs.
UE_CLOG(this->LockedComboInputs.Contains(Unlocked), LogInputBufferComponent, Verbose, TEXT("%s has unlocked."), *Unlocked->ComboInputName.ToString());
this->LockedComboInputs.Remove(Unlocked);
// Check if the newly unlocked combo input is in the hold.
if (Unlocked == this->InputBufferHold)
{
const UComboInputAsset *OriginalActive = this->InputBufferActive;
// Activate the held combo input.
const UComboInputAsset *HeldAsset = this->InputBufferHold;
this->InputBufferHold = nullptr;
if (HeldAsset)
{
this->ActivateComboInput(HeldAsset);
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has expired."), *OriginalActive->ComboInputName.ToString());
}
}
void UInputBufferComponent::ExpireAction(const FInputActionValue &Value, const class UInputAction *Action)
{
// Only send a release event if we haven't already, i.e. if the combo input associated
// with the action is not already buffered for another future activation.
if (this->InputBufferActive && this->InputBufferActive != this->InputBufferHold && this->InputBufferActive->MatchesInputAction(Action))
{
this->OnNewComboInput.Execute(this->InputBufferActive, EComboActionTriggerEvent::Released);
}
// Prepare to expire any buffered combo inputs
this->ExpiringActions.Add(Action);
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->InputReleaseExpirationTimerHandle, this, &UInputBufferComponent::ExpireBufferedActions, Settings->InputReleaseExpirationTimerLength);
}
void UInputBufferComponent::ExpireBufferedActions()
{
// Only bother dealing with this if there's something to deal with in the first place.
if (!this->ExpiringActions.IsEmpty())
{
UE_SUPPRESS(LogInputBufferComponent, Verbose,
{
TArray<FString> ActionNames;
for (const UInputAction *Action : this->ExpiringActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Released actions expired (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
}
);
// If there is an action in the hold, check if it's related
// to our current released buttons, and if so cancel it.
if (this->InputBufferHold)
{
for (const UInputAction *Action : this->ExpiringActions)
{
if (this->InputBufferHold->MatchesInputAction(Action))
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has been cancelled."), *this->InputBufferHold->ComboInputName.ToString());
this->InputBufferHold = nullptr;
break;
}
}
}
this->ExpiringActions.Empty();
}
}