Preperations

We will want to actually do some setup before we create our knock mechanic. Start by going into PrincipalScript. At the top of the script, with all the using namespaces. Add in these new namspaces
using System.Collections;
using System.Collections.Generic;

We will be using lists and IEnumerators today. Hence the namespaces

Now let's get to the copy and pasting coding! Add this above the Start() function
private List<DoorScript> doorsToKnock = new List<DoorScript>();
private AudioSource knockingSource;
[SerializeField] private AudioClip aud_KnockOnDoor;
[SerializeField] private float knockingDistance = 7f;
[SerializeField] private float knockingTime = 3f;
public bool inFaculty;

private void ResetDoors()
{
    doorsToKnock.Clear();
    agent.isStopped = false;
}

This creates the variables we will be needing, as well as our function for clearing the Principal's memory of doors. Now head over to the Start() function and append these lines of code below
knockingSource = base.gameObject.AddComponent<AudioSource>();
knockingSource.playOnAwake = false;
knockingSource.spatialBlend = 1;
knockingSource.rolloffMode = AudioRolloffMode.Linear;
knockingSource.minDistance = 10;
knockingSource.maxDistance = 100;

We create our own AudioSource from scratch because audioDevice has non-ideal 3D sound settings. AKA the knock will be heard all throughout the schoolhouse

Next. Head on over to Update(). Find the logic where the Principal finds the player, and add in ResetDoors();

Next, go to TargetBully() and add in ResetDoors(); as well

With this change. If the principal spots any tardiness in the schoolhouse, he shall forget his manners for a bit to chase after the evildoer. This just prevents any strange interactions that may occur.

Making the Principal knock

Now let's actually create the Principal knocking mechanic! Create a new IEnumerator called GenerateDoors() as shown below

private IEnumerator GenerateDoors()
{
    ResetDoors();
    while (!agent.hasPath) 
    {
        yield return null;
    }

    if (agent.path.corners.Length > 1)
    {
        for (int i = 0; i < agent.path.corners.Length - 1; i++)
        {
            Vector3 direction = agent.path.corners[i + 1] - agent.path.corners[i];

            if (Physics.Raycast(agent.path.corners[i], direction, out this.hit, direction.magnitude))
            {
                DoorScript door = hit.transform.GetComponent<DoorScript>();

                if (door != null && door.outside.sharedMaterial.name.Contains("Faculty"))
                {
                    doorsToKnock.Add(door);
                }
            }
        }
    }

    yield break;
}

Let's call this coroutine whenever the script calls the Wander() function

Let me explain the logic for a bit. GenerateDoors() gets ran whenever the Principal starts to Wander the schoolhouse, as that is when he should be knocking at faculty doors. The coroutine waits until the agent has finished generating the path. Once it does finish, it loop through all the waypoints in the path, and checks if a faculty door is inbetween them. If so, it adds that door to the list of doors to knock.

But now we need to actually make the Principal knock. Create another IEnumerator called KnockAtDoor() as shown
private IEnumerator KnockAtDoor()
{
    agent.isStopped = true;
    coolDown = knockingTime + 1f;
    knockingSource.PlayOneShot(aud_KnockOnDoor);

    yield return new WaitForSeconds(knockingTime);

    agent.isStopped = false;
    yield break;
}


Next. Head on over to the Update() function and add this at the bottom
if (doorsToKnock.Count > 0)
{
    DoorScript currentDoor = doorsToKnock[0];
    if (Vector3.Distance(currentDoor.transform.position, base.transform.position) < knockingDistance)
    {
        if (!(inFaculty || currentDoor.outside.sharedMaterial == currentDoor.open))
        {
            StartCoroutine(this.KnockAtDoor());
        }
        doorsToKnock.RemoveAt(0);
    }
}

We first check to see if there are any knockable doors in our path, and checks for the distance between the Principal and the earliest door in our path. If the distance is less than our knocking distance (which we set to 7f), the Principal will knock on the door! We make sure that the Principal doesn't knock if the door is already open or if the Principal is currently inside the faculty room himself

Is the Principal inside Faculty?

Now, we need to actually check if the Principal is inside a faculty room. Head on over to FacultyTriggerScript. Create a new variable as shown below

public PrincipalScript principal;

Go to the Start() function and add these two lines of code
principal = FindFirstObjectByType<PrincipalScript>(FindObjectsInactive.Include);
Physics.IgnoreLayerCollision(base.gameObject.layer, principal.gameObject.layer, false);

We find PrincipalScript via code using FindFirstObjectByType. And we tell the physics that we want the trigger and the Principal to allow interactions with each other, so that OnTriggerEnter can work. Speaking of which!
Head over to OnTriggerStay and add the following
if (other.gameObject.name == "Principal of the Thing")
{
    principal.inFaculty = true;
}

And also include an OnTriggerExit as shown below
private void OnTriggerExit(Collider other)
{
    if (other.gameObject.name == "Principal of the Thing")
    {
        principal.inFaculty = false;
    }
}

This simply updates the inFaculty boolean that we have made in the PrincipalScript

Final Touches

Save both scripts and head on over to the Unity Inspector. Find the Principal GameObject and look for his script. You will see three new variables on the top

All you need to do is set aud_KnockOnDoor to your Principal knock audio. So that audio would play when he does knock the door.
There are also two values you can change here
You do not need to change them if you are just going for an accurate knock. But feel free to customize these to your hearts content


Playtest

Now we can actually playtest our script! Run the scene and watch as the Principal knocks at one of the doors!