Enum variable not Updating when calling Server Function

Hoping someone can shed some light on the issue I’ve been having lately. Thank you in advance to anyone who responds. I also posted this question on AnswerHub but two days in and it still hasn’t been approved by a mod.

Let me explain the context around the issue

I have a C++ with a mix of blueprint RTS prototype and I’ve been converting the project so far to work with multiplayer. One of the tasks of this conversion led me to make the function that spawns new units to be called on the server instead of locally. Ever since I’ve done this my placement code no longer runs which I have traced to be caused by the PlayerState Enum being changed to Placing, like it should be, in my CreateUnit function, but then when tick is called PlayerState miraculously changes back to Default. This problem only happens on the Client whereas the Server instance still works fine.

Here is my code, most of it is logs and I’ve trimmed irrelevant code out of the functions you see


// Called when U is pressed
void ARtsPlayerController::Server_CreateUnit_Implementation()
{
    if(PlayerState == EPlayerState::Menu) return;
    AActor* NewUnit = GetWorld()->SpawnActor<ARTSPrototypeCharacter>(UnitClass);
    if(NewUnit)
    {
        Cast<ARTSPrototypeCharacter>(NewUnit)->SetOwnerUserName(UserName);
        PlayerPawn->MyUnits.Add(NewUnit);
        FString UnitName = GetDebugName(NewUnit);
        UE_LOG(LogTemp, Warning, TEXT("Unit Buffer in CreateUnit: %s"), *UnitName);
        PrepareUnit(NewUnit);
    }
}

// Made this function incase changing PlayerState Enum in server function was the issue
void ARtsPlayerController::PrepareUnit(AActor* NewUnit)
{
    PlacementBuffer = NewUnit;
    if(NewUnit != nullptr && Cast<ARTSPrototypeCharacter>(NewUnit) && PlacementBuffer == NewUnit)
   {
      UE_LOG(LogTemp, Warning, TEXT("New Unit is good"));
   }
   else
   {
      UE_LOG(LogTemp, Warning, TEXT("New Unit is bad"));
   }
   GEngine->AddOnScreenDebugMessage(0, 2, FColor::Red, TEXT("PrepareUnit Called"));
   // Makes Tick call PositionPlacement()
   ChangePlayerState(EPlayerState::Placing);
   const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EPlayerState"), true);
   GEngine->AddOnScreenDebugMessage(0, 2, FColor::Red, *EnumPtr->GetDisplayNameText((uint8)PlayerState).ToString()); // Returns Placing
}

void ARtsPlayerController::PlayerTick(float DeltaTime)
{
   Super::PlayerTick(DeltaTime);
   // Return Enum Value
   const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EPlayerState"), true);
   FString EnumName = EnumPtr->GetDisplayNameText((uint8)PlayerState).ToString();
   UE_LOG(LogTemp, Warning, TEXT("%s"), *EnumName); // Returns Default regardless of if PrepareUnit calls ChangePlayerState
   // GEngine->AddOnScreenDebugMessage(0, 2, FColor::Red, *EnumPtr->GetDisplayNameText((uint8)PlayerState).ToString());
   if(PlayerState != EPlayerState::Default)
   {
      // Update building location
      PositionPlacement();
   }
}

// The PlayerState is only ever changed with this function
void ARtsPlayerController::ChangePlayerState(EPlayerState NewState)
{
   PlayerState = NewState;
  
   // Logging the change
   const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EPlayerState"), true);
   FString Message = FString("Changed State to: ").Append(*EnumPtr->GetDisplayNameText((uint8)PlayerState).ToString());
   GEngine->AddOnScreenDebugMessage(2, 2, FColor::Red, Message);
   FString EnumName = EnumPtr->GetDisplayNameText((uint8)PlayerState).ToString();
   UE_LOG(LogTemp, Warning, TEXT("Changed State to: %s"), *EnumName);
}

Game Logs for pressing U and calling CreateUnit


 

  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Unit Buffer in CreateUnit: TopDownCharacter_C_0
  1. LogTemp: Warning: New Unit is good
  1. LogTemp: Warning: Changed State to: Placing // Note how ChangePlayerState is called but tick does not change
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default

 

Compared to Game Logs when Create Building is called (not called on server and works as intended)


  

  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogTemp: Warning: Default
  1. LogActor: Warning: BP_UnitBuilding_C/Game/TopDownCPP/Maps/UEDPIE_1_TopDownExampleMap.TopDownExampleMap:PersistentLevel.BP_UnitBuilding_C_0 has natively added scene component(s), but none of them were set as the actor's RootComponent - picking one arbitrarily
  1. LogTemp: Warning: Changed State to: Placing
  1. LogTemp: Warning: Placing
  1. LogTemp: Warning: Placing
  1. LogTemp: Warning: Placing

 

When I log, I have a #define that tells me if that method is called on the server or the client. Too many times, I thought the function was being called on the server when it was running on a client. So if that tick you are logging is on the client, the enum is not getting replicated from the server.



#define NETMODE_WORLD (((GEngine == nullptr) || (GetWorld() == nullptr)) ? TEXT("") \
: (GEngine->GetNetMode(GetWorld()) == NM_Client) ? TEXT("[Client] ") \
: (GEngine->GetNetMode(GetWorld()) == NM_ListenServer) ? TEXT("[ListenServer] ") \
: (GEngine->GetNetMode(GetWorld()) == NM_DedicatedServer) ? TEXT("[DedicatedServer] ") \
: TEXT("[Standalone] "))

UE_LOG(AWGeneralLog, Warning, TEXT("%s() Mode: %s Found %d overlapped actors")
, TEXT(__FUNCTION__)
, NETMODE_WORLD
, m_overlappedActors.Num()
);


Thanks for that logging code and the help!

The Result:


LogTemp: Warning: Default on [Client] // Called from Tick
LogTemp: Warning: Unit Buffer in CreateUnit: TopDownCharacter_C_0[ListenServer]
LogTemp: Warning: New Unit is good[ListenServer] // Called from PrepareUnit() //  Called from CreateUnit()
LogTemp: Warning: Changed State to: Placing[ListenServer] // Called from ChangePlayerState()
LogTemp: Warning: Default on [Client]
LogTemp: Warning: Default on [Client]
LogTemp: Warning: Placing on [Client] // seems to take some time to update but works nonetheless


Just like you said, I hadn’t set the PlayerState variable to replicate! Also had to make the ChangePlayerState() function run on the server so it would work when called from tick(). Seems like a minor mistake but i’m new to multiplayer and this has really helped me understand how to make things work, I thought because the PlayerController was replicated the variables it had were also replicated. That logging code is also already proving invaluable to debugging.

Thanks again!