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

190 lines
6.2 KiB
C++

// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Components/InputBufferComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
DEFINE_LOG_CATEGORY(LogInputBufferComponent);
UInputBufferComponent::UInputBufferComponent()
{
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bTickEvenWhenPaused = false;
PrimaryComponentTick.bCanEverTick = false;
}
void UInputBufferComponent::BeginPlay()
{
Super::BeginPlay();
// Get all unique EnhancedInput actions bound to combo input actions.
TSet<const UInputAction*> InputActionsToBind;
for (const UComboInputAsset *ComboInput : this->ComboActions)
{
for (const UInputAction *InputAction : ComboInput->ActionGroup)
{
InputActionsToBind.Add(InputAction);
}
}
APlayerController *Controller = Cast<APlayerController>(this->GetOwner());
checkf(Controller, TEXT("No player controller found as owner of %s"), *this->GetName());
if (this->EnhancedInputComponent = Cast<UEnhancedInputComponent>(Controller->InputComponent))
{
// Bind the input actions we found to the buffer management functions.
for (const UInputAction *InputAction : InputActionsToBind)
{
this->EnhancedInputComponent->BindAction(InputAction, ETriggerEvent::Started, this, &UInputBufferComponent::AddActionToBuffer, InputAction);
this->EnhancedInputComponent->BindAction(InputAction, ETriggerEvent::Completed, this, &UInputBufferComponent::ExpireAction, InputAction);
}
}
else
{
UE_LOG(LogInputBufferComponent, Error, TEXT("Parent of %s is not a UEnhancedInputComponent type."), *this->GetName());
}
}
void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
{
this->MostRecentActions.Add(Action);
this->ExpiringActions.Remove(Action);
// Find any combo input that matches this action, plus buffered actions.
for (const UComboInputAsset *Combo : this->ComboActions)
{
if (Combo->MatchesInputActions(this->MostRecentActions))
{
this->ActivateComboInput(Combo);
break;
}
}
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, this->MultiPressTimerLength);
}
void UInputBufferComponent::ClearMultiPresses()
{
#if WITH_EDITOR
TArray<FString> ActionNames;
for (const UInputAction *Action : this->MostRecentActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
#endif
this->MostRecentActions.Empty();
}
void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput)
{
checkf(ComboInput, TEXT("Invalid UComboInputAsset"));
// 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.
const bool bMultiPressTimerActive = this->GetWorld()->GetTimerManager().IsTimerActive(this->MultiPressTimerHandle);
const bool bComboInputLocked = this->LockedComboInputs.Contains(ComboInput);
if (bMultiPressTimerActive || !bComboInputLocked)
{
if (!ComboInput->LockedComboInputs.IsEmpty())
{
// Set the combo input as active, and copy its lock data.
this->InputBufferActive = ComboInput;
this->LockedComboInputs = ComboInput->LockedComboInputs;
// Make sure the hold is clear if we're coming off of a multi-press action.
if (bMultiPressTimerActive)
{
this->InputBufferHold = nullptr;
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active."), *ComboInput->ComboInputName.ToString());
}
else
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active and won't lock inputs."), *ComboInput->ComboInputName.ToString());
}
this->NewComboInput.Execute(ComboInput);
}
else
{
this->InputBufferHold = ComboInput;
if (bComboInputLocked)
{
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::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)
{
this->ExpiringActions.Add(Action);
this->GetWorld()->GetTimerManager().SetTimer(this->InputReleaseExpirationTimerHandle, this, &UInputBufferComponent::ExpireBufferedActions, this->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();
}
}