commit 2e5aae7f67e7e6451c4c44427f1c91aad6db9b6d Author: Jamie Greunbaum Date: Sat Sep 9 17:28:41 2023 -0400 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfbcb6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries/ +Intermediate/ diff --git a/ComboInput.uplugin b/ComboInput.uplugin new file mode 100644 index 0000000..322c587 --- /dev/null +++ b/ComboInput.uplugin @@ -0,0 +1,32 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1.0.0", + "FriendlyName": "Combo Input", + "Description": "A set of components and classes for capturing Enhanced Input actions for buffering actions and stringing them into complex combos.", + "Category": "Input", + "CreatedBy": "Jamie Greunbaum", + "EngineVersion": "5.2.0", + "CanContainContent": true, + "IsBetaVersion": true, + "IsExperimentalVersion": true, + "Installed": true, + "Modules": [ + { + "Name": "ComboInput", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "ComboInputEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "EnhancedInput", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000..1231d4a Binary files /dev/null and b/Resources/Icon128.png differ diff --git a/Source/ComboInput/ComboInput.Build.cs b/Source/ComboInput/ComboInput.Build.cs new file mode 100644 index 0000000..e8ee8b2 --- /dev/null +++ b/Source/ComboInput/ComboInput.Build.cs @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ComboInput : ModuleRules +{ + public ComboInput(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + bLegacyPublicIncludePaths = false; + ShadowVariableWarningLevel = WarningLevel.Error; + + PublicIncludePaths.AddRange + ( + new string[] + { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange + ( + new string[] + { + "ComboInput/Private" + } + ); + + + PublicDependencyModuleNames.AddRange + ( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "UMG", + + "EnhancedInput", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange + ( + new string[] + { + "Engine", + "Slate", + "SlateCore", + "GameplayTags", + "DeveloperSettings", + "UMG", + "Projects" + // ... add private dependencies that you statically link with here ... + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + DynamicallyLoadedModuleNames.AddRange + ( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/ComboInput/Private/ComboInput.cpp b/Source/ComboInput/Private/ComboInput.cpp new file mode 100644 index 0000000..f938502 --- /dev/null +++ b/Source/ComboInput/Private/ComboInput.cpp @@ -0,0 +1,24 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "ComboInput.h" + +#include "GameplayTagsManager.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FComboInputModule" + + +void FComboInputModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FComboInputModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FComboInputModule, ComboInput) \ No newline at end of file diff --git a/Source/ComboInput/Private/Components/ComboManagerComponent.cpp b/Source/ComboInput/Private/Components/ComboManagerComponent.cpp new file mode 100644 index 0000000..074c629 --- /dev/null +++ b/Source/ComboInput/Private/Components/ComboManagerComponent.cpp @@ -0,0 +1,97 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Components/ComboManagerComponent.h" + +#include "Components/InputBufferComponent.h" +#include "Interfaces/ComboHandlerInterface.h" + +DEFINE_LOG_CATEGORY(LogComboManagerComponent); + + +UComboManagerComponent::UComboManagerComponent() +{ + PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bTickEvenWhenPaused = false; + PrimaryComponentTick.bCanEverTick = false; +} + +void UComboManagerComponent::BeginPlay() +{ + Super::BeginPlay(); + + const AActor *OwningActor = this->GetOwner(); + if (const IComboHandlerInterface *ComboHandler = Cast(OwningActor)) + { + if (UInputBufferComponent *InputBuffer = IComboHandlerInterface::Execute_GetInputBuffer(OwningActor)) + { + this->AttachedInputBuffer = InputBuffer; + this->AttachedInputBuffer->NewComboInput.BindUObject(this, &UComboManagerComponent::ComboInputReceived); + } + } +} + + +void UComboManagerComponent::ComboInputReceived(const UComboInputAsset *Input) +{ + /************ DEBUG ************/ + for (const TPair, float> &Pair : this->DEBUG__UnlockTimers) + { + if (!this->DEBUG__TimerHandles.Contains(Pair.Key)) + { + this->DEBUG__TimerHandles.Add(Pair.Key); + } + this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__TimerHandles[Pair.Key]); + this->GetWorld()->GetTimerManager().SetTimer(this->DEBUG__TimerHandles[Pair.Key], FTimerDelegate::CreateUObject(this, &UComboManagerComponent::DEBUG__UnlockAction, Pair.Key), Pair.Value, false); + } + /********** END DEBUG **********/ + + // If we received an offset input, perform the offset here and then leave. + if (Input == this->OffsetInput) + { + this->ActiveNode = this->PreviousNode; + this->GetWorld()->GetTimerManager().ClearTimer(this->FinishTransitionTimer); + UE_LOG(LogComboManagerComponent, Verbose, TEXT("Combo has been offset by %s"), *Input->ComboInputName.ToString()); + return; + } + + const TObjectPtr CurrentNode = (this->ActiveNode ? this->ActiveNode : this->DefaultStartNode); + checkf(CurrentNode, TEXT("No combo sequence nodes available.")); + + if (CurrentNode->ComboBranch.Contains(Input)) + { + const FComboSequenceAction &ActionData = CurrentNode->ComboBranch[Input]; + if (ActionData.ComboAction) + { + this->BeginNodeTransition(ActionData.NextNode); + + UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s activated"), *ActionData.ComboAction->ActionName.ToString()); + } + } + else + { + UE_LOG(LogComboManagerComponent, Verbose, TEXT("No branch found for this action")); + } +} +void UComboManagerComponent::DEBUG__UnlockAction(TObjectPtr Unlock) +{ + this->AttachedInputBuffer->UnlockComboInput(Unlock); +} + + +void UComboManagerComponent::BeginNodeTransition(const UComboSequenceNode *NextNode) +{ + this->PreviousNode = this->ActiveNode; + this->ActiveNode = NextNode; + this->GetWorld()->GetTimerManager().SetTimer(this->FinishTransitionTimer, this, &UComboManagerComponent::FinishTransition, 0.5f); + + this->GetWorld()->GetTimerManager().SetTimer(this->DEBUG__ResetComboTimer, this, &UComboManagerComponent::DEBUG__ResetCombo, 1.0f); +} +void UComboManagerComponent::DEBUG__ResetCombo() +{ + this->ActiveNode = this->PreviousNode = nullptr; +} + +void UComboManagerComponent::FinishTransition() +{ + this->PreviousNode = this->ActiveNode; +} diff --git a/Source/ComboInput/Private/Components/InputBufferComponent.cpp b/Source/ComboInput/Private/Components/InputBufferComponent.cpp new file mode 100644 index 0000000..bcdc54a --- /dev/null +++ b/Source/ComboInput/Private/Components/InputBufferComponent.cpp @@ -0,0 +1,191 @@ +// ©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 InputActionsToBind; + for (const UComboInputAsset *ComboInput : this->ComboActions) + { + for (const UInputAction *InputAction : ComboInput->ActionGroup) + { + InputActionsToBind.Add(InputAction); + } + } + + APlayerController *Controller = Cast(this->GetOwner()); + checkf(Controller, TEXT("No player controller found as owner of %s"), *this->GetName()); + + if (this->EnhancedInputComponent = Cast(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 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; + } + + //this->GetWorld()->GetTimerManager().SetTimer(this->ForceUnlockTimerHandle, this, &UInputBufferComponent::ForceUnlockComboInputs, this->ForceUnlockTimerLength); + + 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 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(); + } +} diff --git a/Source/ComboInput/Private/Interfaces/ComboHandlerInterface.cpp b/Source/ComboInput/Private/Interfaces/ComboHandlerInterface.cpp new file mode 100644 index 0000000..7a66c33 --- /dev/null +++ b/Source/ComboInput/Private/Interfaces/ComboHandlerInterface.cpp @@ -0,0 +1,17 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Interfaces/ComboHandlerInterface.h" + +#include "Components/ComboManagerComponent.h" +#include "Components/InputBufferComponent.h" + + +UInputBufferComponent *IComboHandlerInterface::GetInputBuffer_Implementation() const +{ + return nullptr; +} + +UComboManagerComponent *IComboHandlerInterface::GetComboManager_Implementation() const +{ + return nullptr; +} diff --git a/Source/ComboInput/Public/ComboInput.h b/Source/ComboInput/Public/ComboInput.h new file mode 100644 index 0000000..03441a7 --- /dev/null +++ b/Source/ComboInput/Public/ComboInput.h @@ -0,0 +1,37 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + + +class FComboInputModule : public IModuleInterface +{ +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static FComboInputModule &Get() + { + return FModuleManager::LoadModuleChecked("ComboInput"); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ComboInput"); + } + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/ComboInput/Public/Components/ComboManagerComponent.h b/Source/ComboInput/Public/Components/ComboManagerComponent.h new file mode 100644 index 0000000..d364f79 --- /dev/null +++ b/Source/ComboInput/Public/Components/ComboManagerComponent.h @@ -0,0 +1,90 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Engine/DataAsset.h" + +#include "ComboManagerComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogComboManagerComponent, Log, All); + + +USTRUCT(BlueprintType) +struct COMBOINPUT_API FComboSequenceAction +{ + GENERATED_BODY() +public: + // Action to perform when the associated combo sequence node is activated. + UPROPERTY(BlueprintReadOnly, EditAnywhere) + TObjectPtr ComboAction; + + // Sequence node to switch to once this action is complete. + UPROPERTY(BlueprintReadOnly, EditAnywhere) + TObjectPtr NextNode; +}; + +UCLASS(BlueprintType) +class COMBOINPUT_API UComboAction : public UDataAsset +{ + GENERATED_BODY() + +public: + // Human-readable name of this combo action. + UPROPERTY(BlueprintReadOnly, EditAnywhere) + FName ActionName; +}; + +UCLASS(BlueprintType) +class COMBOINPUT_API UComboSequenceNode : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, EditAnywhere) + TMap ComboBranch; +}; + +UCLASS(BlueprintType, ClassGroup=(Input), meta=(BlueprintSpawnableComponent)) +class COMBOINPUT_API UComboManagerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UComboManagerComponent(); + virtual void BeginPlay() override; + +protected: + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + TObjectPtr DefaultStartNode; + + // This input will be recognised as an offset input, which cancels a node transition + // if activated early enough in the transition. This locks the current place in the + // combo and allows this combo string to be continued after this input has executed. + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + TObjectPtr OffsetInput; + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + TMap, float> DEBUG__UnlockTimers; + +private: + void ComboInputReceived(const class UComboInputAsset *Input); + + void BeginNodeTransition(const class UComboSequenceNode *NextNode); + void FinishTransition(); + + void DEBUG__UnlockAction(TObjectPtr Unlock); + void DEBUG__ResetCombo(); + + const class UComboSequenceNode *ActiveNode = nullptr; + const class UComboSequenceNode *PreviousNode = nullptr; + + TObjectPtr AttachedInputBuffer; + + FTimerHandle FinishTransitionTimer; + FTimerHandle DEBUG__ResetComboTimer; + + TMap, FTimerHandle> DEBUG__TimerHandles; +}; diff --git a/Source/ComboInput/Public/Components/InputBufferComponent.h b/Source/ComboInput/Public/Components/InputBufferComponent.h new file mode 100644 index 0000000..960780b --- /dev/null +++ b/Source/ComboInput/Public/Components/InputBufferComponent.h @@ -0,0 +1,122 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EnhancedInputComponent.h" +#include "Components/ActorComponent.h" + +#include "InputBufferComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogInputBufferComponent, Log, All); + + +UCLASS(BlueprintType) +class COMBOINPUT_API UComboInputAsset : public UDataAsset +{ + GENERATED_BODY() + +public: + bool MatchesInputAction(const class UInputAction* Action) const + { + if (this->ActionGroup.Num() == 1 && this->ActionGroup.Contains(Action)) + { + return true; + } + return false; + } + bool MatchesInputActions(TSet Actions) const + { + if (this->ActionGroup.Num() == Actions.Num()) + { + for (const UInputAction *Action : Actions) + { + if (!this->ActionGroup.Contains(Action)) + { + return false; + } + } + return true; + } + return false; + } + + // Human-readable name of this combo input. + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FName ComboInputName; + + // Combined actions that add up to this combo input when activated + // within a short time of one another. If only one is present, then + // this combo input asset will simply represent that action. + UPROPERTY(BlueprintReadWrite, EditAnywhere) + TSet> ActionGroup; + + // Combo inputs that should be prevented from occurring during this + // action. These will be locked when the action is broadcast, and + // should be unlocked by the receiving actor by sending an unlock + // event. + UPROPERTY(BlueprintReadWrite, EditAnywhere) + TSet> LockedComboInputs; +}; + + +UCLASS(BlueprintType, ClassGroup=(Input), meta=(BlueprintSpawnableComponent)) +class COMBOINPUT_API UInputBufferComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UInputBufferComponent(); + virtual void BeginPlay() override; + + UFUNCTION(BlueprintCallable) + void UnlockComboInput(const class UComboInputAsset *Unlocked); + + // List of possible combo inputs that can be taken. A combo input is selected from this list + // either if an action is made while the current combo is inactive, or when the previous + // action expires. + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + TSet ComboActions; + + // Length of time after releasing an input to keep the associated combo action buffered before clearing it. + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="0.0", UIMax="0.5")) + float InputReleaseExpirationTimerLength = 0.0666666666666666667f; + + // Length of time within which we can recognise multiple button presses as one input. + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="0.02", UIMax="0.25")) + float MultiPressTimerLength = 0.025f; + + // Lenght of time before forcibly unlocking any locked combo inputs. + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="1.0", UIMax="10.0")) + float ForceUnlockTimerLength = 5.0f; + + DECLARE_DELEGATE_OneParam(FNewComboInput, const class UComboInputAsset*); + FNewComboInput NewComboInput; + +private: + void AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action); + void ExpireAction(const FInputActionValue &Value, const class UInputAction *Action); + + void ActivateComboInput(const class UComboInputAsset *ComboInput); + + void ClearMultiPresses(); + void ExpireBufferedActions(); + + TObjectPtr EnhancedInputComponent; + + // Currently active combo input. + TObjectPtr InputBufferActive; + + // Combo input held until the current input has expired. + TObjectPtr InputBufferHold; + + // Set of currently locked actions; will not be activated until an unlock signal is received. + TSet> LockedComboInputs; + + TSet MostRecentActions; + TSet ExpiringActions; + + FTimerHandle MultiPressTimerHandle; + FTimerHandle InputReleaseExpirationTimerHandle; + FTimerHandle ForceUnlockTimerHandle; +}; diff --git a/Source/ComboInput/Public/Interfaces/ComboHandlerInterface.h b/Source/ComboInput/Public/Interfaces/ComboHandlerInterface.h new file mode 100644 index 0000000..ef07ed3 --- /dev/null +++ b/Source/ComboInput/Public/Interfaces/ComboHandlerInterface.h @@ -0,0 +1,32 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "ComboHandlerInterface.generated.h" + + +// This class does not need to be modified. +UINTERFACE(MinimalAPI, meta=(Blueprintable)) +class UComboHandlerInterface : public UInterface +{ + GENERATED_BODY() +}; +/** + * Interface for anything that handles combo inputs and contains a + * UComboManagerComponent. + */ +class COMBOINPUT_API IComboHandlerInterface +{ + GENERATED_BODY() + + // Add interface functions to this class. This is the class that will be inherited to implement this interface. +public: + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + class UInputBufferComponent *GetInputBuffer() const; + virtual class UInputBufferComponent *GetInputBuffer_Implementation() const; + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + class UComboManagerComponent *GetComboManager() const; + virtual class UComboManagerComponent *GetComboManager_Implementation() const; +}; diff --git a/Source/ComboInputEditor/ComboInputEditor.build.cs b/Source/ComboInputEditor/ComboInputEditor.build.cs new file mode 100644 index 0000000..b3c3437 --- /dev/null +++ b/Source/ComboInputEditor/ComboInputEditor.build.cs @@ -0,0 +1,53 @@ +using UnrealBuildTool; + +public class ComboInputEditor : ModuleRules +{ + public ComboInputEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + bLegacyPublicIncludePaths = false; + ShadowVariableWarningLevel = WarningLevel.Error; + + PrecompileForTargets = PrecompileTargetsType.None; + bPrecompile = false; + bUsePrecompiled = false; + + PublicDependencyModuleNames.AddRange + (new string[] + { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "AssetTools" + } + ); + + PrivateDependencyModuleNames.AddRange + ( + new string[] + { + "ComboInput", + + "AssetTools", + "Slate", + "SlateCore", + "GraphEditor", + "PropertyEditor", + "EditorStyle", + "Kismet", + "KismetWidgets", + "ApplicationCore", + "ToolMenus", + "DeveloperSettings", + "Projects", + + "BlueprintGraph", + "InputCore", + + "MainFrame" + // ... add private dependencies that you statically link with here ... + } + ); + } +} \ No newline at end of file diff --git a/Source/ComboInputEditor/Private/ComboInputEditor.cpp b/Source/ComboInputEditor/Private/ComboInputEditor.cpp new file mode 100644 index 0000000..a43946c --- /dev/null +++ b/Source/ComboInputEditor/Private/ComboInputEditor.cpp @@ -0,0 +1,23 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "ComboInputEditor.h" + +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FComboInputEditorModule" + + +void FComboInputEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FComboInputEditorModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FComboInputEditorModule, ComboInputEditorModule) \ No newline at end of file diff --git a/Source/ComboInputEditor/Public/ComboInputEditor.h b/Source/ComboInputEditor/Public/ComboInputEditor.h new file mode 100644 index 0000000..b162af8 --- /dev/null +++ b/Source/ComboInputEditor/Public/ComboInputEditor.h @@ -0,0 +1,70 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" +#include "EdGraphUtilities.h" +#include "IAssetTools.h" +// #include "Interfaces/IHttpRequest.h" + +// class FHttpModule; +// class FSlateStyleSet; + +class FComboInputEditorModule : public IModuleInterface +{ + public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static FComboInputEditorModule &Get() + { + return FModuleManager::LoadModuleChecked( "ComboInputEditor" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ComboInputEditor"); + } + + /* Called when the module is loaded */ + virtual void StartupModule() override; + + /* Called when the module is unloaded */ + virtual void ShutdownModule() override; + +private: + + // void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action); + + // void OnGetResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); + // UFUNCTION() void SendHTTPGet(); + + // void PluginButtonClicked(); + // void RegisterMenus(); + +private: + + // TSharedPtr PluginCommands; + // TSharedPtr DialogueTreeSet; + // TSharedPtr MounteaDialogueGraphAssetActions; + // TSharedPtr MounteaDialogueAdditionalDataAssetActions; + // TSharedPtr MounteaDialogueDecoratorAssetAction; + + // TSharedPtr GraphPanelNodeFactory_MounteaDialogueGraph; + // TArray< TSharedPtr > CreatedAssetTypeActions; + + // EAssetTypeCategories::Type MounteaDialogueGraphAssetCategoryBit; + // FHttpModule* Http; + + // TArray RegisteredCustomClassLayouts; + // TArray RegisteredCustomPropertyTypeLayout; +}; \ No newline at end of file