/*
================================================================================
 Model Precache Monitor
 --------------------------------------------------------------------------
 Purpose:
   GoldSrc (Half-Life 1.6) has a hard engine limit of 512 precached models
   per map (MAX_MODELS). When a map + its plugins (e.g. BlockMaker) exceed
   this limit, clients get silently disconnected/crashed with NO error
   message anywhere, because the overflow happens in the client's network
   delta-decoding, not in server-side game logic.

   This plugin hooks the engine's model precache function, counts every
   model precached on map start, logs each one with its index, and warns
   loudly (server console + log file) if the count approaches or exceeds
   the 512 limit. This gives you hard evidence of which maps are at risk
   and by how much, so you know exactly how many models to cut.

 Requirements:
   - AMX Mod X 1.10.x
   - Fakemeta module (ships with AMXX by default)

 Author: (generated for user diagnostic use)
================================================================================
*/

#include <amxmodx>
#include <amxmisc>
#include <fakemeta>

#define PLUGIN_NAME    "Model Precache Monitor"
#define PLUGIN_VERSION "1.0"
#define PLUGIN_AUTHOR  "Diagnostic Tool"

// Hard engine limit for GoldSrc. Do not change - this is fixed by the engine.
#define MAX_MODELS_LIMIT   512

// Threshold at which we start warning (models remaining gets tight).
#define WARNING_THRESHOLD  480

new g_modelCount;
new g_logFile[128];
new g_mapName[64];

// Simple growable log of precached model names for this map load.
new g_modelNames[MAX_MODELS_LIMIT + 64][64];

public plugin_init()
{
    register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR);

    register_concmd("model_report", "cmd_model_report", ADMIN_RCON,
        "- prints the current map's precached model count/report to console");
}

public plugin_precache()
{
    // Reset counters for the new map load. plugin_precache() runs before
    // other plugins' precache in load order, but since AMXX loads plugins
    // in the order listed in plugins.ini, place this plugin FIRST in
    // plugins.ini so it hooks before BlockMaker precaches its models.
    g_modelCount = 0;
    get_mapname(g_mapName, charsmax(g_mapName));

    new dataDir[64];
    formatex(dataDir, charsmax(dataDir), "addons/amxmodx/logs/model_precache");
    // Ensure log directory exists (AMXX will create logs/ itself; this is a subfolder)
    if (!dir_exists(dataDir))
    {
        mkdir(dataDir);
    }

    formatex(g_logFile, charsmax(g_logFile), "%s/%s.log", dataDir, g_mapName);

    // Start fresh log file for this map load
    new f = fopen(g_logFile, "wt");
    if (f)
    {
        fprintf(f, "=== Model Precache Report for map: %s ===^n", g_mapName);
        fprintf(f, "Engine hard limit: %d models^n^n", MAX_MODELS_LIMIT);
        fclose(f);
    }

    // Hook the engine precache_model calls from this point forward.
    // We use a forward on the engine function via fakemeta.
    register_forward(FM_PrecacheModel, "fw_PrecacheModel");
}

public fw_PrecacheModel(const model[])
{
    if (g_modelCount >= (MAX_MODELS_LIMIT + 64))
    {
        // Safety bound on our own tracking array, should never realistically hit this.
        return FMRES_IGNORED;
    }

    // Avoid double-counting exact duplicate precache calls (engine also
    // de-duplicates internally, but we mirror that so our count matches reality).
    for (new i = 0; i < g_modelCount; i++)
    {
        if (equal(g_modelNames[i], model))
        {
            return FMRES_IGNORED; // already counted, not a new index
        }
    }

    copy(g_modelNames[g_modelCount], 63, model);
    g_modelCount++;

    new f = fopen(g_logFile, "a");
    if (f)
    {
        fprintf(f, "[%03d] %s^n", g_modelCount, model);
        fclose(f);
    }

    if (g_modelCount == WARNING_THRESHOLD)
    {
        log_amx("[Model Precache Monitor] WARNING: Map '%s' has reached %d/%d precached models. Getting close to the engine limit!",
            g_mapName, g_modelCount, MAX_MODELS_LIMIT);
        server_print("[Model Precache Monitor] WARNING: %d/%d models precached on map %s",
            g_modelCount, MAX_MODELS_LIMIT, g_mapName);
    }

    if (g_modelCount == MAX_MODELS_LIMIT)
    {
        log_amx("[Model Precache Monitor] CRITICAL: Map '%s' has HIT the %d model limit. Any further precache calls WILL cause client crashes/disconnects.",
            g_mapName, MAX_MODELS_LIMIT);
        server_print("[Model Precache Monitor] CRITICAL: model limit reached on map %s! Clients will start crashing.",
            g_mapName);
    }

    return FMRES_IGNORED;
}

public cmd_model_report(id, level, cid)
{
    if (!cmd_access(id, level, cid, 1))
        return PLUGIN_HANDLED;

    console_print(id, "=== Model Precache Report ===");
    console_print(id, "Map: %s", g_mapName);
    console_print(id, "Total unique models precached: %d / %d", g_modelCount, MAX_MODELS_LIMIT);
    console_print(id, "Remaining headroom: %d", MAX_MODELS_LIMIT - g_modelCount);
    console_print(id, "Full log written to: %s", g_logFile);

    if (g_modelCount >= MAX_MODELS_LIMIT)
    {
        console_print(id, "STATUS: OVER LIMIT - this map WILL crash clients.");
    }
    else if (g_modelCount >= WARNING_THRESHOLD)
    {
        console_print(id, "STATUS: WARNING - close to limit, reduce models soon.");
    }
    else
    {
        console_print(id, "STATUS: OK");
    }

    return PLUGIN_HANDLED;
}
