Handling networking cases - remote actor, server actor, local actor, listen actor?

Hey guys,

So I’m trying to change the settings of my actor based on where it is. Is the following a functional way, in BeginPlay()? (it doesn’t seem to be). The ships in question are used as the default pawn for the active gamemode.


// remote on client
if (!HasAuthority() && !IsLocallyControlled())
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("Ship remote on client."));
}
else if (!HasAuthority() && IsLocallyControlled()) // local client
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, TEXT("Ship local on client."));
}
else if (HasAuthority() && IsLocallyControlled()) // local to listen server
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Black, TEXT("Ship local on listen server."));
}
else if (HasAuthority() && !IsLocallyControlled()) // server (pure)
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Cyan, TEXT("Ship on server (pure)."));
}
else
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, TEXT("Ship case not handled!"));
}

When I have 2 clients connected to a dedicated server, each with a locally-controlled Ship, I get ‘Remote Ship’ 4 times, and ‘On Server (pure)’ twice.

EDIT: With only 1 client connected to a dedicated server, I get ‘Remote ship on client’ and ‘On Server(pure)’.

EDIT#2: Changed the first two to;
GetNetMode() == NM_Client && !IsLocallyControlled() // remote
GetNetMode() == NM_Client && IsLocallyControlled() // locally-controlled on client

It seems that ‘IsLocallyControlled()’ returns false even on clients, for their locally-controlled actors?

EDIT #3:
Seems that all ships not on the server are treated as ROLE_SimulatedProxy, even replicated copies. Have I done something wrong ?

I will try to summarize, as the multiple edits seem to muddy the waters.

The crux of the issue is that I’m trying to modify the settings of my ship depending on the context. For example, if the ship is being updated on the client that is controlling it, physics should be active (I’m doing client-authoritative movement for now), but if it’s being updated on the server, physics should not be active.

The complication is that if it’s being updated on a listen server, but it’s locally controlled by the player that hosted the game, it also needs to have physics enabled (so they can control the ship like the clients are).

I cannot seem to differentiate these cases, no matter the combination of HasAuthority(), IsLocallyControlled(), ENetMode, network role, etc. Is this intentional (and thus I am approaching the problem incorrectly), or have I made a mistake in my differentiation?

Hey, it sounds to me ( and please correct me if I’m wrong ) that you don’t really care if you’re on the server or not, you only care if you control the ship or not.

So the thing you should be finding out is: Am I controlling the ship? YES=Physics on/No=Physics Off. It doesn’t matter if its on server or not in your case, does it?

Sooo IsLocallyControlled should do the trick if your roles are setup correctly.

That’s definitely a vital part.

However, any server-specific stuff needs to be different to stuff for remote/replicated actors as computed by a client. In my example, I’m also wanting to turn on certain collision settings depending on context - remote/replicated actors should have no collision computed (you don’t want to calculate the physics or collision of players around you), locally-controlled should collide with objects in the scene (as the physics are on the client), but on the server is where they collide with bullets. It sounds complicated, but there’s definitely a need for me to able to tell the difference between different contexts.

This is how I’m checking in my Ship class.



void AShip::BeginPlay()
{
	Super::BeginPlay();

	if (IsLocallyControlled())
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, TEXT("Ship is locally controlled!"));
	}
	else if (!IsLocallyControlled())
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, TEXT("Ship is not locally controlled!"));
	}
}

When PIE on a simulated dedicated server, with one client connected, I get ‘not locally controlled’ twice. Somehow, the client does not consider its own pawn to be locally controlled. It is only ever true for a ship being controlled by a listen server - it at least knows it’s locally controlled in that context.

I’ve been having this problem myself and I looked through the Engine code to try and figure this out but still can’t see how it is happening. For some reason the AddOnScreenDebugMessage gets replicated to all clients. I worked around this by using the DrawText function of the HUD (this is only drawn once per frame, so you need some way to store it longer). Anyhow, a listen server will have a locally controlled pawn by definition it is a client acting as a server. If you are running a dedicated server then it will not have any locally controlled pawns. So to prevent the message from being shown twice you need to use a different method of displaying the text. I hope that helps.

If you want to know how I handled it then this should be enough to get you started:



void AWAWHUD::AddText(const FString& Text, const FColor& Color, float Duration)
{
	FInfoItem InfoItem;
	InfoItem.ExpireTime = GetWorld()->RealTimeSeconds + Duration;
	TSharedPtr<FCanvasTextItem> TextItem(new FCanvasTextItem(FVector2D(0, 0), FText::FromString(Text), GEngine->GetSmallFont(), Color));
	TextItem->EnableShadow(FLinearColor::Black);
	TextItem->BlendMode = SE_BLEND_Translucent;
	FFontRenderInfo fri = FFontRenderInfo();
	fri.bClipText = true;
	TextItem->FontRenderInfo = fri;
	InfoItem.TextItem = TextItem;
	InfoItems.Add(InfoItem);
}

float AWAWHUD::ShowInfoItems(float YOffset, float ScaleUI, float TextScale)
{
	float Y = YOffset;
	float CanvasCentre = Canvas->ClipX / 2.0f;

	bool ContainsExpiredMessage = false;
	for (int32 iItem = 0; iItem < InfoItems.Num(); ++iItem)
	{
		float X = 0.0f;
		float SizeX, SizeY;
		FInfoItem& InfoItem = InfoItems[iItem];
		if (InfoItem.ExpireTime < GetWorld()->RealTimeSeconds)
		{
			ContainsExpiredMessage = true;
			InfoItem.Expired = true;
		}
		else
		{
			if (InfoItem.TextItem.IsValid())
			{
				Canvas->StrLen(InfoItem.TextItem->Font, InfoItem.TextItem->Text.ToString(), SizeX, SizeY);
				//Centered on the screen, set X to zero or some other value if you it left-aligned
				X = CanvasCentre - (SizeX * InfoItem.TextItem->Scale.X) / 2.0f;
				Canvas->DrawItem((*(InfoItem.TextItem.Get())), X, Y);
				Y += SizeY * InfoItem.TextItem->Scale.Y;
			}
		}
	}

	//If there were any expired messages then we need to safely remove them
	if (ContainsExpiredMessage)
	{
		InfoItems.RemoveAll(](FInfoItem InfoItem){
			return InfoItem.Expired;
		});
	}

	return Y;
}

void AWAWHUD::DrawHUD()
{
	Super::DrawHUD();
	float ScaleUI = Canvas->ClipY / 1080.0f;
	float TextScale = 1.f;
	ShowInfoItems(0.f, ScaleUI, TextScale);
}