OLS/Source/ols/Private/Components/OLSHeroComponent.cpp
LongLy 57b53b9c0c Implemented OLSCameraMode.
Addressed @TODOs related to custom logs and OLSCameraMode
2025-01-20 14:08:07 -07:00

552 lines
18 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "Components/OLSHeroComponent.h"
#include "EnhancedInputSubsystems.h"
#include "OLSLog.h"
#include "AbilitySystem/OLSAbilitySystemComponent.h"
#include "Camera/OLSCameraMode.h"
#include "Characters/OLSCharacter.h"
#include "Components/GameFrameworkComponentManager.h"
#include "Components/OLSPawnExtensionComponent.h"
#include "DataAssets/OLSPawnDataAsset.h"
#include "GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h"
#include "Player/OLSPlayerController.h"
#include "Player/OLSPlayerState.h"
#if WITH_EDITOR
#include "Misc/UObjectToken.h"
#endif // WITH_EDITOR
DEFINE_LOG_CATEGORY(LogOLSHeroComponent);
namespace OLSHero
{
static const float LookYawRate = 300.0f;
static const float LookPitchRate = 165.0f;
};
const FName UOLSHeroComponent::NAME_BindInputsNow("BindInputsNow");
const FName UOLSHeroComponent::NAME_ActorFeatureName("Hero");
UOLSHeroComponent::UOLSHeroComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
{
AbilityCameraMode = nullptr;
bIsReadyToBindInputs = false;
}
FName UOLSHeroComponent::GetFeatureName() const
{
// Don't call Super since it does not fit in this.
return NAME_ActorFeatureName;
}
bool UOLSHeroComponent::CanChangeInitState(UGameFrameworkComponentManager* manager,
FGameplayTag currentState,
FGameplayTag desiredState) const
{
// Don't call Super since it does not fit into this.
check(manager);
APawn* pawn = GetPawn<APawn>();
//@TODO: implement LyraGameplayTags::InitState_Spawned.
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
//@TODO: implement LyraGameplayTags::InitState_GameplayReady.
// if (!currentState.IsValid() && desiredState == LyraGameplayTags::InitState_Spawned)
// {
// // As long as we have a real pawn, let us transition
// if (pawn)
// {
// return true;
// }
// }
// else if (currentState == LyraGameplayTags::InitState_Spawned && desiredState == LyraGameplayTags::InitState_DataAvailable)
// {
// // The player state is required.
// if (!GetPlayerState<AOLSPlayerState>())
// {
// 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 hasControllerPairedWithPS = (controller != nullptr) && \
// (controller->PlayerState != nullptr) && \
// (controller->PlayerState->GetOwner() == controller);
//
// if (!hasControllerPairedWithPS)
// {
// return false;
// }
// }
//
// const bool isLocallyControlled = pawn->IsLocallyControlled();
// const bool isBot = pawn->IsBotControlled();
//
// if (isLocallyControlled && !isBot)
// {
// AOLSPlayerController* playerController = GetController<AOLSPlayerController>();
//
// // The input component and local player is required when locally controlled.
// if (!pawn->InputComponent || !playerController || !playerController->GetLocalPlayer())
// {
// return false;
// }
// }
//
// return true;
// }
// else if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized)
// {
// // Wait for player state and extension component
// AOLSPlayerState* playerState = GetPlayerState<AOLSPlayerState>();
//
// return playerState && manager->HasFeatureReachedInitState(pawn, UOLSPawnExtensionComponent::NAME_ActorFeatureName, LyraGameplayTags::InitState_DataInitialized);
// }
// else if (currentState == LyraGameplayTags::InitState_DataInitialized && desiredState == LyraGameplayTags::InitState_GameplayReady)
// {
// // TODO add ability initialization checks?
// return true;
// }
return false;
}
void UOLSHeroComponent::HandleChangeInitState(UGameFrameworkComponentManager* manager,
FGameplayTag currentState,
FGameplayTag desiredState)
{
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
// if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized)
// {
// APawn* pawn = GetPawn<APawn>();
// AOLSPlayerState* playerState = GetPlayerState<AOLSPlayerState>();
// if (!ensure(pawn && playerState))
// {
// return;
// }
//
// const UOLSPawnDataAsset* pawnData = nullptr;
//
// if (UOLSPawnExtensionComponent* PawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
// {
// pawnData = PawnExtComp->GetPawnData<UOLSPawnDataAsset>();
//
// // 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(playerState->GetOLSAbilitySystemComponent(), playerState);
// }
//
// if (AOLSPlayerController* playerController = GetController<AOLSPlayerController>())
// {
// if (pawn->InputComponent)
// {
// InitializePlayerInput(pawn->InputComponent);
// }
// }
//
//
// // Hook up the delegate for all pawns, in case we spectate later
// // if (pawnData)
// // {
// // if (UOLSCameraComponent* CameraComponent = UOLSCameraComponent::FindCameraComponent(pawn))
// // {
// // CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode);
// // }
// // }
// }
}
void UOLSHeroComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& params)
{
// if (params.FeatureName == UOLSPawnExtensionComponent::NAME_ActorFeatureName)
// {
// //@TODO: implement LyraGameplayTags::InitState_DataAvailable.
// if (params.FeatureState == LyraGameplayTags::InitState_DataInitialized)
// {
// // If the extension component says all other components are initialized, try to progress to next state
// CheckDefaultInitialization();
// }
// }
}
void UOLSHeroComponent::CheckDefaultInitialization()
{
//@TODO: implement LyraGameplayTags::InitState_Spawned.
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
//@TODO: implement LyraGameplayTags::InitState_GameplayReady.
// static const TArray<FGameplayTag> stateChain = {
// LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable,
// LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::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);
}
UOLSHeroComponent* UOLSHeroComponent::FindHeroComponent(const AActor* actor)
{
return (actor ? actor->FindComponentByClass<UOLSHeroComponent>() : nullptr);
}
void UOLSHeroComponent::SetAbilityCameraMode(TSubclassOf<UOLSCameraMode> CameraMode,
const FGameplayAbilitySpecHandle& OwningSpecHandle)
{
}
void UOLSHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& owningSpecHandle)
{
if (AbilityCameraModeOwningSpecHandle == owningSpecHandle)
{
//@TODO: implement UOLSCameraMode.
// AbilityCameraMode = nullptr;
AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle();
}
}
void UOLSHeroComponent::AddAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig)
{
TArray<uint32> bindHandles;
const APawn* pawn = GetPawn<APawn>();
if (!pawn)
{
return;
}
const APlayerController* playerController = GetController<APlayerController>();
check(playerController);
const ULocalPlayer* localPlayer = playerController->GetLocalPlayer();
check(localPlayer);
UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
check(subsystem);
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
{
//@TODO: Implement UOLSInputComponent.
// UOLSInputComponent* inputComponent = pawn->FindComponentByClass<UOLSInputComponent>();
// if (ensureMsgf(inputComponent,
// TEXT(
// "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it."
// )))
// {
// inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed,
// &ThisClass::Input_AbilityInputTagReleased, /*out*/ bindHandles);
// }
}
}
void UOLSHeroComponent::RemoveAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig)
{
//@TODO: Implement me!
}
bool UOLSHeroComponent::IsReadyToBindInputs() const
{
return bIsReadyToBindInputs;
}
void UOLSHeroComponent::OnRegister()
{
Super::OnRegister();
if (!GetPawn<APawn>())
{
OLS_LOG(LogOLSHeroComponent, Error,
TEXT(
"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("OLSHeroComponent", "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("OLSHeroComponent");
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();
}
}
void UOLSHeroComponent::BeginPlay()
{
Super::BeginPlay();
// Listen for when the pawn extension component changes init state
BindOnActorInitStateChanged(UOLSPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false);
// Notifies that we are done spawning, then try the rest of initialization
//@TODO: implement LyraGameplayTags::InitState_Spawned.
// ensure(TryToChangeInitState(LyraGameplayTags::InitState_Spawned));
CheckDefaultInitialization();
}
void UOLSHeroComponent::EndPlay(const EEndPlayReason::Type endPlayReason)
{
UnregisterInitStateFeature();
Super::EndPlay(endPlayReason);
}
void UOLSHeroComponent::InitializePlayerInput(UInputComponent* playerInputComponent)
{
check(playerInputComponent);
const APawn* Pawn = GetPawn<APawn>();
if (!Pawn)
{
return;
}
const APlayerController* playerController = GetController<APlayerController>();
check(playerController);
//@TODO implement UOLSLocalPlayer.
// const UOLSLocalPlayer* localPlayer = Cast<UOLSLocalPlayer>(playerController->GetLocalPlayer());
// check(localPlayer);
// UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
// check(subsystem);
//
// subsystem->ClearAllMappings();
// if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
// {
// if (const UOLSPawnDataAsset* pawnData = pawnExtComp->GetPawnData<UOLSPawnDataAsset>())
// {
// //@TODO: Implement UOLSInputConfig
// if (const UOLSInputConfig* inputConfig = pawnData->InputConfig)
// {
// for (const FOLSInputMappingContextAndPriority& mapping : DefaultInputMappings)
// {
// if (UInputMappingContext* imc = mapping.InputMapping.Get())
// {
// if (mapping.bShouldRegisterWithSettings)
// {
// if (UEnhancedInputUserSettings* settings = subsystem->GetUserSettings())
// {
// settings->RegisterInputMappingContext(imc);
// }
//
// FModifyContextOptions options = {};
// options.bIgnoreAllPressedKeysUntilRelease = false;
// // Actually add the config to the local player
// subsystem->AddMappingContext(imc, mapping.Priority, options);
// }
// }
// }
//
// // The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action.
// // If you want this functionality but still want to change your input component class, make it a subclass
// // of the ULyraInputComponent or modify this component accordingly.
// //@TODO: Implement UOLSInputComponent.
// // UOLSInputComponent* inputComponent = Cast<UOLSInputComponent>(playerInputComponent);
// if (ensureMsgf(inputComponent,
// TEXT(
// "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it."
// )))
// {
// // Add the key mappings that may have been set by the player
// inputComponent->AddInputMappings(inputConfig, subsystem);
//
// // This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will
// // be triggered directly by these input actions Triggered events.
// TArray<uint32> BindHandles;
// inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
//
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
// }
// }
// }
// }
if (ensure(!bIsReadyToBindInputs))
{
bIsReadyToBindInputs = true;
}
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APlayerController*>(playerController), NAME_BindInputsNow);
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APawn*>(Pawn), NAME_BindInputsNow);
}
void UOLSHeroComponent::Input_AbilityInputTagPressed(FGameplayTag inputTag)
{
if (const APawn* pawn = GetPawn<APawn>())
{
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
{
if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent())
{
asc->AbilityInputTagPressed(inputTag);
}
}
}
}
void UOLSHeroComponent::Input_AbilityInputTagReleased(FGameplayTag inputTag)
{
const APawn* pawn = GetPawn<APawn>();
if (!pawn)
{
return;
}
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
{
if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent())
{
asc->AbilityInputTagReleased(inputTag);
}
}
}
void UOLSHeroComponent::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 (AOLSPlayerController* playerController = Cast<AOLSPlayerController>(controller))
{
//@TODO: Should we have this?
// playerController->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 UOLSHeroComponent::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 UOLSHeroComponent::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 * OLSHero::LookYawRate * world->GetDeltaSeconds());
}
if (value.Y != 0.0f)
{
pawn->AddControllerPitchInput(value.Y * OLSHero::LookPitchRate * world->GetDeltaSeconds());
}
}
void UOLSHeroComponent::Input_Crouch(const FInputActionValue& inputActionValue)
{
if (AOLSCharacter* character = GetPawn<AOLSCharacter>())
{
character->ToggleCrouch();
}
}
void UOLSHeroComponent::Input_AutoRun(const FInputActionValue& inputActionValue)
{
if (APawn* Pawp = GetPawn<APawn>())
{
if (AOLSPlayerController* controller = Cast<AOLSPlayerController>(Pawp->GetController()))
{
//@TODO: Should we have this?
// Toggle auto running
// controller->SetIsAutoRunning(!controller->GetIsAutoRunning());
}
}
}
TSubclassOf<UOLSCameraMode> UOLSHeroComponent::DetermineCameraMode() const
{
if (AbilityCameraMode)
{
return AbilityCameraMode;
}
const APawn* Pawn = GetPawn<APawn>();
if (!Pawn)
{
return nullptr;
}
if (UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
{
if (const UOLSPawnDataAsset* pawnData = pawnExtComp->GetPawnData<UOLSPawnDataAsset>())
{
return pawnData->DefaultCameraMode;
}
}
return nullptr;
}