Загрузка данных
#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 SPAWN_ACCESS ADMIN_CFG
#define MAX_CUSTOM_SPAWNS 256
#define HULL_HUMAN 1
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", SPAWN_ACCESS);
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_datadir", dataDir, charsmax(dataDir));
formatex(
g_szFilePath,
charsmax(g_szFilePath),
"%s/rezp_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);
}
/*
* JSON structure:
*
* {
* "de_dust2": [
* {
* "origin": [0.0, 0.0, 0.0],
* "angles": [0.0, 90.0, 0.0]
* }
* ]
* }
*/
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 = create_entity("info_player_deathmatch");
if (!pev_valid(entity))
continue;
set_pev(entity, pev_origin, data[SpawnOrigin]);
set_pev(entity, pev_angles, data[SpawnAngles]);
DispatchSpawn(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 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];
new Float:angles[3];
get_entvar(spot, var_origin, origin);
get_entvar(spot, var_angles, angles);
origin[2] += 1.0;
set_entvar(id, var_origin, origin);
set_entvar(id, var_v_angle, Float:{0.0, 0.0, 0.0});
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;
new 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)
{
if (!(get_user_flags(id) & SPAWN_ACCESS))
{
console_print(id, "[Spawns] Access denied.");
return PLUGIN_HANDLED;
}
ShowSpawnMenu(id);
return PLUGIN_HANDLED;
}
ShowSpawnMenu(id)
{
new menu = menu_create(
"\yCustom Spawn System",
"SpawnMenu_Handler"
);
menu_additem(menu, "Add point at current position");
menu_additem(menu, "List / delete saved points");
new text[96];
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;
}
if (!(get_user_flags(id) & SPAWN_ACCESS))
{
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_angles, 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];
new text[128];
new 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;
}
if (!(get_user_flags(id) & SPAWN_ACCESS))
{
menu_destroy(menu);
return PLUGIN_HANDLED;
}
new info[12];
new 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;
}