Загрузка данных



#pragma semicolon 1

#include <amxmodx>
#include <fakemeta>
#include <reapi>
#include <json>

#define PLUGIN_NAME    "Custom Spawn System"
#define PLUGIN_VERSION "1.0.0"
#define PLUGIN_AUTHOR  "OpenAI"

#define MAX_CUSTOM_SPAWNS 256

enum _:SpawnData
{
    Float:SpawnOrigin[3],
    Float:SpawnAngles[3]
};

new Array:g_aSpawnPoints;
new Array:g_aSavedPoints;

new g_iLastSpawnId = -1;
new g_iActiveSpawnCount;
new bool:g_bUsingCustomSpawns;

new g_szMapName[32];
new g_szFilePath[PLATFORM_MAX_PATH];

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

    register_clcmd("say /spawns", "Cmd_SpawnMenu");
    register_clcmd("say_team /spawns", "Cmd_SpawnMenu");
    register_concmd("amx_spawns", "Cmd_SpawnMenu");

    RegisterHookChain( RG_CSGameRules_GetPlayerSpawnSpot, "OnGetPlayerSpawnSpot_Pre", false );

    g_aSpawnPoints = ArrayCreate();
    g_aSavedPoints = ArrayCreate(SpawnData);

    get_mapname(g_szMapName, charsmax(g_szMapName));

    new dataDir[PLATFORM_MAX_PATH];
    get_localinfo("amxx_configsdir", dataDir, charsmax(dataDir));
    formatex( g_szFilePath, charsmax(g_szFilePath), "%s/spawns.json", dataDir );

    LoadCustomPoints();
    if (ArraySize(g_aSavedPoints) > 0)
        ActivateCustomPoints();

    if (!g_bUsingCustomSpawns)
        LoadDefaultPoints();

    g_iActiveSpawnCount = ArraySize(g_aSpawnPoints);
    server_print( "[Spawns] Map: %s | Active points: %d | Custom: %s", g_szMapName, g_iActiveSpawnCount, g_bUsingCustomSpawns ? "yes" : "no" );
}

public plugin_end()
{
    if (g_aSpawnPoints != Invalid_Array) ArrayDestroy(g_aSpawnPoints);
    if (g_aSavedPoints != Invalid_Array) ArrayDestroy(g_aSavedPoints);
}

LoadCustomPoints()
{
    if (!file_exists(g_szFilePath))
        return;

    new JSON:root = json_parse(g_szFilePath, true);
    if (root == Invalid_JSON || !json_is_object(root))
    {
        if (root != Invalid_JSON)
            json_free(root);

        log_amx("[Spawns] Invalid JSON file: %s", g_szFilePath);
        return;
    }

    new JSON:points = json_object_get_value(root, g_szMapName);
    if (points == Invalid_JSON || !json_is_array(points))
    {
        if (points != Invalid_JSON)
            json_free(points);

        json_free(root);
        return;
    }

    new count = json_array_get_count(points);

    for (new i = 0; i < count && i < MAX_CUSTOM_SPAWNS; i++)
    {
        new JSON:point = json_array_get_value(points, i);
        if (point == Invalid_JSON || !json_is_object(point))
        {
            if (point != Invalid_JSON)
                json_free(point);

            continue;
        }

        new JSON:origin = json_object_get_value(point, "origin");
        new JSON:angles = json_object_get_value(point, "angles");

        if (origin == Invalid_JSON || angles == Invalid_JSON || !json_is_array(origin) || !json_is_array(angles) || json_array_get_count(origin) < 3 || json_array_get_count(angles) < 3)
        {
            if (origin != Invalid_JSON)
                json_free(origin);

            if (angles != Invalid_JSON)
                json_free(angles);

            json_free(point);
            continue;
        }

        new data[SpawnData];

        for (new axis = 0; axis < 3; axis++)
        {
            data[SpawnOrigin][axis] = json_array_get_real(origin, axis);
            data[SpawnAngles][axis] = json_array_get_real(angles, axis);
        }

        ArrayPushArray(g_aSavedPoints, data);

        json_free(origin);
        json_free(angles);
        json_free(point);
    }

    json_free(points);
    json_free(root);
}

bool:ActivateCustomPoints()
{
    new count = ArraySize(g_aSavedPoints);

    for (new i = 0; i < count; i++)
    {
        new data[SpawnData];
        ArrayGetArray(g_aSavedPoints, i, data);

        new entity = engfunc( EngFunc_CreateNamedEntity, engfunc( EngFunc_AllocString, "info_player_deathmatch" ) );
        if (!pev_valid(entity))
            continue;

        set_pev(entity, pev_origin, data[SpawnOrigin]);
        set_pev(entity, pev_angles, data[SpawnAngles]);

        dllfunc( DLLFunc_Spawn, entity );
        if (!pev_valid(entity))
            continue;

        ArrayPushCell(g_aSpawnPoints, entity);
    }

    if (ArraySize(g_aSpawnPoints) > 0)
    {
        g_bUsingCustomSpawns = true;
        return true;
    }

    return false;
}

LoadDefaultPoints()
{
    new entity = -1;
    while ((entity = engfunc( EngFunc_FindEntityByString, entity, "classname", "info_player_start" )) > 0)
        ArrayPushCell(g_aSpawnPoints, entity);

    entity = -1;
    while ((entity = engfunc( EngFunc_FindEntityByString, entity, "classname", "info_player_deathmatch" )) > 0)
        ArrayPushCell(g_aSpawnPoints, entity);

    g_bUsingCustomSpawns = false;
}

public OnGetPlayerSpawnSpot_Pre(id)
{
    if (!is_user_connected(id))
        return HC_CONTINUE;

    new TeamName: team = get_member(id, m_iTeam);
    if (team != TEAM_TERRORIST && team != TEAM_CT)
        return HC_CONTINUE;

    new spot = SelectSpawnPoint(id);
    if (!pev_valid(spot))
        return HC_CONTINUE;

    new Float:origin[3], Float:angles[3], Float: v_angles[ 3 ];
    get_entvar(spot, var_origin, origin);
    get_entvar(spot, var_angles, angles);
    get_entvar(spot, var_v_angle, v_angles);

    origin[2] += 1.0;
    set_entvar(id, var_origin, origin);
    set_entvar(id, var_v_angle, v_angles);
    set_entvar(id, var_velocity, Float:{0.0, 0.0, 0.0});
    set_entvar(id, var_angles, angles);
    set_entvar(id, var_punchangle, Float:{0.0, 0.0, 0.0});
    set_entvar(id, var_fixangle, 1);

    SetHookChainReturn(ATYPE_INTEGER, spot);
    return HC_SUPERCEDE;
}

SelectSpawnPoint(id)
{
    new count = ArraySize(g_aSpawnPoints);
    if (count <= 0)
        return 0;

    new spot, Float:origin[3];
    for (new attempt = 0; attempt < count; attempt++)
    {
        g_iLastSpawnId++;
        if (g_iLastSpawnId >= count)
            g_iLastSpawnId = 0;

        spot = ArrayGetCell(g_aSpawnPoints, g_iLastSpawnId);
        if (!pev_valid(spot))
            continue;

        get_entvar(spot, var_origin, origin);
        if (!IsHullVacant(id, origin))
            continue;

        return spot;
    }

    return 0;
}

bool:IsHullVacant(id, Float:origin[3])
{
    engfunc( EngFunc_TraceHull, origin, origin, 0, HULL_HUMAN, id, 0 );
    if (get_tr2(0, TR_StartSolid) || get_tr2(0, TR_AllSolid) || !get_tr2(0, TR_InOpen))
        return false;

    return true;
}

public Cmd_SpawnMenu(id)
{
    ShowSpawnMenu(id);
    return PLUGIN_HANDLED;
}

ShowSpawnMenu(id)
{
    new menu = menu_create( "\yCustom Spawn System", "SpawnMenu_Handler" ), text[96];
    menu_additem(menu, "Add point at current position");
    menu_additem(menu, "List / delete saved points");

    formatex( text, charsmax(text), "Saved points: %d", ArraySize(g_aSavedPoints) );
    menu_addtext(menu, text);

    menu_setprop(menu, MPROP_EXITNAME, "Exit");
    menu_display(id, menu);
}

public SpawnMenu_Handler(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu);
        return PLUGIN_HANDLED;
    }

    menu_destroy(menu);

    switch (item)
    {
        case 0: AddSpawnPoint(id);
        case 1: ShowSpawnList(id);
    }

    return PLUGIN_HANDLED;
}

AddSpawnPoint(id)
{
    if (!is_user_alive(id))
    {
        client_print( id, print_chat, "[Spawns] You must be alive to add a point." );
        return;
    }

    if (ArraySize(g_aSavedPoints) >= MAX_CUSTOM_SPAWNS)
    {
        client_print(id, print_chat, "[Spawns] Maximum reached.");
        return;
    }

    new data[SpawnData];
    get_entvar(id, var_origin, data[SpawnOrigin]);
    get_entvar(id, var_v_angle, data[SpawnAngles]);

    ArrayPushArray(g_aSavedPoints, data);

    if (!SaveCustomPoints())
    {
        ArrayDeleteItem( g_aSavedPoints, ArraySize(g_aSavedPoints) - 1 );
        client_print(id, print_chat, "[Spawns] JSON save failed.");
        return;
    }

    client_print( id, print_chat, "[Spawns] Point saved. It will activate next map." );
    ShowSpawnMenu(id);
}

ShowSpawnList(id)
{
    new menu = menu_create( "\ySaved points \r- Select to delete", "SpawnList_Handler" );

    new data[SpawnData], text[128], info[12];
    for (new i = 0; i < ArraySize(g_aSavedPoints); i++)
    {
        ArrayGetArray(g_aSavedPoints, i, data);

        formatex( text, charsmax(text), "#%d: %.0f %.0f %.0f", i + 1, data[SpawnOrigin][0], data[SpawnOrigin][1], data[SpawnOrigin][2] );

        num_to_str(i, info, charsmax(info));
        menu_additem(menu, text, info);
    }

    menu_setprop(menu, MPROP_EXITNAME, "Back");
    menu_display(id, menu);
}

public SpawnList_Handler(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu);
        ShowSpawnMenu(id);
        return PLUGIN_HANDLED;
    }

    new info[12], access, callback;
    menu_item_getinfo( menu, item, access, info, charsmax(info), _, _, callback );

    new index = str_to_num(info);
    menu_destroy(menu);
    if (index < 0 || index >= ArraySize(g_aSavedPoints))
    {
        ShowSpawnMenu(id);
        return PLUGIN_HANDLED;
    }

    ArrayDeleteItem(g_aSavedPoints, index);

    if (!SaveCustomPoints())
    {
        client_print(id, print_chat, "[Spawns] JSON save failed.");
        LoadCustomPoints();
    }

    client_print( id, print_chat, "[Spawns] Point deleted. Changes apply next map." );

    ShowSpawnList(id);
    return PLUGIN_HANDLED;
}

bool:SaveCustomPoints()
{
    new JSON:root;

    if (file_exists(g_szFilePath))
        root = json_parse(g_szFilePath, true);
    else
        root = Invalid_JSON;

    if (root == Invalid_JSON || !json_is_object(root))
    {
        if (root != Invalid_JSON)
            json_free(root);

        root = json_init_object();
    }

    if (ArraySize(g_aSavedPoints) == 0)
        json_object_remove(root, g_szMapName);
    else
    {
        new JSON:points = json_init_array();
        for (new i = 0; i < ArraySize(g_aSavedPoints); i++)
        {
            new data[SpawnData];
            ArrayGetArray(g_aSavedPoints, i, data);

            new JSON:point = json_init_object();
            new JSON:origin = json_init_array();
            new JSON:angles = json_init_array();

            for (new axis = 0; axis < 3; axis++)
            {
                json_array_append_real( origin, data[SpawnOrigin][axis] );
                json_array_append_real( angles, data[SpawnAngles][axis] );
            }

            json_object_set_value(point, "origin", origin);
            json_object_set_value(point, "angles", angles);
            json_array_append_value(points, point);

            json_free(origin);
            json_free(angles);
            json_free(point);
        }

        json_object_set_value(root, g_szMapName, points);
        json_free(points);
    }

    new bool:success = json_serial_to_file( root, g_szFilePath, true );
    json_free(root);
    if (!success) log_amx("[Spawns] Could not write %s", g_szFilePath);
    return success;
}