// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraCharacter.h" #include "AI/Navigation/NavigationTypes.h" #include "AbilitySystem/LyraAbilitySystemComponent.h" #include "AbilitySystemComponent.h" #include "Camera/LyraCameraComponent.h" #include "Character/LyraHealthComponent.h" #include "Character/LyraPawnExtensionComponent.h" #include "Components/CapsuleComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "Engine/EngineBaseTypes.h" #include "Engine/World.h" #include "GameFramework/Character.h" #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/Controller.h" #include "GameplayTagContainer.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "LyraCharacterMovementComponent.h" #include "LyraGameplayTags.h" #include "LyraLogChannels.h" #include "Math/Rotator.h" #include "Math/UnrealMathSSE.h" #include "Math/Vector.h" #include "Misc/AssertionMacros.h" #include "Net/UnrealNetwork.h" #include "Player/LyraPlayerController.h" #include "Player/LyraPlayerState.h" #include "SignificanceManager.h" #include "System/LyraSignificanceManager.h" #include "Templates/Casts.h" #include "TimerManager.h" #include "Trace/Detail/Channel.h" #include "UObject/CoreNetTypes.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/UObjectBaseUtility.h" class AActor; class FLifetimeProperty; class IRepChangedPropertyTracker; class UInputComponent; static FName NAME_LyraCharacterCollisionProfile_Capsule(TEXT("LyraPawnCapsule")); static FName NAME_LyraCharacterCollisionProfile_Mesh(TEXT("LyraPawnMesh")); ALyraCharacter::ALyraCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) { // Avoid ticking characters if possible. PrimaryActorTick.bCanEverTick = false; PrimaryActorTick.bStartWithTickEnabled = false; NetCullDistanceSquared = 900000000.0f; UCapsuleComponent* CapsuleComp = GetCapsuleComponent(); check(CapsuleComp); CapsuleComp->InitCapsuleSize(40.0f, 90.0f); CapsuleComp->SetCollisionProfileName(NAME_LyraCharacterCollisionProfile_Capsule); USkeletalMeshComponent* MeshComp = GetMesh(); check(MeshComp); MeshComp->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f)); // Rotate mesh to be X forward since it is exported as Y forward. MeshComp->SetCollisionProfileName(NAME_LyraCharacterCollisionProfile_Mesh); ULyraCharacterMovementComponent* LyraMoveComp = CastChecked(GetCharacterMovement()); LyraMoveComp->GravityScale = 1.0f; LyraMoveComp->MaxAcceleration = 2400.0f; LyraMoveComp->BrakingFrictionFactor = 1.0f; LyraMoveComp->BrakingFriction = 6.0f; LyraMoveComp->GroundFriction = 8.0f; LyraMoveComp->BrakingDecelerationWalking = 1400.0f; LyraMoveComp->bUseControllerDesiredRotation = false; LyraMoveComp->bOrientRotationToMovement = false; LyraMoveComp->RotationRate = FRotator(0.0f, 720.0f, 0.0f); LyraMoveComp->bAllowPhysicsRotationDuringAnimRootMotion = false; LyraMoveComp->GetNavAgentPropertiesRef().bCanCrouch = true; LyraMoveComp->bCanWalkOffLedgesWhenCrouching = true; LyraMoveComp->SetCrouchedHalfHeight(65.0f); PawnExtComponent = CreateDefaultSubobject(TEXT("PawnExtensionComponent")); PawnExtComponent->OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemInitialized)); PawnExtComponent->OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemUninitialized)); HealthComponent = CreateDefaultSubobject(TEXT("HealthComponent")); HealthComponent->OnDeathStarted.AddDynamic(this, &ThisClass::OnDeathStarted); HealthComponent->OnDeathFinished.AddDynamic(this, &ThisClass::OnDeathFinished); CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); CameraComponent->SetRelativeLocation(FVector(-300.0f, 0.0f, 75.0f)); bUseControllerRotationPitch = false; bUseControllerRotationYaw = true; bUseControllerRotationRoll = false; BaseEyeHeight = 80.0f; CrouchedEyeHeight = 50.0f; } void ALyraCharacter::PreInitializeComponents() { Super::PreInitializeComponents(); } void ALyraCharacter::BeginPlay() { Super::BeginPlay(); UWorld* World = GetWorld(); const bool bRegisterWithSignificanceManager = !IsNetMode(NM_DedicatedServer); if (bRegisterWithSignificanceManager) { if (ULyraSignificanceManager* SignificanceManager = USignificanceManager::Get(World)) { //@TODO: SignificanceManager->RegisterObject(this, (EFortSignificanceType)SignificanceType); } } } void ALyraCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); UWorld* World = GetWorld(); const bool bRegisterWithSignificanceManager = !IsNetMode(NM_DedicatedServer); if (bRegisterWithSignificanceManager) { if (ULyraSignificanceManager* SignificanceManager = USignificanceManager::Get(World)) { SignificanceManager->UnregisterObject(this); } } } void ALyraCharacter::Reset() { DisableMovementAndCollision(); K2_OnReset(); UninitAndDestroy(); } void ALyraCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION(ThisClass, ReplicatedAcceleration, COND_SimulatedOnly); DOREPLIFETIME(ThisClass, MyTeamID) } void ALyraCharacter::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) { Super::PreReplication(ChangedPropertyTracker); if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement()) { // Compress Acceleration: XY components as direction + magnitude, Z component as direct value const double MaxAccel = MovementComponent->MaxAcceleration; const FVector CurrentAccel = MovementComponent->GetCurrentAcceleration(); double AccelXYRadians, AccelXYMagnitude; FMath::CartesianToPolar(CurrentAccel.X, CurrentAccel.Y, AccelXYMagnitude, AccelXYRadians); ReplicatedAcceleration.AccelXYRadians = FMath::FloorToInt((AccelXYRadians / TWO_PI) * 255.0); // [0, 2PI] -> [0, 255] ReplicatedAcceleration.AccelXYMagnitude = FMath::FloorToInt((AccelXYMagnitude / MaxAccel) * 255.0); // [0, MaxAccel] -> [0, 255] ReplicatedAcceleration.AccelZ = FMath::FloorToInt((CurrentAccel.Z / MaxAccel) * 127.0); // [-MaxAccel, MaxAccel] -> [-127, 127] } } void ALyraCharacter::NotifyControllerChanged() { const FGenericTeamId OldTeamId = GetGenericTeamId(); Super::NotifyControllerChanged(); // Update our team ID based on the controller if (HasAuthority() && (Controller != nullptr)) { if (ILyraTeamAgentInterface* ControllerWithTeam = Cast(Controller)) { MyTeamID = ControllerWithTeam->GetGenericTeamId(); ConditionalBroadcastTeamChanged(this, OldTeamId, MyTeamID); } } } ALyraPlayerController* ALyraCharacter::GetLyraPlayerController() const { return CastChecked(Controller, ECastCheckedType::NullAllowed); } ALyraPlayerState* ALyraCharacter::GetLyraPlayerState() const { return CastChecked(GetPlayerState(), ECastCheckedType::NullAllowed); } ULyraAbilitySystemComponent* ALyraCharacter::GetLyraAbilitySystemComponent() const { return Cast(GetAbilitySystemComponent()); } UAbilitySystemComponent* ALyraCharacter::GetAbilitySystemComponent() const { return PawnExtComponent->GetLyraAbilitySystemComponent(); } void ALyraCharacter::OnAbilitySystemInitialized() { ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent(); check(LyraASC); HealthComponent->InitializeWithAbilitySystem(LyraASC); InitializeGameplayTags(); } void ALyraCharacter::OnAbilitySystemUninitialized() { HealthComponent->UninitializeFromAbilitySystem(); } void ALyraCharacter::PossessedBy(AController* NewController) { const FGenericTeamId OldTeamID = MyTeamID; Super::PossessedBy(NewController); PawnExtComponent->HandleControllerChanged(); // Grab the current team ID and listen for future changes if (ILyraTeamAgentInterface* ControllerAsTeamProvider = Cast(NewController)) { MyTeamID = ControllerAsTeamProvider->GetGenericTeamId(); ControllerAsTeamProvider->GetTeamChangedDelegateChecked().AddDynamic(this, &ThisClass::OnControllerChangedTeam); } ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID); } void ALyraCharacter::UnPossessed() { AController* const OldController = Controller; // Stop listening for changes from the old controller const FGenericTeamId OldTeamID = MyTeamID; if (ILyraTeamAgentInterface* ControllerAsTeamProvider = Cast(OldController)) { ControllerAsTeamProvider->GetTeamChangedDelegateChecked().RemoveAll(this); } Super::UnPossessed(); PawnExtComponent->HandleControllerChanged(); // Determine what the new team ID should be afterwards MyTeamID = DetermineNewTeamAfterPossessionEnds(OldTeamID); ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID); } void ALyraCharacter::OnRep_Controller() { Super::OnRep_Controller(); PawnExtComponent->HandleControllerChanged(); } void ALyraCharacter::OnRep_PlayerState() { Super::OnRep_PlayerState(); PawnExtComponent->HandlePlayerStateReplicated(); } void ALyraCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PawnExtComponent->SetupPlayerInputComponent(); } void ALyraCharacter::InitializeGameplayTags() { // Clear tags that may be lingering on the ability system from the previous pawn. if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get(); for (const TPair& TagMapping : GameplayTags.MovementModeTagMap) { if (TagMapping.Value.IsValid()) { LyraASC->SetLooseGameplayTagCount(TagMapping.Value, 0); } } for (const TPair& TagMapping : GameplayTags.CustomMovementModeTagMap) { if (TagMapping.Value.IsValid()) { LyraASC->SetLooseGameplayTagCount(TagMapping.Value, 0); } } ULyraCharacterMovementComponent* LyraMoveComp = CastChecked(GetCharacterMovement()); SetMovementModeTag(LyraMoveComp->MovementMode, LyraMoveComp->CustomMovementMode, true); } } void ALyraCharacter::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const { if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { LyraASC->GetOwnedGameplayTags(TagContainer); } } bool ALyraCharacter::HasMatchingGameplayTag(FGameplayTag TagToCheck) const { if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { return LyraASC->HasMatchingGameplayTag(TagToCheck); } return false; } bool ALyraCharacter::HasAllMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const { if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { return LyraASC->HasAllMatchingGameplayTags(TagContainer); } return false; } bool ALyraCharacter::HasAnyMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const { if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { return LyraASC->HasAnyMatchingGameplayTags(TagContainer); } return false; } void ALyraCharacter::FellOutOfWorld(const class UDamageType& dmgType) { HealthComponent->DamageSelfDestruct(/*bFellOutOfWorld=*/ true); } void ALyraCharacter::OnDeathStarted(AActor*) { DisableMovementAndCollision(); } void ALyraCharacter::OnDeathFinished(AActor*) { GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::DestroyDueToDeath); } void ALyraCharacter::DisableMovementAndCollision() { if (Controller) { Controller->SetIgnoreMoveInput(true); } UCapsuleComponent* CapsuleComp = GetCapsuleComponent(); check(CapsuleComp); CapsuleComp->SetCollisionEnabled(ECollisionEnabled::NoCollision); CapsuleComp->SetCollisionResponseToAllChannels(ECR_Ignore); ULyraCharacterMovementComponent* LyraMoveComp = CastChecked(GetCharacterMovement()); LyraMoveComp->StopMovementImmediately(); LyraMoveComp->DisableMovement(); } void ALyraCharacter::DestroyDueToDeath() { K2_OnDeathFinished(); UninitAndDestroy(); } void ALyraCharacter::UninitAndDestroy() { if (GetLocalRole() == ROLE_Authority) { DetachFromControllerPendingDestroy(); SetLifeSpan(0.1f); } // Uninitialize the ASC if we're still the avatar actor (otherwise another pawn already did it when they became the avatar actor) if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { if (LyraASC->GetAvatarActor() == this) { PawnExtComponent->UninitializeAbilitySystem(); } } SetActorHiddenInGame(true); } void ALyraCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode) { Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode); ULyraCharacterMovementComponent* LyraMoveComp = CastChecked(GetCharacterMovement()); SetMovementModeTag(PrevMovementMode, PreviousCustomMode, false); SetMovementModeTag(LyraMoveComp->MovementMode, LyraMoveComp->CustomMovementMode, true); } void ALyraCharacter::SetMovementModeTag(EMovementMode MovementMode, uint8 CustomMovementMode, bool bTagEnabled) { if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get(); const FGameplayTag* MovementModeTag = nullptr; if (MovementMode == MOVE_Custom) { MovementModeTag = GameplayTags.CustomMovementModeTagMap.Find(CustomMovementMode); } else { MovementModeTag = GameplayTags.MovementModeTagMap.Find(MovementMode); } if (MovementModeTag && MovementModeTag->IsValid()) { LyraASC->SetLooseGameplayTagCount(*MovementModeTag, (bTagEnabled ? 1 : 0)); } } } void ALyraCharacter::ToggleCrouch() { const ULyraCharacterMovementComponent* LyraMoveComp = CastChecked(GetCharacterMovement()); if (bIsCrouched || LyraMoveComp->bWantsToCrouch) { UnCrouch(); } else if (LyraMoveComp->IsMovingOnGround()) { Crouch(); } } void ALyraCharacter::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) { if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { LyraASC->SetLooseGameplayTagCount(FLyraGameplayTags::Get().Status_Crouching, 1); } Super::OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); } void ALyraCharacter::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) { if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent()) { LyraASC->SetLooseGameplayTagCount(FLyraGameplayTags::Get().Status_Crouching, 0); } Super::OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); } bool ALyraCharacter::CanJumpInternal_Implementation() const { // same as ACharacter's implementation but without the crouch check return JumpIsAllowedInternal(); } void ALyraCharacter::OnRep_ReplicatedAcceleration() { if (ULyraCharacterMovementComponent* LyraMovementComponent = Cast(GetCharacterMovement())) { // Decompress Acceleration const double MaxAccel = LyraMovementComponent->MaxAcceleration; const double AccelXYMagnitude = double(ReplicatedAcceleration.AccelXYMagnitude) * MaxAccel / 255.0; // [0, 255] -> [0, MaxAccel] const double AccelXYRadians = double(ReplicatedAcceleration.AccelXYRadians) * TWO_PI / 255.0; // [0, 255] -> [0, 2PI] FVector UnpackedAcceleration(FVector::ZeroVector); FMath::PolarToCartesian(AccelXYMagnitude, AccelXYRadians, UnpackedAcceleration.X, UnpackedAcceleration.Y); UnpackedAcceleration.Z = double(ReplicatedAcceleration.AccelZ) * MaxAccel / 127.0; // [-127, 127] -> [-MaxAccel, MaxAccel] LyraMovementComponent->SetReplicatedAcceleration(UnpackedAcceleration); } } void ALyraCharacter::SetGenericTeamId(const FGenericTeamId& NewTeamID) { if (GetController() == nullptr) { if (HasAuthority()) { const FGenericTeamId OldTeamID = MyTeamID; MyTeamID = NewTeamID; ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID); } else { UE_LOG(LogLyraTeams, Error, TEXT("You can't set the team ID on a character (%s) except on the authority"), *GetPathNameSafe(this)); } } else { UE_LOG(LogLyraTeams, Error, TEXT("You can't set the team ID on a possessed character (%s); it's driven by the associated controller"), *GetPathNameSafe(this)); } } FGenericTeamId ALyraCharacter::GetGenericTeamId() const { return MyTeamID; } FOnLyraTeamIndexChangedDelegate* ALyraCharacter::GetOnTeamIndexChangedDelegate() { return &OnTeamChangedDelegate; } void ALyraCharacter::OnControllerChangedTeam(UObject* TeamAgent, int32 OldTeam, int32 NewTeam) { const FGenericTeamId MyOldTeamID = MyTeamID; MyTeamID = IntegerToGenericTeamId(NewTeam); ConditionalBroadcastTeamChanged(this, MyOldTeamID, MyTeamID); } void ALyraCharacter::OnRep_MyTeamID(FGenericTeamId OldTeamID) { ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID); }