AAIController has OnPossess/OnUnpossess Events, AController has not

We have three classes:

AController
AAIController
APlayerController
where AController is the super class of the other two.

However, only AAIController provides an event OnPossess/OnUnpossess.
In my eyes this is a design flaw. as it should be for every Controller.

Not to mention that OnPosess/OnUnpossess is the only event we can implement in Blueprint.


 [TABLE="class: highlight tab-size js-file-line-container"]

void AAIController::Possess(APawn* InPawn)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// don't even try possessing pending-kill pawns
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (InPawn != nullptr && InPawn->IsPendingKill())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

return;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

Super::Possess(InPawn);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (GetPawn() == nullptr || InPawn == nullptr)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

return;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// no point in doing navigation setup if pawn has no movement component
 		



[TABLE="class: highlight tab-size js-file-line-container"]

const UPawnMovementComponent* MovementComp = InPawn->GetMovementComponent();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (MovementComp != NULL)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UpdateNavigationComponents();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (PathFollowingComponent)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

PathFollowingComponent->Initialize();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (bWantsPlayerState)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

ChangeState(NAME_Playing);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// a Pawn controlled by AI _requires_ a GameplayTasksComponent, so if Pawn 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// doesn't have one we need to create it
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (CachedGameplayTasksComponent == nullptr)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UGameplayTasksComponent* GTComp = InPawn->FindComponentByClass<UGameplayTasksComponent>();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (GTComp == nullptr)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

GTComp = NewObject<UGameplayTasksComponent>(InPawn, TEXT("GameplayTasksComponent"));
 		



[TABLE="class: highlight tab-size js-file-line-container"]

GTComp->RegisterComponent();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

CachedGameplayTasksComponent = GTComp;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (CachedGameplayTasksComponent && !CachedGameplayTasksComponent->OnClaimedResourcesChange.Contains(this, GET_FUNCTION_NAME_CHECKED(AAIController, OnGameplayTaskResourcesClaimed)))
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

CachedGameplayTasksComponent->OnClaimedResourcesChange.AddDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

REDIRECT_OBJECT_TO_VLOG(CachedGameplayTasksComponent, this);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

OnPossess(InPawn);
 		



 }


 [TABLE="class: highlight tab-size js-file-line-container"]

void APlayerController::Possess(APawn* PawnToPossess)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (!HasAuthority())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

FMessageLog("PIE").Warning(FText::Format(
 		



[TABLE="class: highlight tab-size js-file-line-container"]

LOCTEXT("PlayerControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
 		



[TABLE="class: highlight tab-size js-file-line-container"]

FText::FromName(GetFName())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

));
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UE_LOG(LogPlayerController, Warning, TEXT("Trying to possess %s without network authority! Request will be ignored."), *GetNameSafe(PawnToPossess));
 		



[TABLE="class: highlight tab-size js-file-line-container"]

return;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if ( PawnToPossess != NULL &&
 		



[TABLE="class: highlight tab-size js-file-line-container"]

(PlayerState == NULL || !PlayerState->bOnlySpectator) )
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (GetPawn() && GetPawn() != PawnToPossess)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UnPossess();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (PawnToPossess->Controller != NULL)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

PawnToPossess->Controller->UnPossess();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

PawnToPossess->PossessedBy(this);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// update rotation to match possessed pawn's rotation
 		



[TABLE="class: highlight tab-size js-file-line-container"]

SetControlRotation( PawnToPossess->GetActorRotation() );
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

SetPawn(PawnToPossess);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

check(GetPawn() != NULL);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (GetPawn() && GetPawn()->PrimaryActorTick.bStartWithTickEnabled)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

GetPawn()->SetActorTickEnabled(true);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast<INetworkPredictionInterface>(GetPawn()->GetMovementComponent()) : NULL;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (NetworkPredictionInterface)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

NetworkPredictionInterface->ResetPredictionData_Server();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

AcknowledgedPawn = NULL;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs.
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// We're really just trying to avoid calling Restart() multiple times.
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (!IsLocalPlayerController())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

GetPawn()->Restart();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

ClientRestart(GetPawn());
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

ChangeState( NAME_Playing );
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (bAutoManageActiveCameraTarget)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

AutoManageActiveCameraTarget(GetPawn());
 		



[TABLE="class: highlight tab-size js-file-line-container"]

ResetCameraMode();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UpdateNavigationComponents();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



 }


 [TABLE="class: highlight tab-size js-file-line-container"]

void AController::Possess(APawn* InPawn)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (!HasAuthority())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

FMessageLog("PIE").Warning(FText::Format(
 		



[TABLE="class: highlight tab-size js-file-line-container"]

LOCTEXT("ControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
 		



[TABLE="class: highlight tab-size js-file-line-container"]

FText::FromName(GetFName())
 		



[TABLE="class: highlight tab-size js-file-line-container"]

));
 		



[TABLE="class: highlight tab-size js-file-line-container"]

return;
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

REDIRECT_OBJECT_TO_VLOG(InPawn, this);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (InPawn != NULL)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (GetPawn() && GetPawn() != InPawn)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

UnPossess();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

if (InPawn->Controller != NULL)
 		



[TABLE="class: highlight tab-size js-file-line-container"]

{
 		



[TABLE="class: highlight tab-size js-file-line-container"]

InPawn->Controller->UnPossess();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

InPawn->PossessedBy(this);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

SetPawn(InPawn);
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

// update rotation to match possessed pawn's rotation
 		



[TABLE="class: highlight tab-size js-file-line-container"]

SetControlRotation( Pawn->GetActorRotation() );
 		



[TABLE="class: highlight tab-size js-file-line-container"]

 
 		



[TABLE="class: highlight tab-size js-file-line-container"]

Pawn->Restart();
 		



[TABLE="class: highlight tab-size js-file-line-container"]

}
 		



 }

Possess() may be virtual, but it isn’t BlueprintNative or BlueprintImplementable.
(I could create a Base C++ Class for my own Controller, but I use BP as a prototype and I would like to have all features I would have in the future, too)