Multiplayer Client/Host issues

I have been having some issues with the connection between my host and client for Multiplayer mode, when I build the game the Host works fine and displays everything fine and interactions work fine.

However the client can only interact with some of the objects in the scene. For e.g. they can pick up objects from crates but not place them anywhere except the trash can. I have looked through my codes for the following parts of the game however I can not see where my issue is.

Could anyone help me please?

Scripts:

Player Script:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public class Player : NetworkBehaviour, IForgeMaterialsParent
{
    public static event EventHandler OnAnyPlayerSpawned;
    public static event EventHandler OnAnyPickedSomething;
    
    public static void ResetStaticData()
    {
        OnAnyPlayerSpawned = null;
    }
    
    public static Player LocalInstance { get; private set; }


    public event EventHandler OnPickedSomething;
    public event EventHandler<OnSelectedCounterChnagedEventArgs> OnSelectedcounterChanged;
    public class OnSelectedCounterChnagedEventArgs : EventArgs
    {
        public BaseCounter selectedCounter;
    }

    [SerializeField] private float moveSpeed = 10f;
    //[SerializeField] private GameInput gameInput;
    [SerializeField] private LayerMask countersLayerMask;
    [SerializeField] private Transform kitchenObjectHoldParent;

    private bool isWalking;
    private Vector3 lastInteractDir;
    private BaseCounter selectedCounter;
    private ForgeMaterials forgeMaterials;

    /*private void Awake()
   {
      if (Instance != null)
       {
           Debug.LogError("There is more than one Player Instance");
       }
       Instance = this;
}*/

    private void Start()
    {
        GameInput.Instance.OnInteractAction += GameInput_OnInteractAction;
        GameInput.Instance.OnInteractAlternativeAction += GameInput_OnInteractAlternativeAction;
    }

    public override void OnNetworkSpawn()
    {
        if (IsOwner)
        {
            LocalInstance = this;
        }
        OnAnyPlayerSpawned?.Invoke(this, EventArgs.Empty);
    }

    private void GameInput_OnInteractAlternativeAction(object sender, EventArgs e)
    {
        if (!ForgeNFlameGameHandler.Instance.IsGamePlaying()) return;

        if (selectedCounter != null)
        {
            selectedCounter.InteractAlternative(this);
        }
    }

    private void GameInput_OnInteractAction(object sender, System.EventArgs e)
    {
        if (!ForgeNFlameGameHandler.Instance.IsGamePlaying()) return;

        if (selectedCounter != null)
        {
            selectedCounter.Interact(this);
        }
    }

    private void Update()
    {
        if (!IsOwner)
        {
            return;
        }

        HandleMovement();
        HandleInteractions();
    }

    public bool IsWalking()
    {
        return isWalking;
    }

    private void HandleInteractions()
    {
        Vector2 inputVector = GameInput.Instance.GetMovementVectorNormalized();

        Vector3 moveDir = new Vector3(inputVector.x, 0f, inputVector.y);

        if(moveDir != Vector3.zero)
        {
            lastInteractDir = moveDir;
        }

        float interactDistance = 2f;

        if (Physics.Raycast(transform.position, lastInteractDir, out RaycastHit raycastHit, interactDistance, countersLayerMask))
        {
            if (raycastHit.transform.TryGetComponent(out BaseCounter baseCounter)) 
            { 
                // Has ClearCounter
                if (baseCounter != selectedCounter)
                {
                   SetSelectedCounter(baseCounter);
                }
                //clearCounter.Interact();
            }
            else
            {
                SetSelectedCounter(null);
            }
        }
        else
        {
            SetSelectedCounter(null);
        }
    }

    private void HandleMovement()
    {
        Vector2 inputVector = GameInput.Instance.GetMovementVectorNormalized();

        Vector3 moveDir = new Vector3(inputVector.x, 0f, inputVector.y);

        float moveDistance = moveSpeed * Time.deltaTime;
        float playerRadius = .5f;
        float playerHeight = 2f;
        bool canMove = !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeight, playerRadius, moveDir, moveDistance);

        if (!canMove)
        {
            //cannot move towards moveDir

            //Attempt only X Movement
            Vector3 moveDirX = new Vector3(moveDir.x, 0, 0).normalized;
            canMove = (moveDir.x < - .5f || moveDir.x > +.5f) && !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeight, playerRadius, moveDirX, moveDistance);

            if (canMove)
            {
                //can move only on the X axis
                moveDir = moveDirX;
            }
            else
            {
                // Cannot move only on the X

                //Attempt only Z movement
                Vector3 moveDirZ = new Vector3(0, 0, moveDir.z).normalized;
                canMove = (moveDir.z < -.5f || moveDir.z > +.5f) && !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeight, playerRadius, moveDirZ, moveDistance);

                if (canMove)
                {
                    // can move only on the Z
                    moveDir = moveDirZ;
                }
                else
                {
                    //can not move in any direction
                }
            }

        }

        if (canMove)
        {
            //transform.position += moveDir * moveDistance;

            transform.position += moveDir * moveSpeed * Time.deltaTime;
        }

        isWalking = moveDir != Vector3.zero;

        float rotateSpeed = 10f;
        transform.forward = Vector3.Slerp(transform.forward, moveDir, Time.deltaTime * rotateSpeed);
    }

    private void SetSelectedCounter(BaseCounter selectedCounter)
    {
        this.selectedCounter = selectedCounter;

        OnSelectedcounterChanged?.Invoke(this, new OnSelectedCounterChnagedEventArgs
        {
            selectedCounter = selectedCounter
        });
    }

    public Transform GetForgeMaterialFollowTransform()
    {
        return kitchenObjectHoldParent;
    }

    public void SetForgeMaterial(ForgeMaterials forgeMaterials)
    {
        this.forgeMaterials = forgeMaterials;

        if(forgeMaterials != null)
        {
            OnPickedSomething?.Invoke(this, EventArgs.Empty);
            OnAnyPickedSomething?.Invoke(this, EventArgs.Empty);
        }
    }

    public ForgeMaterials GetForgeMaterials()
    {
        return forgeMaterials;
    }

    public void ClearForgeMaterials()
    {
        forgeMaterials = null;
    }

    public bool HasForgeMaterials()
    {
        return forgeMaterials != null;
    }

    public NetworkObject GetNetworkObject()
    {
        return NetworkObject;
    }
}

Hammer Counter Script:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.EventSystems;

public class HammerCounter : BaseCounter, IHasProgress
{
    public static event EventHandler OnAnyHammer;

    new public static void ResetStaticData()
    {
        OnAnyHammer = null;
    }
    
    public event EventHandler<IHasProgress.OnProgressChangedEventArgs> OnProgressChanged;
    public event EventHandler OnHammer;

    [SerializeField] private HammerRecipeSO[] hammeredForgeMaterialsSOArray;

    private int hammerProgress;

    public override void Interact(Player player)
    {
        if (!HasForgeMaterials())
        {
            //there is no Forge Material here
            if (player.HasForgeMaterials())
            {
                //Player is carrying something
                if (HasRecipeWithInput(player.GetForgeMaterials().GetForgeMaterialsSO()))
                {
                    //player carrying something that can be hammered
                    ForgeMaterials forgeMaterials = player.GetForgeMaterials();
                    forgeMaterials.SetForgeMaterialsParent(this);

                    InteractLogicPlaceObjectOnCounterServerRpc();
                }
            }
            else
            {
                // Player not carrying anything

            }
        }
        else
        {
            // There is forge material
            if (player.HasForgeMaterials())
            {
                // Player is carrying something
                if (player.GetForgeMaterials().TryGetPlate(out SlateForgeMaterialObject slateForgeMaterialObject))
                {
                    if (slateForgeMaterialObject.TryAddIngredient(GetForgeMaterials().GetForgeMaterialsSO()))
                    {
                        GetForgeMaterials().DestroySelf();
                    }
                }
            }
            else
            {
                // Player is not carrying anything
                GetForgeMaterials().SetForgeMaterialsParent(player);
            }
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void InteractLogicPlaceObjectOnCounterServerRpc()
    {
        InteractLogicPlaceObjectOnCounterClientRpc();
    }

    [ClientRpc]
    private void InteractLogicPlaceObjectOnCounterClientRpc()
    {
        hammerProgress = 0;

        OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
        {
            progressNormalized = 0f
        });
    }

    public override void InteractAlternative(Player player)
    {
        if (HasForgeMaterials() && HasRecipeWithInput(GetForgeMaterials().GetForgeMaterialsSO()))
        {
            //There is a ForgeMaterial here and it can be hammered 
            HammerObjectServerRpc();
            TestHammerProgressDoneServerRpc();
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void HammerObjectServerRpc()
    {
        HammerObjectClientRpc();
    }

    [ClientRpc]
    private void HammerObjectClientRpc()
    {
        hammerProgress++;

        OnHammer?.Invoke(this, EventArgs.Empty);
        OnAnyHammer?.Invoke(this, EventArgs.Empty);

        HammerRecipeSO hammerRecipeSO = GetHammerRecipeSOWithInput(GetForgeMaterials().GetForgeMaterialsSO());

        OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
        {
            progressNormalized = (float)hammerProgress / hammerRecipeSO.hammerProgressMax
        });
    }

    [ServerRpc(RequireOwnership = false)]
    private void TestHammerProgressDoneServerRpc()
    {
        HammerRecipeSO hammerRecipeSO = GetHammerRecipeSOWithInput(GetForgeMaterials().GetForgeMaterialsSO());

        if (hammerProgress >= hammerRecipeSO.hammerProgressMax)
        {
            ForgeMaterialsSO outputForgeMaterialSO = GetOutputForInput(GetForgeMaterials().GetForgeMaterialsSO());

            ForgeMaterials.DestroyForgeMaterials(GetForgeMaterials());

            ForgeMaterials.SpawnForgeMaterials(outputForgeMaterialSO, this);
        }
    }

    private bool HasRecipeWithInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        HammerRecipeSO hammerRecipeSO = GetHammerRecipeSOWithInput(inputForgeMaterialsSO);
        return hammerRecipeSO != null;
    }

    private ForgeMaterialsSO GetOutputForInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        HammerRecipeSO hammerRecipeSO = GetHammerRecipeSOWithInput(inputForgeMaterialsSO);
        if (hammerRecipeSO != null)
        {
            return hammerRecipeSO.output;
        }
        else
        {
            return null;
        }
    }

    private HammerRecipeSO GetHammerRecipeSOWithInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        foreach (HammerRecipeSO hammerRecipeSO in hammeredForgeMaterialsSOArray)
        {
            if (hammerRecipeSO.input == inputForgeMaterialsSO)
            {
                return hammerRecipeSO;
            }
        }
        return null;
    }
}

Slate Counter:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public class SlateCounter : BaseCounter
{
    public event EventHandler OnSlateSpawned;
    public event EventHandler OnSlateRemoved;


    [SerializeField] private ForgeMaterialsSO slateForgeObjectSO;

    private float spawnSlateTimer;
    private float spawnSlateTimerMax = 4f;
    private int slateSpawnAmount;
    private int slateSpawnAmountMax = 4;

    private void Update()
    {
        if (!IsServer)
        {
            return;
        }

        spawnSlateTimer += Time.deltaTime;
        if (spawnSlateTimer > spawnSlateTimerMax)
        {
            spawnSlateTimer = 0f;

            if (ForgeNFlameGameHandler.Instance.IsGamePlaying() && slateSpawnAmount < slateSpawnAmountMax)
            {
                SpawnSlateServerRpc();
            }
        }
    }

    [ServerRpc]
    private void SpawnSlateServerRpc()
    {
        SpawnSlateClientRpc();
    }

    [ClientRpc]
    private void SpawnSlateClientRpc()
    {
        slateSpawnAmount++;

        OnSlateSpawned?.Invoke(this, EventArgs.Empty);
    }

    public override void Interact(Player player)
    {
        if (!player.HasForgeMaterials())
        {
            // Player is empty handed
            if (slateSpawnAmount > 0)
            {
                ForgeMaterials.SpawnForgeMaterials(slateForgeObjectSO, player);
                
                InteractLogicServerRpc();
            }
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void InteractLogicServerRpc()
    {
        InteractLogicClientRpc();
    }
    [ClientRpc]
    private void InteractLogicClientRpc()
    {
        slateSpawnAmount--;

        OnSlateRemoved?.Invoke(this, EventArgs.Empty);
    }
}

Base Counter:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public class BaseCounter : NetworkBehaviour, IForgeMaterialsParent
{
    public static event EventHandler OnAnyObjectPlacedHere;

    public static void ResetStaticData()
    {
        OnAnyObjectPlacedHere = null;
    }
    
    [SerializeField] private Transform counterTopPoint;

    private ForgeMaterials forgeMaterials;
    public virtual void Interact(Player player)
    {
        Debug.LogError("BaseCounter.Interact();");
    }
    public virtual void InteractAlternative(Player player)
    {
        //Debug.LogError("BaseCounter.InteractAlternative();");
    }

    public Transform GetForgeMaterialFollowTransform()
    {
        return counterTopPoint;
    }

    public void SetForgeMaterial(ForgeMaterials forgeMaterial)
    {
        this.forgeMaterials = forgeMaterial;

        if (forgeMaterial != null )
        {
            OnAnyObjectPlacedHere?.Invoke(this, EventArgs.Empty);
        }
    }

    public ForgeMaterials GetForgeMaterials()
    {
        return forgeMaterials;
    }

    public void ClearForgeMaterials()
    {
        forgeMaterials = null;
    }

    public bool HasForgeMaterials()
    {
        return forgeMaterials != null;
    }

    public NetworkObject GetNetworkObject()
    {
        return NetworkObject;
    }
}

Clear Counter:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ClearCounter : BaseCounter
{
    //[SerializeField] private ForgeMaterialsSO forgeMaterialsSO;

    public override void Interact(Player player)
    {
        if (!HasForgeMaterials())
        {
            //there is no Forge Material here
            if (player.HasForgeMaterials())
            {
                //Player is carrying something
                player.GetForgeMaterials().SetForgeMaterialsParent(this);
            }
            else
            {
                // Player not carrying anything

            }
        }
        else
        {
            // There is forge material
            if (player.HasForgeMaterials())
            {
                // Player is carrying something
                if (player.GetForgeMaterials().TryGetPlate(out SlateForgeMaterialObject slateForgeMaterialObject))
                {
                    if (slateForgeMaterialObject.TryAddIngredient(GetForgeMaterials().GetForgeMaterialsSO()))
                    {
                        GetForgeMaterials().DestroySelf();
                    }
                }
                else
                {
                    //Player is not carrying slate but something else
                    if (GetForgeMaterials().TryGetPlate(out slateForgeMaterialObject))
                    {
                        //Counter is holding a Slate
                        if (slateForgeMaterialObject.TryAddIngredient(player.GetForgeMaterials().GetForgeMaterialsSO()))
                        {
                            player.GetForgeMaterials().DestroySelf();
                        }
                    }
                }
            }
            else
            {
                // Player is not carrying anything
                GetForgeMaterials().SetForgeMaterialsParent(player);
            }
        }
    }
}

Flame Counter:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using static HammerCounter;

public class FlameCounter : BaseCounter, IHasProgress
{
    public event EventHandler<IHasProgress.OnProgressChangedEventArgs> OnProgressChanged;

    public event EventHandler<OnStateChangedEventArgs> OnStateChanged;
    public class OnStateChangedEventArgs : EventArgs
    {
        public State state;
    }
    public enum State
    {
        Idle,
        Flaming,
        Flamed,
        Burnt
    }
    
    [SerializeField] private FlameRecipeSO[] flameRecipeSOArray;
    [SerializeField] private BurnRecipeSO[] burnRecipeSOArray;

    private NetworkVariable<State> state = new NetworkVariable<State>(State.Idle);
    private NetworkVariable<float> flameTimer = new NetworkVariable<float>(0f);
    private FlameRecipeSO flameRecipeSO;
    private NetworkVariable<float> burnTimer = new NetworkVariable<float>(0f);
    private BurnRecipeSO burnRecipeSO;

    public override void OnNetworkSpawn()
    {
        flameTimer.OnValueChanged += FlameTimer_OnValueChanged;
        burnTimer.OnValueChanged += BurnTimer_OnValueChanged;
        state.OnValueChanged += State_OnValueChanged;
    }

    private void FlameTimer_OnValueChanged(float previousValue, float newValue)
    {
        float flameTimerMax = flameRecipeSO != null ? flameRecipeSO.flameTimerMax : 1f;

        OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
        {
            progressNormalized = flameTimer.Value / flameTimerMax
        });
    }

    private void BurnTimer_OnValueChanged(float previousValue, float newValue)
    {
        float burnTimerMax = burnRecipeSO != null ? burnRecipeSO.burnTimerMax : 1f;

        OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
        {
            progressNormalized = burnTimer.Value / burnTimerMax
        });
    }

    private void State_OnValueChanged(State previousState, State newState)
    {
        OnStateChanged?.Invoke(this, new OnStateChangedEventArgs
        {
            state = state.Value
        });

        if (state.Value == State.Burnt || state.Value == State.Idle)
        {
            OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
            {
                progressNormalized = 0f
            });
        }

    }
    private void Update()
    {
        if (!IsServer)
        {
            return;
        }

        if (HasForgeMaterials())
        {
            switch (state.Value)
            {
                case State.Idle:
                    break;
                case State.Flaming:
                    flameTimer.Value += Time.deltaTime;

                    if (flameTimer.Value > flameRecipeSO.flameTimerMax)
                    {
                        //flamed
                        ForgeMaterials.DestroyForgeMaterials(GetForgeMaterials());

                        ForgeMaterials.SpawnForgeMaterials(flameRecipeSO.output, this);

                        state.Value = State.Flamed;
                        burnTimer.Value = 0f;
                        SetBurnRecipeSOClientRpc(ForgeGameMultiplayer.Instance.GetForgeMaterialsSOIndex(GetForgeMaterials().GetForgeMaterialsSO()));
                    }
                    break;
                case State.Flamed:
                    burnTimer.Value += Time.deltaTime;

                    if (burnTimer.Value > burnRecipeSO.burnTimerMax)
                    {
                        //flamed
                        ForgeMaterials.DestroyForgeMaterials(GetForgeMaterials());

                        ForgeMaterials.SpawnForgeMaterials(burnRecipeSO.output, this);
                        
                        state.Value = State.Burnt;
                    }
                        break;
                case State.Burnt:
                    break;
            }
        }
    }


    public override void Interact(Player player)
    {
        if (!HasForgeMaterials())
        {
            //there is no Forge Material here
            if (player.HasForgeMaterials())
            {
                //Player is carrying something
                if (HasRecipeWithInput(player.GetForgeMaterials().GetForgeMaterialsSO()))
                {
                    //player carrying something that can be flamed
                    ForgeMaterials forgeMaterials = player.GetForgeMaterials();

                    forgeMaterials.SetForgeMaterialsParent(this);

                    InteractLogicPlaceObjectOnCounterServerRpc(ForgeGameMultiplayer.Instance.GetForgeMaterialsSOIndex(forgeMaterials.GetForgeMaterialsSO()));
                }
            }
            else
            {
                // Player not carrying anything
            }
        }
        else
        {
            // There is forge material
            if (player.HasForgeMaterials())
            {
                // Player is carrying something
                if (player.GetForgeMaterials().TryGetPlate(out SlateForgeMaterialObject slateForgeMaterialObject))
                {
                    if (slateForgeMaterialObject.TryAddIngredient(GetForgeMaterials().GetForgeMaterialsSO()))
                    {
                        GetForgeMaterials().DestroySelf();

                        state.Value = State.Idle;
                    }
                }
            }
            else
            {
                // Player is not carrying anything
                GetForgeMaterials().SetForgeMaterialsParent(player);

                SetStateIdleServerRpc();
            }
        }
    }
    [ServerRpc(RequireOwnership = false)]
    private void SetStateIdleServerRpc()
    {
        state.Value = State.Idle;
    }

    [ServerRpc(RequireOwnership = false)]
    private void InteractLogicPlaceObjectOnCounterServerRpc(int forgeMaterialSOIndex)
    {
        flameTimer.Value = 0f;
        state.Value = State.Flaming;

        SetFlameRecipeSOClientRpc(forgeMaterialSOIndex);
    }
    
    [ClientRpc]
    private void SetFlameRecipeSOClientRpc(int forgeMaterialSOIndex)
    {
        ForgeMaterialsSO forgeMaterialsSO = ForgeGameMultiplayer.Instance.GetForgeMaterialsSOFromIndex(forgeMaterialSOIndex);
        flameRecipeSO = GetFlameRecipeSOWithInput(forgeMaterialsSO);
    }

    [ClientRpc]
    private void SetBurnRecipeSOClientRpc(int forgeMaterialSOIndex)
    {
        ForgeMaterialsSO forgeMaterialsSO = ForgeGameMultiplayer.Instance.GetForgeMaterialsSOFromIndex(forgeMaterialSOIndex);
        burnRecipeSO = GetBurnRecipeSOWithInput(forgeMaterialsSO);
    }
    private bool HasRecipeWithInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        FlameRecipeSO flameRecipeSO = GetFlameRecipeSOWithInput(inputForgeMaterialsSO);
        return flameRecipeSO != null;
    }

    private ForgeMaterialsSO GetOutputForInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        FlameRecipeSO flameRecipeSO = GetFlameRecipeSOWithInput(inputForgeMaterialsSO);
        if (flameRecipeSO != null)
        {
            return flameRecipeSO.output;
        }
        else
        {
            return null;
        }
    }

    private FlameRecipeSO GetFlameRecipeSOWithInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        foreach (FlameRecipeSO flameRecipeSO in flameRecipeSOArray)
        {
            if (flameRecipeSO.input == inputForgeMaterialsSO)
            {
                return flameRecipeSO;
            }
        }
        return null;
    }

    private BurnRecipeSO GetBurnRecipeSOWithInput(ForgeMaterialsSO inputForgeMaterialsSO)
    {
        foreach (BurnRecipeSO burnRecipeSO in burnRecipeSOArray)
        {
            if (burnRecipeSO.input == inputForgeMaterialsSO)
            {
                return burnRecipeSO;
            }
        }
        return null;
    }

    public bool IsFlamed()
    {
        return state.Value == State.Flamed;
    }
}

Apologies for all of the scripts but these are the ones I am having the issues with, the Base Counter Script is on all of the prefabricated counters and on the Client they can not be seen to be interacting with them you can put items down or pick them up again like you can on the host.

Any advice or thoughts on fixes would be greatly appreciated.

Many thanks,

Duncan

In general, if something doesn’t work for clients it’s either because your code is using incorrect conditionals (IsServer, IsHost, IsClient, IsOwner), or the ownership does not allow the interaction, or the interaction is not actually or incorrectly networked. Or the necessary initialization only occurs for the host but not remote players, for instance if data or actions have been sent via RPC prior to clients joining rather than a network variable.

It looks like each player can have its own resources (forge materials). Are these properly synchronized between clients?

Try debugging the code and step through to see where the issue is. Logging is also helpful.

Do you have a common place where you call these?
If they’re supposed to be automatically called they are missing the [InitializeOnLoadMethod] attribute.