Hi !
I am trying to build a game with dedicated server, with click-to-move mechanics.
The server spawns a pawn, possessed by an AI controller, which is referenced by a Player Controller, so the player can specify targets and pawn moves using path-finder.
For some reason, the pointer to the Server Controlled Pawn is referencing the wrong pawn on all the clients !
So what am I doing wrong here ?
This is the output i get when i run dedicated server + 4 clients:
(part1)
you can see here the pawn & controller names
LogGameMode: AOpenWorldGameMode::InitNewPlayer>> Spawned pawn [ServerPlayerPawnBP_C_0] for controller [367]
LogGameMode: AOpenWorldGameMode::InitNewPlayer>> Spawned pawn [ServerPlayerPawnBP_C_1] for controller [368]
LogGameMode: AOpenWorldGameMode::InitNewPlayer>> Spawned pawn [ServerPlayerPawnBP_C_2] for controller [369]
LogGameMode: AOpenWorldGameMode::InitNewPlayer>> Spawned pawn [ServerPlayerPawnBP_C_3] for controller [370]
And there’s the next part 2 of log, where you can see the replication callbacks are being executed but all the referenced objects are wrong !
LogPlayerController:Warning: AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> Controller [ClientPlayerController_C_0] ServerControlledPawn is [ServerPlayerPawnBP_C_2]
LogPlayerController:Warning: AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> Controller [368] ServerControlledPawn is [ServerPlayerPawnBP_C_0]
LogPlayerController:Warning: AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> Controller [369] ServerControlledPawn is [ServerPlayerPawnBP_C_3]
LogPlayerController:Warning: AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> Controller [ClientPlayerController_C_0] ServerControlledPawn is [ServerPlayerPawnBP_C_3]
… and here is the source code:
OpenWorldGameMode.h
UCLASS()
class LEGACYMMOPROJECT_API AOpenWorldGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
UPROPERTY( EditAnywhere, NoClear, BlueprintReadOnly, Category = Classes )
TSubclassOf<APlayerPawnBase> DefaultServerPlayerPawnClass;
UPROPERTY( EditAnywhere, NoClear, BlueprintReadOnly, Category = Classes )
TSubclassOf<AServerPlayerAIControllerBase> DefaultServerPlayerAIControllerClass;
AOpenWorldGameMode();
virtual void PreLogin( const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage ) override;
virtual FString InitNewPlayer( APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal = TEXT( "" ) ) override;
virtual void PostLogin( APlayerController* NewPlayer ) override;
};
OpenWorldGameMode.cpp
AOpenWorldGameMode::AOpenWorldGameMode() {
DefaultPawnClass = nullptr;
}
void AOpenWorldGameMode::PreLogin( const FString & Options,
const FString & Address,
const FUniqueNetIdRepl & UniqueId,
FString & ErrorMessage ) {
Super::PreLogin( Options, Address, UniqueId, ErrorMessage );
if ( !ErrorMessage.IsEmpty() )
return;
// check username/password
}
FString AOpenWorldGameMode::InitNewPlayer( APlayerController * NewPlayerController,
const FUniqueNetIdRepl & UniqueId,
const FString & Options,
const FString & Portal ) {
FString ret = Super::InitNewPlayer( NewPlayerController, UniqueId, Options, Portal );
if ( !ret.IsEmpty() )
return ret;
AClientPlayerControllerBase* ClientPlayerCtrl = Cast<AClientPlayerControllerBase>( NewPlayerController );
if ( !ClientPlayerCtrl ) {
UE_LOG( LogGameMode, Warning, TEXT( "AOpenWorldGameMode::InitNewPlayer>> Cast to AClientPlayerControllerBase failed") );
return FString::Printf( TEXT( "failed to cast to PlayerController to AClientPlayerControllerBase" ) );
}
check(ClientPlayerCtrl->ServerControlledPawn == nullptr);
check(ClientPlayerCtrl->ServerAIController == nullptr);
ClientPlayerCtrl->ServerControlledPawn = GetWorld()->SpawnActor<APlayerPawnBase>(DefaultServerPlayerPawnClass, NewPlayerController->StartSpot->GetTransform() );
UE_LOG( LogGameMode, Log, TEXT( "AOpenWorldGameMode::InitNewPlayer>> Spawned pawn [%s] for controller [%s]" ), *ClientPlayerCtrl->ServerControlledPawn->GetHumanReadableName(), *ClientPlayerCtrl->GetHumanReadableName() );
if ( ClientPlayerCtrl->ServerControlledPawn == nullptr ) {
UE_LOG( LogGameMode, Warning, TEXT( "AOpenWorldGameMode::InitNewPlayer>> : SpawnActor<APlayerPawnBase> failed " ) );
return FString::Printf( TEXT( "failed to spawn %s" ), *GetNameSafe( DefaultServerPlayerPawnClass ) );
}
ClientPlayerCtrl->ServerAIController = GetWorld()->SpawnActor<AServerPlayerAIControllerBase>( DefaultServerPlayerAIControllerClass );
if ( ClientPlayerCtrl->ServerAIController == nullptr ) {
UE_LOG( LogGameMode, Warning, TEXT( "AOpenWorldGameMode::InitNewPlayer>> : SpawnActor<AServerPlayerAIControllerBase> failed " ) );
return FString::Printf( TEXT( "failed to spawn %s" ), *GetNameSafe( DefaultServerPlayerAIControllerClass ) );
}
ClientPlayerCtrl->ServerAIController->Possess( ClientPlayerCtrl->ServerControlledPawn );
return ret;
}
void AOpenWorldGameMode::PostLogin( APlayerController * NewPlayer ) {
Super::PostLogin( NewPlayer );
AClientPlayerControllerBase* ClientPlayerCtrl = Cast<AClientPlayerControllerBase>( NewPlayer );
UE_LOG( LogGameMode, Log, TEXT( "AOpenWorldGameMode::PostLogin( %s )>> ServerControlledPawn is [%s]" ), *NewPlayer->GetHumanReadableName(), *ClientPlayerCtrl->ServerControlledPawn->GetHumanReadableName() );
}
ClientPlayerControllerBase.h
UCLASS()
class LEGACYMMOPROJECT_API AClientPlayerControllerBase : public APlayerController
{
GENERATED_BODY()
public:
AClientPlayerControllerBase();
UPROPERTY( BlueprintReadOnly, ReplicatedUsing = OnReplicate_ServerControlledPawnReference )
APlayerPawnBase* ServerControlledPawn;
//UPROPERTY( BlueprintReadOnly )
AServerPlayerAIControllerBase* ServerAIController;
//UFUNCTION( Client, Reliable )
//void ClientSetControlledPawnReference( APlayerPawnBase* NewServerPawn );
void GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const;
private:
AFloatingCamera* FloatingCamera;
// attach camera to the controlled pawn
UFUNCTION()
void OnReplicate_ServerControlledPawnReference();
// Create Camera actor on first demand
bool ValidateCameraActor();
};
ClientPlayerController.cpp
DEFINE_LOG_CATEGORY( LogPlayerController );
AClientPlayerControllerBase::AClientPlayerControllerBase() {
ServerControlledPawn = nullptr;
ServerAIController = nullptr;
FloatingCamera = nullptr;
}
void AClientPlayerControllerBase::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const {
Super::GetLifetimeReplicatedProps( OutLifetimeProps );
DOREPLIFETIME_CONDITION( AClientPlayerControllerBase, ServerControlledPawn, COND_None );
}
bool AClientPlayerControllerBase::ValidateCameraActor() {
if ( !FloatingCamera ) {
FloatingCamera = (AFloatingCamera*) GetWorld()->SpawnActor( AFloatingCamera::StaticClass() ) ;
if ( !FloatingCamera ) {
UE_LOG( LogPlayerController, Warning, TEXT( "AClientPlayerControllerBase::ValidateCameraActor()>> CameraActor spawn failed at controller %s"), *GetHumanReadableName() );
return false;
}
UE_LOG( LogPlayerController, Log, TEXT( "AClientPlayerControllerBase::ValidateCameraActor()>> CameraActor spawned at controller %s"), *GetHumanReadableName() );
}
return true;
}
void AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference() {
UE_LOG( LogPlayerController, Warning, TEXT( "AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> Controller [%s] ServerControlledPawn is [%s] " ), *GetHumanReadableName(), *ServerControlledPawn->GetHumanReadableName() );
if ( !ServerControlledPawn ) {
UE_LOG( LogPlayerController, Warning, TEXT( "AClientPlayerControllerBase::OnReplicate_ServerControlledPawnReference>> ServerControlledPawn is null" ) );
return;
}
if ( !ValidateCameraActor() )
return;
FloatingCamera->SetupForController( this );
FloatingCamera->Follow( ServerControlledPawn );
}