Загрузка данных
#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;
}