#include "GasaPlayerController.h" #include "GasaPlayerController_Inlines.h" #include "Networking/GasaNetLibrary_Inlines.h" #include "AbilitySystemComponent.h" #include "DrawDebugHelpers.h" #include "Engine/LocalPlayer.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "Characters/GasaCharacter.h" #include "Characters/PlayerCharacter.h" #include "Components/CapsuleComponent.h" #include "Interfaces/NetworkPredictionInterface.h" #include "Kismet/KismetSystemLibrary.h" #include "Net/UnrealNetwork.h" #include "GameFramework/PawnMovementComponent.h" #include "GasaDevOptions.h" #include "GasaGameInstance.h" #include "GasaGameState.h" #include "GasaPlayerState.h" #include "AbilitySystem/GasaAbilitySystemComponent.h" #include "Actors/CameraMount.h" #include "UI/GasaHUD.h" #include "UI/WidgetController.h" using namespace Gasa; AGasaPlayerController::AGasaPlayerController() { PrimaryActorTick.bCanEverTick = true; bAutoManageActiveCameraTarget = false; // Replication bReplicates = true; } void AGasaPlayerController::OnSeamlessTravelStart() { } #pragma region GameFramework void AGasaPlayerController::Client_CheckIfOwnerReady() { if (IsServer()) return; UGasaGameInstance* GI = GetGameInstance(); if ( ! GI->IsGameFrameworkInitialized() || PlayerState == NULL || ! IsValid(GetPawn())) return; NetOwner_OnReady(); } void AGasaPlayerController::NetOwner_OnReady() { NetLog("Net Owner of controller is ready to play."); if ( ! IsNetOwner() || bNetOwnerReady) return; BP_NetOwner_OnReady(); Event_NetOwner_OnReady.Broadcast(this); bNetOwnerReady = true; AGasaGameState* GS = Cast(GetWorld()->GetGameState()); if (GS) GS->NotifyPlayerPawnReady(GetPawn()); if (IsClient()) ServerRPC_R_NotifyOwningClientReady(); Cam = GetWorld()->SpawnActor(GetDevOptions()->Template_PlayerCamera.Get(), FActorSpawnParameters() ); SetViewTarget(Cam); AGasaPlayerState* PS = GetPlayerState(); APlayerCharacter* PlayerChar = GetPawn(); { PlayerChar->AbilitySystem = PS->AbilitySystem; PlayerChar->Attributes = PS->Attributes; PS->AbilitySystem->InitAbilityActorInfo(PS, PlayerChar); Cast(PS->AbilitySystem)->OnAbilityActorInfoSet(); PlayerChar->InitDefaultAttributes(); } Cam->AttachToActor(PlayerChar, FAttachmentTransformRules::KeepRelativeTransform); } void AGasaPlayerController::OnGameFrameworkInitialized() { NetLog("Received game framework initialization."); if (IsNetOwner()) { Server_SetNetOwner_GameFrameworkInitialized(); Client_CheckIfOwnerReady(); } AGasaGameState* GS = GetGameState(this); NullGuard_DEV(GS, Log, "OnGameFrameworkInitialized: GS is null"); GS->Event_OnSeamlessTravelStart.AddDynamic( this, & ThisClass::OnSeamlessTravelStart ); BP_OnGameFrameworkInitialized(); } void AGasaPlayerController::OnPawnReady() { NetLog("Player is ready."); // Originally: Super::OnPossess(PawnToPossess); { ChangeState(NAME_Playing); if (bAutoManageActiveCameraTarget) { AutoManageActiveCameraTarget(GetPawn()); ResetCameraMode(); } } // Override this and add your own conditions... BP_OnPawnReady(); if (IsServer() && IsNetOwner()) { // The server host doesn't have to wait for the player state to replicate. NetOwner_OnReady(); } } void AGasaPlayerController::Server_SetupOnPawnReadyBinds(APawn* PawnToBindTo) { if (IsClient()) return; #if 0 if (PawnToBindTo->IsA(AGasaPawn::StaticClass())) { Cast(PawnToBindTo)->Event_OnPawnReady.AddUniqueDynamic(this, & ThisClass::OnPawnReady); } else #endif if (PawnToBindTo->IsA(AGasaCharacter::StaticClass())) { Cast(PawnToBindTo)->Event_OnPawnReady.AddUniqueDynamic(this, & ThisClass::OnPawnReady); } } void AGasaPlayerController::Server_SetNetOwner_GameFrameworkInitialized() { if (IsClient()) { ServerRPC_R_SetNetOwner_GameFrameworkInitialized(); return; } bNetOwner_GameFrameworkInitialized = true; if (Event_NetOwner_OnGameFrameworkInitialized.IsBound()) { Event_NetOwner_OnGameFrameworkInitialized.Broadcast(this); Event_NetOwner_OnGameFrameworkInitialized.Clear(); } } void AGasaPlayerController::ServerRPC_R_NotifyOwningClientReady_Implementation() { NetLog("Net Owner Ready: Notified via RPC."); BP_NetOwner_OnReady(); bNetOwnerReady = true; Event_NetOwner_OnReady.Broadcast(this); Event_NetOwner_OnReady.Clear(); AGasaGameState* GS = GetGameState(this); if (GS) GS->NotifyPlayerPawnReady(GetPawn()); } void AGasaPlayerController::ServerRPC_R_SetNetOwner_GameFrameworkInitialized_Implementation() { Server_SetNetOwner_GameFrameworkInitialized(); } #pragma endregion GameFramework #pragma region Input void AGasaPlayerController::Move(FInputActionValue const& ActionValue) { APawn* pawn = GetPawn(); if (pawn == nullptr ) return; // Note(Ed): I did the follow optimization for practice, they are completely unnecessary for this context. #if 0 FVector2D AxisV = ActionValue.Get(); FRotator ControlRot = GetControlRotation(); FRotator YawRot = FRotator(0.f, ControlRot.Yaw, 0.f); FVector FwdDir = FRotationMatrix(YawRot).GetUnitAxis(EAxis::X); FVector RightDir = FRotationMatrix(YawRot).GetUnitAxis(EAxis::Y); PPawn->AddMovementInput(FwdDir, AxisV.Y); PPawn->AddMovementInput(RightDir, AxisV.X); #else FVector2f AxisV = FVector2f(ActionValue.Get()); FQuat4f // FQuat isomorphic to FRotor (Hypothetical Def) ControlRotor = FQuat4f(GetControlRotation().Quaternion()); // ControlRotor.Normalize(); // The Quaternion should always be a versor with UE... FVector3f HorizontalForward = ControlRotor.RotateVector(FVector3f::ForwardVector); // HorizontalForward.Normalize(); // TODO(Ed): Profile which is faster just to know... (atan2 vs FindBetweenVectors) // HorizontalForward.Z = 0; // FQuat4f // YawRotor = FQuat4f::FindBetweenVectors(FVector3f::ForwardVector, HorizontalForward); // YawRotor.Normalize(); // The Quaternion should always be a versor with UE... // Need only one axis of rotation so this might be a possible optimization float YawAngle = FMath::Atan2(HorizontalForward.Y, HorizontalForward.X); FQuat4f YawRotor = FQuat4f(FVector3f::UpVector, YawAngle); // Rotate the combined input by the yaw rotor to get the movement direction FVector MoveDir = (FVector) YawRotor.RotateVector( FVector3f(AxisV.Y, AxisV.X, 0.f)); pawn->AddMovementInput( MoveDir ); #endif } #pragma endregion Input #pragma region PlayerController bool AGasaPlayerController::CanRestartPlayer() { bool BaseCheck = PlayerState && !PlayerState->IsOnlyASpectator() && HasClientLoadedCurrentWorld() && PendingSwapConnection == NULL ; return BaseCheck && bNetOwner_GameFrameworkInitialized; } void AGasaPlayerController::ClientSetHUD_Implementation(TSubclassOf NewHUDClass) { Super::ClientSetHUD_Implementation(NewHUDClass); AGasaPlayerState* PS = GetPlayerState(); AGasaHUD* HUD = GetHUD(); FWidgetControllerData Data = { this, PS, PS->AbilitySystem, PS->Attributes }; HUD->InitHostWidget(& Data); } void AGasaPlayerController::ClientUpdateLevelStreamingStatus_Implementation(FName PackageName, bool bNewShouldBeLoaded, bool bNewShouldBeVisible, bool bNewShouldBlockOnLoad, int32 LODIndex, FNetLevelVisibilityTransactionId TransactionId, bool bNewShouldBlockOnUnload) { Super::ClientUpdateLevelStreamingStatus_Implementation(PackageName, bNewShouldBeLoaded, bNewShouldBeVisible, bNewShouldBlockOnLoad, LODIndex, TransactionId, bNewShouldBlockOnUnload); NetLog("ClientUpdateLevelStreamingStatus"); NetLog(FString("PackageName : ") + PackageName.ToString()); NetLog(FString("NewShouldBeLoaded : ") + FString(bNewShouldBeLoaded ? "true" : "false")); NetLog(FString("NewShouldBeVisible : ") + FString(bNewShouldBeVisible ? "true" : "false")); NetLog(FString("bNewShouldBlockOnLoad : ") + FString(bNewShouldBlockOnLoad ? "true" : "false")); NetLog(FString("bNewShouldBlockOnUnload: ") + FString(bNewShouldBlockOnUnload ? "true" : "false")); NetLog(FString("LODIndex : ") + FString::FromInt( LODIndex )); } // TODO(Ed): We need to setup Net Slime... void AGasaPlayerController::OnPossess(APawn* PawnPossesed) { // Super::OnPossess(PawnPossesed); { if (PawnPossesed && (PlayerState == NULL || !PlayerState->IsOnlyASpectator()) ) { // ====================================================================Originally: Super::OnPossess(PawnToPossess); const bool bNewPawn = (GetPawn() != PawnPossesed); if (GetPawn() && bNewPawn) UnPossess(); if (PawnPossesed->Controller != NULL) PawnPossesed->Controller->UnPossess(); PawnPossesed->PossessedBy(this); // update rotation to match possessed pawn's rotation SetControlRotation( PawnPossesed->GetActorRotation() ); SetPawn(PawnPossesed); check(GetPawn() != NULL); if (GetPawn() && GetPawn()->PrimaryActorTick.bStartWithTickEnabled) GetPawn()->SetActorTickEnabled(true); INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast(GetPawn()->GetMovementComponent()) : nullptr; if (NetworkPredictionInterface) NetworkPredictionInterface->ResetPredictionData_Server(); AcknowledgedPawn = NULL; // Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs. // We're really just trying to avoid calling Restart() multiple times. if (!IsLocalPlayerController()) GetPawn()->Restart(); ClientRestart(GetPawn()); // Moved to: void AGasaPlayerController::OnPawnReady #if 0 ChangeState( NAME_Playing ); if (bAutoManageActiveCameraTarget) { AutoManageActiveCameraTarget(GetPawn()); ResetCameraMode(); } #endif //==========================================================End of=================== Originally: Super::OnPossess(PawnToPossess); NetLog("OnPossess"); Server_SetupOnPawnReadyBinds(PawnPossesed); Event_OnPawnPossessed.Broadcast(); } } } void AGasaPlayerController::OnRep_Pawn() { Super::OnRep_Pawn(); NetLog("OnRep_Pawn"); Client_CheckIfOwnerReady(); } void AGasaPlayerController::OnUnPossess() { Super::OnUnPossess(); } void AGasaPlayerController::PlayerTick(float DeltaTime) { Super::PlayerTick(DeltaTime); // Cursor Trace for (int32 do_once = 0; do_once != 1; ++ do_once) { FHitResult CursorHit; GetHitResultUnderCursor(ECC_Pawn, false, CursorHit); if (! CursorHit.bBlockingHit) break; HoverPrev = HoverCurr; HoverCurr = Cast(CursorHit.GetActor()); if (HoverPrev == nullptr) { // We didn't have a prev to de-highlight so we just need to highlight newly detected character. if (HoverCurr) HoverCurr->Highlight(); // No matter what we need to not go to the next case as there is no previous. break; } //else Previous is valid... // We are no longer hovering the previous with no new character, we just need to de-highlight previous. if ( HoverCurr == nullptr ) HoverPrev->Dehighlight(); // We had a prev and curr change between frames. They both don't match; we need to switch highlighting. else if ( HoverPrev != HoverCurr ) { HoverPrev->Dehighlight(); HoverCurr->Highlight(); } } } void AGasaPlayerController::PostSeamlessTravel() { Super::PostSeamlessTravel(); } void AGasaPlayerController::SetupInputComponent() { Super::SetupInputComponent(); UEnhancedInputComponent* EIC = CastChecked(InputComponent); { EIC->BindAction(IA_Move, ETriggerEvent::Triggered, this, &ThisClass::Move); } } void AGasaPlayerController::SpawnDefaultHUD() { Super::SpawnDefaultHUD(); } #pragma endregion PlayerController #pragma region Actor void AGasaPlayerController::BeginPlay() { Super::BeginPlay(); NetLog("BeginPlay"); UGasaGameInstance* GI = GetGameInstance(); GI->Event_OnGameFrameworkInitialized.AddUniqueDynamic(this, & AGasaPlayerController::OnGameFrameworkInitialized); GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::PlayerController); if (IsLocalController()) { check(IMC); UEnhancedInputLocalPlayerSubsystem* EILP_Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); check(EILP_Subsystem); EILP_Subsystem->AddMappingContext(IMC, 0); { bShowMouseCursor = true; DefaultMouseCursor = EMouseCursor::Default; FInputModeGameAndUI MouseMode; MouseMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); MouseMode.SetHideCursorDuringCapture(false); SetInputMode(MouseMode); } } } void AGasaPlayerController::PostInitializeComponents() { Super::PostInitializeComponents(); } void AGasaPlayerController::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); #if 0 switch (HighlightState) { case EHighlight::Disabled: break; case EHighlight::Enabled: { UCapsuleComponent* Capsule = GetCapsuleComponent(); UKismetSystemLibrary::DrawDebugCapsule(this , Capsule->GetComponentLocation() , Capsule->GetScaledCapsuleHalfHeight() , Capsule->GetScaledCapsuleRadius() , Capsule->GetComponentRotation() , HighlightColor , 0.f , 1.f ); } break; } #endif } void AGasaPlayerController::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AGasaPlayerController, bNetOwner_GameFrameworkInitialized); } #pragma endregion Actor