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.

Code:
 
void AAIController::Possess(APawn* InPawn)
{
// don't even try possessing pending-kill pawns
if (InPawn != nullptr && InPawn->IsPendingKill())
{
return;
}
Super::Possess(InPawn);
if (GetPawn() == nullptr || InPawn == nullptr)
{
return;
}
// no point in doing navigation setup if pawn has no movement component
const UPawnMovementComponent* MovementComp = InPawn->GetMovementComponent();
if (MovementComp != NULL)
{
UpdateNavigationComponents();
}
if (PathFollowingComponent)
{
PathFollowingComponent->Initialize();
}
if (bWantsPlayerState)
{
ChangeState(NAME_Playing);
}
// a Pawn controlled by AI _requires_ a GameplayTasksComponent, so if Pawn
// doesn't have one we need to create it
if (CachedGameplayTasksComponent == nullptr)
{
UGameplayTasksComponent* GTComp = InPawn->FindComponentByClass<UGameplayTasksComponent>();
if (GTComp == nullptr)
{
GTComp = NewObject<UGameplayTasksComponent>(InPawn, TEXT("GameplayTasksComponent"));
GTComp->RegisterComponent();
}
CachedGameplayTasksComponent = GTComp;
}
if (CachedGameplayTasksComponent && !CachedGameplayTasksComponent->OnClaimedResourcesChange.Contains(this, GET_FUNCTION_NAME_CHECKED(AAIController, OnGameplayTaskResourcesClaimed)))
{
CachedGameplayTasksComponent->OnClaimedResourcesChange.AddDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed);
REDIRECT_OBJECT_TO_VLOG(CachedGameplayTasksComponent, this);
}
OnPossess(InPawn);
}
Code:
 
void APlayerController::Possess(APawn* PawnToPossess)
{
if (!HasAuthority())
{
FMessageLog("PIE").Warning(FText::Format(
LOCTEXT("PlayerControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
FText::FromName(GetFName())
));
UE_LOG(LogPlayerController, Warning, TEXT("Trying to possess %s without network authority! Request will be ignored."), *GetNameSafe(PawnToPossess));
return;
}
if ( PawnToPossess != NULL &&
(PlayerState == NULL || !PlayerState->bOnlySpectator) )
{
if (GetPawn() && GetPawn() != PawnToPossess)
{
UnPossess();
}
if (PawnToPossess->Controller != NULL)
{
PawnToPossess->Controller->UnPossess();
}
PawnToPossess->PossessedBy(this);
// update rotation to match possessed pawn's rotation
SetControlRotation( PawnToPossess->GetActorRotation() );
SetPawn(PawnToPossess);
check(GetPawn() != NULL);
if (GetPawn() && GetPawn()->PrimaryActorTick.bStartWithTickEnabled)
{
GetPawn()->SetActorTickEnabled(true);
}
INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast<INetworkPredictionInterface>(GetPawn()->GetMovementComponent()) : NULL;
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());
ChangeState( NAME_Playing );
if (bAutoManageActiveCameraTarget)
{
AutoManageActiveCameraTarget(GetPawn());
ResetCameraMode();
}
UpdateNavigationComponents();
}
}
Code:
 
void AController::Possess(APawn* InPawn)
{
if (!HasAuthority())
{
FMessageLog("PIE").Warning(FText::Format(
LOCTEXT("ControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
FText::FromName(GetFName())
));
return;
}
REDIRECT_OBJECT_TO_VLOG(InPawn, this);
if (InPawn != NULL)
{
if (GetPawn() && GetPawn() != InPawn)
{
UnPossess();
}
if (InPawn->Controller != NULL)
{
InPawn->Controller->UnPossess();
}
InPawn->PossessedBy(this);
SetPawn(InPawn);
// update rotation to match possessed pawn's rotation
SetControlRotation( Pawn->GetActorRotation() );
Pawn->Restart();
}
}
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)