TF2-DMB / CBaseNPC

Provides a friendly interface for plugins to use in order to create custom NPCs on the game Team Fortress 2
37 stars 6 forks source link

Add NavMesh natives #12

Closed KitRifty closed 2 years ago

KitRifty commented 2 years ago

Adds some more useful natives for CNavMesh, CNavArea, and HidingSpot. All areas and hiding spots can be accessed using the TheNavAreas and TheHidingSpots global vectors, respectively. Also, the actual pointer to TheNavMesh is exposed to plugins, allowing for hooking with DHooks, or just calling any other function on the mesh itself. A list of all added natives can be found in the commit message.

EDIT: Forgot to put CNavArea.GetCorner() and CNavMesh.GetNavAreaByID() in the commit message but they're in there.

KitRifty commented 2 years ago

For testing, the following code was used:

DynamicHook hOnRoundRestart;

public void OnPluginStart()
{
    hOnRoundRestart = new DynamicHook(18, HookType_Raw, ReturnType_Void, ThisPointer_Address);
    hOnRoundRestart.HookRaw(Hook_Post, TheNavMesh.Address, HookOnRoundRestart);
}

public void OnMapStart()
{
    TestNav();
}

static MRESReturn HookOnRoundRestart(Address thisAddr)
{
    PrintToServer("TheNavMesh.OnRoundRestart() fired");

    return MRES_Ignored;
}

void TestNav()
{
    PrintToServer("BEGIN NAV TEST");

    PrintToServer("TheNavMesh.IsLoaded = %d", TheNavMesh.IsLoaded());
    PrintToServer("TheNavMesh.IsAnalyzed = %d", TheNavMesh.IsAnalyzed());
    PrintToServer("TheNavMesh.IsOutOfDate = %d", TheNavMesh.IsOutOfDate());
    PrintToServer("TheNavMesh.GetNavAreaCount() = %d", TheNavMesh.GetNavAreaCount());
    PrintToServer("TheNavAreas.Length = %d", TheNavAreas.Length);
    PrintToServer("TheHidingSpots.Length = %d", TheHidingSpots.Length);

    if (TheNavMesh.GetNavAreaCount() != TheNavAreas.Length)
        ThrowError("TheNavMesh.GetNavAreaCount() and TheNavAreas.Length do not match!");

    if (TheNavAreas.Length > 0)
    {
        float flAverageLightIntensity = 0.0;

        for (int i = 0; i < TheNavAreas.Length; i++)
        {
            CNavArea area = TheNavAreas.Get(i);
            if (!area)
                ThrowError("NULL area found in TheNavAreas!");

            int iId = area.GetID();
            if (iId == -1)
                ThrowError("Area has bad id!");

            if (TheNavMesh.GetNavAreaByID(iId) != area)
                ThrowError("Area mismatch by GetAreaByID(%d)!", iId);

            flAverageLightIntensity += area.GetLightIntensity();

            for (NavDirType dir = view_as<NavDirType>(0); dir < NUM_DIRECTIONS; dir++)
            {
                ArrayList adjacent = new ArrayList();
                area.GetAdjacentAreas(dir, adjacent);

                for (int j = 0; j < adjacent.Length; j++)
                {
                    CNavArea connected = view_as<CNavArea>(adjacent.Get(j));
                    if (!connected)
                        ThrowError("Area has outgoing connection to a NULL area!");

                    if (!area.IsConnected(connected, dir))
                        ThrowError("Area has a bad outgoing connection to an area!");
                }

                delete adjacent;

                ArrayList incoming = new ArrayList();
                area.GetIncomingConnections(dir, incoming);

                for (int j = 0; j < incoming.Length; j++)
                {
                    CNavArea inc = view_as<CNavArea>(incoming.Get(j));
                    if (!inc)
                        ThrowError("Area has outgoing connection to a NULL area!");

                    if (!inc.IsConnected(area, OppositeDirection(dir)))
                        ThrowError("Area has an inconsistent incoming connection to an area!");
                }

                delete incoming;
            }

            ArrayList hidingSpots = new ArrayList();
            area.GetHidingSpots(hidingSpots);

            for (int j = 0; j < hidingSpots.Length; j++)
            {
                HidingSpot hidingSpot = view_as<HidingSpot>(hidingSpots.Get(j));
                if (!hidingSpot)
                    ThrowError("Area has NULL hiding spot!");
            }

            delete hidingSpots;
        }

        flAverageLightIntensity /= TheNavAreas.Length;
        if (flAverageLightIntensity < 0.0 || flAverageLightIntensity > 1.0)
            ThrowError("Light intensity is out of range!");

        PrintToServer("Average light intensity: %0.2f", flAverageLightIntensity);

    }

    PrintToServer("END NAV TEST");
}

Test results on cp_gorge:

BEGIN NAV TEST
TheNavMesh.IsLoaded = 1
TheNavMesh.IsAnalyzed = 1
TheNavMesh.IsOutOfDate = 0
TheNavMesh.GetNavAreaCount() = 1596
TheNavAreas.Length = 1596
TheHidingSpots.Length = 825
Average light intensity: 1.00
END NAV TEST

Test results on cp_badlands:

BEGIN NAV TEST
TheNavMesh.IsLoaded = 1
TheNavMesh.IsAnalyzed = 1
TheNavMesh.IsOutOfDate = 0
TheNavMesh.GetNavAreaCount() = 3193
TheNavAreas.Length = 3193
TheHidingSpots.Length = 1402
Average light intensity: 1.00
END NAV TEST
bot
Cannot verify load for invalid steam ID [A:1:0:1]
autokick is disabled for Bot01
TheNavMesh.OnRoundRestart() fired
mp_restartgame 1
TheNavMesh.OnRoundRestart() fired

All tests were done on Windows.