Search Unity

Question Sending Information through RPCs

Discussion in 'Multiplayer' started by Coalmander, Mar 21, 2024.

  1. Coalmander

    Coalmander

    Joined:
    May 7, 2023
    Posts:
    3
    Hello,

    I'm new to game development and I've been working on this Pokemon Battle Simulator for the past few months. Currently, I am trying to find a way to send a player's move selection through RPCs back to the server for processing the turns and then relaying the info to the players, but I have classes that hold the information (Like which Attack the player wants to use) and cannot be sent as parameters through RPCs. Is there any way to send this information through the server?
    Code (CSharp):
    1.     public async void SendMoveSelect(int type)
    2.     {
    3.         GameManager gm = GameObject.Find("Game Manager").GetComponent<GameManager>();
    4.         var selection = await SelectMove();
    5.         gm.RecieveMoveSelectionRpc(type, selection);
    6.     }
    Code (CSharp):
    1.     [Rpc(SendTo.Server)]
    2.     public void RecieveMoveSelectionRpc(int type, IPlayerAction action)
    3.     {
    4.         if (type == 1) // Host
    5.         {
    6.             SetTrainer1Move(action);
    7.         }
    8.         else if (type == 2) // Client
    9.         {
    10.             SetTrainer2Move(action);
    11.         }
    12.     }
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,012
    Please use an Inspector reference for that. If you call that every time this means going through every game object and doing a string match.

    Moreover and generally, Find is very prone to breaking in an offline game but for a multiplayer game it's downright disastrous as you may have objects named differently depending on server, host, client or owner.

    Find is a method that should have long been marked as deprecated, obsolete, and dangerous.

    Either these attacks are always the same (eg "fireball") then it should be a scriptable object and thus it's available on the server. You would only need to index these attacks somehow, like stating that 1 equals "fireball" (enum).

    You can't pass an interface to RPC methods.

    Post your IPlayerAction and relevant code to get more info. Best course of action really depends on the context.
     
  3. Coalmander

    Coalmander

    Joined:
    May 7, 2023
    Posts:
    3
    These are the main classes that use them, and all my attacks derive from the 'Attack' class,
    while the 'Switch' class doesn't have any classes that derive from it.
    Code (CSharp):
    1. public interface IPlayerAction
    2. {
    3.     IPlayerAction PerformAction(Pokemon pokemon1, Pokemon pokemon2);
    4.     IPlayerAction PerformAction(Trainer trainer, Pokemon pokemon2);
    5. }
    Code (CSharp):
    1. public abstract class Attack : IPlayerAction
    2. {
    3.     protected string attackName;
    4.     protected string description;
    5.     protected Type type = null!;
    6.     protected AttackCategory moveCategory;
    7.     protected int power;
    8.     protected int accuracy;
    9.     protected int priority;
    10.     protected int currPowerPoints;
    11.     protected int maxPowerPoints;
    12.  
    13.     public Attack()
    14.     {
    15.         this.attackName = "Generic Attack";
    16.     }
    17.  
    18.     public string GetAttackName()
    19.     {
    20.         return attackName;
    21.     }
    22.     public string GetAttackDescription()
    23.     {
    24.         return description;
    25.     }
    26.     public Type GetAttackType()
    27.     {
    28.         return type;
    29.     }
    30.     public AttackCategory GetAttackCategory()
    31.     {
    32.         return moveCategory;
    33.     }
    34.     public int GetAttackPower()
    35.     {
    36.         return power;
    37.     }
    38.     public int GetAttackAccuracy()
    39.     {
    40.         return accuracy;
    41.     }
    42.     public int GetAttackPriority()
    43.     {
    44.         return priority;
    45.     }
    46.     public int GetCurrentPP()
    47.     {
    48.         return currPowerPoints;
    49.     }
    50.     public int GetMaxPP()
    51.     {
    52.         return maxPowerPoints;
    53.     }
    54.     protected virtual bool UseAttack(Pokemon attacker, Pokemon target)
    55.     {
    56.         if (this.accuracy == 100)
    57.         {
    58.             int damage = CalculateDamage(this, attacker, target);
    59.             DealDamage(damage, attacker, target);
    60.             if (target.GetHPStat() == 0)
    61.             {
    62.                 return true;
    63.             }
    64.             // Some effects trigger after KO
    65.             TriggerEffect(attacker, target);
    66.         }
    67.         else
    68.         {
    69.             int generatedValue = Random.Range(0, 99);
    70.             int accurateRange = 100 - this.accuracy;
    71.             if (generatedValue < accurateRange)
    72.             {
    73.                 Debug.Log("The Attack Missed");
    74.                 return false;
    75.             }
    76.             else
    77.             {
    78.                 int damage = CalculateDamage(this, attacker, target);
    79.                 DealDamage(damage, attacker, target);
    80.                 if (target.GetHPStat() == 0)
    81.                 {
    82.                     return true;
    83.                 }
    84.                 TriggerEffect(attacker, target);
    85.             }
    86.         }
    87.         //Debug.Log("The Attack Landed");
    88.         return true;
    89.     }
    90.     protected virtual void TriggerEffect(Pokemon attacker, Pokemon target)
    91.     {
    92.         currPowerPoints -= 1;
    93.         return;
    94.     }
    95.     protected virtual int CalculateDamage(Attack attack, Pokemon attacker, Pokemon target)
    96.     {
    97.         float stab = IsStab(attacker); // Same Type Attack Bonus
    98.         float typeMatchup = Effectiveness(attacker, target);
    99.         int damageRange = Random.Range(217, 255);
    100.         int critChance = Random.Range(1, 16);
    101.         int damage;
    102.         // Damage Formula comes from this attack https://www.math.miami.edu/~jam/azure/compendium/battdam.htm
    103.         // Visual for Formula https://gamerant.com/pokemon-damage-calculation-help-guide/
    104.         if (attack.GetAttackCategory() == AttackCategory.Physical)
    105.         {
    106.             //Debug.Log($"Fired a Physical Attack = {attack.attackName}");
    107.             int step1 = (2 * attacker.GetLevel() / 5 + 2);
    108.             //Debug.Log($"Step 1 = {step1}");
    109.             int step2 = step1 * attacker.GetAttackStat() * attack.GetAttackPower();
    110.             //Debug.Log($"Step 2 = {step2}");
    111.             int step3 = step2 / target.GetDefenseStat();
    112.             //Debug.Log($"Step 3 = {step3}");
    113.             int step4 = step3 / 50;
    114.             //Debug.Log($"Step 4 = {step4}");
    115.             int step5 = step4 + 2;
    116.             //Debug.Log($"Step 5 = {step5}");
    117.             float step6 = step5 * stab;
    118.             //Debug.Log($"Step 6 = {step6}");
    119.             float step7 = step6 * typeMatchup;
    120.             //Debug.Log($"Step 7 = {step7}");
    121.             float step8 = step7 * damageRange;
    122.             //Debug.Log($"Step 8 = {step8}");
    123.             int step9 = Mathf.FloorToInt(step8 / 255);
    124.             //Debug.Log($"Step 9 = {step9}");
    125.             damage = step9;
    126.             // Move Power needs to be halved
    127.             if (attacker.Status == StatusConditions.Burn)
    128.             {
    129.                 damage /= 2;
    130.             }
    131.             if (critChance == 1)
    132.             {
    133.                 damage = Mathf.FloorToInt(damage * 1.5f);
    134.             }
    135.         }
    136.         else if (attack.GetAttackCategory() == AttackCategory.Special)
    137.         {
    138.             Debug.Log($"Fired a Special Attack = {attack.attackName}");
    139.             //Debug.Log($"Special Attack Stat = {pokemon1.GetSpecialAttackStat()}");
    140.             int step1 = (2 * attacker.GetLevel() / 5 + 2);
    141.             //Debug.Log($"Step 1 = {step1}");
    142.             int step2 = step1 * attacker.GetSpecialAttackStat() * attack.GetAttackPower();
    143.             //Debug.Log($"Step 2 = {step2}");
    144.             int step3 = step2 / target.GetSpecialDefenseStat();
    145.             //Debug.Log($"Step 3 = {step3}");
    146.             int step4 = step3 / 50;
    147.             //Debug.Log($"Step 4 = {step4}");
    148.             int step5 = step4 + 2;
    149.             //Debug.Log($"Step 5 = {step5}");
    150.             float step6 = step5 * stab;
    151.             //Debug.Log($"Step 6 = {step6}");
    152.             float step7 = step6 * typeMatchup;
    153.             //Debug.Log($"Step 7 = {step7}");
    154.             float step8 = step7 * damageRange;
    155.             //Debug.Log($"Step 8 = {step8}");
    156.             int step9 = Mathf.FloorToInt(step8 / 255);
    157.             //Debug.Log($"Step 9 = {step9}");
    158.             damage = step9;
    159.             if (critChance == 1)
    160.             {
    161.                 Debug.Log("You landed a critical hit");
    162.                 damage = Mathf.FloorToInt(damage * 1.5f);
    163.             }
    164.         }
    165.         else
    166.         {
    167.             return 0;
    168.         }
    169.         return damage;
    170.     }
    171.     public virtual void DealDamage(int damage, Pokemon attacker, Pokemon target)
    172.     {
    173.         if (target.GetHPStat() - damage <= 0)
    174.         {
    175.             target.SetHPStat(0);
    176.             Debug.Log("Opponent fainted");
    177.             //Object.Destroy(target.gameObject);
    178.             return;
    179.         }
    180.         target.SetHPStat(target.GetHPStat() - damage);
    181.     }
    182.     public IPlayerAction PerformAction()
    183.     {
    184.         return this;
    185.  
    186.     }
    187.  
    188.     public IPlayerAction PerformAction(Pokemon attacker, Pokemon target)
    189.     {
    190.         UseAttack(attacker, target);
    191.         return this;
    192.     }
    193.  
    194.     public IPlayerAction PerformAction(Trainer trainer, Pokemon pokemon2)
    195.     {
    196.         throw new System.NotImplementedException();
    197.     }
    198.  
    199.     protected virtual float IsStab(Pokemon pokemon)
    200.     {
    201.         if (this.type == pokemon.GetType1() || (pokemon.GetType2() != null && this.type == pokemon.GetType2())) {
    202.             //Debug.Log($"{this.GetAttackName()} is STAB");
    203.             return 1.5f;
    204.         }
    205.         else
    206.         {
    207.             return 1;
    208.         }
    209.     }
    210.  
    211.     protected virtual float Effectiveness(Pokemon attacker, Pokemon target)
    212.     {
    213.         float effectiveness = 1f;
    214.         if (target.GetType1().immunities.Contains(this.type) || (target.GetType2() != null && target.GetType2().immunities.Contains(this.type)))
    215.         {
    216.             return 0;
    217.         }
    218.  
    219.         if (target.GetType1().weaknesses.Contains(this.type))
    220.         {
    221.             effectiveness *= 2;
    222.         }
    223.  
    224.         if (target.GetType2() != null && target.GetType2().weaknesses.Contains(this.type))
    225.         {
    226.             Debug.Log($"{target.GetType2().GetType().Name} is weak to {this.type.GetType().Name}");
    227.             effectiveness *= 2;
    228.         }
    229.  
    230.         if (target.GetType1().resistances.Contains(this.type))
    231.         {
    232.             effectiveness /= 2;
    233.         }
    234.  
    235.         if (target.GetType2() != null && target.GetType2().resistances.Contains(this.type))
    236.         {
    237.             effectiveness /= 2;
    238.         }
    239.         var dialogueBoxControllers = GameObject.FindObjectsByType<DialogueBoxController>(FindObjectsSortMode.None);
    240.         foreach (var controller in dialogueBoxControllers)
    241.         {
    242.             if (effectiveness > 1f)
    243.             {
    244.                 controller.SendDialogueToHostRpc($"{this.GetAttackName()} is super effective. Effectiveness = {effectiveness}.");
    245.                 controller.SendDialogueToClientRpc($"{this.GetAttackName()} is super effective. Effectiveness = {effectiveness}.");
    246.             }
    247.             else if (effectiveness == 1f)
    248.             {
    249.                 controller.SendDialogueToHostRpc($"{this.GetAttackName()} is effective");
    250.                 controller.SendDialogueToClientRpc($"{this.GetAttackName()} is effective");
    251.             }
    252.             else
    253.             {
    254.                 controller.SendDialogueToHostRpc($"{this.GetAttackName()} is not very effective. Effectiveness = {effectiveness}.");
    255.                 controller.SendDialogueToClientRpc($"{this.GetAttackName()} is not very effective. Effectiveness = {effectiveness}.");
    256.             }
    257.         }
    258.         return effectiveness;
    259.     }
    260. }
    Code (CSharp):
    1. public class Switch : IPlayerAction
    2. {
    3.     private Trainer trainer;
    4.     private Pokemon pokemon;
    5.  
    6.     public Switch(Trainer trainer, Pokemon pokemon)
    7.     {
    8.         this.trainer = trainer;
    9.         this.pokemon = pokemon;
    10.     }
    11.     public void SwitchPokemon(Trainer trainer, Pokemon newPokemon)
    12.     {
    13.         //if (newPokemon == trainer.GetActivePokemon())
    14.         //{
    15.         //    Debug.Log("You can't swap with the Pokemon that is already out.");
    16.         //    return;
    17.         //}
    18.         for (int i = 1; i < trainer.GetPokemonTeam().Length; i++)
    19.         {
    20.             if (trainer.GetPokemonTeam()[i] == null)
    21.             {
    22.                 break;
    23.             }
    24.             if (trainer.GetPokemonTeam()[i].GetSpeciesName() == newPokemon.GetSpeciesName() && newPokemon.GetHPStat() != 0)
    25.             {
    26.                 trainer.Switch(i);
    27.                 EventsToTriggerManager.AlertEventTriggered(EventsToTrigger.YourPokemonSwitched);
    28.                 return;
    29.             }
    30.  
    31.         }
    32.         Debug.Log("Failed to find something to switch with");
    33.     }
    34.  
    35.     public Trainer GetTrainer()
    36.     {
    37.         return trainer;
    38.     }
    39.  
    40.     public Pokemon GetPokemon()
    41.     {
    42.         return pokemon;
    43.     }
    44.  
    45.     public IPlayerAction PerformAction()
    46.     {
    47.      
    48.         return this;
    49.     }
    50.  
    51.     public IPlayerAction PerformAction(Pokemon pokemon1, Pokemon pokemon2)
    52.     {
    53.         throw new System.NotImplementedException();
    54.     }
    55.  
    56.     public IPlayerAction PerformAction(Trainer trainer, Pokemon newPokemon)
    57.     {
    58.         SwitchPokemon(trainer, newPokemon);
    59.         return this;
    60.     }
    61. }
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,012
    That's 28 bytes PLUS two variable-length strings per attack, so it could easily be 50+ bytes per attack. Try to trim this down as much as you can. Consider that some values may be fine as byte rather than int. Definitely don't send the strings, everyone should have that info, or be able to look it up by index.

    You need to use a struct that implements INetworkSerializabe to send custom data like this. But ideally just send what's really needed, not everything every time.
     
    Coalmander likes this.
  5. Coalmander

    Coalmander

    Joined:
    May 7, 2023
    Posts:
    3
    I took this idea and was able to make a solution from this. Thank You, very much!