499 lines
16 KiB
C++
499 lines
16 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LyraHeroComponent.h"
|
|
#include "LyraLogChannels.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "EnhancedInputComponent.h"
|
|
#include "EnhancedInputSubsystems.h"
|
|
#include "Player/LyraPlayerController.h"
|
|
#include "Player/LyraPlayerState.h"
|
|
#include "Player/LyraLocalPlayer.h"
|
|
#include "Character/LyraPawnExtensionComponent.h"
|
|
#include "Character/LyraPawnData.h"
|
|
#include "Character/LyraCharacter.h"
|
|
#include "AbilitySystem/LyraAbilitySystemComponent.h"
|
|
#include "Input/LyraInputConfig.h"
|
|
#include "Input/LyraInputComponent.h"
|
|
#include "Camera/LyraCameraComponent.h"
|
|
#include "LyraGameplayTags.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "Components/GameFrameworkComponentManager.h"
|
|
#include "Settings/LyraSettingsLocal.h"
|
|
#include "System/LyraAssetManager.h"
|
|
#include "PlayerMappableInputConfig.h"
|
|
#include "Camera/LyraCameraMode.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Misc/UObjectToken.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
namespace LyraHero
|
|
{
|
|
static const float LookYawRate = 300.0f;
|
|
static const float LookPitchRate = 165.0f;
|
|
};
|
|
|
|
const FName ULyraHeroComponent::NAME_BindInputsNow("BindInputsNow");
|
|
const FName ULyraHeroComponent::NAME_ActorFeatureName("Hero");
|
|
|
|
ULyraHeroComponent::ULyraHeroComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
AbilityCameraMode = nullptr;
|
|
bReadyToBindInputs = false;
|
|
}
|
|
|
|
void ULyraHeroComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
if (!GetPawn<APawn>())
|
|
{
|
|
UE_LOG(LogLyra, Error, TEXT("[ULyraHeroComponent::OnRegister] This component has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint."));
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
static const FText Message = NSLOCTEXT("LyraHeroComponent", "NotOnPawnError", "has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint. This will cause a crash if you PIE!");
|
|
static const FName HeroMessageLogName = TEXT("LyraHeroComponent");
|
|
|
|
FMessageLog(HeroMessageLogName).Error()
|
|
->AddToken(FUObjectToken::Create(this, FText::FromString(GetNameSafe(this))))
|
|
->AddToken(FTextToken::Create(Message));
|
|
|
|
FMessageLog(HeroMessageLogName).Open();
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Register with the init state system early, this will only work if this is a game world
|
|
RegisterInitStateFeature();
|
|
}
|
|
}
|
|
|
|
bool ULyraHeroComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const
|
|
{
|
|
check(Manager);
|
|
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
|
|
if (!CurrentState.IsValid() && DesiredState == InitTags.InitState_Spawned)
|
|
{
|
|
// As long as we have a real pawn, let us transition
|
|
if (Pawn)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (CurrentState == InitTags.InitState_Spawned && DesiredState == InitTags.InitState_DataAvailable)
|
|
{
|
|
// The player state is required.
|
|
if (!GetPlayerState<ALyraPlayerState>())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we're authority or autonomous, we need to wait for a controller with registered ownership of the player state.
|
|
if (Pawn->GetLocalRole() != ROLE_SimulatedProxy)
|
|
{
|
|
AController* Controller = GetController<AController>();
|
|
|
|
const bool bHasControllerPairedWithPS = (Controller != nullptr) && \
|
|
(Controller->PlayerState != nullptr) && \
|
|
(Controller->PlayerState->GetOwner() == Controller);
|
|
|
|
if (!bHasControllerPairedWithPS)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const bool bIsLocallyControlled = Pawn->IsLocallyControlled();
|
|
const bool bIsBot = Pawn->IsBotControlled();
|
|
|
|
if (bIsLocallyControlled && !bIsBot)
|
|
{
|
|
ALyraPlayerController* LyraPC = GetController<ALyraPlayerController>();
|
|
|
|
// The input component and local player is required when locally controlled.
|
|
if (!Pawn->InputComponent || !LyraPC || !LyraPC->GetLocalPlayer())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (CurrentState == InitTags.InitState_DataAvailable && DesiredState == InitTags.InitState_DataInitialized)
|
|
{
|
|
// Wait for player state and extension component
|
|
ALyraPlayerState* LyraPS = GetPlayerState<ALyraPlayerState>();
|
|
|
|
return LyraPS && Manager->HasFeatureReachedInitState(Pawn, ULyraPawnExtensionComponent::NAME_ActorFeatureName, InitTags.InitState_DataInitialized);
|
|
}
|
|
else if (CurrentState == InitTags.InitState_DataInitialized && DesiredState == InitTags.InitState_GameplayReady)
|
|
{
|
|
// TODO add ability initialization checks?
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULyraHeroComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
|
|
{
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
if (CurrentState == FLyraGameplayTags::Get().InitState_DataAvailable && DesiredState == FLyraGameplayTags::Get().InitState_DataInitialized)
|
|
{
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
ALyraPlayerState* LyraPS = GetPlayerState<ALyraPlayerState>();
|
|
if (!ensure(Pawn && LyraPS))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bIsLocallyControlled = Pawn->IsLocallyControlled();
|
|
const ULyraPawnData* PawnData = nullptr;
|
|
|
|
if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
PawnData = PawnExtComp->GetPawnData<ULyraPawnData>();
|
|
|
|
// The player state holds the persistent data for this player (state that persists across deaths and multiple pawns).
|
|
// The ability system component and attribute sets live on the player state.
|
|
PawnExtComp->InitializeAbilitySystem(LyraPS->GetLyraAbilitySystemComponent(), LyraPS);
|
|
}
|
|
|
|
if (ALyraPlayerController* LyraPC = GetController<ALyraPlayerController>())
|
|
{
|
|
if (Pawn->InputComponent != nullptr)
|
|
{
|
|
InitializePlayerInput(Pawn->InputComponent);
|
|
}
|
|
}
|
|
|
|
if (bIsLocallyControlled && PawnData)
|
|
{
|
|
if (ULyraCameraComponent* CameraComponent = ULyraCameraComponent::FindCameraComponent(Pawn))
|
|
{
|
|
CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params)
|
|
{
|
|
if (Params.FeatureName == ULyraPawnExtensionComponent::NAME_ActorFeatureName)
|
|
{
|
|
if (Params.FeatureState == FLyraGameplayTags::Get().InitState_DataInitialized)
|
|
{
|
|
// If the extension component says all all other components are initialized, try to progress to next state
|
|
CheckDefaultInitialization();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::CheckDefaultInitialization()
|
|
{
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
static const TArray<FGameplayTag> StateChain = { InitTags.InitState_Spawned, InitTags.InitState_DataAvailable, InitTags.InitState_DataInitialized, InitTags.InitState_GameplayReady };
|
|
|
|
// This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready
|
|
ContinueInitStateChain(StateChain);
|
|
}
|
|
|
|
void ULyraHeroComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// Listen for when the pawn extension component changes init state
|
|
BindOnActorInitStateChanged(ULyraPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false);
|
|
|
|
// Notifies that we are done spawning, then try the rest of initialization
|
|
ensure(TryToChangeInitState(FLyraGameplayTags::Get().InitState_Spawned));
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraHeroComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
UnregisterInitStateFeature();
|
|
|
|
Super::EndPlay(EndPlayReason);
|
|
}
|
|
|
|
void ULyraHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent)
|
|
{
|
|
check(PlayerInputComponent);
|
|
|
|
const APawn* Pawn = GetPawn<APawn>();
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const APlayerController* PC = GetController<APlayerController>();
|
|
check(PC);
|
|
|
|
const ULyraLocalPlayer* LP = Cast<ULyraLocalPlayer>(PC->GetLocalPlayer());
|
|
check(LP);
|
|
|
|
UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
|
check(Subsystem);
|
|
|
|
Subsystem->ClearAllMappings();
|
|
|
|
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData<ULyraPawnData>())
|
|
{
|
|
if (const ULyraInputConfig* InputConfig = PawnData->InputConfig)
|
|
{
|
|
const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get();
|
|
|
|
// Register any default input configs with the settings so that they will be applied to the player during AddInputMappings
|
|
for (const FMappableConfigPair& Pair : DefaultInputConfigs)
|
|
{
|
|
if (Pair.bShouldActivateAutomatically && Pair.CanBeActivated())
|
|
{
|
|
FModifyContextOptions Options = {};
|
|
Options.bIgnoreAllPressedKeysUntilRelease = false;
|
|
// Actually add the config to the local player
|
|
Subsystem->AddPlayerMappableConfig(Pair.Config.LoadSynchronous(), Options);
|
|
}
|
|
}
|
|
|
|
ULyraInputComponent* LyraIC = CastChecked<ULyraInputComponent>(PlayerInputComponent);
|
|
LyraIC->AddInputMappings(InputConfig, Subsystem);
|
|
|
|
TArray<uint32> BindHandles;
|
|
LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
|
|
|
|
LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
|
|
LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
|
|
LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
|
|
LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
|
|
LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ensure(!bReadyToBindInputs))
|
|
{
|
|
bReadyToBindInputs = true;
|
|
}
|
|
|
|
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APlayerController*>(PC), NAME_BindInputsNow);
|
|
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APawn*>(Pawn), NAME_BindInputsNow);
|
|
}
|
|
|
|
void ULyraHeroComponent::AddAdditionalInputConfig(const ULyraInputConfig* InputConfig)
|
|
{
|
|
TArray<uint32> BindHandles;
|
|
|
|
const APawn* Pawn = GetPawn<APawn>();
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ULyraInputComponent* LyraIC = Pawn->FindComponentByClass<ULyraInputComponent>();
|
|
check(LyraIC);
|
|
|
|
const APlayerController* PC = GetController<APlayerController>();
|
|
check(PC);
|
|
|
|
const ULocalPlayer* LP = PC->GetLocalPlayer();
|
|
check(LP);
|
|
|
|
UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
|
check(Subsystem);
|
|
|
|
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::RemoveAdditionalInputConfig(const ULyraInputConfig* InputConfig)
|
|
{
|
|
//@TODO: Implement me!
|
|
}
|
|
|
|
bool ULyraHeroComponent::IsReadyToBindInputs() const
|
|
{
|
|
return bReadyToBindInputs;
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag)
|
|
{
|
|
if (const APawn* Pawn = GetPawn<APawn>())
|
|
{
|
|
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent())
|
|
{
|
|
LyraASC->AbilityInputTagPressed(InputTag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_AbilityInputTagReleased(FGameplayTag InputTag)
|
|
{
|
|
const APawn* Pawn = GetPawn<APawn>();
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent())
|
|
{
|
|
LyraASC->AbilityInputTagReleased(InputTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_Move(const FInputActionValue& InputActionValue)
|
|
{
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
AController* Controller = Pawn ? Pawn->GetController() : nullptr;
|
|
|
|
// If the player has attempted to move again then cancel auto running
|
|
if (ALyraPlayerController* LyraController = Cast<ALyraPlayerController>(Controller))
|
|
{
|
|
LyraController->SetIsAutoRunning(false);
|
|
}
|
|
|
|
if (Controller)
|
|
{
|
|
const FVector2D Value = InputActionValue.Get<FVector2D>();
|
|
const FRotator MovementRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
|
|
|
|
if (Value.X != 0.0f)
|
|
{
|
|
const FVector MovementDirection = MovementRotation.RotateVector(FVector::RightVector);
|
|
Pawn->AddMovementInput(MovementDirection, Value.X);
|
|
}
|
|
|
|
if (Value.Y != 0.0f)
|
|
{
|
|
const FVector MovementDirection = MovementRotation.RotateVector(FVector::ForwardVector);
|
|
Pawn->AddMovementInput(MovementDirection, Value.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_LookMouse(const FInputActionValue& InputActionValue)
|
|
{
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FVector2D Value = InputActionValue.Get<FVector2D>();
|
|
|
|
if (Value.X != 0.0f)
|
|
{
|
|
Pawn->AddControllerYawInput(Value.X);
|
|
}
|
|
|
|
if (Value.Y != 0.0f)
|
|
{
|
|
Pawn->AddControllerPitchInput(Value.Y);
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_LookStick(const FInputActionValue& InputActionValue)
|
|
{
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FVector2D Value = InputActionValue.Get<FVector2D>();
|
|
|
|
const UWorld* World = GetWorld();
|
|
check(World);
|
|
|
|
if (Value.X != 0.0f)
|
|
{
|
|
Pawn->AddControllerYawInput(Value.X * LyraHero::LookYawRate * World->GetDeltaSeconds());
|
|
}
|
|
|
|
if (Value.Y != 0.0f)
|
|
{
|
|
Pawn->AddControllerPitchInput(Value.Y * LyraHero::LookPitchRate * World->GetDeltaSeconds());
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_Crouch(const FInputActionValue& InputActionValue)
|
|
{
|
|
if (ALyraCharacter* Character = GetPawn<ALyraCharacter>())
|
|
{
|
|
Character->ToggleCrouch();
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::Input_AutoRun(const FInputActionValue& InputActionValue)
|
|
{
|
|
if (APawn* Pawn = GetPawn<APawn>())
|
|
{
|
|
if (ALyraPlayerController* Controller = Cast<ALyraPlayerController>(Pawn->GetController()))
|
|
{
|
|
// Toggle auto running
|
|
Controller->SetIsAutoRunning(!Controller->GetIsAutoRunning());
|
|
}
|
|
}
|
|
}
|
|
|
|
TSubclassOf<ULyraCameraMode> ULyraHeroComponent::DetermineCameraMode() const
|
|
{
|
|
if (AbilityCameraMode)
|
|
{
|
|
return AbilityCameraMode;
|
|
}
|
|
|
|
const APawn* Pawn = GetPawn<APawn>();
|
|
if (!Pawn)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
|
{
|
|
if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData<ULyraPawnData>())
|
|
{
|
|
return PawnData->DefaultCameraMode;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ULyraHeroComponent::SetAbilityCameraMode(TSubclassOf<ULyraCameraMode> CameraMode, const FGameplayAbilitySpecHandle& OwningSpecHandle)
|
|
{
|
|
if (CameraMode)
|
|
{
|
|
AbilityCameraMode = CameraMode;
|
|
AbilityCameraModeOwningSpecHandle = OwningSpecHandle;
|
|
}
|
|
}
|
|
|
|
void ULyraHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& OwningSpecHandle)
|
|
{
|
|
if (AbilityCameraModeOwningSpecHandle == OwningSpecHandle)
|
|
{
|
|
AbilityCameraMode = nullptr;
|
|
AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle();
|
|
}
|
|
}
|