Fix ClientSideAim RPC spam from observer clients

Use GetLocalRole()==ROLE_AutonomousProxy instead of GetRemoteRole()==ROLE_Authority
to restrict the ClientAim/ShootRepCSA RPC path to the owning client only.
Observers (SimulatedProxy) early-out instead of attempting to send Server RPCs
on weapons they don't own, which was spamming "No owning connection" warnings
and doing useless work. Default ClientAimUpdateFrequency raised to 60 Hz.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 16:06:22 +02:00
parent f6a0cdc1c4
commit b98d89c609
5 changed files with 7 additions and 5 deletions

View File

@@ -14,7 +14,7 @@ void UEBBarrel::SpawnBulletEventMulticast_Implementation(FVector Start, FVector
} }
void UEBBarrel::Shoot(bool Trigger, int nextFireID) { void UEBBarrel::Shoot(bool Trigger, int nextFireID) {
if (ClientSideAim && GetOwner()->GetRemoteRole() == ROLE_Authority && Trigger) { if (ClientSideAim && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy && Trigger) {
Aim = GetComponentTransform().GetUnitAxis(EAxis::X); Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
Location = GetComponentTransform().GetLocation(); Location = GetComponentTransform().GetLocation();
nextFireEventID = nextFireID; nextFireEventID = nextFireID;

View File

@@ -53,7 +53,8 @@ void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon
UpdateTransformHistory(); UpdateTransformHistory();
if (ClientSideAim){ if (ClientSideAim){
if (GetOwner()->GetRemoteRole()==ROLE_Authority){ const ENetRole LocalRole = GetOwner()->GetLocalRole();
if (LocalRole == ROLE_AutonomousProxy){
TimeSinceAimUpdate += DeltaTime; TimeSinceAimUpdate += DeltaTime;
if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) { if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) {
@@ -62,7 +63,7 @@ void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon
ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim); ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim);
TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency); TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency);
}; };
}else{ }else if (LocalRole == ROLE_Authority){
if (!RemoteAimReceived) { if (!RemoteAimReceived) {
ComputeAntiRecoilTransform(); ComputeAntiRecoilTransform();
} }
@@ -74,6 +75,7 @@ void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon
} }
} }
} }
// ROLE_SimulatedProxy: observer, aim comes from replicated transform — nothing to do
} }
else { else {
ComputeAntiRecoilTransform(); ComputeAntiRecoilTransform();

View File

@@ -226,8 +226,8 @@ public:
bool ReplicateShotFiredEvents = true; bool ReplicateShotFiredEvents = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "When true, the owning client computes the barrel aim position/direction locally and sends it to the server via RPC. The server uses the client's aim to spawn the bullet. Essential for VR/tracked controllers where the server cannot know the exact barrel orientation. When false, the server uses the barrel component's replicated transform.")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "When true, the owning client computes the barrel aim position/direction locally and sends it to the server via RPC. The server uses the client's aim to spawn the bullet. Essential for VR/tracked controllers where the server cannot know the exact barrel orientation. When false, the server uses the barrel component's replicated transform."))
bool ClientSideAim=false; bool ClientSideAim=false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "How often (Hz) the client sends aim updates to the server when ClientSideAim is true. Higher values = more accurate server-side aim at the cost of bandwidth. 15 Hz is a good default. Only relevant when ClientSideAim is enabled.", EditCondition = "ClientSideAim", ClampMin = "1")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "How often (Hz) the owning client sends aim updates to the server when ClientSideAim is true. Higher values = more accurate server-side aim at the cost of bandwidth. 60 Hz matches typical VR tracking rates. Only relevant when ClientSideAim is enabled. Only the owning client sends these updates (observers skip the RPC).", EditCondition = "ClientSideAim", ClampMin = "1"))
float ClientAimUpdateFrequency = 15.0f; float ClientAimUpdateFrequency = 60.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "Maximum allowed distance (UU/cm) between the client-reported aim position and the server's barrel position. Acts as an anti-cheat clamp: if the client aim is further than this, the server clamps it. 200 = 2 meters. Set higher if the tracker has large offsets from the replicated barrel position.", EditCondition = "ClientSideAim", ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication", meta = (ToolTip = "Maximum allowed distance (UU/cm) between the client-reported aim position and the server's barrel position. Acts as an anti-cheat clamp: if the client aim is further than this, the server clamps it. 200 = 2 meters. Set higher if the tracker has large offsets from the replicated barrel position.", EditCondition = "ClientSideAim", ClampMin = "0"))
float ClientAimDistanceLimit = 200.0f; float ClientAimDistanceLimit = 200.0f;