RPC Confusion :S

I have a system that is made up of weapons and abilities. The abilities are components that are attached to the weapons to make them do something. For example, a pistol would have a line trace ability. Rocket launcher would have a projectile ability.

So, I have the usual setup where firing happens on the server and the clients only simulate this.

I will walk through my code.

First I have the character:

// ****FPSCharacter.h****
// Handles start weapon/ability fire
void OnStartFire();

// ****FPSCharacter.cpp****
void AFPSCharacter::OnStartFire()
{
	Cast<AFPSWeapon>(EquippedComponent)->StartFire();
}

Next, I have the weapon.

// ****FPSWeapon.h****
// TODO: is there a way to do this without having server calls in both Weapon and Ability?
// Start firing the weapon on the server
UFUNCTION(Reliable, Server, WithValidation)
void ServerStartFire();
void ServerStartFire_Implementation();
bool ServerStartFire_Validate();

// Start weapon fire
virtual void StartFire();

// ****FPSWeapon.cpp****
bool AFPSWeapon::ServerStartFire_Validate()
{
	return true;
}

void AFPSWeapon::StartFire()
{
	// Tell the server we want to fire
	ServerStartFire();
	
	// WHY CAN'T I JUST DO THIS HERE?
	// Get the ability components attached to this weapon
	/*TArray<UActorComponent*> AbilityComponents = GetComponentsByClass(UFPSAbilityComponent::StaticClass());
	if (AbilityComponents.Num() > 0)
	{
		// Execute the first ability
		Cast<UFPSAbilityComponent>(AbilityComponents[0])->ExecuteAbility();
	}*/
}

void AFPSWeapon::ServerStartFire_Implementation()
{
	// Get the ability components attached to this weapon
	TArray<UActorComponent*> AbilityComponents = GetComponentsByClass(UFPSAbilityComponent::StaticClass());
	if (AbilityComponents.Num() > 0)
	{
		// Execute the first ability
		Cast<UFPSAbilityComponent>(AbilityComponents[0])->ExecuteAbility();
	}
}

And the ability…

// ****FPSAbility.h****
UPROPERTY(Transient, Replicated)
bool IsFiring;

virtual void ExecuteAbility();

UFUNCTION(Reliable, Server, WithValidation)
void ServerStartAbility();
void ServerStartAbility_Implementation();
bool ServerStartAbility_Validate();

UFUNCTION(Reliable, Server, WithValidation)
void ClientStartAbility();
void ClientStartAbility_Implementation();
bool ClientStartAbility_Validate();


// ****FPSAbility.cpp****
void UFPSAbilityComponent::ExecuteAbility()
{
	ServerStartAbility();
}

void UFPSAbilityComponent::ServerStartAbility_Implementation()
{
	if (GetOwningCharacter()->Role == ROLE_Authority)
	{
		ClientStartAbility();
	}
}

bool UFPSAbilityComponent::ServerStartAbility_Validate()
{
	return true;
}

void UFPSAbilityComponent::ClientStartAbility_Implementation()
{
	GetOwningCharacter()->SetIsFiring(true);
}

bool UFPSAbilityComponent::ClientStartAbility_Validate()
{
	return true;
}

So, my question is…

Why does AFPSCharacter::ServerStartFire() need to be a server RPC since it calls a function that calls a Server RPC?

Originally I had it coded without AFPSCharacter::ServerStartFire().
Instead, I just had AFPSCharacter::StartFire() call the ExecuteAbility function.
However, this was causing the ability logic to be called on the client instead.

This is confusing me. Can anyone break it down?

Thanks!

I rewrote your code to be correct as it should be easier to understand that way. Logic is as follows:

  • Client performs action

  • Client tells server of action

  • Server validates action and executes server logic for action

  • Server replicates the execution of that action to all other clients

    void AFPSWeapon::StartFire()
    {
    // If were a client
    if (!HasAuthority())
    {
    // Tell the server we want to fire
    ServerStartFire();
    }

     // If were a server
     if (HasAuthority())
     {
     	// Get the ability components attached to this weapon
     	TArray<UActorComponent*> AbilityComponents = GetComponentsByClass(UFPSAbilityComponent::StaticClass());
     	if (AbilityComponents.Num() > 0)
     	{
     		// Execute the first ability
     		Cast<UFPSAbilityComponent>(AbilityComponents[0])->ExecuteAbility();
     	}
     }
    

    }

    void AFPSWeapon::ServerStartFire_Implementation()
    {
    StartFire()
    }

    // This should maybe be set to Cond_SkipOwner
    UPROPERTY(Transient, ReplicatedUsing = “OnRep_IsFiring”)
    bool IsFiring;

    void UFPSAbilityComponent::ExecuteAbility()
    {
    // Double check were on the server
    if (HasAuthority())
    {
    // only do stuff on server
    IsFiring = true;
    }
    }

    void UFPSAbilityComponent::OnRep_IsFiring()
    {
    // I’m a client told that the server version of this ability just fired
    // So do stuff
    }

This is very helpful, thank you. One caveat that I forgot to mention is that Abilities (UFPSAbilityComponent) can also be attached directly to the character (weaponless-abilities). So that is why I had the server rpc logic within the Ability. In other words, abilities need to be coded so that they can fire without a weapon as well.

Could your approach be shuffled around to support that?

It would still follow the same route:

  • Client (player input) performs action
  • Client tells server of action (player calls server rpc which then on server calls execute on that ability component)
  • Server validates action and executes server logic for action
  • Server replicates the execution of that action to all other clients

As long as the ability component is marked to replicate it can handle exactly the same way. With the weapon case, the weapon component is just an intermediary. If you don’t want that logic on the player you could just rename/refactor the weaponcomponent to be like AbilityController and subclass Weapon logic into WeaponAbilityController if there’s custom stuff to put there.
The AbilityController responds to input and calls the rpcs.