From 47cc3856d2f9a20393a6a35cd4fd94fc7e33503e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sat, 28 Mar 2026 00:58:02 +0300 Subject: [PATCH 01/30] Impl placeholders --- .gitignore | 1 + amxmodx/scripting/ParamsController.sma | 15 +- .../Placeholders/API/General.inc | 60 ++++ .../Placeholders/API/Group.inc | 72 +++++ .../ParamsController/Placeholders/API/Key.inc | 88 ++++++ .../Placeholders/Objects/PHGroup.inc | 267 ++++++++++++++++++ .../scripting/include/ParamsController.inc | 205 ++++++++++++++ 7 files changed, 706 insertions(+), 2 deletions(-) create mode 100644 amxmodx/scripting/ParamsController/Placeholders/API/General.inc create mode 100644 amxmodx/scripting/ParamsController/Placeholders/API/Group.inc create mode 100644 amxmodx/scripting/ParamsController/Placeholders/API/Key.inc create mode 100644 amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc diff --git a/.gitignore b/.gitignore index 29c91b0..40b0a40 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.zip .build plugins-*.ini +.claude diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index bf960f1..4a8d7b7 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -4,6 +4,7 @@ #include "ParamsController/Forwards" #include "ParamsController/Objects/Param" +#include "ParamsController/Placeholders/Objects/PHGroup" #include "ParamsController/DefaultObjects/Registrar" public stock const PluginName[] = "Params Controller"; @@ -27,14 +28,18 @@ PluginInit() { Forwards_Init(); Param_Init(); + PHGroup_Init(); DefaultObjects_ParamType_Register(); - // Тут регать типы + // Тут регать типы параметров Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); + // Тут регать группы и ключи плейсхолдеров + Forwards_RegAndCall("ParamsController_PH_OnRegisterGroups", ET_IGNORE); + register_srvcmd("params_controller_types", "@SrvCmd_Types"); - // После этого можно юзать типы + // После этого можно юзать типы и плейсхолдеры Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); } @@ -49,9 +54,15 @@ bool:IsPluginInited() { #include "ParamsController/API/General" #include "ParamsController/API/Param" #include "ParamsController/API/ParamType" +#include "ParamsController/Placeholders/API/General" +#include "ParamsController/Placeholders/API/Group" +#include "ParamsController/Placeholders/API/Key" public plugin_natives() { API_General_Register(); API_Param_Register(); API_ParamType_Register(); + API_PH_General_Register(); + API_PH_Group_Register(); + API_PH_Key_Register(); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc new file mode 100644 index 0000000..4264fec --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc @@ -0,0 +1,60 @@ +#include +#include "ParamsController/API/Utils" +#include "ParamsController/Placeholders/Objects/PHGroup" + +API_PH_General_Register() { + register_native("ParamsController_PH_Format", "@API_PH_Format"); + register_native("ParamsController_PH_SetValue", "@API_PH_SetValue"); + register_native("ParamsController_PH_SetIntValue", "@API_PH_SetIntValue"); + register_native("ParamsController_PH_SetFloatValue", "@API_PH_SetFloatValue"); +} + +@API_PH_Format() { + enum {Arg_Out = 1, Arg_OutLen, Arg_Format} + + API_CheckPluginInited(); + + static sFormat[PH_FORMAT_MAX_LEN]; + get_string(Arg_Format, sFormat, charsmax(sFormat)); + + static sOut[PH_FORMAT_MAX_LEN]; + PHGroup_FormatString(sFormat, sOut, charsmax(sOut)); + + set_string(Arg_Out, sOut, get_param(Arg_OutLen)); +} + +@API_PH_SetValue() { + enum {Arg_Value = 1} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static sValue[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, sValue, charsmax(sValue)); + + PHGroup_SetCurrentValue(sValue); +} + +@API_PH_SetIntValue() { + enum {Arg_Value = 1} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static sValue[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), sValue, charsmax(sValue)); + + PHGroup_SetCurrentValue(sValue); +} + +@API_PH_SetFloatValue() { + enum {Arg_Value = 1} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static sValue[PH_VALUE_MAX_LEN]; + format(sValue, charsmax(sValue), "%.2f", get_param_f(Arg_Value)); + + PHGroup_SetCurrentValue(sValue); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc new file mode 100644 index 0000000..4441d19 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc @@ -0,0 +1,72 @@ +#include +#include "ParamsController/API/Utils" +#include "ParamsController/Placeholders/Objects/PHGroup" + +API_PH_Group_Register() { + register_native("ParamsController_PH_RegisterGroup", "@API_PH_RegisterGroup"); + register_native("ParamsController_PH_FindGroup", "@API_PH_FindGroup"); + register_native("ParamsController_PH_GetGlobalGroup", "@API_PH_GetGlobalGroup"); + register_native("ParamsController_PH_PushContext", "@API_PH_PushContext"); + register_native("ParamsController_PH_PopContext", "@API_PH_PopContext"); +} + +T_PHGroup:@API_PH_RegisterGroup() { + enum {Arg_Prefix = 1} + + API_CheckPluginInited(); + + static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, sPrefix, charsmax(sPrefix)); + + if (sPrefix[0] == 0) { + abort(AMX_ERR_PARAMS, "PH group prefix must not be empty."); + return Invalid_PHGroup; + } + + if (PHGroup_Find(sPrefix) != Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "PH group with prefix '%s' is already registered.", sPrefix); + return Invalid_PHGroup; + } + + return PHGroup_Construct(sPrefix); +} + +T_PHGroup:@API_PH_FindGroup() { + enum {Arg_Prefix = 1} + + API_CheckPluginInited(); + + static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, sPrefix, charsmax(sPrefix)); + + return PHGroup_Find(sPrefix); +} + +T_PHGroup:@API_PH_GetGlobalGroup() { + API_CheckPluginInited(); + + return PHGroup_GetGlobal(); +} + +@API_PH_PushContext() { + enum {Arg_Group = 1, Arg_ContextKey} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + + PHGroup_PushContext(hGroup, sContextKey); +} + +@API_PH_PopContext() { + enum {Arg_Group = 1} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + PHGroup_PopContext(hGroup); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc new file mode 100644 index 0000000..a4110b5 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc @@ -0,0 +1,88 @@ +#include +#include "ParamsController/API/Utils" +#include "ParamsController/Placeholders/Objects/PHGroup" + +API_PH_Key_Register() { + register_native("ParamsController_PH_RegisterGroupKey", "@API_PH_RegisterGroupKey"); + register_native("ParamsController_PH_SetGroupKeyCallback", "@API_PH_SetGroupKeyCallback"); + register_native("ParamsController_PH_Set", "@API_PH_Set"); + register_native("ParamsController_PH_SetInt", "@API_PH_SetInt"); + register_native("ParamsController_PH_SetFloat", "@API_PH_SetFloat"); +} + +@API_PH_RegisterGroupKey() { + enum {Arg_Group = 1, Arg_Key} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sKey[PH_KEY_MAX_LEN]; + get_string(Arg_Key, sKey, charsmax(sKey)); + + PHGroup_RegisterKey(hGroup, sKey); +} + +@API_PH_SetGroupKeyCallback(const pluginIndex) { + enum {Arg_Group = 1, Arg_Key, Arg_Callback} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sKey[PH_KEY_MAX_LEN]; + get_string(Arg_Key, sKey, charsmax(sKey)); + + static sCallback[PH_CALLBACK_MAX_LEN]; + get_string(Arg_Callback, sCallback, charsmax(sCallback)); + + PHGroup_SetKeyCallback(hGroup, sKey, sCallback, pluginIndex); +} + +@API_PH_Set() { + enum {Arg_Group = 1, Arg_Key, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sKey[PH_KEY_MAX_LEN]; + get_string(Arg_Key, sKey, charsmax(sKey)); + + static sValue[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, sValue, charsmax(sValue)); + + PHGroup_SetValue(hGroup, sKey, sValue); +} + +@API_PH_SetInt() { + enum {Arg_Group = 1, Arg_Key, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sKey[PH_KEY_MAX_LEN]; + get_string(Arg_Key, sKey, charsmax(sKey)); + + static sValue[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), sValue, charsmax(sValue)); + + PHGroup_SetValue(hGroup, sKey, sValue); +} + +@API_PH_SetFloat() { + enum {Arg_Group = 1, Arg_Key, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + + static sKey[PH_KEY_MAX_LEN]; + get_string(Arg_Key, sKey, charsmax(sKey)); + + static sValue[PH_VALUE_MAX_LEN]; + format(sValue, charsmax(sValue), "%.2f", get_param_f(Arg_Value)); + + PHGroup_SetValue(hGroup, sKey, sValue); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc new file mode 100644 index 0000000..61d70b0 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -0,0 +1,267 @@ +#if defined __pc_src_phgroup_included + #endinput +#endif +#define __pc_src_phgroup_included + +#include +#include + +enum _:S_PHGroup { + PHGroup_Prefix[PH_GROUP_PREFIX_MAX_LEN], + Trie:PHGroup_Callbacks, // ключ → хендлер форварда (INVALID_HANDLE если колбека нет) + Trie:PHGroup_Values, // ключ → текущее значение (строка) + Stack:PHGroup_ContextStack, // стек сохранённых предыдущих контекстов + PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) +} + +static Array:g_aPHGroups = Invalid_Array; +static Trie:g_tPHGroupsMap = Invalid_Trie; +static T_PHGroup:g_hPHGlobalGroup = Invalid_PHGroup; + +static T_PHGroup:g_hCurrentPHGroup = Invalid_PHGroup; +static g_sCurrentPHKey[PH_KEY_MAX_LEN]; + +PHGroup_Init() { + if (g_aPHGroups == Invalid_Array) { + g_aPHGroups = ArrayCreate(S_PHGroup, 1); + } + + if (g_tPHGroupsMap == Invalid_Trie) { + g_tPHGroupsMap = TrieCreate(); + } + + g_hPHGlobalGroup = PHGroup_Construct(""); +} + +T_PHGroup:PHGroup_Construct(const sPrefix[]) { + new groupData[S_PHGroup]; + + copy(groupData[PHGroup_Prefix], charsmax(groupData[PHGroup_Prefix]), sPrefix); + groupData[PHGroup_Callbacks] = TrieCreate(); + groupData[PHGroup_Values] = TrieCreate(); + groupData[PHGroup_ContextStack] = Invalid_Stack; + groupData[PHGroup_CurrentContext][0] = 0; + + new T_PHGroup:hGroup = T_PHGroup:ArrayPushArray(g_aPHGroups, groupData); + + if (sPrefix[0] != 0) { + TrieSetCell(g_tPHGroupsMap, sPrefix, hGroup); + } + + return hGroup; +} + +T_PHGroup:PHGroup_Find(const sPrefix[]) { + new T_PHGroup:hGroup = Invalid_PHGroup; + TrieGetCell(g_tPHGroupsMap, sPrefix, hGroup); + return hGroup; +} + +T_PHGroup:PHGroup_GetGlobal() { + return g_hPHGlobalGroup; +} + +static PHGroup_Get(const T_PHGroup:hGroup, groupData[S_PHGroup]) { + ArrayGetArray(g_aPHGroups, _:hGroup, groupData); +} + +static PHGroup_Update(const T_PHGroup:hGroup, const groupData[S_PHGroup]) { + ArraySetArray(g_aPHGroups, _:hGroup, groupData); +} + +PHGroup_RegisterKey(const T_PHGroup:hGroup, const sKey[]) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + + if (!TrieKeyExists(groupData[PHGroup_Callbacks], sKey)) { + TrieSetCell(groupData[PHGroup_Callbacks], sKey, INVALID_HANDLE); + } +} + +PHGroup_SetKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[], const pluginIndex) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + + new iOldForward; + if (TrieGetCell(groupData[PHGroup_Callbacks], sKey, iOldForward) && iOldForward != INVALID_HANDLE) { + DestroyForward(iOldForward); + } + + // (const sKey[], const sContextKey[]) + new iForward = CreateOneForward(pluginIndex, sCallback, FP_STRING, FP_STRING); + if (iForward == INVALID_HANDLE) { + abort(AMX_ERR_GENERAL, "Can't create PH callback by '%s' func of #%d plugin.", sCallback, pluginIndex); + return; + } + + TrieSetCell(groupData[PHGroup_Callbacks], sKey, iForward); +} + +PHGroup_PushContext(const T_PHGroup:hGroup, const sContextKey[]) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + + if (groupData[PHGroup_ContextStack] == Invalid_Stack) { + groupData[PHGroup_ContextStack] = CreateStack(PH_CONTEXT_KEY_MAX_LEN); + } + + PushStackArray(groupData[PHGroup_ContextStack], groupData[PHGroup_CurrentContext], PH_CONTEXT_KEY_MAX_LEN); + copy(groupData[PHGroup_CurrentContext], charsmax(groupData[PHGroup_CurrentContext]), sContextKey); + + PHGroup_Update(hGroup, groupData); +} + +PHGroup_PopContext(const T_PHGroup:hGroup) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + + if ( + groupData[PHGroup_ContextStack] != Invalid_Stack + && !IsStackEmpty(groupData[PHGroup_ContextStack]) + ) { + PopStackArray(groupData[PHGroup_ContextStack], groupData[PHGroup_CurrentContext], PH_CONTEXT_KEY_MAX_LEN); + } else { + groupData[PHGroup_CurrentContext][0] = 0; + } + + PHGroup_Update(hGroup, groupData); +} + +PHGroup_SetValue(const T_PHGroup:hGroup, const sKey[], const sValue[]) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + TrieSetString(groupData[PHGroup_Values], sKey, sValue); +} + +static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const sKey[], sOut[], const iOutLen) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + return bool:TrieGetString(groupData[PHGroup_Values], sKey, sOut, iOutLen); +} + +static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const sKey[]) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + return bool:TrieKeyExists(groupData[PHGroup_Callbacks], sKey); +} + +static PHGroup_CallCallback(const T_PHGroup:hGroup, const sKey[]) { + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + + new iForward; + if (!TrieGetCell(groupData[PHGroup_Callbacks], sKey, iForward) || iForward == INVALID_HANDLE) { + return; + } + + g_hCurrentPHGroup = hGroup; + copy(g_sCurrentPHKey, charsmax(g_sCurrentPHKey), sKey); + + new iRet; + ExecuteForward(iForward, iRet, sKey, groupData[PHGroup_CurrentContext]); + + g_hCurrentPHGroup = Invalid_PHGroup; + g_sCurrentPHKey[0] = 0; +} + +PHGroup_FormatString(const sFormat[], sOut[], const iOutLen) { + if (iOutLen < 1) { + return; + } + + new iOutPos = 0; + new iInPos = 0; + new iFormatLen = strlen(sFormat); + + while (iInPos < iFormatLen && iOutPos < iOutLen - 1) { + if (sFormat[iInPos] != '{') { + sOut[iOutPos++] = sFormat[iInPos++]; + continue; + } + + new iClosing = iInPos + 1; + while (iClosing < iFormatLen && sFormat[iClosing] != '}') { + ++iClosing; + } + + if (iClosing >= iFormatLen) { + // Незакрытая скобка — выводим как есть + sOut[iOutPos++] = sFormat[iInPos++]; + continue; + } + + static sPlaceholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; + new iPlaceholderLen = min(iClosing - iInPos - 1, charsmax(sPlaceholder)); + + for (new k = 0; k < iPlaceholderLen; ++k) { + sPlaceholder[k] = sFormat[iInPos + 1 + k]; + } + sPlaceholder[iPlaceholderLen] = 0; + + static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; + static sKey[PH_KEY_MAX_LEN]; + + new iColon = -1; + for (new k = 0; k < iPlaceholderLen; ++k) { + if (sPlaceholder[k] == ':') { + iColon = k; + break; + } + } + + if (iColon < 0) { + sPrefix[0] = 0; + copy(sKey, charsmax(sKey), sPlaceholder); + } else { + new iPrefixLen = min(iColon, charsmax(sPrefix)); + for (new k = 0; k < iPrefixLen; ++k) { + sPrefix[k] = sPlaceholder[k]; + } + sPrefix[iPrefixLen] = 0; + copy(sKey, charsmax(sKey), sPlaceholder[iColon + 1]); + } + + new T_PHGroup:hGroup = (sPrefix[0] == 0) + ? g_hPHGlobalGroup + : PHGroup_Find(sPrefix); + + if (hGroup != Invalid_PHGroup && PHGroup_IsKeyRegistered(hGroup, sKey)) { + PHGroup_CallCallback(hGroup, sKey); + + static sValue[PH_VALUE_MAX_LEN]; + if (PHGroup_GetValue(hGroup, sKey, sValue, charsmax(sValue))) { + new iValueLen = strlen(sValue); + for (new k = 0; k < iValueLen && iOutPos < iOutLen - 1; ++k) { + sOut[iOutPos++] = sValue[k]; + } + } + } else { + // Неизвестная группа или незарегистрированный ключ — сохраняем как есть + sOut[iOutPos++] = '{'; + for (new k = 0; k < iPlaceholderLen && iOutPos < iOutLen - 1; ++k) { + sOut[iOutPos++] = sPlaceholder[k]; + } + if (iOutPos < iOutLen - 1) { + sOut[iOutPos++] = '}'; + } + } + + iInPos = iClosing + 1; + } + + sOut[iOutPos] = 0; +} + +bool:PHGroup_IsInCallback() { + return g_hCurrentPHGroup != Invalid_PHGroup; +} + +PHGroup_SetCurrentValue(const sValue[]) { + PHGroup_SetValue(g_hCurrentPHGroup, g_sCurrentPHKey, sValue); +} + +API_CheckCurrentPH() { + if (!PHGroup_IsInCallback()) { + abort(AMX_ERR_PARAMS, "Attempt to set PH value outside the key callback."); + } +} diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 99372fa..e062cdc 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1321,6 +1321,211 @@ stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { return str; } +// ===== Placeholders ===== + +#define PH_GROUP_PREFIX_MAX_LEN 16 +#define PH_KEY_MAX_LEN 64 +#define PH_VALUE_MAX_LEN 512 +#define PH_CONTEXT_KEY_MAX_LEN 64 +#define PH_CALLBACK_MAX_LEN 64 +#define PH_FORMAT_MAX_LEN 4096 + +enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } + +/** + * Вызывается когда контроллер готов к регистрации групп и ключей плейсхолдеров. + * Вызывается до форварда *_OnInited. + * + * @noreturn + */ +forward ParamsController_PH_OnRegisterGroups(); + +/** + * Регистрация группы плейсхолдеров с указанным префиксом. + * Плейсхолдеры этой группы в строках имеют вид {prefix:key}. + * + * @param sPrefix Уникальный префикс группы (например, "p" для группы игрока) + * + * @return Хендлер зарегистрированной группы + */ +native T_PHGroup:ParamsController_PH_RegisterGroup(const sPrefix[]); + +/** + * Поиск группы плейсхолдеров по префиксу. + * + * @param sPrefix Префикс группы + * + * @return Хендлер группы или Invalid_PHGroup если не найдена + */ +native T_PHGroup:ParamsController_PH_FindGroup(const sPrefix[]); + +/** + * Получение хендлера глобальной группы. + * Плейсхолдеры глобальной группы имеют вид {key} (без префикса). + * + * @return Хендлер глобальной группы + */ +native T_PHGroup:ParamsController_PH_GetGlobalGroup(); + +/** + * Регистрация ключа в группе без колбека (проактивный режим). + * Плагин сам по своей инициативе обновляет значение через *_PH_Set*. + * + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * + * @noreturn + */ +native ParamsController_PH_RegisterGroupKey(const T_PHGroup:hGroup, const sKey[]); + +/** + * Установка колбека для ключа в группе. + * Если ключ ещё не зарегистрирован — регистрирует его автоматически. + * + * @note Сигнатура колбека: (const sKey[], const sContextKey[]) + * sKey: ключ запрашиваемого плейсхолдера + * sContextKey: текущий контекст группы (например, индекс игрока) + * Колбек должен вызвать *_PH_SetValue / *_PH_SetIntValue / *_PH_SetFloatValue + * для установки актуального значения. + * + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param sCallback Название функции-колбека + * + * @noreturn + */ +native ParamsController_PH_SetGroupKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[]); + +/** + * Помещение нового контекста на стек группы. + * Контекст — произвольная строка-идентификатор (например, номер entity игрока). + * + * @param hGroup Хендлер группы + * @param sContextKey Идентификатор нового контекста + * + * @noreturn + */ +native ParamsController_PH_PushContext(const T_PHGroup:hGroup, const sContextKey[]); + +/** + * Снятие верхнего контекста со стека группы. + * + * @param hGroup Хендлер группы + * + * @noreturn + */ +native ParamsController_PH_PopContext(const T_PHGroup:hGroup); + +/** + * Установка строкового значения текущего плейсхолдера. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param sValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_SetValue(const sValue[]); + +/** + * Установка числового (int) значения текущего плейсхолдера. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param iValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_SetIntValue(const any:iValue); + +/** + * Установка числового (float) значения текущего плейсхолдера. + * Форматируется с точностью до 2 знаков после запятой. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param fValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_SetFloatValue(const Float:fValue); + +/** + * Проактивная установка строкового значения ключа в группе (вне колбека). + * + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param sValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_Set(const T_PHGroup:hGroup, const sKey[], const sValue[]); + +/** + * Проактивная установка числового (int) значения ключа в группе (вне колбека). + * + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param iValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_SetInt(const T_PHGroup:hGroup, const sKey[], const any:iValue); + +/** + * Проактивная установка числового (float) значения ключа в группе (вне колбека). + * Форматируется с точностью до 2 знаков после запятой. + * + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param fValue Устанавливаемое значение + * + * @noreturn + */ +native ParamsController_PH_SetFloat(const T_PHGroup:hGroup, const sKey[], const Float:fValue); + +/** + * Замена плейсхолдеров в строке sFormat, результат записывается в out. + * Плейсхолдеры вида {key} и {prefix:key} заменяются на актуальные значения. + * Неизвестные группы и незарегистрированные ключи сохраняются как есть. + * + * @param out Буфер для результирующей строки + * @param iOutLen Размер буфера + * @param sFormat Исходная строка с плейсхолдерами + * + * @noreturn + */ +native ParamsController_PH_Format(out[], const iOutLen, const sFormat[]); + +/** + * Регистрация ключа в глобальной группе без колбека (проактивный режим). + */ +stock ParamsController_PH_RegisterKey(const sKey[]) { + ParamsController_PH_RegisterGroupKey(ParamsController_PH_GetGlobalGroup(), sKey); +} + +/** + * Установка колбека для ключа в глобальной группе. + */ +stock ParamsController_PH_SetKeyCallback(const sKey[], const sCallback[]) { + ParamsController_PH_SetGroupKeyCallback(ParamsController_PH_GetGlobalGroup(), sKey, sCallback); +} + +/** + * Регистрация ключа в группе с одновременной установкой колбека. + */ +stock ParamsController_PH_RegGroupKey(const T_PHGroup:hGroup, const sKey[], const sCallback[]) { + ParamsController_PH_RegisterGroupKey(hGroup, sKey); + ParamsController_PH_SetGroupKeyCallback(hGroup, sKey, sCallback); +} + +/** + * Регистрация ключа в глобальной группе с одновременной установкой колбека. + */ +stock ParamsController_PH_RegKey(const sKey[], const sCallback[]) { + ParamsController_PH_RegGroupKey(ParamsController_PH_GetGlobalGroup(), sKey, sCallback); +} + // Cvars stock PCCvar_Const(const name[], const value[]) { From 797166126089d191364a57ee8cc4b997784fbb0b Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sat, 28 Mar 2026 01:28:00 +0300 Subject: [PATCH 02/30] Fix api & add default placeholders --- amxmodx/scripting/ParamsController.sma | 10 +++ .../DefaultObjects/Placeholder/Global.inc | 12 +++ .../DefaultObjects/Placeholder/Player.inc | 14 ++++ .../DefaultObjects/PlaceholderRegistrar.inc | 14 ++++ .../ParamsController/Placeholders/API/Key.inc | 21 +++-- .../Placeholders/Objects/PHGroup.inc | 46 ++++++++--- .../scripting/include/ParamsController.inc | 78 ++++++++++++++++--- 7 files changed, 167 insertions(+), 28 deletions(-) create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index 4a8d7b7..43014cb 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -6,6 +6,7 @@ #include "ParamsController/Objects/Param" #include "ParamsController/Placeholders/Objects/PHGroup" #include "ParamsController/DefaultObjects/Registrar" +#include "ParamsController/DefaultObjects/PlaceholderRegistrar" public stock const PluginName[] = "Params Controller"; public stock const PluginVersion[] = PARAMS_CONTROLLER_VERSION; @@ -30,6 +31,7 @@ PluginInit() { Param_Init(); PHGroup_Init(); DefaultObjects_ParamType_Register(); + DefaultObjects_Placeholder_Register(); // Тут регать типы параметров Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); @@ -43,6 +45,14 @@ PluginInit() { Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); } +public client_authorized(id, const sAuthID[]) { + if (!IsPluginInited()) { + return; + } + + DefaultObjects_Placeholder_OnClientAuth(id, sAuthID); // id → playerIndex внутри +} + @SrvCmd_Types() { ParamType_PrintListToConsole(); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc new file mode 100644 index 0000000..54fd4a2 --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc @@ -0,0 +1,12 @@ +#include +#include + +DefaultObjects_PH_Global_Register() { + new T_PHGroup:hGlobal = ParamsController_PH_GetGlobalGroup(); + + ParamsController_PH_RegisterGroupKey(hGlobal, DEFAULT_PH_GLOBAL_MAP_KEY); + + static sMap[64]; + get_mapname(sMap, charsmax(sMap)); + ParamsController_PH_Set(hGlobal, DEFAULT_PH_GLOBAL_MAP_KEY, "", sMap); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc new file mode 100644 index 0000000..5513986 --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -0,0 +1,14 @@ +#include +#include + +static T_PHGroup:g_hPlayerGroup = Invalid_PHGroup; + +DefaultObjects_PH_Player_Register() { + g_hPlayerGroup = ParamsController_PH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); + + ParamsController_PH_RegisterGroupKey(g_hPlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); +} + +DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const sAuthID[]) { + ParamsController_PH_SetForInt(g_hPlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, sAuthID); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc new file mode 100644 index 0000000..93b54cd --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc @@ -0,0 +1,14 @@ +#include +#include + +#include "ParamsController/DefaultObjects/Placeholder/Global" +#include "ParamsController/DefaultObjects/Placeholder/Player" + +DefaultObjects_Placeholder_Register() { + DefaultObjects_PH_Global_Register(); + DefaultObjects_PH_Player_Register(); +} + +DefaultObjects_Placeholder_OnClientAuth(const playerIndex, const sAuthID[]) { + DefaultObjects_PH_Player_OnClientAuth(playerIndex, sAuthID); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc index a4110b5..d92277c 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc @@ -40,7 +40,7 @@ API_PH_Key_Register() { } @API_PH_Set() { - enum {Arg_Group = 1, Arg_Key, Arg_Value} + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); @@ -49,14 +49,17 @@ API_PH_Key_Register() { static sKey[PH_KEY_MAX_LEN]; get_string(Arg_Key, sKey, charsmax(sKey)); + static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static sValue[PH_VALUE_MAX_LEN]; get_string(Arg_Value, sValue, charsmax(sValue)); - PHGroup_SetValue(hGroup, sKey, sValue); + PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); } @API_PH_SetInt() { - enum {Arg_Group = 1, Arg_Key, Arg_Value} + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); @@ -65,14 +68,17 @@ API_PH_Key_Register() { static sKey[PH_KEY_MAX_LEN]; get_string(Arg_Key, sKey, charsmax(sKey)); + static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static sValue[PH_VALUE_MAX_LEN]; num_to_str(get_param(Arg_Value), sValue, charsmax(sValue)); - PHGroup_SetValue(hGroup, sKey, sValue); + PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); } @API_PH_SetFloat() { - enum {Arg_Group = 1, Arg_Key, Arg_Value} + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); @@ -81,8 +87,11 @@ API_PH_Key_Register() { static sKey[PH_KEY_MAX_LEN]; get_string(Arg_Key, sKey, charsmax(sKey)); + static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static sValue[PH_VALUE_MAX_LEN]; format(sValue, charsmax(sValue), "%.2f", get_param_f(Arg_Value)); - PHGroup_SetValue(hGroup, sKey, sValue); + PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 61d70b0..584e760 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -9,7 +9,7 @@ enum _:S_PHGroup { PHGroup_Prefix[PH_GROUP_PREFIX_MAX_LEN], Trie:PHGroup_Callbacks, // ключ → хендлер форварда (INVALID_HANDLE если колбека нет) - Trie:PHGroup_Values, // ключ → текущее значение (строка) + Trie:PHGroup_Values, // "ключ:контекст" → текущее значение (строка) Stack:PHGroup_ContextStack, // стек сохранённых предыдущих контекстов PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) } @@ -20,6 +20,7 @@ static T_PHGroup:g_hPHGlobalGroup = Invalid_PHGroup; static T_PHGroup:g_hCurrentPHGroup = Invalid_PHGroup; static g_sCurrentPHKey[PH_KEY_MAX_LEN]; +static g_sCurrentPHContext[PH_CONTEXT_KEY_MAX_LEN]; PHGroup_Init() { if (g_aPHGroups == Invalid_Array) { @@ -69,6 +70,15 @@ static PHGroup_Update(const T_PHGroup:hGroup, const groupData[S_PHGroup]) { ArraySetArray(g_aPHGroups, _:hGroup, groupData); } +// Составной ключ для Values Trie: "key:contextKey" или просто "key" если контекст пустой +static PHGroup_BuildValueKey(const sKey[], const sContextKey[], sOut[], const iOutLen) { + if (sContextKey[0] == 0) { + copy(sOut, iOutLen, sKey); + } else { + format(sOut, iOutLen, "%s:%s", sKey, sContextKey); + } +} + PHGroup_RegisterKey(const T_PHGroup:hGroup, const sKey[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); @@ -127,16 +137,24 @@ PHGroup_PopContext(const T_PHGroup:hGroup) { PHGroup_Update(hGroup, groupData); } -PHGroup_SetValue(const T_PHGroup:hGroup, const sKey[], const sValue[]) { +PHGroup_SetValue(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const sValue[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - TrieSetString(groupData[PHGroup_Values], sKey, sValue); + + static sValueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; + PHGroup_BuildValueKey(sKey, sContextKey, sValueKey, charsmax(sValueKey)); + + TrieSetString(groupData[PHGroup_Values], sValueKey, sValue); } -static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const sKey[], sOut[], const iOutLen) { +static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const sKey[], const sContextKey[], sOut[], const iOutLen) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - return bool:TrieGetString(groupData[PHGroup_Values], sKey, sOut, iOutLen); + + static sValueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; + PHGroup_BuildValueKey(sKey, sContextKey, sValueKey, charsmax(sValueKey)); + + return bool:TrieGetString(groupData[PHGroup_Values], sValueKey, sOut, iOutLen); } static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const sKey[]) { @@ -145,7 +163,7 @@ static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const sKey[]) { return bool:TrieKeyExists(groupData[PHGroup_Callbacks], sKey); } -static PHGroup_CallCallback(const T_PHGroup:hGroup, const sKey[]) { +static PHGroup_CallCallback(const T_PHGroup:hGroup, const sKey[], const sContextKey[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); @@ -156,12 +174,14 @@ static PHGroup_CallCallback(const T_PHGroup:hGroup, const sKey[]) { g_hCurrentPHGroup = hGroup; copy(g_sCurrentPHKey, charsmax(g_sCurrentPHKey), sKey); + copy(g_sCurrentPHContext, charsmax(g_sCurrentPHContext), sContextKey); new iRet; - ExecuteForward(iForward, iRet, sKey, groupData[PHGroup_CurrentContext]); + ExecuteForward(iForward, iRet, sKey, sContextKey); g_hCurrentPHGroup = Invalid_PHGroup; g_sCurrentPHKey[0] = 0; + g_sCurrentPHContext[0] = 0; } PHGroup_FormatString(const sFormat[], sOut[], const iOutLen) { @@ -226,10 +246,16 @@ PHGroup_FormatString(const sFormat[], sOut[], const iOutLen) { : PHGroup_Find(sPrefix); if (hGroup != Invalid_PHGroup && PHGroup_IsKeyRegistered(hGroup, sKey)) { - PHGroup_CallCallback(hGroup, sKey); + // Захватываем контекст до вызова колбека + static sCurrentContext[PH_CONTEXT_KEY_MAX_LEN]; + new groupData[S_PHGroup]; + PHGroup_Get(hGroup, groupData); + copy(sCurrentContext, charsmax(sCurrentContext), groupData[PHGroup_CurrentContext]); + + PHGroup_CallCallback(hGroup, sKey, sCurrentContext); static sValue[PH_VALUE_MAX_LEN]; - if (PHGroup_GetValue(hGroup, sKey, sValue, charsmax(sValue))) { + if (PHGroup_GetValue(hGroup, sKey, sCurrentContext, sValue, charsmax(sValue))) { new iValueLen = strlen(sValue); for (new k = 0; k < iValueLen && iOutPos < iOutLen - 1; ++k) { sOut[iOutPos++] = sValue[k]; @@ -257,7 +283,7 @@ bool:PHGroup_IsInCallback() { } PHGroup_SetCurrentValue(const sValue[]) { - PHGroup_SetValue(g_hCurrentPHGroup, g_sCurrentPHKey, sValue); + PHGroup_SetValue(g_hCurrentPHGroup, g_sCurrentPHKey, g_sCurrentPHContext, sValue); } API_CheckCurrentPH() { diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index e062cdc..84fdedc 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1330,6 +1330,14 @@ stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { #define PH_CALLBACK_MAX_LEN 64 #define PH_FORMAT_MAX_LEN 4096 +// --- Default placeholders --- + +stock const DEFAULT_PH_GLOBAL_MAP_KEY[] = "map"; + +stock const DEFAULT_PH_PLAYER_GROUP_PREFIX[] = "p"; +stock const DEFAULT_PH_PLAYER_AUTHID_KEY[] = "authid"; +#define DEFAULT_PH_PLAYER_AUTHID_MAX_LEN 32 + enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } /** @@ -1453,36 +1461,39 @@ native ParamsController_PH_SetFloatValue(const Float:fValue); /** * Проактивная установка строкового значения ключа в группе (вне колбека). * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param sValue Устанавливаемое значение + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param sContextKey Контекстный ключ (например, индекс игрока в виде строки) + * @param sValue Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_Set(const T_PHGroup:hGroup, const sKey[], const sValue[]); +native ParamsController_PH_Set(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const sValue[]); /** * Проактивная установка числового (int) значения ключа в группе (вне колбека). * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param iValue Устанавливаемое значение + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param sContextKey Контекстный ключ + * @param iValue Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetInt(const T_PHGroup:hGroup, const sKey[], const any:iValue); +native ParamsController_PH_SetInt(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const any:iValue); /** * Проактивная установка числового (float) значения ключа в группе (вне колбека). * Форматируется с точностью до 2 знаков после запятой. * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param fValue Устанавливаемое значение + * @param hGroup Хендлер группы + * @param sKey Ключ плейсхолдера + * @param sContextKey Контекстный ключ + * @param fValue Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetFloat(const T_PHGroup:hGroup, const sKey[], const Float:fValue); +native ParamsController_PH_SetFloat(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const Float:fValue); /** * Замена плейсхолдеров в строке sFormat, результат записывается в out. @@ -1497,6 +1508,49 @@ native ParamsController_PH_SetFloat(const T_PHGroup:hGroup, const sKey[], const */ native ParamsController_PH_Format(out[], const iOutLen, const sFormat[]); +/** + * Помещение числового контекста на стек группы. + * Удобная обёртка над *_PushContext для случаев, когда контекстный ключ — число (например, индекс игрока). + * + * @param hGroup Хендлер группы + * @param iContextKey Числовой идентификатор контекста + * + * @noreturn + */ +stock ParamsController_PH_PushIntContext(const T_PHGroup:hGroup, const any:iContextKey) { + static sContextKey[12]; + num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); + ParamsController_PH_PushContext(hGroup, sContextKey); +} + +/** + * Проактивная установка строкового значения с числовым контекстным ключом. + * Удобная обёртка для групп, где контекст — число (например, индекс игрока). + */ +stock ParamsController_PH_SetForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const sValue[]) { + static sContextKey[12]; + num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); + ParamsController_PH_Set(hGroup, sKey, sContextKey, sValue); +} + +/** + * Проактивная установка числового (int) значения с числовым контекстным ключом. + */ +stock ParamsController_PH_SetIntForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const any:iValue) { + static sContextKey[12]; + num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); + ParamsController_PH_SetInt(hGroup, sKey, sContextKey, iValue); +} + +/** + * Проактивная установка числового (float) значения с числовым контекстным ключом. + */ +stock ParamsController_PH_SetFloatForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const Float:fValue) { + static sContextKey[12]; + num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); + ParamsController_PH_SetFloat(hGroup, sKey, sContextKey, fValue); +} + /** * Регистрация ключа в глобальной группе без колбека (проактивный режим). */ From dc821186963241d439c7d07baafda98316409d03 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sat, 28 Mar 2026 13:10:46 +0300 Subject: [PATCH 03/30] Gen guideline for claude --- CLAUDE.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8c3051f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,45 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build + +**Requirements:** AmxModX 1.9.0+, AMxModX compiler configured as `amxx190` in `config.bat` + +```bat +build.bat +``` + +Output goes to `.build/` directory, final artifact is a ZIP package. Build settings are in `.build-config`. Нужен только для локальной сборки. + +There are no test or lint commands — the CI pipeline (`.github/workflows/CI.yml`) compiles all `.sma` files as the validation step. + +## Architecture + +ParamsController is an AmxModX plugin library (written in PAWN) that provides a JSON-based parameter parsing framework for other plugins. + +**Core flow:** A consumer plugin calls `ParamsController_Init()`, constructs typed parameters with `ParamsController_Param_Construct()`, then reads a JSON object using `ParamsController_Param_ReadList()`. Each parameter is validated against its registered type handler. + +### Layer structure + +| Layer | Path | Role | +|---|---|---| +| Public API | `amxmodx/scripting/include/ParamsController.inc` | All native/forward declarations; this is what consumers include | +| Main plugin | `amxmodx/scripting/ParamsController.sma` | Entry point, library registration, forward init | +| API impl | `ParamsController/API/` | Native implementations (General, Param, ParamType, Utils) | +| Object model | `ParamsController/Objects/` | `Param.inc` (Array + free-list storage), `ParamType.inc` (Trie-keyed type registry) | +| Built-in types | `ParamsController/DefaultObjects/ParamType/` | 14 type handlers: Boolean, Integer, Float, String, RGB, Model, Sound, Resource, File, Dir, ChatMessage, Time, TimeInterval, WeekDay, Flags | +| Placeholders | `ParamsController/Placeholders/` | Global and per-player placeholder substitution system | +| Registrars | `ParamsController/DefaultObjects/Registrar.inc`, `PlaceholderRegistrar.inc` | Wire up default types and placeholders on plugin load | +| Forwards | `ParamsController/Forwards.inc` | Generic forward registration utility used throughout | + +### Key design patterns + +- **Parameter storage:** Array-based with a free-list for handle reuse (`Param.inc`) +- **Type registry:** Trie-keyed by type name string (`ParamType.inc`) +- **Extensibility:** `CreateMultiForward` / `ExecuteForward` — consumers can register custom parameter types and placeholder groups via natives +- **Error reporting:** `E_ParamsReadErrorType` enum returned from `ReadList`; error details (param name, expected/got type) accessible via `ParamsController_GetErrorInfo()` + +### Language notes + +PAWN uses `#include` for all modular decomposition. `.inc` files are not independently compiled — they are all included into `ParamsController.sma`. The public header (`ParamsController.inc`) only declares natives/forwards; it contains no implementation. From 6075e1e85ad782918072e4ad621f317299f0ff0b Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sat, 28 Mar 2026 13:22:04 +0300 Subject: [PATCH 04/30] Reformat code --- .../ParamsController/API/General.inc | 4 +- .../scripting/ParamsController/API/Param.inc | 55 +++-- .../scripting/ParamsController/API/Utils.inc | 2 +- .../ParamsController/Objects/Param.inc | 16 +- .../ParamsController/Objects/ParamType.inc | 52 ++-- .../Placeholders/API/General.inc | 28 +-- .../Placeholders/API/Group.inc | 18 +- .../Placeholders/Objects/PHGroup.inc | 228 +++++++++--------- 8 files changed, 201 insertions(+), 202 deletions(-) diff --git a/amxmodx/scripting/ParamsController/API/General.inc b/amxmodx/scripting/ParamsController/API/General.inc index 4c2d39c..4714d21 100644 --- a/amxmodx/scripting/ParamsController/API/General.inc +++ b/amxmodx/scripting/ParamsController/API/General.inc @@ -17,7 +17,7 @@ API_General_Register() { API_CheckPluginInited(); API_CheckCurrentParams(); - return TrieSetCell(g_tCurrentParams, g_sCurrentParamName, get_param(Arg_Cell)); + return TrieSetCell(CurrentParams, CurrentParamName, get_param(Arg_Cell)); } @API_SetString() { @@ -29,5 +29,5 @@ API_General_Register() { static sString[PARAM_LONG_STRING_MAX_LEN]; get_string(Arg_String, sString, charsmax(sString)); - return TrieSetString(g_tCurrentParams, g_sCurrentParamName, sString); + return TrieSetString(CurrentParams, CurrentParamName, sString); } \ No newline at end of file diff --git a/amxmodx/scripting/ParamsController/API/Param.inc b/amxmodx/scripting/ParamsController/API/Param.inc index 8580265..2e621c3 100644 --- a/amxmodx/scripting/ParamsController/API/Param.inc +++ b/amxmodx/scripting/ParamsController/API/Param.inc @@ -9,54 +9,53 @@ API_Param_Register() { } T_Param:@API_Param_Construct() { - enum {Arg_sKey = 1, Arg_sParamTypeName, Arg_bRequired} + enum {Arg_Key = 1, Arg_ParamTypeName, Arg_Required} API_CheckPluginInited(); - new sKey[PARAM_KEY_MAX_LEN]; - get_string(Arg_sKey, sKey, charsmax(sKey)); + new key[PARAM_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN + PARAM_TYPE_TAG_MAX_LEN + 1]; - get_string(Arg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); - - new bool:bRequired = !!get_param(Arg_bRequired); + new paramTypeName[PARAM_TYPE_NAME_MAX_LEN + PARAM_TYPE_TAG_MAX_LEN + 1]; + get_string(Arg_ParamTypeName, paramTypeName, charsmax(paramTypeName)); + new bool:required = !!get_param(Arg_Required); - new sParamType[PARAM_TYPE_NAME_MAX_LEN], sParamTag[PARAM_TYPE_TAG_MAX_LEN]; - ParamType_GetTypeAndTag(sParamTypeName, - sParamType, charsmax(sParamType), - sParamTag, charsmax(sParamTag) + new typeName[PARAM_TYPE_NAME_MAX_LEN], paramTag[PARAM_TYPE_TAG_MAX_LEN]; + ParamType_GetTypeAndTag(paramTypeName, + typeName, charsmax(typeName), + paramTag, charsmax(paramTag) ); - new T_ParamType:iParamType = ParamType_Find(sParamType, .orFail = true); + new T_ParamType:paramType = ParamType_Find(typeName, .orFail = true); - return Param_Construct(sKey, iParamType, bRequired, sParamTag); + return Param_Construct(key, paramType, required, paramTag); } -// native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj, &Trie:tAppend = Invalid_Trie, &E_ParamsReadErrorType:iErrType, sErrParamName[], const iErrParamNameLen = 0); +// native Trie:ParamsController_Param_ReadList(const Array:params, const JSON:objectJson, &Trie:append = Invalid_Trie, &E_ParamsReadErrorType:errType, errParamName[], const errParamNameLen = 0); Trie:@API_Param_ReadList() { - enum {Arg_aParams = 1, Arg_jObj, Arg_tAppend, Arg_iErrType, Arg_sErrParamName, Arg_iErrParamNameLen} + enum {Arg_Params = 1, Arg_ObjectJson, Arg_Append, Arg_ErrType, Arg_ErrParamName, Arg_ErrParamNameLen} API_CheckPluginInited(); - new Array:aParams = Array:get_param(Arg_aParams); - new JSON:jObj = JSON:get_param(Arg_jObj); - new Trie:tAppend = Trie:get_param_byref(Arg_tAppend); - new E_ParamsReadErrorType:iErrType = E_ParamsReadErrorType:get_param_byref(Arg_iErrType); - new sErrParamName[PARAM_KEY_MAX_LEN]; - get_string(Arg_sErrParamName, sErrParamName, charsmax(sErrParamName)); - new iErrParamNameLen = get_param(Arg_iErrParamNameLen); + new Array:params = Array:get_param(Arg_Params); + new JSON:objectJson = JSON:get_param(Arg_ObjectJson); + new Trie:append = Trie:get_param_byref(Arg_Append); + new E_ParamsReadErrorType:errType = E_ParamsReadErrorType:get_param_byref(Arg_ErrType); + new errParamName[PARAM_KEY_MAX_LEN]; + get_string(Arg_ErrParamName, errParamName, charsmax(errParamName)); + new errParamNameLen = get_param(Arg_ErrParamNameLen); - if (!json_is_object(jObj)) { - log_error(AMX_ERR_PARAMS, "[ERROR] Param jObj must be an object. (%d)", jObj); + if (!json_is_object(objectJson)) { + log_error(AMX_ERR_PARAMS, "[ERROR] Param objectJson must be an object. (%d)", objectJson); return Invalid_Trie; } - new Trie:tRet = Param_ReadList(aParams, jObj, tAppend, iErrType, sErrParamName, iErrParamNameLen); + new Trie:ret = Param_ReadList(params, objectJson, append, errType, errParamName, errParamNameLen); - set_param_byref(Arg_iErrType, _:iErrType); - set_string(Arg_sErrParamName, sErrParamName, iErrParamNameLen); + set_param_byref(Arg_ErrType, _:errType); + set_string(Arg_ErrParamName, errParamName, errParamNameLen); - return tRet; + return ret; } bool:@API_Param_Read() { diff --git a/amxmodx/scripting/ParamsController/API/Utils.inc b/amxmodx/scripting/ParamsController/API/Utils.inc index 7eab564..d866693 100644 --- a/amxmodx/scripting/ParamsController/API/Utils.inc +++ b/amxmodx/scripting/ParamsController/API/Utils.inc @@ -12,7 +12,7 @@ stock API_CheckPluginInited() { } stock API_CheckCurrentParams() { - if (g_tCurrentParams == Invalid_Trie) { + if (CurrentParams == Invalid_Trie) { abort(AMX_ERR_PARAMS, "Attempt to set param value outside the read callback."); } } diff --git a/amxmodx/scripting/ParamsController/Objects/Param.inc b/amxmodx/scripting/ParamsController/Objects/Param.inc index 4f0bb9b..a34bcaf 100644 --- a/amxmodx/scripting/ParamsController/Objects/Param.inc +++ b/amxmodx/scripting/ParamsController/Objects/Param.inc @@ -92,9 +92,9 @@ Trie:Param_ReadList( const Array:params, const JSON:objectJson, &Trie:p = Invalid_Trie, - &E_ParamsReadErrorType:iErrType = ParamsReadError_None, - sErrParamName[] = "", - const iErrParamNameLen = 0 + &E_ParamsReadErrorType:errType = ParamsReadError_None, + errParamName[] = "", + const errParamNameLen = 0 ) { if (p == Invalid_Trie) { p = TrieCreate(); @@ -110,13 +110,13 @@ Trie:Param_ReadList( new paramObject[S_Param]; Param_Get(param, paramObject); - if (iErrParamNameLen > 0) { - copy(sErrParamName, iErrParamNameLen, paramObject[Param_Key]); + if (errParamNameLen > 0) { + copy(errParamName, errParamNameLen, paramObject[Param_Key]); } if (!json_object_has_value(objectJson, paramObject[Param_Key])) { if (paramObject[Param_Required]) { - iErrType = ParamsReadError_RequiredParamNotPresented; + errType = ParamsReadError_RequiredParamNotPresented; return p; } @@ -128,7 +128,7 @@ Trie:Param_ReadList( json_free(valueJson); if (!ret) { - iErrType = ParamsReadError_ParamValueIsInvalid; + errType = ParamsReadError_ParamValueIsInvalid; return p; } @@ -136,6 +136,6 @@ Trie:Param_ReadList( abort(AMX_ERR_GENERAL, "Param type '%s' dont write value with key '%s' to trie.", ParamType_iGetName(paramObject[Param_Type]), paramObject[Param_Key]); } } - + return p; } diff --git a/amxmodx/scripting/ParamsController/Objects/ParamType.inc b/amxmodx/scripting/ParamsController/Objects/ParamType.inc index 7b48eae..15e2722 100644 --- a/amxmodx/scripting/ParamsController/Objects/ParamType.inc +++ b/amxmodx/scripting/ParamsController/Objects/ParamType.inc @@ -88,38 +88,38 @@ enum _:S_CurrentParam { Trie:CurrentParam_Params, CurrentParam_Name[PARAM_KEY_MAX_LEN], } -new Stack:g_stCurrentParams = Invalid_Stack; -new Trie:g_tCurrentParams = Invalid_Trie; -new g_sCurrentParamName[PARAM_KEY_MAX_LEN] = ""; +new Stack:CurrentParamsStack = Invalid_Stack; +new Trie:CurrentParams = Invalid_Trie; +new CurrentParamName[PARAM_KEY_MAX_LEN] = ""; -static PushCurrentParam(const Trie:tParams, const sParamName[]) { - if (g_stCurrentParams == Invalid_Stack) { - g_stCurrentParams = CreateStack(S_CurrentParam); +static PushCurrentParam(const Trie:params, const paramName[]) { + if (CurrentParamsStack == Invalid_Stack) { + CurrentParamsStack = CreateStack(S_CurrentParam); } - if (g_tCurrentParams != Invalid_Trie) { - new CurrentParam[S_CurrentParam]; - copy(CurrentParam[CurrentParam_Name], charsmax(CurrentParam[CurrentParam_Name]), g_sCurrentParamName); - CurrentParam[CurrentParam_Params] = g_tCurrentParams; - PushStackArray(g_stCurrentParams, CurrentParam); + if (CurrentParams != Invalid_Trie) { + new entry[S_CurrentParam]; + copy(entry[CurrentParam_Name], charsmax(entry[CurrentParam_Name]), CurrentParamName); + entry[CurrentParam_Params] = CurrentParams; + PushStackArray(CurrentParamsStack, entry); } - g_tCurrentParams = tParams; - copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), sParamName); + CurrentParams = params; + copy(CurrentParamName, charsmax(CurrentParamName), paramName); } static PopCurrentParam() { if ( - g_stCurrentParams != Invalid_Stack - && !IsStackEmpty(g_stCurrentParams) + CurrentParamsStack != Invalid_Stack + && !IsStackEmpty(CurrentParamsStack) ) { - new CurrentParam[S_CurrentParam]; - PopStackArray(g_stCurrentParams, CurrentParam); - copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), CurrentParam[CurrentParam_Name]); - g_tCurrentParams = CurrentParam[CurrentParam_Params]; + new entry[S_CurrentParam]; + PopStackArray(CurrentParamsStack, entry); + copy(CurrentParamName, charsmax(CurrentParamName), entry[CurrentParam_Name]); + CurrentParams = entry[CurrentParam_Params]; } else { - g_tCurrentParams = Invalid_Trie; - g_sCurrentParamName[0] = 0; + CurrentParams = Invalid_Trie; + CurrentParamName[0] = 0; } } @@ -137,13 +137,13 @@ bool:ParamType_Read( PCJson_HandleLinkedValue(valueJson, linkedJson); PushCurrentParam(p, key); - new bool:ret; - ExecuteForward(typeObject[ParamType_ReadCallback], ret, linkedJson, p, key, tag); + new bool:result; + ExecuteForward(typeObject[ParamType_ReadCallback], result, linkedJson, p, key, tag); PopCurrentParam(); PCJson_FreeLinked(linkedJson, valueJson); - return ret; + return result; } ParamType_PrintListToConsole() { @@ -158,6 +158,6 @@ ParamType_PrintListToConsole() { } } -ParamType_GetTypeAndTag(const str[], sParamType[], const iParamTypeLen, sParamTag[], const iParamTagLen) { - return strtok2(str, sParamType, iParamTypeLen, sParamTag, iParamTagLen, PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR); +ParamType_GetTypeAndTag(const str[], paramType[], const paramTypeLen, paramTag[], const paramTagLen) { + return strtok2(str, paramType, paramTypeLen, paramTag, paramTagLen, PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc index 4264fec..1d4362e 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc @@ -14,13 +14,13 @@ API_PH_General_Register() { API_CheckPluginInited(); - static sFormat[PH_FORMAT_MAX_LEN]; - get_string(Arg_Format, sFormat, charsmax(sFormat)); + static formatStr[PH_FORMAT_MAX_LEN]; + get_string(Arg_Format, formatStr, charsmax(formatStr)); - static sOut[PH_FORMAT_MAX_LEN]; - PHGroup_FormatString(sFormat, sOut, charsmax(sOut)); + static out[PH_FORMAT_MAX_LEN]; + PHGroup_FormatString(formatStr, out, charsmax(out)); - set_string(Arg_Out, sOut, get_param(Arg_OutLen)); + set_string(Arg_Out, out, get_param(Arg_OutLen)); } @API_PH_SetValue() { @@ -29,10 +29,10 @@ API_PH_General_Register() { API_CheckPluginInited(); API_CheckCurrentPH(); - static sValue[PH_VALUE_MAX_LEN]; - get_string(Arg_Value, sValue, charsmax(sValue)); + static value[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, value, charsmax(value)); - PHGroup_SetCurrentValue(sValue); + PHGroup_SetCurrentValue(value); } @API_PH_SetIntValue() { @@ -41,10 +41,10 @@ API_PH_General_Register() { API_CheckPluginInited(); API_CheckCurrentPH(); - static sValue[PH_VALUE_MAX_LEN]; - num_to_str(get_param(Arg_Value), sValue, charsmax(sValue)); + static value[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), value, charsmax(value)); - PHGroup_SetCurrentValue(sValue); + PHGroup_SetCurrentValue(value); } @API_PH_SetFloatValue() { @@ -53,8 +53,8 @@ API_PH_General_Register() { API_CheckPluginInited(); API_CheckCurrentPH(); - static sValue[PH_VALUE_MAX_LEN]; - format(sValue, charsmax(sValue), "%.2f", get_param_f(Arg_Value)); + static value[PH_VALUE_MAX_LEN]; + format(value, charsmax(value), "%.2f", get_param_f(Arg_Value)); - PHGroup_SetCurrentValue(sValue); + PHGroup_SetCurrentValue(value); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc index 4441d19..feb50f4 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc @@ -15,20 +15,20 @@ T_PHGroup:@API_PH_RegisterGroup() { API_CheckPluginInited(); - static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; - get_string(Arg_Prefix, sPrefix, charsmax(sPrefix)); + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, prefix, charsmax(prefix)); - if (sPrefix[0] == 0) { + if (prefix[0] == 0) { abort(AMX_ERR_PARAMS, "PH group prefix must not be empty."); return Invalid_PHGroup; } - if (PHGroup_Find(sPrefix) != Invalid_PHGroup) { - abort(AMX_ERR_PARAMS, "PH group with prefix '%s' is already registered.", sPrefix); + if (PHGroup_Find(prefix) != Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "PH group with prefix '%s' is already registered.", prefix); return Invalid_PHGroup; } - return PHGroup_Construct(sPrefix); + return PHGroup_Construct(prefix); } T_PHGroup:@API_PH_FindGroup() { @@ -36,10 +36,10 @@ T_PHGroup:@API_PH_FindGroup() { API_CheckPluginInited(); - static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; - get_string(Arg_Prefix, sPrefix, charsmax(sPrefix)); + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, prefix, charsmax(prefix)); - return PHGroup_Find(sPrefix); + return PHGroup_Find(prefix); } T_PHGroup:@API_PH_GetGlobalGroup() { diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 584e760..a4431d9 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -14,100 +14,100 @@ enum _:S_PHGroup { PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) } -static Array:g_aPHGroups = Invalid_Array; -static Trie:g_tPHGroupsMap = Invalid_Trie; -static T_PHGroup:g_hPHGlobalGroup = Invalid_PHGroup; +static Array:PHGroups = Invalid_Array; +static Trie:PHGroupsMap = Invalid_Trie; +static T_PHGroup:PHGlobalGroup = Invalid_PHGroup; -static T_PHGroup:g_hCurrentPHGroup = Invalid_PHGroup; -static g_sCurrentPHKey[PH_KEY_MAX_LEN]; -static g_sCurrentPHContext[PH_CONTEXT_KEY_MAX_LEN]; +static T_PHGroup:CurrentPHGroup = Invalid_PHGroup; +static CurrentPHKey[PH_KEY_MAX_LEN]; +static CurrentPHContext[PH_CONTEXT_KEY_MAX_LEN]; PHGroup_Init() { - if (g_aPHGroups == Invalid_Array) { - g_aPHGroups = ArrayCreate(S_PHGroup, 1); + if (PHGroups == Invalid_Array) { + PHGroups = ArrayCreate(S_PHGroup, 1); } - if (g_tPHGroupsMap == Invalid_Trie) { - g_tPHGroupsMap = TrieCreate(); + if (PHGroupsMap == Invalid_Trie) { + PHGroupsMap = TrieCreate(); } - g_hPHGlobalGroup = PHGroup_Construct(""); + PHGlobalGroup = PHGroup_Construct(""); } -T_PHGroup:PHGroup_Construct(const sPrefix[]) { +T_PHGroup:PHGroup_Construct(const prefix[]) { new groupData[S_PHGroup]; - copy(groupData[PHGroup_Prefix], charsmax(groupData[PHGroup_Prefix]), sPrefix); + copy(groupData[PHGroup_Prefix], charsmax(groupData[PHGroup_Prefix]), prefix); groupData[PHGroup_Callbacks] = TrieCreate(); groupData[PHGroup_Values] = TrieCreate(); groupData[PHGroup_ContextStack] = Invalid_Stack; groupData[PHGroup_CurrentContext][0] = 0; - new T_PHGroup:hGroup = T_PHGroup:ArrayPushArray(g_aPHGroups, groupData); + new T_PHGroup:hGroup = T_PHGroup:ArrayPushArray(PHGroups, groupData); - if (sPrefix[0] != 0) { - TrieSetCell(g_tPHGroupsMap, sPrefix, hGroup); + if (prefix[0] != 0) { + TrieSetCell(PHGroupsMap, prefix, hGroup); } return hGroup; } -T_PHGroup:PHGroup_Find(const sPrefix[]) { +T_PHGroup:PHGroup_Find(const prefix[]) { new T_PHGroup:hGroup = Invalid_PHGroup; - TrieGetCell(g_tPHGroupsMap, sPrefix, hGroup); + TrieGetCell(PHGroupsMap, prefix, hGroup); return hGroup; } T_PHGroup:PHGroup_GetGlobal() { - return g_hPHGlobalGroup; + return PHGlobalGroup; } static PHGroup_Get(const T_PHGroup:hGroup, groupData[S_PHGroup]) { - ArrayGetArray(g_aPHGroups, _:hGroup, groupData); + ArrayGetArray(PHGroups, _:hGroup, groupData); } static PHGroup_Update(const T_PHGroup:hGroup, const groupData[S_PHGroup]) { - ArraySetArray(g_aPHGroups, _:hGroup, groupData); + ArraySetArray(PHGroups, _:hGroup, groupData); } // Составной ключ для Values Trie: "key:contextKey" или просто "key" если контекст пустой -static PHGroup_BuildValueKey(const sKey[], const sContextKey[], sOut[], const iOutLen) { - if (sContextKey[0] == 0) { - copy(sOut, iOutLen, sKey); +static PHGroup_BuildValueKey(const key[], const contextKey[], out[], const outLen) { + if (contextKey[0] == 0) { + copy(out, outLen, key); } else { - format(sOut, iOutLen, "%s:%s", sKey, sContextKey); + format(out, outLen, "%s:%s", key, contextKey); } } -PHGroup_RegisterKey(const T_PHGroup:hGroup, const sKey[]) { +PHGroup_RegisterKey(const T_PHGroup:hGroup, const key[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - if (!TrieKeyExists(groupData[PHGroup_Callbacks], sKey)) { - TrieSetCell(groupData[PHGroup_Callbacks], sKey, INVALID_HANDLE); + if (!TrieKeyExists(groupData[PHGroup_Callbacks], key)) { + TrieSetCell(groupData[PHGroup_Callbacks], key, INVALID_HANDLE); } } -PHGroup_SetKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[], const pluginIndex) { +PHGroup_SetKeyCallback(const T_PHGroup:hGroup, const key[], const callback[], const pluginIndex) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - new iOldForward; - if (TrieGetCell(groupData[PHGroup_Callbacks], sKey, iOldForward) && iOldForward != INVALID_HANDLE) { - DestroyForward(iOldForward); + new oldFwd; + if (TrieGetCell(groupData[PHGroup_Callbacks], key, oldFwd) && oldFwd != INVALID_HANDLE) { + DestroyForward(oldFwd); } - // (const sKey[], const sContextKey[]) - new iForward = CreateOneForward(pluginIndex, sCallback, FP_STRING, FP_STRING); - if (iForward == INVALID_HANDLE) { - abort(AMX_ERR_GENERAL, "Can't create PH callback by '%s' func of #%d plugin.", sCallback, pluginIndex); + // (const key[], const contextKey[]) + new fwd = CreateOneForward(pluginIndex, callback, FP_STRING, FP_STRING); + if (fwd == INVALID_HANDLE) { + abort(AMX_ERR_GENERAL, "Can't create PH callback by '%s' func of #%d plugin.", callback, pluginIndex); return; } - TrieSetCell(groupData[PHGroup_Callbacks], sKey, iForward); + TrieSetCell(groupData[PHGroup_Callbacks], key, fwd); } -PHGroup_PushContext(const T_PHGroup:hGroup, const sContextKey[]) { +PHGroup_PushContext(const T_PHGroup:hGroup, const contextKey[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); @@ -116,7 +116,7 @@ PHGroup_PushContext(const T_PHGroup:hGroup, const sContextKey[]) { } PushStackArray(groupData[PHGroup_ContextStack], groupData[PHGroup_CurrentContext], PH_CONTEXT_KEY_MAX_LEN); - copy(groupData[PHGroup_CurrentContext], charsmax(groupData[PHGroup_CurrentContext]), sContextKey); + copy(groupData[PHGroup_CurrentContext], charsmax(groupData[PHGroup_CurrentContext]), contextKey); PHGroup_Update(hGroup, groupData); } @@ -137,153 +137,153 @@ PHGroup_PopContext(const T_PHGroup:hGroup) { PHGroup_Update(hGroup, groupData); } -PHGroup_SetValue(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const sValue[]) { +PHGroup_SetValue(const T_PHGroup:hGroup, const key[], const contextKey[], const value[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - static sValueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; - PHGroup_BuildValueKey(sKey, sContextKey, sValueKey, charsmax(sValueKey)); + static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; + PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); - TrieSetString(groupData[PHGroup_Values], sValueKey, sValue); + TrieSetString(groupData[PHGroup_Values], valueKey, value); } -static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const sKey[], const sContextKey[], sOut[], const iOutLen) { +static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const key[], const contextKey[], out[], const outLen) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - static sValueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; - PHGroup_BuildValueKey(sKey, sContextKey, sValueKey, charsmax(sValueKey)); + static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; + PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); - return bool:TrieGetString(groupData[PHGroup_Values], sValueKey, sOut, iOutLen); + return bool:TrieGetString(groupData[PHGroup_Values], valueKey, out, outLen); } -static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const sKey[]) { +static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const key[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - return bool:TrieKeyExists(groupData[PHGroup_Callbacks], sKey); + return bool:TrieKeyExists(groupData[PHGroup_Callbacks], key); } -static PHGroup_CallCallback(const T_PHGroup:hGroup, const sKey[], const sContextKey[]) { +static PHGroup_CallCallback(const T_PHGroup:hGroup, const key[], const contextKey[]) { new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - new iForward; - if (!TrieGetCell(groupData[PHGroup_Callbacks], sKey, iForward) || iForward == INVALID_HANDLE) { + new fwd; + if (!TrieGetCell(groupData[PHGroup_Callbacks], key, fwd) || fwd == INVALID_HANDLE) { return; } - g_hCurrentPHGroup = hGroup; - copy(g_sCurrentPHKey, charsmax(g_sCurrentPHKey), sKey); - copy(g_sCurrentPHContext, charsmax(g_sCurrentPHContext), sContextKey); + CurrentPHGroup = hGroup; + copy(CurrentPHKey, charsmax(CurrentPHKey), key); + copy(CurrentPHContext, charsmax(CurrentPHContext), contextKey); - new iRet; - ExecuteForward(iForward, iRet, sKey, sContextKey); + new ret; + ExecuteForward(fwd, ret, key, contextKey); - g_hCurrentPHGroup = Invalid_PHGroup; - g_sCurrentPHKey[0] = 0; - g_sCurrentPHContext[0] = 0; + CurrentPHGroup = Invalid_PHGroup; + CurrentPHKey[0] = 0; + CurrentPHContext[0] = 0; } -PHGroup_FormatString(const sFormat[], sOut[], const iOutLen) { - if (iOutLen < 1) { +PHGroup_FormatString(const formatStr[], out[], const outLen) { + if (outLen < 1) { return; } - new iOutPos = 0; - new iInPos = 0; - new iFormatLen = strlen(sFormat); + new outPos = 0; + new inPos = 0; + new formatLen = strlen(formatStr); - while (iInPos < iFormatLen && iOutPos < iOutLen - 1) { - if (sFormat[iInPos] != '{') { - sOut[iOutPos++] = sFormat[iInPos++]; + while (inPos < formatLen && outPos < outLen - 1) { + if (formatStr[inPos] != '{') { + out[outPos++] = formatStr[inPos++]; continue; } - new iClosing = iInPos + 1; - while (iClosing < iFormatLen && sFormat[iClosing] != '}') { - ++iClosing; + new closing = inPos + 1; + while (closing < formatLen && formatStr[closing] != '}') { + ++closing; } - if (iClosing >= iFormatLen) { + if (closing >= formatLen) { // Незакрытая скобка — выводим как есть - sOut[iOutPos++] = sFormat[iInPos++]; + out[outPos++] = formatStr[inPos++]; continue; } - static sPlaceholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; - new iPlaceholderLen = min(iClosing - iInPos - 1, charsmax(sPlaceholder)); + static placeholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; + new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); - for (new k = 0; k < iPlaceholderLen; ++k) { - sPlaceholder[k] = sFormat[iInPos + 1 + k]; + for (new k = 0; k < placeholderLen; ++k) { + placeholder[k] = formatStr[inPos + 1 + k]; } - sPlaceholder[iPlaceholderLen] = 0; + placeholder[placeholderLen] = 0; - static sPrefix[PH_GROUP_PREFIX_MAX_LEN]; - static sKey[PH_KEY_MAX_LEN]; + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + static key[PH_KEY_MAX_LEN]; - new iColon = -1; - for (new k = 0; k < iPlaceholderLen; ++k) { - if (sPlaceholder[k] == ':') { - iColon = k; + new colon = -1; + for (new k = 0; k < placeholderLen; ++k) { + if (placeholder[k] == ':') { + colon = k; break; } } - if (iColon < 0) { - sPrefix[0] = 0; - copy(sKey, charsmax(sKey), sPlaceholder); + if (colon < 0) { + prefix[0] = 0; + copy(key, charsmax(key), placeholder); } else { - new iPrefixLen = min(iColon, charsmax(sPrefix)); - for (new k = 0; k < iPrefixLen; ++k) { - sPrefix[k] = sPlaceholder[k]; + new prefixLen = min(colon, charsmax(prefix)); + for (new k = 0; k < prefixLen; ++k) { + prefix[k] = placeholder[k]; } - sPrefix[iPrefixLen] = 0; - copy(sKey, charsmax(sKey), sPlaceholder[iColon + 1]); + prefix[prefixLen] = 0; + copy(key, charsmax(key), placeholder[colon + 1]); } - new T_PHGroup:hGroup = (sPrefix[0] == 0) - ? g_hPHGlobalGroup - : PHGroup_Find(sPrefix); + new T_PHGroup:hGroup = (prefix[0] == 0) + ? PHGlobalGroup + : PHGroup_Find(prefix); - if (hGroup != Invalid_PHGroup && PHGroup_IsKeyRegistered(hGroup, sKey)) { + if (hGroup != Invalid_PHGroup && PHGroup_IsKeyRegistered(hGroup, key)) { // Захватываем контекст до вызова колбека - static sCurrentContext[PH_CONTEXT_KEY_MAX_LEN]; + static currentContext[PH_CONTEXT_KEY_MAX_LEN]; new groupData[S_PHGroup]; PHGroup_Get(hGroup, groupData); - copy(sCurrentContext, charsmax(sCurrentContext), groupData[PHGroup_CurrentContext]); + copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); - PHGroup_CallCallback(hGroup, sKey, sCurrentContext); + PHGroup_CallCallback(hGroup, key, currentContext); - static sValue[PH_VALUE_MAX_LEN]; - if (PHGroup_GetValue(hGroup, sKey, sCurrentContext, sValue, charsmax(sValue))) { - new iValueLen = strlen(sValue); - for (new k = 0; k < iValueLen && iOutPos < iOutLen - 1; ++k) { - sOut[iOutPos++] = sValue[k]; + static value[PH_VALUE_MAX_LEN]; + if (PHGroup_GetValue(hGroup, key, currentContext, value, charsmax(value))) { + new valueLen = strlen(value); + for (new k = 0; k < valueLen && outPos < outLen - 1; ++k) { + out[outPos++] = value[k]; } } } else { // Неизвестная группа или незарегистрированный ключ — сохраняем как есть - sOut[iOutPos++] = '{'; - for (new k = 0; k < iPlaceholderLen && iOutPos < iOutLen - 1; ++k) { - sOut[iOutPos++] = sPlaceholder[k]; + out[outPos++] = '{'; + for (new k = 0; k < placeholderLen && outPos < outLen - 1; ++k) { + out[outPos++] = placeholder[k]; } - if (iOutPos < iOutLen - 1) { - sOut[iOutPos++] = '}'; + if (outPos < outLen - 1) { + out[outPos++] = '}'; } } - iInPos = iClosing + 1; + inPos = closing + 1; } - sOut[iOutPos] = 0; + out[outPos] = 0; } bool:PHGroup_IsInCallback() { - return g_hCurrentPHGroup != Invalid_PHGroup; + return CurrentPHGroup != Invalid_PHGroup; } -PHGroup_SetCurrentValue(const sValue[]) { - PHGroup_SetValue(g_hCurrentPHGroup, g_sCurrentPHKey, g_sCurrentPHContext, sValue); +PHGroup_SetCurrentValue(const value[]) { + PHGroup_SetValue(CurrentPHGroup, CurrentPHKey, CurrentPHContext, value); } API_CheckCurrentPH() { From d988197b2e0902f979f805dbd3c57a7e6c589e71 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 16:01:27 +0300 Subject: [PATCH 05/30] Update ph api --- PLACEHOLDERS.md | 300 ++++++++++++++++++ amxmodx/scripting/ParamsController.sma | 2 +- .../DefaultObjects/Placeholder/Global.inc | 6 +- .../DefaultObjects/Placeholder/Player.inc | 10 +- .../Placeholders/API/General.inc | 20 +- .../Placeholders/API/Group.inc | 22 +- .../ParamsController/Placeholders/API/Key.inc | 84 ++--- .../Placeholders/Objects/PHGroup.inc | 99 +++--- .../scripting/include/ParamsController.inc | 230 ++++++++------ 9 files changed, 558 insertions(+), 215 deletions(-) create mode 100644 PLACEHOLDERS.md diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md new file mode 100644 index 0000000..6100910 --- /dev/null +++ b/PLACEHOLDERS.md @@ -0,0 +1,300 @@ +# Placeholder System + +Плейсхолдеры — это механизм подстановки динамических значений в строки вида `{key}` или `{prefix:key}`. Используется для форматирования строк с данными, которые меняются в рантайме (имя карты, authid игрока и т.п.). + +## Концепции + +### Группа (`T_PHGroup`) + +Группа объединяет логически связанные ключи под одним **префиксом**. В строках плейсхолдеры этой группы выглядят как `{prefix:key}`. + +Специальная **глобальная группа** (пустой префикс) — для плейсхолдеров без префикса: `{key}`. Доступна через `PCPH_GetGlobalGroup()`. + +### Ключ + +Конкретный плейсхолдер внутри группы, например `authid` в группе `p`. Ключи регистрируются заранее; незарегистрированные плейсхолдеры сохраняются в строке как есть. + +### Контекст + +Когда одна группа описывает несколько сущностей (например, разные игроки), каждый плейсхолдер может иметь разное значение в зависимости от **контекстного ключа** — произвольной строки-идентификатора (обычно индекс игрока). + +Перед вызовом `PCPH_Format` нужный контекст кладётся на стек группы через `PCPH_PushContext` / `PCPH_PushIntContext`, а после — снимается через `PCPH_PopContext`. + +### Режимы хранения значений + +| Режим | Когда использовать | Как обновлять | +|---|---|---| +| **Проактивный** | Значение известно заранее и меняется по событиям (например, authid при авторизации) | `PCPH_Set` / `PCPH_SetForStr` / `PCPH_SetForInt` и аналоги | +| **Реактивный (колбек)** | Значение нужно вычислять на лету в момент форматирования | `PCPH_SetGroupKeyCallback`, внутри колбека — `PCPH_Cb_Set` / `PCPH_Cb_SetInt` / `PCPH_Cb_SetFloat` | + +--- + +## Жизненный цикл + +``` +plugin_init / plugin_precache + └─ ParamsController_Init() + ├─ регистрирует встроенные типы параметров + ├─ регистрирует встроенные плейсхолдеры ({map}, {p:authid}) + ├─ ParamsController_OnRegisterTypes ← пользовательские типы + ├─ PCPH_OnRegisterGroups ← пользовательские группы и ключи + └─ ParamsController_OnInited ← можно использовать API +``` + +Регистрировать группы и ключи нужно в `PCPH_OnRegisterGroups`. После `OnInited` регистрация тоже работает, но семантически правильнее делать это в форварде. + +--- + +## API + +### Форвард + +```pawn +forward PCPH_OnRegisterGroups(); +``` + +Вызывается до `OnInited`. Здесь нужно регистрировать свои группы и ключи. + +--- + +### Группы + +```pawn +native T_PHGroup:PCPH_RegisterGroup(const sPrefix[]); +``` +Регистрирует новую группу с уникальным префиксом. Возвращает хендлер группы. + +```pawn +native T_PHGroup:PCPH_FindGroup(const sPrefix[]); +``` +Ищет группу по префиксу. Возвращает `Invalid_PHGroup` если не найдена. + +```pawn +native T_PHGroup:PCPH_GetGlobalGroup(); +``` +Возвращает хендлер глобальной группы (плейсхолдеры без префикса). + +--- + +### Регистрация ключей + +```pawn +native PCPH_RegisterGroupKey(const T_PHGroup:hGroup, const sKey[]); +``` +Регистрирует ключ в группе без колбека (проактивный режим). + +```pawn +native PCPH_SetGroupKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[]); +``` +Устанавливает колбек для **уже зарегистрированного** ключа. Если ключ не зарегистрирован — ошибка. + +Сигнатура колбека: +```pawn +public MyCallback(const sKey[], const sContextKey[]) { + // sKey — ключ плейсхолдера + // sContextKey — текущий контекст группы + PCPH_Cb_Set("значение"); +} +``` + +Внутри колбека **обязательно** вызвать один из `PCPH_Cb_Set` / `PCPH_Cb_SetInt` / `PCPH_Cb_SetFloat`. + +**Вспомогательные stocks:** + +```pawn +// Регистрация + колбек в одном вызове (для группы) +stock PCPH_RegGroupKey(const T_PHGroup:hGroup, const sKey[], const sCallback[]); + +// Регистрация + колбек в глобальной группе +stock PCPH_Gl_RegKey(const sKey[], const sCallback[]); + +// Регистрация в глобальной группе без колбека +stock PCPH_Gl_RegisterKey(const sKey[]); + +// Установка колбека в глобальной группе +stock PCPH_Gl_SetKeyCallback(const sKey[], const sCallback[]); +``` + +--- + +### Управление контекстом + +```pawn +native PCPH_PushContext(const T_PHGroup:hGroup, const sContextKey[]); +native PCPH_PopContext(const T_PHGroup:hGroup); + +// Удобная обёртка для числового контекста (например, индекс игрока) +stock PCPH_PushIntContext(const T_PHGroup:hGroup, const any:iContextKey); +``` + +Контекст — стековый: можно пушить несколько уровней и попать обратно. При `PCPH_Format` используется текущий верхний контекст группы. + +--- + +### Установка значений внутри колбека (`PCPH_Cb_*`) + +Можно вызывать **только** из колбека ключа: + +```pawn +native PCPH_Cb_Set(const sValue[]); +native PCPH_Cb_SetInt(const any:iValue); +native PCPH_Cb_SetFloat(const Float:fValue); // форматируется как "%.2f" +``` + +--- + +### Проактивная установка значений (вне колбека) + +Три семейства сеттеров по типу контекстного ключа: + +| Суффикс | Контекст | Пример | +|---|---|---| +| *(нет)* | пустая строка `""` | `PCPH_Set(hGroup, "key", "value")` | +| `ForStr` | явная строка | `PCPH_SetForStr(hGroup, "key", "ctx", "value")` | +| `ForInt` | число (конвертируется в строку) | `PCPH_SetForInt(hGroup, "key", id, "value")` | + +```pawn +// Пустой контекст +stock PCPH_Set(const T_PHGroup:hGroup, const sKey[], const sValue[]); +stock PCPH_SetInt(const T_PHGroup:hGroup, const sKey[], const any:iValue); +stock PCPH_SetFloat(const T_PHGroup:hGroup, const sKey[], const Float:fValue); + +// Явный строковый контекст +native PCPH_SetForStr(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const sValue[]); +native PCPH_SetIntForStr(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const any:iValue); +native PCPH_SetFloatForStr(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const Float:fValue); + +// Числовой контекст +stock PCPH_SetForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const sValue[]); +stock PCPH_SetIntForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const any:iValue); +stock PCPH_SetFloatForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const Float:fValue); +``` + +--- + +### Форматирование строк + +```pawn +native PCPH_Format(out[], const iOutLen, const sFormat[]); +``` + +Заменяет все плейсхолдеры вида `{key}` и `{prefix:key}` в строке `sFormat`. Незарегистрированные ключи и неизвестные группы сохраняются как есть. Максимальная длина результата — `PH_FORMAT_MAX_LEN` (4096). + +--- + +## Константы + +```pawn +#define PH_GROUP_PREFIX_MAX_LEN 16 +#define PH_KEY_MAX_LEN 64 +#define PH_VALUE_MAX_LEN 512 +#define PH_CONTEXT_KEY_MAX_LEN 64 +#define PH_CALLBACK_MAX_LEN 64 +#define PH_FORMAT_MAX_LEN 4096 +``` + +**Встроенные плейсхолдеры:** + +| Плейсхолдер | Константы | Описание | +|---|---|---| +| `{map}` | `DEFAULT_PH_GLOBAL_MAP_KEY` | Текущая карта (проактивный, устанавливается при инициализации) | +| `{p:authid}` | `DEFAULT_PH_PLAYER_GROUP_PREFIX`, `DEFAULT_PH_PLAYER_AUTHID_KEY` | AuthID игрока (проактивный, устанавливается при авторизации) | + +--- + +## Примеры использования + +### Форматирование строки + +```pawn +new out[256]; +PCPH_Format(out, charsmax(out), "Карта: {map}, игрок: {p:authid}"); +// При активном контексте игрока: "Карта: de_dust2, игрок: STEAM_0:1:12345" +``` + +### Форматирование с контекстом игрока + +```pawn +new T_PHGroup:hPlayerGroup = PCPH_FindGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); + +PCPH_PushIntContext(hPlayerGroup, id); +new out[256]; +PCPH_Format(out, charsmax(out), "Привет, {p:authid}!"); +PCPH_PopContext(hPlayerGroup); +``` + +--- + +## Примеры реализации + +### Реактивный плейсхолдер (колбек) + +Значение вычисляется каждый раз при форматировании. Подходит для данных, которые меняются часто или дорого хранить. + +```pawn +#include +#include + +public PCPH_OnRegisterGroups() { + // {time} в глобальной группе + PCPH_Gl_RegKey("time", "PH_TimeCallback"); + + // {sv:name} и {sv:players} + new T_PHGroup:hGroup = PCPH_RegisterGroup("sv"); + PCPH_RegGroupKey(hGroup, "name", "PH_ServerCallback"); + PCPH_RegGroupKey(hGroup, "players", "PH_ServerCallback"); +} + +public PH_TimeCallback(const sKey[], const sContextKey[]) { + static sTime[16]; + format_time(sTime, charsmax(sTime), "%H:%M"); + PCPH_Cb_Set(sTime); +} + +public PH_ServerCallback(const sKey[], const sContextKey[]) { + if (equal(sKey, "name")) { + static sName[64]; + get_hostname(sName, charsmax(sName)); + PCPH_Cb_Set(sName); + } else if (equal(sKey, "players")) { + PCPH_Cb_SetInt(get_playersnum()); + } +} +``` + +### Проактивный плейсхолдер с контекстом + +Значение устанавливается по событиям. Подходит для данных игрока, которые нужно читать часто. + +```pawn +#include +#include + +static T_PHGroup:g_hGroup = Invalid_PHGroup; + +public PCPH_OnRegisterGroups() { + g_hGroup = PCPH_RegisterGroup("p"); + PCPH_RegisterGroupKey(g_hGroup, "name"); + PCPH_RegisterGroupKey(g_hGroup, "kills"); +} + +public client_putinserver(id) { + UpdatePlayerPlaceholders(id); +} + +UpdatePlayerPlaceholders(id) { + static sName[32]; + get_user_name(id, sName, charsmax(sName)); + PCPH_SetForInt(g_hGroup, "name", id, sName); + + new kills, deaths; + get_user_frags(id, kills, deaths); + PCPH_SetIntForInt(g_hGroup, "kills", id, kills); +} + +FormatForPlayer(id, const sTemplate[], out[], outLen) { + PCPH_PushIntContext(g_hGroup, id); + PCPH_Format(out, outLen, sTemplate); + PCPH_PopContext(g_hGroup); +} +``` diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index 43014cb..cd34a84 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -37,7 +37,7 @@ PluginInit() { Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); // Тут регать группы и ключи плейсхолдеров - Forwards_RegAndCall("ParamsController_PH_OnRegisterGroups", ET_IGNORE); + Forwards_RegAndCall("PCPH_OnRegisterGroups", ET_IGNORE); register_srvcmd("params_controller_types", "@SrvCmd_Types"); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc index 54fd4a2..2786f6a 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc @@ -2,11 +2,11 @@ #include DefaultObjects_PH_Global_Register() { - new T_PHGroup:hGlobal = ParamsController_PH_GetGlobalGroup(); + new T_PHGroup:group = PCPH_GetGlobalGroup(); - ParamsController_PH_RegisterGroupKey(hGlobal, DEFAULT_PH_GLOBAL_MAP_KEY); + PCPH_Group_RegisterKey(group, DEFAULT_PH_GLOBAL_MAP_KEY); static sMap[64]; get_mapname(sMap, charsmax(sMap)); - ParamsController_PH_Set(hGlobal, DEFAULT_PH_GLOBAL_MAP_KEY, "", sMap); + PCPH_Set(group, DEFAULT_PH_GLOBAL_MAP_KEY, sMap); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc index 5513986..a222f45 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -1,14 +1,14 @@ #include #include -static T_PHGroup:g_hPlayerGroup = Invalid_PHGroup; +static T_PHGroup:playerGroup = Invalid_PHGroup; DefaultObjects_PH_Player_Register() { - g_hPlayerGroup = ParamsController_PH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); + playerGroup = PCPH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); - ParamsController_PH_RegisterGroupKey(g_hPlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); + PCPH_Group_RegisterKey(playerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); } -DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const sAuthID[]) { - ParamsController_PH_SetForInt(g_hPlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, sAuthID); +DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const authId[]) { + PCPH_SetForInt(playerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc index 1d4362e..a525ba4 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc @@ -3,10 +3,10 @@ #include "ParamsController/Placeholders/Objects/PHGroup" API_PH_General_Register() { - register_native("ParamsController_PH_Format", "@API_PH_Format"); - register_native("ParamsController_PH_SetValue", "@API_PH_SetValue"); - register_native("ParamsController_PH_SetIntValue", "@API_PH_SetIntValue"); - register_native("ParamsController_PH_SetFloatValue", "@API_PH_SetFloatValue"); + register_native("PCPH_Format", "@API_PH_Format"); + register_native("PCPH_Cb_Set", "@API_PH_Cb_Set"); + register_native("PCPH_Cb_SetInt", "@API_PH_Cb_SetInt"); + register_native("PCPH_Cb_SetFloat", "@API_PH_Cb_SetFloat"); } @API_PH_Format() { @@ -14,16 +14,16 @@ API_PH_General_Register() { API_CheckPluginInited(); - static formatStr[PH_FORMAT_MAX_LEN]; - get_string(Arg_Format, formatStr, charsmax(formatStr)); + static str[PH_FORMAT_MAX_LEN]; + get_string(Arg_Format, str, charsmax(str)); static out[PH_FORMAT_MAX_LEN]; - PHGroup_FormatString(formatStr, out, charsmax(out)); + PHGroup_FormatString(str, out, charsmax(out)); set_string(Arg_Out, out, get_param(Arg_OutLen)); } -@API_PH_SetValue() { +@API_PH_Cb_Set() { enum {Arg_Value = 1} API_CheckPluginInited(); @@ -35,7 +35,7 @@ API_PH_General_Register() { PHGroup_SetCurrentValue(value); } -@API_PH_SetIntValue() { +@API_PH_Cb_SetInt() { enum {Arg_Value = 1} API_CheckPluginInited(); @@ -47,7 +47,7 @@ API_PH_General_Register() { PHGroup_SetCurrentValue(value); } -@API_PH_SetFloatValue() { +@API_PH_Cb_SetFloat() { enum {Arg_Value = 1} API_CheckPluginInited(); diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc index feb50f4..c06555f 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc @@ -3,11 +3,11 @@ #include "ParamsController/Placeholders/Objects/PHGroup" API_PH_Group_Register() { - register_native("ParamsController_PH_RegisterGroup", "@API_PH_RegisterGroup"); - register_native("ParamsController_PH_FindGroup", "@API_PH_FindGroup"); - register_native("ParamsController_PH_GetGlobalGroup", "@API_PH_GetGlobalGroup"); - register_native("ParamsController_PH_PushContext", "@API_PH_PushContext"); - register_native("ParamsController_PH_PopContext", "@API_PH_PopContext"); + register_native("PCPH_RegisterGroup", "@API_PH_RegisterGroup"); + register_native("PCPH_FindGroup", "@API_PH_FindGroup"); + register_native("PCPH_GetGlobalGroup", "@API_PH_GetGlobalGroup"); + register_native("PCPH_Group_PushContext", "@API_PH_PushContext"); + register_native("PCPH_Group_PopContext", "@API_PH_PopContext"); } T_PHGroup:@API_PH_RegisterGroup() { @@ -53,12 +53,12 @@ T_PHGroup:@API_PH_GetGlobalGroup() { API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; - get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); - PHGroup_PushContext(hGroup, sContextKey); + PHGroup_PushContext(group, contextKey); } @API_PH_PopContext() { @@ -66,7 +66,7 @@ T_PHGroup:@API_PH_GetGlobalGroup() { API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - PHGroup_PopContext(hGroup); + PHGroup_PopContext(group); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc index d92277c..e07cbc8 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc @@ -3,11 +3,11 @@ #include "ParamsController/Placeholders/Objects/PHGroup" API_PH_Key_Register() { - register_native("ParamsController_PH_RegisterGroupKey", "@API_PH_RegisterGroupKey"); - register_native("ParamsController_PH_SetGroupKeyCallback", "@API_PH_SetGroupKeyCallback"); - register_native("ParamsController_PH_Set", "@API_PH_Set"); - register_native("ParamsController_PH_SetInt", "@API_PH_SetInt"); - register_native("ParamsController_PH_SetFloat", "@API_PH_SetFloat"); + register_native("PCPH_Group_RegisterKey", "@API_PH_RegisterGroupKey"); + register_native("PCPH_Group_SetKeyCallback", "@API_PH_SetGroupKeyCallback"); + register_native("PCPH_SetForStr", "@API_PH_SetForStr"); + register_native("PCPH_SetIntForStr", "@API_PH_SetIntForStr"); + register_native("PCPH_SetFloatForStr", "@API_PH_SetFloatForStr"); } @API_PH_RegisterGroupKey() { @@ -15,12 +15,12 @@ API_PH_Key_Register() { API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sKey[PH_KEY_MAX_LEN]; - get_string(Arg_Key, sKey, charsmax(sKey)); + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - PHGroup_RegisterKey(hGroup, sKey); + PHGroup_RegisterKey(group, key); } @API_PH_SetGroupKeyCallback(const pluginIndex) { @@ -28,70 +28,70 @@ API_PH_Key_Register() { API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sKey[PH_KEY_MAX_LEN]; - get_string(Arg_Key, sKey, charsmax(sKey)); + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - static sCallback[PH_CALLBACK_MAX_LEN]; - get_string(Arg_Callback, sCallback, charsmax(sCallback)); + static callback[PH_CALLBACK_MAX_LEN]; + get_string(Arg_Callback, callback, charsmax(callback)); - PHGroup_SetKeyCallback(hGroup, sKey, sCallback, pluginIndex); + PHGroup_SetKeyCallback(group, key, callback, pluginIndex); } -@API_PH_Set() { +@API_PH_SetForStr() { enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sKey[PH_KEY_MAX_LEN]; - get_string(Arg_Key, sKey, charsmax(sKey)); + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; - get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); - static sValue[PH_VALUE_MAX_LEN]; - get_string(Arg_Value, sValue, charsmax(sValue)); + static value[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, value, charsmax(value)); - PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); + PHGroup_SetValue(group, key, contextKey, value); } -@API_PH_SetInt() { +@API_PH_SetIntForStr() { enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sKey[PH_KEY_MAX_LEN]; - get_string(Arg_Key, sKey, charsmax(sKey)); + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; - get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); - static sValue[PH_VALUE_MAX_LEN]; - num_to_str(get_param(Arg_Value), sValue, charsmax(sValue)); + static value[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), value, charsmax(value)); - PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); + PHGroup_SetValue(group, key, contextKey, value); } -@API_PH_SetFloat() { +@API_PH_SetFloatForStr() { enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} API_CheckPluginInited(); - new T_PHGroup:hGroup = T_PHGroup:get_param(Arg_Group); + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); - static sKey[PH_KEY_MAX_LEN]; - get_string(Arg_Key, sKey, charsmax(sKey)); + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); - static sContextKey[PH_CONTEXT_KEY_MAX_LEN]; - get_string(Arg_ContextKey, sContextKey, charsmax(sContextKey)); + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); - static sValue[PH_VALUE_MAX_LEN]; - format(sValue, charsmax(sValue), "%.2f", get_param_f(Arg_Value)); + static value[PH_VALUE_MAX_LEN]; + format(value, charsmax(value), "%.2f", get_param_f(Arg_Value)); - PHGroup_SetValue(hGroup, sKey, sContextKey, sValue); + PHGroup_SetValue(group, key, contextKey, value); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index a4431d9..1c4cb1b 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -41,33 +41,33 @@ T_PHGroup:PHGroup_Construct(const prefix[]) { groupData[PHGroup_Callbacks] = TrieCreate(); groupData[PHGroup_Values] = TrieCreate(); groupData[PHGroup_ContextStack] = Invalid_Stack; - groupData[PHGroup_CurrentContext][0] = 0; + groupData[PHGroup_CurrentContext][0] = EOS; - new T_PHGroup:hGroup = T_PHGroup:ArrayPushArray(PHGroups, groupData); + new T_PHGroup:group = T_PHGroup:ArrayPushArray(PHGroups, groupData); if (prefix[0] != 0) { - TrieSetCell(PHGroupsMap, prefix, hGroup); + TrieSetCell(PHGroupsMap, prefix, group); } - return hGroup; + return group; } T_PHGroup:PHGroup_Find(const prefix[]) { - new T_PHGroup:hGroup = Invalid_PHGroup; - TrieGetCell(PHGroupsMap, prefix, hGroup); - return hGroup; + new T_PHGroup:group = Invalid_PHGroup; + TrieGetCell(PHGroupsMap, prefix, group); + return group; } T_PHGroup:PHGroup_GetGlobal() { return PHGlobalGroup; } -static PHGroup_Get(const T_PHGroup:hGroup, groupData[S_PHGroup]) { - ArrayGetArray(PHGroups, _:hGroup, groupData); +static PHGroup_Get(const T_PHGroup:group, groupData[S_PHGroup]) { + ArrayGetArray(PHGroups, _:group, groupData); } -static PHGroup_Update(const T_PHGroup:hGroup, const groupData[S_PHGroup]) { - ArraySetArray(PHGroups, _:hGroup, groupData); +static PHGroup_Update(const T_PHGroup:group, const groupData[S_PHGroup]) { + ArraySetArray(PHGroups, _:group, groupData); } // Составной ключ для Values Trie: "key:contextKey" или просто "key" если контекст пустой @@ -79,18 +79,23 @@ static PHGroup_BuildValueKey(const key[], const contextKey[], out[], const outLe } } -PHGroup_RegisterKey(const T_PHGroup:hGroup, const key[]) { +PHGroup_RegisterKey(const T_PHGroup:group, const key[]) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); if (!TrieKeyExists(groupData[PHGroup_Callbacks], key)) { TrieSetCell(groupData[PHGroup_Callbacks], key, INVALID_HANDLE); } } -PHGroup_SetKeyCallback(const T_PHGroup:hGroup, const key[], const callback[], const pluginIndex) { +PHGroup_SetKeyCallback(const T_PHGroup:group, const key[], const callback[], const pluginIndex) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); + + if (!TrieKeyExists(groupData[PHGroup_Callbacks], key)) { + abort(AMX_ERR_PARAMS, "PH key '%s' is not registered. Call RegisterGroupKey first.", key); + return; + } new oldFwd; if (TrieGetCell(groupData[PHGroup_Callbacks], key, oldFwd) && oldFwd != INVALID_HANDLE) { @@ -107,9 +112,9 @@ PHGroup_SetKeyCallback(const T_PHGroup:hGroup, const key[], const callback[], co TrieSetCell(groupData[PHGroup_Callbacks], key, fwd); } -PHGroup_PushContext(const T_PHGroup:hGroup, const contextKey[]) { +PHGroup_PushContext(const T_PHGroup:group, const contextKey[]) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); if (groupData[PHGroup_ContextStack] == Invalid_Stack) { groupData[PHGroup_ContextStack] = CreateStack(PH_CONTEXT_KEY_MAX_LEN); @@ -118,12 +123,12 @@ PHGroup_PushContext(const T_PHGroup:hGroup, const contextKey[]) { PushStackArray(groupData[PHGroup_ContextStack], groupData[PHGroup_CurrentContext], PH_CONTEXT_KEY_MAX_LEN); copy(groupData[PHGroup_CurrentContext], charsmax(groupData[PHGroup_CurrentContext]), contextKey); - PHGroup_Update(hGroup, groupData); + PHGroup_Update(group, groupData); } -PHGroup_PopContext(const T_PHGroup:hGroup) { +PHGroup_PopContext(const T_PHGroup:group) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); if ( groupData[PHGroup_ContextStack] != Invalid_Stack @@ -131,15 +136,15 @@ PHGroup_PopContext(const T_PHGroup:hGroup) { ) { PopStackArray(groupData[PHGroup_ContextStack], groupData[PHGroup_CurrentContext], PH_CONTEXT_KEY_MAX_LEN); } else { - groupData[PHGroup_CurrentContext][0] = 0; + groupData[PHGroup_CurrentContext][0] = EOS; } - PHGroup_Update(hGroup, groupData); + PHGroup_Update(group, groupData); } -PHGroup_SetValue(const T_PHGroup:hGroup, const key[], const contextKey[], const value[]) { +PHGroup_SetValue(const T_PHGroup:group, const key[], const contextKey[], const value[]) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); @@ -147,9 +152,9 @@ PHGroup_SetValue(const T_PHGroup:hGroup, const key[], const contextKey[], const TrieSetString(groupData[PHGroup_Values], valueKey, value); } -static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const key[], const contextKey[], out[], const outLen) { +static bool:PHGroup_GetValue(const T_PHGroup:group, const key[], const contextKey[], out[], const outLen) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); @@ -157,22 +162,22 @@ static bool:PHGroup_GetValue(const T_PHGroup:hGroup, const key[], const contextK return bool:TrieGetString(groupData[PHGroup_Values], valueKey, out, outLen); } -static bool:PHGroup_IsKeyRegistered(const T_PHGroup:hGroup, const key[]) { +static bool:PHGroup_IsKeyRegistered(const T_PHGroup:group, const key[]) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); return bool:TrieKeyExists(groupData[PHGroup_Callbacks], key); } -static PHGroup_CallCallback(const T_PHGroup:hGroup, const key[], const contextKey[]) { +static PHGroup_CallCallback(const T_PHGroup:group, const key[], const contextKey[]) { new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); new fwd; if (!TrieGetCell(groupData[PHGroup_Callbacks], key, fwd) || fwd == INVALID_HANDLE) { return; } - CurrentPHGroup = hGroup; + CurrentPHGroup = group; copy(CurrentPHKey, charsmax(CurrentPHKey), key); copy(CurrentPHContext, charsmax(CurrentPHContext), contextKey); @@ -180,33 +185,33 @@ static PHGroup_CallCallback(const T_PHGroup:hGroup, const key[], const contextKe ExecuteForward(fwd, ret, key, contextKey); CurrentPHGroup = Invalid_PHGroup; - CurrentPHKey[0] = 0; - CurrentPHContext[0] = 0; + CurrentPHKey[0] = EOS; + CurrentPHContext[0] = EOS; } -PHGroup_FormatString(const formatStr[], out[], const outLen) { +PHGroup_FormatString(const src[], out[], const outLen) { if (outLen < 1) { return; } new outPos = 0; new inPos = 0; - new formatLen = strlen(formatStr); + new srcLen = strlen(src); - while (inPos < formatLen && outPos < outLen - 1) { - if (formatStr[inPos] != '{') { - out[outPos++] = formatStr[inPos++]; + while (inPos < srcLen && outPos < outLen - 1) { + if (src[inPos] != '{') { + out[outPos++] = src[inPos++]; continue; } new closing = inPos + 1; - while (closing < formatLen && formatStr[closing] != '}') { + while (closing < srcLen && src[closing] != '}') { ++closing; } - if (closing >= formatLen) { + if (closing >= srcLen) { // Незакрытая скобка — выводим как есть - out[outPos++] = formatStr[inPos++]; + out[outPos++] = src[inPos++]; continue; } @@ -214,7 +219,7 @@ PHGroup_FormatString(const formatStr[], out[], const outLen) { new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); for (new k = 0; k < placeholderLen; ++k) { - placeholder[k] = formatStr[inPos + 1 + k]; + placeholder[k] = src[inPos + 1 + k]; } placeholder[placeholderLen] = 0; @@ -241,21 +246,21 @@ PHGroup_FormatString(const formatStr[], out[], const outLen) { copy(key, charsmax(key), placeholder[colon + 1]); } - new T_PHGroup:hGroup = (prefix[0] == 0) + new T_PHGroup:group = (prefix[0] == 0) ? PHGlobalGroup : PHGroup_Find(prefix); - if (hGroup != Invalid_PHGroup && PHGroup_IsKeyRegistered(hGroup, key)) { + if (group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { // Захватываем контекст до вызова колбека static currentContext[PH_CONTEXT_KEY_MAX_LEN]; new groupData[S_PHGroup]; - PHGroup_Get(hGroup, groupData); + PHGroup_Get(group, groupData); copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); - PHGroup_CallCallback(hGroup, key, currentContext); + PHGroup_CallCallback(group, key, currentContext); static value[PH_VALUE_MAX_LEN]; - if (PHGroup_GetValue(hGroup, key, currentContext, value, charsmax(value))) { + if (PHGroup_GetValue(group, key, currentContext, value, charsmax(value))) { new valueLen = strlen(value); for (new k = 0; k < valueLen && outPos < outLen - 1; ++k) { out[outPos++] = value[k]; diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 84fdedc..69bb0be 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1323,12 +1323,12 @@ stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { // ===== Placeholders ===== -#define PH_GROUP_PREFIX_MAX_LEN 16 -#define PH_KEY_MAX_LEN 64 -#define PH_VALUE_MAX_LEN 512 -#define PH_CONTEXT_KEY_MAX_LEN 64 -#define PH_CALLBACK_MAX_LEN 64 -#define PH_FORMAT_MAX_LEN 4096 +#define PH_GROUP_PREFIX_MAX_LEN 16 +#define PH_KEY_MAX_LEN 64 +#define PH_VALUE_MAX_LEN 512 +#define PH_CONTEXT_KEY_MAX_LEN 64 +#define PH_CALLBACK_MAX_LEN 64 +#define PH_FORMAT_MAX_LEN 4096 // --- Default placeholders --- @@ -1346,238 +1346,276 @@ enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } * * @noreturn */ -forward ParamsController_PH_OnRegisterGroups(); +forward PCPH_OnRegisterGroups(); /** * Регистрация группы плейсхолдеров с указанным префиксом. * Плейсхолдеры этой группы в строках имеют вид {prefix:key}. * - * @param sPrefix Уникальный префикс группы (например, "p" для группы игрока) + * @param prefix Уникальный префикс группы (например, "p" для группы игрока) * - * @return Хендлер зарегистрированной группы + * @return Хендлер зарегистрированной группы */ -native T_PHGroup:ParamsController_PH_RegisterGroup(const sPrefix[]); +native T_PHGroup:PCPH_RegisterGroup(const prefix[]); /** * Поиск группы плейсхолдеров по префиксу. * - * @param sPrefix Префикс группы + * @param prefix Префикс группы * - * @return Хендлер группы или Invalid_PHGroup если не найдена + * @return Хендлер группы или Invalid_PHGroup если не найдена */ -native T_PHGroup:ParamsController_PH_FindGroup(const sPrefix[]); +native T_PHGroup:PCPH_FindGroup(const prefix[]); /** * Получение хендлера глобальной группы. * Плейсхолдеры глобальной группы имеют вид {key} (без префикса). * - * @return Хендлер глобальной группы + * @return Хендлер глобальной группы */ -native T_PHGroup:ParamsController_PH_GetGlobalGroup(); +native T_PHGroup:PCPH_GetGlobalGroup(); /** * Регистрация ключа в группе без колбека (проактивный режим). - * Плагин сам по своей инициативе обновляет значение через *_PH_Set*. + * Значение устанавливается вручную через PCPH_Set / PCPH_SetForStr / PCPH_SetForInt. * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера + * @param group Хендлер группы + * @param key Ключ плейсхолдера * * @noreturn */ -native ParamsController_PH_RegisterGroupKey(const T_PHGroup:hGroup, const sKey[]); +native PCPH_Group_RegisterKey(const T_PHGroup:group, const key[]); /** - * Установка колбека для ключа в группе. - * Если ключ ещё не зарегистрирован — регистрирует его автоматически. + * Установка колбека для уже зарегистрированного ключа в группе. * - * @note Сигнатура колбека: (const sKey[], const sContextKey[]) - * sKey: ключ запрашиваемого плейсхолдера - * sContextKey: текущий контекст группы (например, индекс игрока) - * Колбек должен вызвать *_PH_SetValue / *_PH_SetIntValue / *_PH_SetFloatValue - * для установки актуального значения. + * @note Сигнатура колбека: (const key[], const contextKey[]) + * key: ключ запрашиваемого плейсхолдера + * contextKey: текущий контекст группы (например, индекс игрока) + * Колбек должен вызвать PCPH_Cb_Set / PCPH_Cb_SetInt / PCPH_Cb_SetFloat + * для установки актуального значения. * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param sCallback Название функции-колбека + * @param group Хендлер группы + * @param key Ключ плейсхолдера (должен быть предварительно зарегистрирован) + * @param callback Название функции-колбека * * @noreturn */ -native ParamsController_PH_SetGroupKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[]); +native PCPH_Group_SetKeyCallback(const T_PHGroup:group, const key[], const callback[]); /** * Помещение нового контекста на стек группы. * Контекст — произвольная строка-идентификатор (например, номер entity игрока). * - * @param hGroup Хендлер группы - * @param sContextKey Идентификатор нового контекста + * @param group Хендлер группы + * @param contextKey Идентификатор нового контекста * * @noreturn */ -native ParamsController_PH_PushContext(const T_PHGroup:hGroup, const sContextKey[]); +native PCPH_Group_PushContext(const T_PHGroup:group, const contextKey[]); /** * Снятие верхнего контекста со стека группы. * - * @param hGroup Хендлер группы + * @param group Хендлер группы * * @noreturn */ -native ParamsController_PH_PopContext(const T_PHGroup:hGroup); +native PCPH_Group_PopContext(const T_PHGroup:group); /** * Установка строкового значения текущего плейсхолдера. * - * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * @note Можно вызывать только в рамках колбека ключа плейсхолдера * - * @param sValue Устанавливаемое значение + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetValue(const sValue[]); +native PCPH_Cb_Set(const value[]); /** * Установка числового (int) значения текущего плейсхолдера. * - * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * @note Можно вызывать только в рамках колбека ключа плейсхолдера * - * @param iValue Устанавливаемое значение + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetIntValue(const any:iValue); +native PCPH_Cb_SetInt(const any:value); /** * Установка числового (float) значения текущего плейсхолдера. * Форматируется с точностью до 2 знаков после запятой. * - * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param value Устанавливаемое значение + * + * @noreturn + */ +native PCPH_Cb_SetFloat(const Float:value); + +/** + * Проактивная установка строкового значения для пустого контекста (вне колбека). * - * @param fValue Устанавливаемое значение + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetFloatValue(const Float:fValue); +stock PCPH_Set(const T_PHGroup:group, const key[], const value[]) { + PCPH_SetForStr(group, key, "", value); +} + +/** + * Проактивная установка числового (int) значения для пустого контекста (вне колбека). + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param value Устанавливаемое значение + * + * @noreturn + */ +stock PCPH_SetInt(const T_PHGroup:group, const key[], const any:value) { + PCPH_SetIntForStr(group, key, "", value); +} + +/** + * Проактивная установка числового (float) значения для пустого контекста (вне колбека). + * Форматируется с точностью до 2 знаков после запятой. + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param value Устанавливаемое значение + * + * @noreturn + */ +stock PCPH_SetFloat(const T_PHGroup:group, const key[], const Float:value) { + PCPH_SetFloatForStr(group, key, "", value); +} /** - * Проактивная установка строкового значения ключа в группе (вне колбека). + * Проактивная установка строкового значения с явным строковым контекстным ключом (вне колбека). * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param sContextKey Контекстный ключ (например, индекс игрока в виде строки) - * @param sValue Устанавливаемое значение + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_Set(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const sValue[]); +native PCPH_SetForStr(const T_PHGroup:group, const key[], const contextKey[], const value[]); /** - * Проактивная установка числового (int) значения ключа в группе (вне колбека). + * Проактивная установка числового (int) значения с явным строковым контекстным ключом (вне колбека). * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param sContextKey Контекстный ключ - * @param iValue Устанавливаемое значение + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetInt(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const any:iValue); +native PCPH_SetIntForStr(const T_PHGroup:group, const key[], const contextKey[], const any:value); /** - * Проактивная установка числового (float) значения ключа в группе (вне колбека). + * Проактивная установка числового (float) значения с явным строковым контекстным ключом (вне колбека). * Форматируется с точностью до 2 знаков после запятой. * - * @param hGroup Хендлер группы - * @param sKey Ключ плейсхолдера - * @param sContextKey Контекстный ключ - * @param fValue Устанавливаемое значение + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ + * @param value Устанавливаемое значение * * @noreturn */ -native ParamsController_PH_SetFloat(const T_PHGroup:hGroup, const sKey[], const sContextKey[], const Float:fValue); +native PCPH_SetFloatForStr(const T_PHGroup:group, const key[], const contextKey[], const Float:value); /** - * Замена плейсхолдеров в строке sFormat, результат записывается в out. + * Замена плейсхолдеров в строке format, результат записывается в out. * Плейсхолдеры вида {key} и {prefix:key} заменяются на актуальные значения. * Неизвестные группы и незарегистрированные ключи сохраняются как есть. * - * @param out Буфер для результирующей строки - * @param iOutLen Размер буфера - * @param sFormat Исходная строка с плейсхолдерами + * @param out Буфер для результирующей строки + * @param outLen Размер буфера + * @param format Исходная строка с плейсхолдерами * * @noreturn */ -native ParamsController_PH_Format(out[], const iOutLen, const sFormat[]); +native PCPH_Format(out[], const outLen, const format[]); /** * Помещение числового контекста на стек группы. - * Удобная обёртка над *_PushContext для случаев, когда контекстный ключ — число (например, индекс игрока). + * Удобная обёртка над PCPH_Group_PushContext для случаев, когда контекстный ключ — число (например, индекс игрока). * - * @param hGroup Хендлер группы - * @param iContextKey Числовой идентификатор контекста + * @param group Хендлер группы + * @param contextKey Числовой идентификатор контекста * * @noreturn */ -stock ParamsController_PH_PushIntContext(const T_PHGroup:hGroup, const any:iContextKey) { - static sContextKey[12]; - num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); - ParamsController_PH_PushContext(hGroup, sContextKey); +stock PCPH_Group_PushIntContext(const T_PHGroup:group, const any:contextKey) { + static buf[12]; + num_to_str(contextKey, buf, charsmax(buf)); + PCPH_Group_PushContext(group, buf); } /** * Проактивная установка строкового значения с числовым контекстным ключом. - * Удобная обёртка для групп, где контекст — число (например, индекс игрока). */ -stock ParamsController_PH_SetForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const sValue[]) { - static sContextKey[12]; - num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); - ParamsController_PH_Set(hGroup, sKey, sContextKey, sValue); +stock PCPH_SetForInt(const T_PHGroup:group, const key[], const any:contextKey, const value[]) { + static buf[12]; + num_to_str(contextKey, buf, charsmax(buf)); + PCPH_SetForStr(group, key, buf, value); } /** * Проактивная установка числового (int) значения с числовым контекстным ключом. */ -stock ParamsController_PH_SetIntForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const any:iValue) { - static sContextKey[12]; - num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); - ParamsController_PH_SetInt(hGroup, sKey, sContextKey, iValue); +stock PCPH_SetIntForInt(const T_PHGroup:group, const key[], const any:contextKey, const any:value) { + static buf[12]; + num_to_str(contextKey, buf, charsmax(buf)); + PCPH_SetIntForStr(group, key, buf, value); } /** * Проактивная установка числового (float) значения с числовым контекстным ключом. */ -stock ParamsController_PH_SetFloatForInt(const T_PHGroup:hGroup, const sKey[], const any:iContextKey, const Float:fValue) { - static sContextKey[12]; - num_to_str(iContextKey, sContextKey, charsmax(sContextKey)); - ParamsController_PH_SetFloat(hGroup, sKey, sContextKey, fValue); +stock PCPH_SetFloatForInt(const T_PHGroup:group, const key[], const any:contextKey, const Float:value) { + static buf[12]; + num_to_str(contextKey, buf, charsmax(buf)); + PCPH_SetFloatForStr(group, key, buf, value); } /** * Регистрация ключа в глобальной группе без колбека (проактивный режим). */ -stock ParamsController_PH_RegisterKey(const sKey[]) { - ParamsController_PH_RegisterGroupKey(ParamsController_PH_GetGlobalGroup(), sKey); +stock PCPH_Gl_RegisterKey(const key[]) { + PCPH_Group_RegisterKey(PCPH_GetGlobalGroup(), key); } /** * Установка колбека для ключа в глобальной группе. */ -stock ParamsController_PH_SetKeyCallback(const sKey[], const sCallback[]) { - ParamsController_PH_SetGroupKeyCallback(ParamsController_PH_GetGlobalGroup(), sKey, sCallback); +stock PCPH_Gl_SetKeyCallback(const key[], const callback[]) { + PCPH_Group_SetKeyCallback(PCPH_GetGlobalGroup(), key, callback); } /** * Регистрация ключа в группе с одновременной установкой колбека. */ -stock ParamsController_PH_RegGroupKey(const T_PHGroup:hGroup, const sKey[], const sCallback[]) { - ParamsController_PH_RegisterGroupKey(hGroup, sKey); - ParamsController_PH_SetGroupKeyCallback(hGroup, sKey, sCallback); +stock PCPH_Group_RegKey(const T_PHGroup:group, const key[], const callback[]) { + PCPH_Group_RegisterKey(group, key); + PCPH_Group_SetKeyCallback(group, key, callback); } /** * Регистрация ключа в глобальной группе с одновременной установкой колбека. */ -stock ParamsController_PH_RegKey(const sKey[], const sCallback[]) { - ParamsController_PH_RegGroupKey(ParamsController_PH_GetGlobalGroup(), sKey, sCallback); +stock PCPH_Gl_RegKey(const key[], const callback[]) { + PCPH_Group_RegKey(PCPH_GetGlobalGroup(), key, callback); } // Cvars From 2a51f811481f1cea25ac0bad96a897abcfaa0ab4 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 17:07:53 +0300 Subject: [PATCH 06/30] Impl templates --- .github/workflows/CI.yml | 4 +- PLACEHOLDERS.md | 48 +++++ .../Placeholders/API/General.inc | 40 ++++ .../Placeholders/Objects/PHGroup.inc | 17 +- .../Placeholders/Objects/PHTemplate.inc | 195 ++++++++++++++++++ .../scripting/include/ParamsController.inc | 45 ++++ 6 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9c516b8..cee241f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -57,7 +57,6 @@ jobs: working-directory: amxmodx/scripting/ env: REAPI_INCLUDE: ${{ env.REAPI_INCLUDE_PATH }} - PARAMS_CONRTOLLER_INCLUDE: ${{ env.PARAMS_CONRTOLLER_INCLUDE_PATH }} run: | compile() { sourcefile=$1 @@ -69,8 +68,7 @@ jobs: echo -n "Compiling $sourcefile ... " amxxpc $sourcefile -o"$output_path" \ -i"include" \ - -i"$REAPI_INCLUDE" \ - -i"$PARAMS_CONRTOLLER_INCLUDE" + -i"$REAPI_INCLUDE" } export -f compile diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md index 6100910..a73e462 100644 --- a/PLACEHOLDERS.md +++ b/PLACEHOLDERS.md @@ -62,16 +62,19 @@ forward PCPH_OnRegisterGroups(); ```pawn native T_PHGroup:PCPH_RegisterGroup(const sPrefix[]); ``` + Регистрирует новую группу с уникальным префиксом. Возвращает хендлер группы. ```pawn native T_PHGroup:PCPH_FindGroup(const sPrefix[]); ``` + Ищет группу по префиксу. Возвращает `Invalid_PHGroup` если не найдена. ```pawn native T_PHGroup:PCPH_GetGlobalGroup(); ``` + Возвращает хендлер глобальной группы (плейсхолдеры без префикса). --- @@ -81,14 +84,17 @@ native T_PHGroup:PCPH_GetGlobalGroup(); ```pawn native PCPH_RegisterGroupKey(const T_PHGroup:hGroup, const sKey[]); ``` + Регистрирует ключ в группе без колбека (проактивный режим). ```pawn native PCPH_SetGroupKeyCallback(const T_PHGroup:hGroup, const sKey[], const sCallback[]); ``` + Устанавливает колбек для **уже зарегистрированного** ключа. Если ключ не зарегистрирован — ошибка. Сигнатура колбека: + ```pawn public MyCallback(const sKey[], const sContextKey[]) { // sKey — ключ плейсхолдера @@ -182,6 +188,48 @@ native PCPH_Format(out[], const iOutLen, const sFormat[]); --- +### Скомпилированные шаблоны + +Для строк, которые форматируются часто (HUD-сообщения, периодические уведомления), можно предварительно скомпилировать шаблон. Компиляция разбирает строку один раз и кеширует для каждого плейсхолдера хендлер группы и хендлер форварда. При каждом последующем `PCPH_FormatTemplate` сканирование строки и поиск в Trie (группа, форвард) не выполняются. + +**Что кешируется:** хендлер группы и хендлер форварда. +**Что НЕ кешируется:** текущий контекст и значение — они читаются при каждом форматировании. +**Ограничение:** текстовые сегменты длиннее `PH_VALUE_MAX_LEN` (512) символов обрезаются. + +```pawn +// Создание — один раз после регистрации всех групп и ключей +native T_PHTemplate:PCPH_CompileTemplate(const sFormat[]); + +// Форматирование — при каждом обновлении HUD и т.п. +native PCPH_FormatTemplate(const T_PHTemplate:hTmpl, out[], const iOutLen); + +// Уничтожение — при выгрузке плагина или когда шаблон больше не нужен +// Вызов с Invalid_PHTemplate безопасен +native PCPH_DestroyTemplate(const T_PHTemplate:hTmpl); +``` + +**Когда компилировать:** после завершения регистрации всех групп, ключей и колбеков — например, в `ParamsController_OnInited`. Плейсхолдеры с неизвестными на момент компиляции группами или ключами фиксируются как литеральный текст и не будут заменены даже если группа зарегистрируется позже. + +**Когда НЕ использовать:** + +- строка динамическая (меняется между вызовами) — для таких строк только `PCPH_Format`; +- форматирование редкое (раз в несколько секунд и реже) — выигрыш незначителен, PCPH_Format достаточно. + +### Форматирование с контекстом через скомпилированный шаблон + +```pawn +new T_PHGroup:hPlayerGroup = PCPH_FindGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); +new T_PHTemplate:hTmpl = PCPH_CompileTemplate("HP: {p:health} | Score: {p:score}"); + +FormatForPlayer(id, out[], outLen) { + PCPH_PushIntContext(hPlayerGroup, id); + PCPH_FormatTemplate(hTmpl, out, outLen); + PCPH_PopContext(hPlayerGroup); +} +``` + +--- + ## Константы ```pawn diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc index a525ba4..375580f 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/General.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc @@ -1,9 +1,13 @@ #include #include "ParamsController/API/Utils" #include "ParamsController/Placeholders/Objects/PHGroup" +#include "ParamsController/Placeholders/Objects/PHTemplate" API_PH_General_Register() { register_native("PCPH_Format", "@API_PH_Format"); + register_native("PCPH_CompileTemplate", "@API_PH_CompileTemplate"); + register_native("PCPH_FormatTemplate", "@API_PH_FormatTemplate"); + register_native("PCPH_DestroyTemplate", "@API_PH_DestroyTemplate"); register_native("PCPH_Cb_Set", "@API_PH_Cb_Set"); register_native("PCPH_Cb_SetInt", "@API_PH_Cb_SetInt"); register_native("PCPH_Cb_SetFloat", "@API_PH_Cb_SetFloat"); @@ -23,6 +27,42 @@ API_PH_General_Register() { set_string(Arg_Out, out, get_param(Arg_OutLen)); } +@API_PH_CompileTemplate() { + enum {Arg_Format = 1} + + API_CheckPluginInited(); + + static str[PH_FORMAT_MAX_LEN]; + get_string(Arg_Format, str, charsmax(str)); + + return _:PHTemplate_Compile(str); +} + +@API_PH_FormatTemplate() { + enum {Arg_Tmpl = 1, Arg_Out, Arg_OutLen} + + API_CheckPluginInited(); + + new T_PHTemplate:tmpl = T_PHTemplate:get_param(Arg_Tmpl); + if (tmpl == Invalid_PHTemplate) { + abort(AMX_ERR_PARAMS, "Invalid PHTemplate handle."); + return; + } + + static out[PH_FORMAT_MAX_LEN]; + PHTemplate_Format(tmpl, out, charsmax(out)); + set_string(Arg_Out, out, get_param(Arg_OutLen)); +} + +@API_PH_DestroyTemplate() { + enum {Arg_Tmpl = 1} + + API_CheckPluginInited(); + + new T_PHTemplate:tmpl = T_PHTemplate:get_param(Arg_Tmpl); + PHTemplate_Destroy(tmpl); +} + @API_PH_Cb_Set() { enum {Arg_Value = 1} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 1c4cb1b..5af89f0 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -8,10 +8,10 @@ enum _:S_PHGroup { PHGroup_Prefix[PH_GROUP_PREFIX_MAX_LEN], - Trie:PHGroup_Callbacks, // ключ → хендлер форварда (INVALID_HANDLE если колбека нет) - Trie:PHGroup_Values, // "ключ:контекст" → текущее значение (строка) - Stack:PHGroup_ContextStack, // стек сохранённых предыдущих контекстов - PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) + Trie:PHGroup_Callbacks, // ключ → хендлер форварда (INVALID_HANDLE если колбека нет) + Trie:PHGroup_Values, // "ключ:контекст" → текущее значение (строка) + Stack:PHGroup_ContextStack, // стек сохранённых предыдущих контекстов + PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) } static Array:PHGroups = Invalid_Array; @@ -283,6 +283,15 @@ PHGroup_FormatString(const src[], out[], const outLen) { out[outPos] = 0; } +static PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { + new groupData[S_PHGroup]; + PHGroup_Get(group, groupData); + + new fwd = INVALID_HANDLE; + TrieGetCell(groupData[PHGroup_Callbacks], key, fwd); + return fwd; +} + bool:PHGroup_IsInCallback() { return CurrentPHGroup != Invalid_PHGroup; } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc new file mode 100644 index 0000000..eacf7a6 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc @@ -0,0 +1,195 @@ +#if defined __pc_src_phtemplate_included + #endinput +#endif +#define __pc_src_phtemplate_included + +#include +#include + +enum E_PHTemplatePartType { + PHTemplatePartType_Text, + PHTemplatePartType_Placeholder, +} + +enum _:S_PHTemplatePart { + E_PHTemplatePartType:PHTemplatePart_Type, // тип части шаблона + PHTemplatePart_Content[PH_VALUE_MAX_LEN], // текст сегмента или ключ плейсхолдера + T_PHGroup:PHTemplatePart_Group, // хендлер группы (только для Placeholder) + PHTemplatePart_Forward, // хендлер форварда (INVALID_HANDLE если проактивный) +} + +static PHTemplate_AddTextPart(Array:parts, const text[], const textLen) { + static partData[S_PHTemplatePart]; + partData[PHTemplatePart_Type] = PHTemplatePartType_Text; + + new copyLen = min(textLen, charsmax(partData[PHTemplatePart_Content])); + for (new k = 0; k < copyLen; ++k) { + partData[PHTemplatePart_Content][k] = text[k]; + } + partData[PHTemplatePart_Content][copyLen] = EOS; + + partData[PHTemplatePart_Group] = Invalid_PHGroup; + partData[PHTemplatePart_Forward] = INVALID_HANDLE; + ArrayPushArray(parts, partData); +} + +// Компилирует строку с плейсхолдерами в предварительно разобранный шаблон. +// Кеширует хендлеры групп и форвардов, чтобы при каждом PCPH_FormatTemplate +// не выполнялись сканирование строки, парсинг префикса/ключа и Trie-поиск группы/форварда. +// Вызывать следует после завершения регистрации всех групп, ключей и колбеков. +T_PHTemplate:PHTemplate_Compile(const src[]) { + new Array:parts = ArrayCreate(S_PHTemplatePart, 4); + + new srcLen = strlen(src); + new inPos = 0; + + static text[PH_FORMAT_MAX_LEN]; + new textLen = 0; + + static placeholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + static key[PH_KEY_MAX_LEN]; + + while (inPos < srcLen) { + if (src[inPos] != '{') { + if (textLen < charsmax(text)) { + text[textLen++] = src[inPos]; + } + ++inPos; + continue; + } + + new closing = inPos + 1; + while (closing < srcLen && src[closing] != '}') { + ++closing; + } + + if (closing >= srcLen) { + // Незакрытая скобка — выводим как есть + if (textLen < charsmax(text)) { + text[textLen++] = src[inPos]; + } + ++inPos; + continue; + } + + if (textLen > 0) { + PHTemplate_AddTextPart(parts, text, textLen); + textLen = 0; + } + + new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); + for (new k = 0; k < placeholderLen; ++k) { + placeholder[k] = src[inPos + 1 + k]; + } + placeholder[placeholderLen] = EOS; + + new colon = -1; + for (new k = 0; k < placeholderLen; ++k) { + if (placeholder[k] == ':') { + colon = k; + break; + } + } + + if (colon < 0) { + prefix[0] = EOS; + copy(key, charsmax(key), placeholder); + } else { + new prefixLen = min(colon, charsmax(prefix)); + for (new k = 0; k < prefixLen; ++k) { + prefix[k] = placeholder[k]; + } + prefix[prefixLen] = EOS; + copy(key, charsmax(key), placeholder[colon + 1]); + } + + new T_PHGroup:group = (prefix[0] == EOS) + ? PHGroup_GetGlobal() + : PHGroup_Find(prefix); + + if (group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { + static partData[S_PHTemplatePart]; + partData[PHTemplatePart_Type] = PHTemplatePartType_Placeholder; + copy(partData[PHTemplatePart_Content], charsmax(partData[PHTemplatePart_Content]), key); + partData[PHTemplatePart_Group] = group; + partData[PHTemplatePart_Forward] = PHGroup_GetKeyForward(group, key); + ArrayPushArray(parts, partData); + } else { + // Неизвестный плейсхолдер — сохраняем как текст с фигурными скобками + text[0] = '{'; + textLen = 1; + for (new k = 0; k < placeholderLen && textLen < charsmax(text); ++k) { + text[textLen++] = placeholder[k]; + } + if (textLen < charsmax(text)) { + text[textLen++] = '}'; + } + PHTemplate_AddTextPart(parts, text, textLen); + textLen = 0; + } + + inPos = closing + 1; + } + + if (textLen > 0) { + PHTemplate_AddTextPart(parts, text, textLen); + } + + return T_PHTemplate:parts; +} + +PHTemplate_Format(const T_PHTemplate:tmpl, out[], const outLen) { + if (outLen < 1) { + return; + } + + new outPos = 0; + new partCount = ArraySize(Array:tmpl); + new ret; + static partData[S_PHTemplatePart]; + static currentContext[PH_CONTEXT_KEY_MAX_LEN]; + static value[PH_VALUE_MAX_LEN]; + new groupData[S_PHGroup]; + + for (new i = 0; i < partCount && outPos < outLen - 1; ++i) { + ArrayGetArray(Array:tmpl, i, partData); + + if (partData[PHTemplatePart_Type] == PHTemplatePartType_Text) { + for (new k = 0; partData[PHTemplatePart_Content][k] != EOS && outPos < outLen - 1; ++k) { + out[outPos++] = partData[PHTemplatePart_Content][k]; + } + } else { + new T_PHGroup:group = T_PHGroup:partData[PHTemplatePart_Group]; + PHGroup_Get(group, groupData); + copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); + + new fwd = partData[PHTemplatePart_Forward]; + if (fwd != INVALID_HANDLE) { + CurrentPHGroup = group; + copy(CurrentPHKey, charsmax(CurrentPHKey), partData[PHTemplatePart_Content]); + copy(CurrentPHContext, charsmax(CurrentPHContext), currentContext); + + ExecuteForward(fwd, ret, partData[PHTemplatePart_Content], currentContext); + + CurrentPHGroup = Invalid_PHGroup; + CurrentPHKey[0] = EOS; + CurrentPHContext[0] = EOS; + } + + if (PHGroup_GetValue(group, partData[PHTemplatePart_Content], currentContext, value, charsmax(value))) { + for (new k = 0; value[k] != EOS && outPos < outLen - 1; ++k) { + out[outPos++] = value[k]; + } + } + } + } + + out[outPos] = EOS; +} + +PHTemplate_Destroy(const T_PHTemplate:tmpl) { + if (tmpl != Invalid_PHTemplate) { + ArrayDestroy(Array:tmpl); + } +} diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 69bb0be..0e696bb 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1547,6 +1547,51 @@ native PCPH_SetFloatForStr(const T_PHGroup:group, const key[], const contextKey[ */ native PCPH_Format(out[], const outLen, const format[]); +/** + * Тип хендлера скомпилированного шаблона плейсхолдеров. + * Создаётся через PCPH_CompileTemplate, уничтожается через PCPH_DestroyTemplate. + */ +enum T_PHTemplate { Invalid_PHTemplate = INVALID_HANDLE } + +/** + * Компилирует строку с плейсхолдерами в предварительно разобранный шаблон. + * Кеширует хендлеры групп и форвардов — при каждом вызове PCPH_FormatTemplate + * не выполняются сканирование строки, парсинг префикса/ключа и Trie-поиск группы/форварда. + * + * @note Компилировать следует после завершения регистрации всех групп, ключей и колбеков + * (например, в конце PCPH_OnRegisterGroups или в ParamsController_OnInited). + * Плейсхолдеры с неизвестными группами или ключами фиксируются как литеральный текст. + * Текстовые сегменты длиннее PH_VALUE_MAX_LEN символов будут обрезаны. + * + * @param format Строка с плейсхолдерами + * + * @return Хендлер скомпилированного шаблона + */ +native T_PHTemplate:PCPH_CompileTemplate(const format[]); + +/** + * Форматирует скомпилированный шаблон, подставляя текущие значения плейсхолдеров. + * Быстрее PCPH_Format: нет сканирования строки, нет разбора плейсхолдеров, + * хендлеры группы и форварда берутся из кеша. + * + * @param tmpl Хендлер скомпилированного шаблона + * @param out Буфер для результирующей строки + * @param outLen Размер буфера + * + * @noreturn + */ +native PCPH_FormatTemplate(const T_PHTemplate:tmpl, out[], const outLen); + +/** + * Уничтожает скомпилированный шаблон и освобождает занятую память. + * Вызов с Invalid_PHTemplate — безопасная операция. + * + * @param tmpl Хендлер скомпилированного шаблона + * + * @noreturn + */ +native PCPH_DestroyTemplate(const T_PHTemplate:tmpl); + /** * Помещение числового контекста на стек группы. * Удобная обёртка над PCPH_Group_PushContext для случаев, когда контекстный ключ — число (например, индекс игрока). From aeea5b917d2d23aeee11e8da7c38601e8324f827 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 17:46:29 +0300 Subject: [PATCH 07/30] Fix --- .../Placeholders/Objects/PHGroup.inc | 28 ++++++++++++- .../Placeholders/Objects/PHTemplate.inc | 34 ++++----------- .../scripting/include/ParamsController.inc | 42 +++++++++---------- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 5af89f0..058e9e5 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -162,7 +162,7 @@ static bool:PHGroup_GetValue(const T_PHGroup:group, const key[], const contextKe return bool:TrieGetString(groupData[PHGroup_Values], valueKey, out, outLen); } -static bool:PHGroup_IsKeyRegistered(const T_PHGroup:group, const key[]) { +bool:PHGroup_IsKeyRegistered(const T_PHGroup:group, const key[]) { new groupData[S_PHGroup]; PHGroup_Get(group, groupData); return bool:TrieKeyExists(groupData[PHGroup_Callbacks], key); @@ -283,7 +283,7 @@ PHGroup_FormatString(const src[], out[], const outLen) { out[outPos] = 0; } -static PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { +PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { new groupData[S_PHGroup]; PHGroup_Get(group, groupData); @@ -292,6 +292,30 @@ static PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { return fwd; } +// Вызывает колбек (если fwd != INVALID_HANDLE) и записывает текущее значение ключа в out. +// Используется скомпилированными шаблонами: группа и форвард уже известны, контекст берётся в рантайме. +bool:PHGroup_FillValue(const T_PHGroup:group, const key[], const fwd, out[], const outLen) { + static currentContext[PH_CONTEXT_KEY_MAX_LEN]; + new groupData[S_PHGroup]; + PHGroup_Get(group, groupData); + copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); + + if (fwd != INVALID_HANDLE) { + CurrentPHGroup = group; + copy(CurrentPHKey, charsmax(CurrentPHKey), key); + copy(CurrentPHContext, charsmax(CurrentPHContext), currentContext); + + new ret; + ExecuteForward(fwd, ret, key, currentContext); + + CurrentPHGroup = Invalid_PHGroup; + CurrentPHKey[0] = EOS; + CurrentPHContext[0] = EOS; + } + + return PHGroup_GetValue(group, key, currentContext, out, outLen); +} + bool:PHGroup_IsInCallback() { return CurrentPHGroup != Invalid_PHGroup; } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc index eacf7a6..1aa5cca 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc @@ -5,6 +5,7 @@ #include #include +#include "ParamsController/Placeholders/Objects/PHGroup" enum E_PHTemplatePartType { PHTemplatePartType_Text, @@ -12,10 +13,10 @@ enum E_PHTemplatePartType { } enum _:S_PHTemplatePart { - E_PHTemplatePartType:PHTemplatePart_Type, // тип части шаблона - PHTemplatePart_Content[PH_VALUE_MAX_LEN], // текст сегмента или ключ плейсхолдера - T_PHGroup:PHTemplatePart_Group, // хендлер группы (только для Placeholder) - PHTemplatePart_Forward, // хендлер форварда (INVALID_HANDLE если проактивный) + E_PHTemplatePartType:PHTemplatePart_Type, // тип части шаблона + PHTemplatePart_Content[PH_VALUE_MAX_LEN], // текст сегмента или ключ плейсхолдера + T_PHGroup:PHTemplatePart_Group, // хендлер группы (только для Placeholder) + PHTemplatePart_Forward, // хендлер форварда (INVALID_HANDLE если проактивный) } static PHTemplate_AddTextPart(Array:parts, const text[], const textLen) { @@ -146,11 +147,8 @@ PHTemplate_Format(const T_PHTemplate:tmpl, out[], const outLen) { new outPos = 0; new partCount = ArraySize(Array:tmpl); - new ret; static partData[S_PHTemplatePart]; - static currentContext[PH_CONTEXT_KEY_MAX_LEN]; static value[PH_VALUE_MAX_LEN]; - new groupData[S_PHGroup]; for (new i = 0; i < partCount && outPos < outLen - 1; ++i) { ArrayGetArray(Array:tmpl, i, partData); @@ -160,24 +158,7 @@ PHTemplate_Format(const T_PHTemplate:tmpl, out[], const outLen) { out[outPos++] = partData[PHTemplatePart_Content][k]; } } else { - new T_PHGroup:group = T_PHGroup:partData[PHTemplatePart_Group]; - PHGroup_Get(group, groupData); - copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); - - new fwd = partData[PHTemplatePart_Forward]; - if (fwd != INVALID_HANDLE) { - CurrentPHGroup = group; - copy(CurrentPHKey, charsmax(CurrentPHKey), partData[PHTemplatePart_Content]); - copy(CurrentPHContext, charsmax(CurrentPHContext), currentContext); - - ExecuteForward(fwd, ret, partData[PHTemplatePart_Content], currentContext); - - CurrentPHGroup = Invalid_PHGroup; - CurrentPHKey[0] = EOS; - CurrentPHContext[0] = EOS; - } - - if (PHGroup_GetValue(group, partData[PHTemplatePart_Content], currentContext, value, charsmax(value))) { + if (PHGroup_FillValue(partData[PHTemplatePart_Group], partData[PHTemplatePart_Content], partData[PHTemplatePart_Forward], value, charsmax(value))) { for (new k = 0; value[k] != EOS && outPos < outLen - 1; ++k) { out[outPos++] = value[k]; } @@ -190,6 +171,7 @@ PHTemplate_Format(const T_PHTemplate:tmpl, out[], const outLen) { PHTemplate_Destroy(const T_PHTemplate:tmpl) { if (tmpl != Invalid_PHTemplate) { - ArrayDestroy(Array:tmpl); + new Array:arr = Array:tmpl; + ArrayDestroy(arr); } } diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 0e696bb..c82fcb0 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1458,81 +1458,81 @@ native PCPH_Cb_SetInt(const any:value); native PCPH_Cb_SetFloat(const Float:value); /** - * Проактивная установка строкового значения для пустого контекста (вне колбека). + * Проактивная установка строкового значения с явным строковым контекстным ключом (вне колбека). * * @param group Хендлер группы * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -stock PCPH_Set(const T_PHGroup:group, const key[], const value[]) { - PCPH_SetForStr(group, key, "", value); -} +native PCPH_SetForStr(const T_PHGroup:group, const key[], const contextKey[], const value[]); /** - * Проактивная установка числового (int) значения для пустого контекста (вне колбека). + * Проактивная установка числового (int) значения с явным строковым контекстным ключом (вне колбека). * * @param group Хендлер группы * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -stock PCPH_SetInt(const T_PHGroup:group, const key[], const any:value) { - PCPH_SetIntForStr(group, key, "", value); -} +native PCPH_SetIntForStr(const T_PHGroup:group, const key[], const contextKey[], const any:value); /** - * Проактивная установка числового (float) значения для пустого контекста (вне колбека). + * Проактивная установка числового (float) значения с явным строковым контекстным ключом (вне колбека). * Форматируется с точностью до 2 знаков после запятой. * * @param group Хендлер группы * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -stock PCPH_SetFloat(const T_PHGroup:group, const key[], const Float:value) { - PCPH_SetFloatForStr(group, key, "", value); -} +native PCPH_SetFloatForStr(const T_PHGroup:group, const key[], const contextKey[], const Float:value); /** - * Проактивная установка строкового значения с явным строковым контекстным ключом (вне колбека). + * Проактивная установка строкового значения для пустого контекста (вне колбека). * * @param group Хендлер группы * @param key Ключ плейсхолдера - * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -native PCPH_SetForStr(const T_PHGroup:group, const key[], const contextKey[], const value[]); +stock PCPH_Set(const T_PHGroup:group, const key[], const value[]) { + PCPH_SetForStr(group, key, "", value); +} /** - * Проактивная установка числового (int) значения с явным строковым контекстным ключом (вне колбека). + * Проактивная установка числового (int) значения для пустого контекста (вне колбека). * * @param group Хендлер группы * @param key Ключ плейсхолдера - * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -native PCPH_SetIntForStr(const T_PHGroup:group, const key[], const contextKey[], const any:value); +stock PCPH_SetInt(const T_PHGroup:group, const key[], const any:value) { + PCPH_SetIntForStr(group, key, "", value); +} /** - * Проактивная установка числового (float) значения с явным строковым контекстным ключом (вне колбека). + * Проактивная установка числового (float) значения для пустого контекста (вне колбека). * Форматируется с точностью до 2 знаков после запятой. * * @param group Хендлер группы * @param key Ключ плейсхолдера - * @param contextKey Контекстный ключ * @param value Устанавливаемое значение * * @noreturn */ -native PCPH_SetFloatForStr(const T_PHGroup:group, const key[], const contextKey[], const Float:value); +stock PCPH_SetFloat(const T_PHGroup:group, const key[], const Float:value) { + PCPH_SetFloatForStr(group, key, "", value); +} /** * Замена плейсхолдеров в строке format, результат записывается в out. From f588abcf04d45c93180d6269026ca67270e33a09 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 18:08:12 +0300 Subject: [PATCH 08/30] Impl test plugin for ph --- amxmodx/scripting/PHTest.sma | 159 ++++++++++++++++++ .../Placeholders/Objects/PHGroup.inc | 3 + 2 files changed, 162 insertions(+) create mode 100644 amxmodx/scripting/PHTest.sma diff --git a/amxmodx/scripting/PHTest.sma b/amxmodx/scripting/PHTest.sma new file mode 100644 index 0000000..870045b --- /dev/null +++ b/amxmodx/scripting/PHTest.sma @@ -0,0 +1,159 @@ +#include +#include + +// Группа тест-плагина для игроков: {t:name}, {t:health} +static T_PHGroup:g_testGroup = Invalid_PHGroup; + +// Ссылка на встроенную группу игрока {p:authid} — кешируем, чтобы не искать по строке на каждый вызов +static T_PHGroup:g_defaultPlayerGroup = Invalid_PHGroup; + +// Скомпилированные шаблоны +static T_PHTemplate:g_templateHud = Invalid_PHTemplate; +static T_PHTemplate:g_templateGreet = Invalid_PHTemplate; + +public plugin_init() { + register_plugin("PH Test", "1.0", "Test"); + + register_clcmd("say /ph_test", "CmdInline"); + register_clcmd("say /ph_compiled", "CmdCompiled"); + register_clcmd("say /ph_context", "CmdContext"); + + ParamsController_Init(); +} + +// Компилируем шаблоны после того как все группы и ключи зарегистрированы +public ParamsController_OnInited() { + g_templateHud = PCPH_CompileTemplate( + "Map: {map} | Player: {t:name} | HP: {t:health} | Auth: {p:authid}" + ); + g_templateGreet = PCPH_CompileTemplate( + "Hi {t:name}! Time: {time} | Server: {server} | Unknown: {bad:key}" + ); +} + +// ===== Регистрация ===== + +public PCPH_OnRegisterGroups() { + // Кешируем встроенную группу игрока — к этому моменту она уже зарегистрирована + g_defaultPlayerGroup = PCPH_FindGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); + + // Глобальная группа: реактивный {time} и проактивный {server} + PCPH_Gl_RegKey("time", "PH_Time"); + PCPH_Gl_RegisterKey("server"); + + static hostname[64]; + get_user_name(0, hostname, charsmax(hostname)); + PCPH_Set(PCPH_GetGlobalGroup(), "server", hostname); + + // Группа тест-плагина: {t:name} — проактивный, {t:health} — реактивный + g_testGroup = PCPH_RegisterGroup("t"); + PCPH_Group_RegisterKey(g_testGroup, "name"); + PCPH_Group_RegKey(g_testGroup, "health", "PH_Health"); +} + +// ===== Колбеки плейсхолдеров ===== + +public PH_Time(const key[], const contextKey[]) { + static buf[16]; + format_time(buf, charsmax(buf), "%H:%M:%S"); + PCPH_Cb_Set(buf); +} + +// contextKey содержит индекс игрока, переданный через PushIntContext +public PH_Health(const key[], const contextKey[]) { + PCPH_Cb_SetInt(get_user_health(str_to_num(contextKey))); +} + +// ===== Обновление проактивных значений ===== + +public client_putinserver(playerIndex) { + UpdateName(playerIndex); +} + +public client_infochanged(playerIndex) { + UpdateName(playerIndex); +} + +UpdateName(playerIndex) { + static name[32]; + get_user_name(playerIndex, name, charsmax(name)); + PCPH_SetForInt(g_testGroup, "name", playerIndex, name); +} + +// ===== Хелперы для управления контекстом ===== + +PushPlayerContexts(playerIndex) { + PCPH_Group_PushIntContext(g_testGroup, playerIndex); + PCPH_Group_PushIntContext(g_defaultPlayerGroup, playerIndex); +} + +PopPlayerContexts() { + PCPH_Group_PopContext(g_defaultPlayerGroup); + PCPH_Group_PopContext(g_testGroup); +} + +// ===== Команды ===== + +// /ph_test — инлайн форматирование: глобальный контекст, контекст игрока, неизвестный ключ +public CmdInline(playerIndex) { + static out[512]; + + // Только глобальные плейсхолдеры — контекст игрока не нужен + PCPH_Format(out, charsmax(out), "Global: map={map} time={time} server={server}"); + client_print(playerIndex, print_chat, "[inline/global] %s", out); + + // Плейсхолдеры игрока — нужен контекст + PushPlayerContexts(playerIndex); + PCPH_Format(out, charsmax(out), "Player: name={t:name} hp={t:health} auth={p:authid}"); + client_print(playerIndex, print_chat, "[inline/player] %s", out); + PopPlayerContexts(); + + // Неизвестный плейсхолдер остаётся как есть в строке + PCPH_Format(out, charsmax(out), "known={map} unknown={bad:key}"); + client_print(playerIndex, print_chat, "[inline/unknown] %s", out); + + return PLUGIN_HANDLED; +} + +// /ph_compiled — скомпилированные шаблоны +public CmdCompiled(playerIndex) { + static out[512]; + + PushPlayerContexts(playerIndex); + + PCPH_FormatTemplate(g_templateHud, out, charsmax(out)); + client_print(playerIndex, print_chat, "[compiled/hud] %s", out); + + PCPH_FormatTemplate(g_templateGreet, out, charsmax(out)); + client_print(playerIndex, print_chat, "[compiled/greet] %s", out); + + PopPlayerContexts(); + + return PLUGIN_HANDLED; +} + +// /ph_context — демонстрация стека контекстов +// Показывает три уровня: текущий игрок → другой игрок → возврат к текущему +public CmdContext(playerIndex) { + static out[512]; + + // Уровень 1: контекст текущего игрока + PCPH_Group_PushIntContext(g_testGroup, playerIndex); + PCPH_Format(out, charsmax(out), "L1 (self): name={t:name} hp={t:health}"); + client_print(playerIndex, print_chat, "[ctx/L1] %s", out); + + // Уровень 2: поверх кладём другого игрока — стек g_testGroup теперь [playerIndex, other] + new other = (playerIndex == 1) ? 2 : 1; + PCPH_Group_PushIntContext(g_testGroup, other); + PCPH_Format(out, charsmax(out), "L2: name={t:name} hp={t:health}"); + client_print(playerIndex, print_chat, "[ctx/L2 #%d] %s", other, out); + + // Pop — стек возвращается к playerIndex + PCPH_Group_PopContext(g_testGroup); + PCPH_Format(out, charsmax(out), "L1 again (self): name={t:name} hp={t:health}"); + client_print(playerIndex, print_chat, "[ctx/back] %s", out); + + PCPH_Group_PopContext(g_testGroup); + + return PLUGIN_HANDLED; +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 058e9e5..b6894c4 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -47,6 +47,9 @@ T_PHGroup:PHGroup_Construct(const prefix[]) { if (prefix[0] != 0) { TrieSetCell(PHGroupsMap, prefix, group); + log_amx("[INFO] PH group registered: prefix='%s'", prefix); + } else { + log_amx("[INFO] PH global group registered"); } return group; From 604fb8e8d66c4adb7fffc50116baaeb7e4952987 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 10:55:59 +0300 Subject: [PATCH 09/30] Add `Regexp` type --- .../DefaultObjects/ParamType/Regexp.inc | 24 +++++++++++++++++++ .../DefaultObjects/Registrar.inc | 2 ++ .../scripting/include/ParamsController.inc | 1 + 3 files changed, 27 insertions(+) create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc new file mode 100644 index 0000000..5238828 --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +DefaultObjects_ParamType_Regexp_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_REGEXP_NAME, "@DefaultObjects_ParamType_Regexp_OnRead"); +} + +bool:@DefaultObjects_ParamType_Regexp_OnRead(const JSON:valueJson) { + static pattern[PARAM_VALUE_MAX_LEN]; + json_get_string(valueJson, pattern, charsmax(pattern)); + + static error[256]; + new ret; + new Regex:re = regex_compile(pattern, ret, error, charsmax(error)); + + if (re == REGEX_PATTERN_FAIL) { + PCJson_LogForFile(valueJson, "WARNING", "Invalid regexp pattern '%s': %s.", pattern, error); + return false; + } + + return ParamsController_SetCell(re); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index 046b8e8..ddf5dea 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -17,6 +17,7 @@ #include "ParamsController/DefaultObjects/ParamType/TimeInterval" #include "ParamsController/DefaultObjects/ParamType/WeekDay" #include "ParamsController/DefaultObjects/ParamType/Flags" +#include "ParamsController/DefaultObjects/ParamType/Regexp" DefaultObjects_ParamType_Register() { DefaultObjects_ParamType_Boolean_Register(); @@ -36,4 +37,5 @@ DefaultObjects_ParamType_Register() { DefaultObjects_ParamType_Time_Register(); DefaultObjects_ParamType_TimeInterval_Register(); DefaultObjects_ParamType_WeekDay_Register(); + DefaultObjects_ParamType_Regexp_Register(); } diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index c82fcb0..bfbdb65 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -32,6 +32,7 @@ stock const DEFAULT_PARAMS_TIME_INTERVAL_NAME[] = "TimeInterval"; stock const DEFAULT_PARAMS_TIME_NAME[] = "Time"; stock const DEFAULT_PARAMS_WEEK_DAY_NAME[] = "WeekDay"; stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; +stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; #define PARAMS_CONTROLLER_VERSION "1.3.3" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; From 71d9e6925195a8fc4a92353034869aecf916cfd5 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 11:08:52 +0300 Subject: [PATCH 10/30] Add pcget and pcsingle for regexp --- .../scripting/include/ParamsController.inc | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index bfbdb65..6bb7468 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -545,6 +545,12 @@ stock PCSingle_ObjString(const JSON:objectJson, const key[], out[], const outLen return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_VALUE_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_String(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_STR_NAME, out, outLen, def, orFailKey); } @@ -572,6 +578,12 @@ stock PCSingle_ObjShortString(const JSON:objectJson, const key[], out[], const o return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjShortString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_SHORT_STRING_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_ShortString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, orFailKey); } @@ -599,6 +611,12 @@ stock PCSingle_ObjLongString(const JSON:objectJson, const key[], out[], const ou return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjLongString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_LONG_STRING_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_LongString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, orFailKey); } @@ -626,6 +644,12 @@ stock PCSingle_ObjModel(const JSON:objectJson, const key[], out[], const outLen, return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_Model(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, orFailKey); } @@ -653,6 +677,12 @@ stock PCSingle_ObjSound(const JSON:objectJson, const key[], out[], const outLen, return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjSound(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_Sound(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, orFailKey); } @@ -680,6 +710,12 @@ stock PCSingle_ObjResource(const JSON:objectJson, const key[], out[], const outL return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjResource(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_Resource(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, orFailKey); } @@ -707,6 +743,12 @@ stock PCSingle_ObjFile(const JSON:objectJson, const key[], out[], const outLen, return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjFile(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_File(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, orFailKey); } @@ -734,6 +776,12 @@ stock PCSingle_ObjDir(const JSON:objectJson, const key[], out[], const outLen, c return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjDir(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_Dir(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { return PCSingle_Str(valueJson, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, orFailKey); } @@ -761,6 +809,12 @@ stock PCSingle_ObjChatMessage(const JSON:objectJson, const key[], out[], const o return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, dotNot, orFail); } +stock PCSingle_iObjChatMessage(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_CHAT_MESSAGE_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + stock PCSingle_ChatMessage(const JSON:valueJson, out[], const outLen, const def[] = "", const bool:orFail = false) { return PCSingle_Str(valueJson, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, orFail); } @@ -771,6 +825,25 @@ stock PCSingle_iChatMessage(const JSON:valueJson, const def[] = "", const bool:o return out; } +/** + * Чтение параметра типа "Regexp" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Хендлер скомпилированного выражения. + */ +stock Regex:PCSingle_ObjRegexp(const JSON:objectJson, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL, const bool:dotNot = false, const bool:orFail = false) { + return Regex:PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_REGEXP_NAME, def, dotNot, orFail); +} + +stock Regex:PCSingle_Regexp(const JSON:valueJson, const Regex:def = Regex:REGEX_PATTERN_FAIL, const orFailKey[] = "") { + return Regex:PCSingle_Cell(valueJson, DEFAULT_PARAMS_REGEXP_NAME, def, orFailKey); +} + // Getters stock any:PCGet_Cell(const Trie:p, const key[], const any:def = 0) { @@ -795,6 +868,10 @@ stock PCGet_iStr(const Trie:p, const key[], const def[] = "") { return str; } +stock Regex:PCGet_Regexp(const Trie:p, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL) { + return Regex:PCGet_Cell(p, key, def); +} + stock PCGet_iChatMessage(const Trie:p, const key[], const def[] = "") { new str[PARAM_CHAT_MESSAGE_MAX_LEN]; PCGet_Str(p, key, str, charsmax(str), def); From 90c734caac28908a44141d46197c43b190c8a780 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 11:14:37 +0300 Subject: [PATCH 11/30] Fix inc --- amxmodx/scripting/include/ParamsController.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 6bb7468..4d79fd2 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -5,6 +5,7 @@ #include #include +#include #define PARAM_TYPE_NAME_MAX_LEN 64 #define PARAM_KEY_MAX_LEN 64 From 2d6bb48fc794d61fd628e3158bfa2146e15afe5e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 11:49:17 +0300 Subject: [PATCH 12/30] Minor fixes in pcjson --- amxmodx/scripting/include/ParamsController.inc | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 4d79fd2..b3b2f98 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -972,11 +972,11 @@ stock PCPath_iMakePath(const path[], const absolutePrefix[] = "", const forceFil stock bool:PCPath_GetFileName(const path[], out[], const outLen) { new i = strlen(path) - 1; - if (PCPath_IsPathSeparator(path[i])) { + if (i < 0 || PCPath_IsPathSeparator(path[i])) { return false; } - while (!PCPath_IsPathSeparator(path[i - 1])) { + while (i > 0 && !PCPath_IsPathSeparator(path[i - 1])) { --i; } @@ -1103,10 +1103,12 @@ stock bool:PCJson_IsRoot(const JSON:value) { if (value == Invalid_JSON) { return false; } - + new JSON:parent = json_get_parent(value); new bool:ret = parent == Invalid_JSON; - json_free(parent); + if (parent != Invalid_JSON) { + json_free(parent); + } return ret; } @@ -1114,10 +1116,12 @@ stock bool:PCJson_IsWrappedRoot(const JSON:value) { if (value == Invalid_JSON) { return false; } - + new JSON:parent = json_get_parent(value); new bool:ret = PCJson_IsWrapper(parent); - json_free(parent); + if (parent != Invalid_JSON) { + json_free(parent); + } return ret; } @@ -1254,6 +1258,8 @@ stock bool:PCJson_HandleLinkedValue(const JSON:value, &JSON:linkedOrSameValue) { PCJson_GetWrapperWorkdir(wrapper, workdir, charsmax(workdir)); } + json_free(wrapper); + static path[PLATFORM_MAX_PATH]; PCPath_MakePath( linkPath, path, charsmax(path), From befb08ecaf0b21d32b4d780119ef19246a54e549 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 12:27:46 +0300 Subject: [PATCH 13/30] Impl `PCGet_RegexpMatch` func --- amxmodx/scripting/include/ParamsController.inc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index b3b2f98..32b1e7b 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -873,6 +873,15 @@ stock Regex:PCGet_Regexp(const Trie:p, const key[], const Regex:def = Regex:REGE return Regex:PCGet_Cell(p, key, def); } +stock bool:PCGet_RegexpMatch(const Trie:p, const key[], const string[]) { + new Regex:re = PCGet_Regexp(p, key); + if (re == Regex:REGEX_PATTERN_FAIL) { + return false; + } + + return regex_match_c(string, re) > 0; +} + stock PCGet_iChatMessage(const Trie:p, const key[], const def[] = "") { new str[PARAM_CHAT_MESSAGE_MAX_LEN]; PCGet_Str(p, key, str, charsmax(str), def); From 4bb086df746c7f136f466d1056701939d7f13c25 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 10 Apr 2026 17:44:56 +0300 Subject: [PATCH 14/30] Add PCParam-style for params list registering --- .../scripting/include/ParamsController.inc | 129 ++++++++++++++++-- 1 file changed, 114 insertions(+), 15 deletions(-) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 32b1e7b..fd5d1f1 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -35,7 +35,7 @@ stock const DEFAULT_PARAMS_WEEK_DAY_NAME[] = "WeekDay"; stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; -#define PARAMS_CONTROLLER_VERSION "1.3.3" +#define PARAMS_CONTROLLER_VERSION "1.4.0" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; @@ -228,6 +228,85 @@ native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj */ native bool:ParamsController_Param_Read(const T_Param:param, const JSON:valueJson, const Trie:dest, const orFailKey[] = ""); +// Маркеры формата PC-аргументов натива. +// Первая ячейка всегда '$', вторая определяет тип содержимого. +#define PCARG_MARKER '$' +#define PCARG_TYPE_PARAM 'P' // одиночный T_Param хендл +#define PCARG_TYPE_ARRAY 'A' // Array: хендл со списком T_Param + +/** + * Конструктор параметра для передачи через аргументы натива. + * Компактная альтернатива тройке ("ключ", "тип", required). + * + * @note Возвращаемое значение передаётся как ОДИН аргумент натива вместо трёх. + * Совместимо с ParamsController_Param_ListFromNativeParams — оба стиля можно + * смешивать в одном вызове. + * + * @param key Ключ параметра + * @param typeName Название типа параметра + * @param required Является ли параметр обязательным + * + * @return Закодированный хендлер параметра (строка-маркер для передачи в натив) + */ +stock PCParam(const key[], const typeName[], const bool:required = false) { + // Формат: [PCARG_MARKER, PCARG_TYPE_PARAM, handle + 1, EOS] + // +1 чтобы handle=0 не читался как EOS при get_string на стороне сервера. + new buf[4]; + buf[0] = PCARG_MARKER; + buf[1] = PCARG_TYPE_PARAM; + buf[2] = _:ParamsController_Param_Construct(key, typeName, required) + 1; + buf[3] = EOS; + return buf; +} + +/** + * Упаковка набора PCParam()-значений в один аргумент натива. + * + * @note Возвращаемое значение передаётся как ОДИН аргумент натива. + * Совместимо с ParamsController_Param_ListFromNativeParams. + * Временный массив уничтожается внутри ListFromNativeParams. + * + * @param ... Результаты вызовов PCParam() + * + * @return Закодированный хендлер массива параметров (строка-маркер для передачи в натив) + */ +stock PCParams(any:...) { + new Array:arr = ArrayCreate(1, 1); + + for (new i = 0, count = numargs(); i < count; i++) { + if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { + ArrayPushCell(arr, T_Param:(getarg(i, 2) - 1)); + } + } + + // Формат: [PCARG_MARKER, PCARG_TYPE_ARRAY, array_handle + 1, EOS] + new buf[4]; + buf[0] = PCARG_MARKER; + buf[1] = PCARG_TYPE_ARRAY; + buf[2] = _:arr + 1; + buf[3] = EOS; + return buf; +} + +/** + * Создание Array: с хендлерами параметров из набора PCParam()-значений. + * + * @param ... Результаты вызовов PCParam() + * + * @return Хендлер динамического массива с параметрами + */ +stock Array:PCParamsArray(any:...) { + new Array:result = ArrayCreate(1, 1); + + for (new i = 0, count = numargs(); i < count; i++) { + if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { + ArrayPushCell(result, T_Param:(getarg(i, 2) - 1)); + } + } + + return result; +} + /** * Создание параметра из параметров натива * @@ -248,35 +327,55 @@ stock T_Param:ParamsController_Param_FromNativeParams(const iParamOffset) { new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN]; get_string(iParamOffset + ParamArg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); - + new bool:bRequired = bool:get_param_byref(iParamOffset + ParamArg_bRequired); return ParamsController_Param_Construct(sKey, sParamTypeName, bRequired); } /** - * Создание списка параметров из параметров натива + * Создание списка параметров из параметров натива. * - * @note Должно вызываться только в рамках обработки вызова натива + * @note Должно вызываться только в рамках обработки вызова натива. * - * @param iParamOffset Номер параметра, с которого начинать читать - * @param iParamsCount Общее количество параметров, переданных в натив (передаётся в обработчик натива первым параметром) - * @param aAppend Массив, в который нужно дописывать созданные параметры (по умолчанию создастся новый) + * @note Поддерживает несколько стилей передачи параметров (можно смешивать): + * - Классический: три аргумента натива на параметр ("ключ", "тип", required) + * - PCParam(): один аргумент натива — одиночный параметр + * - PCParams(): один аргумент натива — массив параметров * - * @return Хендлер динамического массива с прочитанными параметрами (если был передан параметр aAppend, то вернётся его значение) + * @param iParamOffset Номер аргумента натива, с которого начинать читать + * @param iParamsCount Общее количество аргументов натива (первый параметр обработчика) + * @param aAppend Массив, в который дописывать параметры (по умолчанию создаётся новый) + * + * @return Хендлер динамического массива с прочитанными параметрами */ stock Array:ParamsController_Param_ListFromNativeParams(const iParamOffset, const iParamsCount, &Array:aAppend = Invalid_Array) { if (aAppend == Invalid_Array) { aAppend = ArrayCreate(1, 1); } - new iCount = (iParamsCount - iParamOffset + 1) / E_NativeParam; - if (iCount < 1) { - return aAppend; - } - - for (new i = 0; i < iCount; ++i) { - ArrayPushCell(aAppend, ParamsController_Param_FromNativeParams(iParamOffset + (i * E_NativeParam))); + new i = iParamOffset; + new val[4]; + while (i <= iParamsCount) { + get_string(i, val, charsmax(val)); + if (val[0] == PCARG_MARKER) { + if (val[1] == PCARG_TYPE_PARAM) { + // PCParam(): одиночный параметр + ArrayPushCell(aAppend, T_Param:(val[2] - 1)); + } else if (val[1] == PCARG_TYPE_ARRAY) { + // PCParams(): временный массив параметров + new Array:arr = Array:(val[2] - 1); + for (new j = 0, jj = ArraySize(arr); j < jj; j++) { + ArrayPushCell(aAppend, ArrayGetCell(arr, j)); + } + ArrayDestroy(arr); + } + i += 1; + } else { + // Классический стиль: тройка ("ключ", "тип", required) + ArrayPushCell(aAppend, ParamsController_Param_FromNativeParams(i)); + i += E_NativeParam; + } } return aAppend; From dacd504d3b39435b4e89698d3a15c642ab38128e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 18:19:54 +0300 Subject: [PATCH 15/30] Remove test plugin --- amxmodx/scripting/PHTest.sma | 159 ------------------ amxmodx/scripting/ParamsController.sma | 6 +- .../DefaultObjects/PlaceholderRegistrar.inc | 14 -- .../DefaultObjects/Registrar.inc | 12 +- 4 files changed, 13 insertions(+), 178 deletions(-) delete mode 100644 amxmodx/scripting/PHTest.sma delete mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc diff --git a/amxmodx/scripting/PHTest.sma b/amxmodx/scripting/PHTest.sma deleted file mode 100644 index 870045b..0000000 --- a/amxmodx/scripting/PHTest.sma +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include - -// Группа тест-плагина для игроков: {t:name}, {t:health} -static T_PHGroup:g_testGroup = Invalid_PHGroup; - -// Ссылка на встроенную группу игрока {p:authid} — кешируем, чтобы не искать по строке на каждый вызов -static T_PHGroup:g_defaultPlayerGroup = Invalid_PHGroup; - -// Скомпилированные шаблоны -static T_PHTemplate:g_templateHud = Invalid_PHTemplate; -static T_PHTemplate:g_templateGreet = Invalid_PHTemplate; - -public plugin_init() { - register_plugin("PH Test", "1.0", "Test"); - - register_clcmd("say /ph_test", "CmdInline"); - register_clcmd("say /ph_compiled", "CmdCompiled"); - register_clcmd("say /ph_context", "CmdContext"); - - ParamsController_Init(); -} - -// Компилируем шаблоны после того как все группы и ключи зарегистрированы -public ParamsController_OnInited() { - g_templateHud = PCPH_CompileTemplate( - "Map: {map} | Player: {t:name} | HP: {t:health} | Auth: {p:authid}" - ); - g_templateGreet = PCPH_CompileTemplate( - "Hi {t:name}! Time: {time} | Server: {server} | Unknown: {bad:key}" - ); -} - -// ===== Регистрация ===== - -public PCPH_OnRegisterGroups() { - // Кешируем встроенную группу игрока — к этому моменту она уже зарегистрирована - g_defaultPlayerGroup = PCPH_FindGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); - - // Глобальная группа: реактивный {time} и проактивный {server} - PCPH_Gl_RegKey("time", "PH_Time"); - PCPH_Gl_RegisterKey("server"); - - static hostname[64]; - get_user_name(0, hostname, charsmax(hostname)); - PCPH_Set(PCPH_GetGlobalGroup(), "server", hostname); - - // Группа тест-плагина: {t:name} — проактивный, {t:health} — реактивный - g_testGroup = PCPH_RegisterGroup("t"); - PCPH_Group_RegisterKey(g_testGroup, "name"); - PCPH_Group_RegKey(g_testGroup, "health", "PH_Health"); -} - -// ===== Колбеки плейсхолдеров ===== - -public PH_Time(const key[], const contextKey[]) { - static buf[16]; - format_time(buf, charsmax(buf), "%H:%M:%S"); - PCPH_Cb_Set(buf); -} - -// contextKey содержит индекс игрока, переданный через PushIntContext -public PH_Health(const key[], const contextKey[]) { - PCPH_Cb_SetInt(get_user_health(str_to_num(contextKey))); -} - -// ===== Обновление проактивных значений ===== - -public client_putinserver(playerIndex) { - UpdateName(playerIndex); -} - -public client_infochanged(playerIndex) { - UpdateName(playerIndex); -} - -UpdateName(playerIndex) { - static name[32]; - get_user_name(playerIndex, name, charsmax(name)); - PCPH_SetForInt(g_testGroup, "name", playerIndex, name); -} - -// ===== Хелперы для управления контекстом ===== - -PushPlayerContexts(playerIndex) { - PCPH_Group_PushIntContext(g_testGroup, playerIndex); - PCPH_Group_PushIntContext(g_defaultPlayerGroup, playerIndex); -} - -PopPlayerContexts() { - PCPH_Group_PopContext(g_defaultPlayerGroup); - PCPH_Group_PopContext(g_testGroup); -} - -// ===== Команды ===== - -// /ph_test — инлайн форматирование: глобальный контекст, контекст игрока, неизвестный ключ -public CmdInline(playerIndex) { - static out[512]; - - // Только глобальные плейсхолдеры — контекст игрока не нужен - PCPH_Format(out, charsmax(out), "Global: map={map} time={time} server={server}"); - client_print(playerIndex, print_chat, "[inline/global] %s", out); - - // Плейсхолдеры игрока — нужен контекст - PushPlayerContexts(playerIndex); - PCPH_Format(out, charsmax(out), "Player: name={t:name} hp={t:health} auth={p:authid}"); - client_print(playerIndex, print_chat, "[inline/player] %s", out); - PopPlayerContexts(); - - // Неизвестный плейсхолдер остаётся как есть в строке - PCPH_Format(out, charsmax(out), "known={map} unknown={bad:key}"); - client_print(playerIndex, print_chat, "[inline/unknown] %s", out); - - return PLUGIN_HANDLED; -} - -// /ph_compiled — скомпилированные шаблоны -public CmdCompiled(playerIndex) { - static out[512]; - - PushPlayerContexts(playerIndex); - - PCPH_FormatTemplate(g_templateHud, out, charsmax(out)); - client_print(playerIndex, print_chat, "[compiled/hud] %s", out); - - PCPH_FormatTemplate(g_templateGreet, out, charsmax(out)); - client_print(playerIndex, print_chat, "[compiled/greet] %s", out); - - PopPlayerContexts(); - - return PLUGIN_HANDLED; -} - -// /ph_context — демонстрация стека контекстов -// Показывает три уровня: текущий игрок → другой игрок → возврат к текущему -public CmdContext(playerIndex) { - static out[512]; - - // Уровень 1: контекст текущего игрока - PCPH_Group_PushIntContext(g_testGroup, playerIndex); - PCPH_Format(out, charsmax(out), "L1 (self): name={t:name} hp={t:health}"); - client_print(playerIndex, print_chat, "[ctx/L1] %s", out); - - // Уровень 2: поверх кладём другого игрока — стек g_testGroup теперь [playerIndex, other] - new other = (playerIndex == 1) ? 2 : 1; - PCPH_Group_PushIntContext(g_testGroup, other); - PCPH_Format(out, charsmax(out), "L2: name={t:name} hp={t:health}"); - client_print(playerIndex, print_chat, "[ctx/L2 #%d] %s", other, out); - - // Pop — стек возвращается к playerIndex - PCPH_Group_PopContext(g_testGroup); - PCPH_Format(out, charsmax(out), "L1 again (self): name={t:name} hp={t:health}"); - client_print(playerIndex, print_chat, "[ctx/back] %s", out); - - PCPH_Group_PopContext(g_testGroup); - - return PLUGIN_HANDLED; -} diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index cd34a84..2db8236 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -6,7 +6,6 @@ #include "ParamsController/Objects/Param" #include "ParamsController/Placeholders/Objects/PHGroup" #include "ParamsController/DefaultObjects/Registrar" -#include "ParamsController/DefaultObjects/PlaceholderRegistrar" public stock const PluginName[] = "Params Controller"; public stock const PluginVersion[] = PARAMS_CONTROLLER_VERSION; @@ -30,8 +29,7 @@ PluginInit() { Forwards_Init(); Param_Init(); PHGroup_Init(); - DefaultObjects_ParamType_Register(); - DefaultObjects_Placeholder_Register(); + DefaultObjects_Register(); // Тут регать типы параметров Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); @@ -50,7 +48,7 @@ public client_authorized(id, const sAuthID[]) { return; } - DefaultObjects_Placeholder_OnClientAuth(id, sAuthID); // id → playerIndex внутри + DefaultObjects_OnClientAuth(id, sAuthID); } @SrvCmd_Types() { diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc deleted file mode 100644 index 93b54cd..0000000 --- a/amxmodx/scripting/ParamsController/DefaultObjects/PlaceholderRegistrar.inc +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -#include "ParamsController/DefaultObjects/Placeholder/Global" -#include "ParamsController/DefaultObjects/Placeholder/Player" - -DefaultObjects_Placeholder_Register() { - DefaultObjects_PH_Global_Register(); - DefaultObjects_PH_Player_Register(); -} - -DefaultObjects_Placeholder_OnClientAuth(const playerIndex, const sAuthID[]) { - DefaultObjects_PH_Player_OnClientAuth(playerIndex, sAuthID); -} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index ddf5dea..e79a224 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -19,7 +19,10 @@ #include "ParamsController/DefaultObjects/ParamType/Flags" #include "ParamsController/DefaultObjects/ParamType/Regexp" -DefaultObjects_ParamType_Register() { +#include "ParamsController/DefaultObjects/Placeholder/Global" +#include "ParamsController/DefaultObjects/Placeholder/Player" + +DefaultObjects_Register() { DefaultObjects_ParamType_Boolean_Register(); DefaultObjects_ParamType_Integer_Register(); DefaultObjects_ParamType_Float_Register(); @@ -38,4 +41,11 @@ DefaultObjects_ParamType_Register() { DefaultObjects_ParamType_TimeInterval_Register(); DefaultObjects_ParamType_WeekDay_Register(); DefaultObjects_ParamType_Regexp_Register(); + + DefaultObjects_PH_Global_Register(); + DefaultObjects_PH_Player_Register(); +} + +DefaultObjects_OnClientAuth(const playerIndex, const sAuthID[]) { + DefaultObjects_PH_Player_OnClientAuth(playerIndex, sAuthID); } From 15ee6d29d77691f67e24255c65c4d4af752e409d Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 21:17:51 +0300 Subject: [PATCH 16/30] Impl default ph keys for global context --- PLACEHOLDERS.md | 2 +- amxmodx/scripting/ParamsController.sma | 20 +++++- .../DefaultObjects/Placeholder/Global.inc | 67 +++++++++++++++++-- .../DefaultObjects/Placeholder/Player.inc | 8 +-- .../DefaultObjects/Registrar.inc | 12 +++- .../scripting/include/ParamsController.inc | 18 ++++- 6 files changed, 112 insertions(+), 15 deletions(-) diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md index a73e462..f75b329 100644 --- a/PLACEHOLDERS.md +++ b/PLACEHOLDERS.md @@ -245,7 +245,7 @@ FormatForPlayer(id, out[], outLen) { | Плейсхолдер | Константы | Описание | |---|---|---| -| `{map}` | `DEFAULT_PH_GLOBAL_MAP_KEY` | Текущая карта (проактивный, устанавливается при инициализации) | +| `{real-map}` | `DEFAULT_PH_GLOBAL_REAL_MAP_KEY` | Текущая карта (проактивный, устанавливается при инициализации) | | `{p:authid}` | `DEFAULT_PH_PLAYER_GROUP_PREFIX`, `DEFAULT_PH_PLAYER_AUTHID_KEY` | AuthID игрока (проактивный, устанавливается при авторизации) | --- diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index 2db8236..fe68bbd 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -43,12 +43,28 @@ PluginInit() { Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); } -public client_authorized(id, const sAuthID[]) { +public client_authorized(playerIndex, const authId[]) { if (!IsPluginInited()) { return; } - DefaultObjects_OnClientAuth(id, sAuthID); + DefaultObjects_OnClientAuth(playerIndex, authId); +} + +public client_putinserver(playerIndex) { + if (!IsPluginInited()) { + return; + } + + DefaultObjects_OnClientPutInServer(playerIndex); +} + +public client_disconnected(playerIndex) { + if (!IsPluginInited()) { + return; + } + + DefaultObjects_OnClientDisconnected(playerIndex); } @SrvCmd_Types() { diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc index 2786f6a..592ab1d 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc @@ -1,12 +1,69 @@ #include +#include #include DefaultObjects_PH_Global_Register() { - new T_PHGroup:group = PCPH_GetGlobalGroup(); + new value[PH_VALUE_MAX_LEN]; + + rh_get_mapname(value, charsmax(value), MNT_SET); + PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_MAP_KEY, value); + + rh_get_mapname(value, charsmax(value), MNT_TRUE); + PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_REAL_MAP_KEY, value); + + get_user_name(0, value, charsmax(value)); + PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_SERVER_NAME_KEY, value); + + get_user_ip(0, value, charsmax(value)); + PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_SERVER_IP_KEY, value); + + PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_MAX_PLAYERS_COUNT_KEY, fmt("%d", get_maxplayers())); + + PCPH_Gl_RegisterKey(DEFAULT_PH_GLOBAL_DATE_KEY); + @DefaultObjects_PH_Global_UpdateDate(); + set_task(60.0, "@DefaultObjects_PH_Global_UpdateDate", .flags = "b"); + + PCPH_Gl_RegisterKey(DEFAULT_PH_GLOBAL_PLAYERS_COUNT_KEY); + DefaultObjects_PH_Global_UpdatePlayersCount(); - PCPH_Group_RegisterKey(group, DEFAULT_PH_GLOBAL_MAP_KEY); + PCPH_Gl_RegKey(DEFAULT_PH_GLOBAL_TIME_KEY, "@DefaultObjects_PH_Global_Callback_Time"); + PCPH_Gl_RegKey(DEFAULT_PH_GLOBAL_DATETIME_KEY, "@DefaultObjects_PH_Global_Callback_Datetime"); +} + +DefaultObjects_PH_Global_OnClientPutInServer(const playerIndex) { + #pragma unused playerIndex + DefaultObjects_PH_Global_UpdatePlayersCount(); +} + +DefaultObjects_PH_Global_OnClientDisconnected(const playerIndex) { + #pragma unused playerIndex + DefaultObjects_PH_Global_UpdatePlayersCount(); +} + +DefaultObjects_PH_Global_UpdatePlayersCount() { + PCPH_SetInt(PCPH_GetGlobalGroup(), DEFAULT_PH_GLOBAL_PLAYERS_COUNT_KEY, get_playersnum()); +} + +@DefaultObjects_PH_Global_UpdateDate() { + new year, month, day; + date(year, month, day); + + PCPH_Set(PCPH_GetGlobalGroup(), DEFAULT_PH_GLOBAL_DATE_KEY, fmt("%02d.%02d.%04d", day, month, year)); +} + +@DefaultObjects_PH_Global_Callback_Time() { + new hour, minute, second; + time(hour, minute, second); + + PCPH_Cb_Set(fmt("%d:%02d:%02d", hour, minute, second)); +} + +@DefaultObjects_PH_Global_Callback_Datetime() { + new hour, minute, second; + time(hour, minute, second); + + new year, month, day; + date(year, month, day); - static sMap[64]; - get_mapname(sMap, charsmax(sMap)); - PCPH_Set(group, DEFAULT_PH_GLOBAL_MAP_KEY, sMap); + PCPH_Cb_Set(fmt("%d:%02d:%02d %02d.%02d.%04d", hour, minute, second, day, month, year)); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc index a222f45..63999df 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -1,14 +1,14 @@ #include #include -static T_PHGroup:playerGroup = Invalid_PHGroup; +static T_PHGroup:PlayerGroup = Invalid_PHGroup; DefaultObjects_PH_Player_Register() { - playerGroup = PCPH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); + PlayerGroup = PCPH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); - PCPH_Group_RegisterKey(playerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); + PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); } DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const authId[]) { - PCPH_SetForInt(playerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index e79a224..439c98a 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -46,6 +46,14 @@ DefaultObjects_Register() { DefaultObjects_PH_Player_Register(); } -DefaultObjects_OnClientAuth(const playerIndex, const sAuthID[]) { - DefaultObjects_PH_Player_OnClientAuth(playerIndex, sAuthID); +DefaultObjects_OnClientAuth(const playerIndex, const authId[]) { + DefaultObjects_PH_Player_OnClientAuth(playerIndex, authId); +} + +DefaultObjects_OnClientPutInServer(const playerIndex) { + DefaultObjects_PH_Global_OnClientPutInServer(playerIndex); +} + +DefaultObjects_OnClientDisconnected(const playerIndex) { + DefaultObjects_PH_Global_OnClientDisconnected(playerIndex); } diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index fd5d1f1..7df3931 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1525,11 +1525,18 @@ stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { // --- Default placeholders --- +stock const DEFAULT_PH_GLOBAL_REAL_MAP_KEY[] = "real-map"; stock const DEFAULT_PH_GLOBAL_MAP_KEY[] = "map"; +stock const DEFAULT_PH_GLOBAL_SERVER_NAME_KEY[] = "server-name"; +stock const DEFAULT_PH_GLOBAL_SERVER_IP_KEY[] = "server-ip"; +stock const DEFAULT_PH_GLOBAL_DATE_KEY[] = "date"; +stock const DEFAULT_PH_GLOBAL_TIME_KEY[] = "time"; +stock const DEFAULT_PH_GLOBAL_DATETIME_KEY[] = "datetime"; +stock const DEFAULT_PH_GLOBAL_PLAYERS_COUNT_KEY[] = "players-count"; +stock const DEFAULT_PH_GLOBAL_MAX_PLAYERS_COUNT_KEY[] = "max-players-count"; stock const DEFAULT_PH_PLAYER_GROUP_PREFIX[] = "p"; stock const DEFAULT_PH_PLAYER_AUTHID_KEY[] = "authid"; -#define DEFAULT_PH_PLAYER_AUTHID_MAX_LEN 32 enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } @@ -1856,6 +1863,15 @@ stock PCPH_Gl_RegKey(const key[], const callback[]) { PCPH_Group_RegKey(PCPH_GetGlobalGroup(), key, callback); } +/** + * Регистрация ключа в глобальной группе с одновременной установкой значения. + */ +stock PCPH_Gl_ConstKey(const key[], const value[]) { + new T_PHGroup:group = PCPH_GetGlobalGroup(); + PCPH_Group_RegisterKey(group, key); + PCPH_Set(group, key, value); +} + // Cvars stock PCCvar_Const(const name[], const value[]) { From 93e9241b489676ddce0617712557790d0e7a2a0e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 3 May 2026 22:10:34 +0300 Subject: [PATCH 17/30] Impl default keys for player context --- .../DefaultObjects/Placeholder/Player.inc | 14 ++++++++++++++ amxmodx/scripting/include/ParamsController.inc | 3 +++ 2 files changed, 17 insertions(+) diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc index 63999df..f415b16 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -1,4 +1,5 @@ #include +#include #include static T_PHGroup:PlayerGroup = Invalid_PHGroup; @@ -7,8 +8,21 @@ DefaultObjects_PH_Player_Register() { PlayerGroup = PCPH_RegisterGroup(DEFAULT_PH_PLAYER_GROUP_PREFIX); PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY); + PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_NAME_KEY); + PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_IP_KEY); + PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY); } DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const authId[]) { PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); + + static value[PH_VALUE_MAX_LEN]; + + get_user_name(playerIndex, value, charsmax(value)); + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_NAME_KEY, playerIndex, value); + + get_user_ip(playerIndex, value, charsmax(value)); + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_IP_KEY, playerIndex, value); + + PCPH_SetIntForInt(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY, playerIndex, get_user_userid(playerIndex)); } diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 7df3931..b5104ee 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1537,6 +1537,9 @@ stock const DEFAULT_PH_GLOBAL_MAX_PLAYERS_COUNT_KEY[] = "max-players-count"; stock const DEFAULT_PH_PLAYER_GROUP_PREFIX[] = "p"; stock const DEFAULT_PH_PLAYER_AUTHID_KEY[] = "authid"; +stock const DEFAULT_PH_PLAYER_NAME_KEY[] = "name"; +stock const DEFAULT_PH_PLAYER_IP_KEY[] = "ip"; +stock const DEFAULT_PH_PLAYER_USERID_KEY[] = "userid"; enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } From 9f93509b837effad2585ab7c39c9a63516360cb3 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 4 May 2026 00:55:29 +0300 Subject: [PATCH 18/30] Impl proxy ph groups --- PLACEHOLDERS.md | 23 +++++++- amxmodx/scripting/ParamsController.sma | 3 + .../Placeholders/API/Group.inc | 28 +++++++++ .../Placeholders/Objects/PHGroup.inc | 59 +++++++++++++++++-- .../scripting/include/ParamsController.inc | 27 +++++++++ 5 files changed, 134 insertions(+), 6 deletions(-) diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md index f75b329..387ad6b 100644 --- a/PLACEHOLDERS.md +++ b/PLACEHOLDERS.md @@ -20,6 +20,14 @@ Перед вызовом `PCPH_Format` нужный контекст кладётся на стек группы через `PCPH_PushContext` / `PCPH_PushIntContext`, а после — снимается через `PCPH_PopContext`. +### Прокси-группа + +Прокси-группа — это группа с собственным префиксом и **независимым стеком контекстов**, которая разделяет ключи, колбеки и хранилище значений с группой-источником. + +Позволяет держать два независимых контекста одной логической группы одновременно. Типичный кейс — пункты меню, где каждый пункт соответствует игроку: `{p:name}` — тот, кто открыл меню, `{target:name}` — игрок в пункте меню, оба читают данные из группы `p`. + +--- + ### Режимы хранения значений | Режим | Когда использовать | Как обновлять | @@ -38,6 +46,7 @@ plugin_init / plugin_precache ├─ регистрирует встроенные плейсхолдеры ({map}, {p:authid}) ├─ ParamsController_OnRegisterTypes ← пользовательские типы ├─ PCPH_OnRegisterGroups ← пользовательские группы и ключи + ├─ PCPH_OnRegisterProxyGroups ← прокси-группы (все обычные группы уже доступны) └─ ParamsController_OnInited ← можно использовать API ``` @@ -47,7 +56,7 @@ plugin_init / plugin_precache ## API -### Форвард +### Форварды ```pawn forward PCPH_OnRegisterGroups(); @@ -55,6 +64,12 @@ forward PCPH_OnRegisterGroups(); Вызывается до `OnInited`. Здесь нужно регистрировать свои группы и ключи. +```pawn +forward PCPH_OnRegisterProxyGroups(); +``` + +Вызывается после `PCPH_OnRegisterGroups`, когда все обычные группы (встроенные и пользовательские) уже зарегистрированы. Здесь нужно регистрировать прокси-группы. + --- ### Группы @@ -71,6 +86,12 @@ native T_PHGroup:PCPH_FindGroup(const sPrefix[]); Ищет группу по префиксу. Возвращает `Invalid_PHGroup` если не найдена. +```pawn +native T_PHGroup:PCPH_RegisterProxyGroup(const sPrefix[], const T_PHGroup:hSourceGroup); +``` + +Регистрирует прокси-группу с уникальным префиксом. Прокси разделяет ключи, колбеки и хранилище значений с `hSourceGroup`, но имеет собственный стек контекстов. Регистрировать в `PCPH_OnRegisterProxyGroups`. Регистрировать ключи и колбеки — только на группе-источнике. + ```pawn native T_PHGroup:PCPH_GetGlobalGroup(); ``` diff --git a/amxmodx/scripting/ParamsController.sma b/amxmodx/scripting/ParamsController.sma index fe68bbd..35880a4 100644 --- a/amxmodx/scripting/ParamsController.sma +++ b/amxmodx/scripting/ParamsController.sma @@ -37,6 +37,9 @@ PluginInit() { // Тут регать группы и ключи плейсхолдеров Forwards_RegAndCall("PCPH_OnRegisterGroups", ET_IGNORE); + // Тут регать прокси-группы (все обычные группы уже зарегистрированы) + Forwards_RegAndCall("PCPH_OnRegisterProxyGroups", ET_IGNORE); + register_srvcmd("params_controller_types", "@SrvCmd_Types"); // После этого можно юзать типы и плейсхолдеры diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc index c06555f..49769d2 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc @@ -4,6 +4,7 @@ API_PH_Group_Register() { register_native("PCPH_RegisterGroup", "@API_PH_RegisterGroup"); + register_native("PCPH_RegisterProxyGroup", "@API_PH_RegisterProxyGroup"); register_native("PCPH_FindGroup", "@API_PH_FindGroup"); register_native("PCPH_GetGlobalGroup", "@API_PH_GetGlobalGroup"); register_native("PCPH_Group_PushContext", "@API_PH_PushContext"); @@ -31,6 +32,33 @@ T_PHGroup:@API_PH_RegisterGroup() { return PHGroup_Construct(prefix); } +T_PHGroup:@API_PH_RegisterProxyGroup() { + enum {Arg_Prefix = 1, Arg_SourceGroup} + + API_CheckPluginInited(); + + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, prefix, charsmax(prefix)); + + if (prefix[0] == EOS) { + abort(AMX_ERR_PARAMS, "PH proxy group prefix must not be empty."); + return Invalid_PHGroup; + } + + if (PHGroup_Find(prefix) != Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "PH group with prefix '%s' is already registered.", prefix); + return Invalid_PHGroup; + } + + new T_PHGroup:sourceGroup = T_PHGroup:get_param(Arg_SourceGroup); + if (sourceGroup == Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "Invalid source group handle for proxy PH group."); + return Invalid_PHGroup; + } + + return PHGroup_ConstructProxy(prefix, sourceGroup); +} + T_PHGroup:@API_PH_FindGroup() { enum {Arg_Prefix = 1} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index b6894c4..5c8e3cb 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -12,6 +12,7 @@ enum _:S_PHGroup { Trie:PHGroup_Values, // "ключ:контекст" → текущее значение (строка) Stack:PHGroup_ContextStack, // стек сохранённых предыдущих контекстов PHGroup_CurrentContext[PH_CONTEXT_KEY_MAX_LEN], // текущий активный контекст (вершина стека) + T_PHGroup:PHGroup_Source, // источник данных (Invalid_PHGroup для обычных групп) } static Array:PHGroups = Invalid_Array; @@ -42,6 +43,7 @@ T_PHGroup:PHGroup_Construct(const prefix[]) { groupData[PHGroup_Values] = TrieCreate(); groupData[PHGroup_ContextStack] = Invalid_Stack; groupData[PHGroup_CurrentContext][0] = EOS; + groupData[PHGroup_Source] = Invalid_PHGroup; new T_PHGroup:group = T_PHGroup:ArrayPushArray(PHGroups, groupData); @@ -55,6 +57,38 @@ T_PHGroup:PHGroup_Construct(const prefix[]) { return group; } +static T_PHGroup:PHGroup_DataGroup(const T_PHGroup:group) { + new groupData[S_PHGroup]; + PHGroup_Get(group, groupData); + if (groupData[PHGroup_Source] != Invalid_PHGroup) { + return groupData[PHGroup_Source]; + } + return group; +} + +T_PHGroup:PHGroup_ConstructProxy(const prefix[], const T_PHGroup:sourceGroup) { + new srcData[S_PHGroup]; + PHGroup_Get(sourceGroup, srcData); + + new groupData[S_PHGroup]; + copy(groupData[PHGroup_Prefix], charsmax(groupData[PHGroup_Prefix]), prefix); + groupData[PHGroup_Callbacks] = Invalid_Trie; + groupData[PHGroup_Values] = Invalid_Trie; + groupData[PHGroup_ContextStack] = Invalid_Stack; + groupData[PHGroup_CurrentContext][0] = EOS; + groupData[PHGroup_Source] = sourceGroup; + + new T_PHGroup:group = T_PHGroup:ArrayPushArray(PHGroups, groupData); + TrieSetCell(PHGroupsMap, prefix, group); + + log_amx("[INFO] PH proxy group registered: prefix='%s' -> source='%s'", + prefix, + srcData[PHGroup_Prefix][0] != EOS ? srcData[PHGroup_Prefix] : "(global)" + ); + + return group; +} + T_PHGroup:PHGroup_Find(const prefix[]) { new T_PHGroup:group = Invalid_PHGroup; TrieGetCell(PHGroupsMap, prefix, group); @@ -86,6 +120,11 @@ PHGroup_RegisterKey(const T_PHGroup:group, const key[]) { new groupData[S_PHGroup]; PHGroup_Get(group, groupData); + if (groupData[PHGroup_Source] != Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "Cannot register key on a proxy PH group."); + return; + } + if (!TrieKeyExists(groupData[PHGroup_Callbacks], key)) { TrieSetCell(groupData[PHGroup_Callbacks], key, INVALID_HANDLE); } @@ -95,6 +134,11 @@ PHGroup_SetKeyCallback(const T_PHGroup:group, const key[], const callback[], con new groupData[S_PHGroup]; PHGroup_Get(group, groupData); + if (groupData[PHGroup_Source] != Invalid_PHGroup) { + abort(AMX_ERR_PARAMS, "Cannot set key callback on a proxy PH group."); + return; + } + if (!TrieKeyExists(groupData[PHGroup_Callbacks], key)) { abort(AMX_ERR_PARAMS, "PH key '%s' is not registered. Call RegisterGroupKey first.", key); return; @@ -146,8 +190,9 @@ PHGroup_PopContext(const T_PHGroup:group) { } PHGroup_SetValue(const T_PHGroup:group, const key[], const contextKey[], const value[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); new groupData[S_PHGroup]; - PHGroup_Get(group, groupData); + PHGroup_Get(dataGroup, groupData); static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); @@ -156,8 +201,9 @@ PHGroup_SetValue(const T_PHGroup:group, const key[], const contextKey[], const v } static bool:PHGroup_GetValue(const T_PHGroup:group, const key[], const contextKey[], out[], const outLen) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); new groupData[S_PHGroup]; - PHGroup_Get(group, groupData); + PHGroup_Get(dataGroup, groupData); static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); @@ -166,14 +212,16 @@ static bool:PHGroup_GetValue(const T_PHGroup:group, const key[], const contextKe } bool:PHGroup_IsKeyRegistered(const T_PHGroup:group, const key[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); new groupData[S_PHGroup]; - PHGroup_Get(group, groupData); + PHGroup_Get(dataGroup, groupData); return bool:TrieKeyExists(groupData[PHGroup_Callbacks], key); } static PHGroup_CallCallback(const T_PHGroup:group, const key[], const contextKey[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); new groupData[S_PHGroup]; - PHGroup_Get(group, groupData); + PHGroup_Get(dataGroup, groupData); new fwd; if (!TrieGetCell(groupData[PHGroup_Callbacks], key, fwd) || fwd == INVALID_HANDLE) { @@ -287,8 +335,9 @@ PHGroup_FormatString(const src[], out[], const outLen) { } PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); new groupData[S_PHGroup]; - PHGroup_Get(group, groupData); + PHGroup_Get(dataGroup, groupData); new fwd = INVALID_HANDLE; TrieGetCell(groupData[PHGroup_Callbacks], key, fwd); diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index b5104ee..5677fc6 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -1551,6 +1551,14 @@ enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } */ forward PCPH_OnRegisterGroups(); +/** + * Вызывается после PCPH_OnRegisterGroups, когда все обычные группы уже зарегистрированы. + * Здесь следует регистрировать прокси-группы. + * + * @noreturn + */ +forward PCPH_OnRegisterProxyGroups(); + /** * Регистрация группы плейсхолдеров с указанным префиксом. * Плейсхолдеры этой группы в строках имеют вид {prefix:key}. @@ -1570,6 +1578,25 @@ native T_PHGroup:PCPH_RegisterGroup(const prefix[]); */ native T_PHGroup:PCPH_FindGroup(const prefix[]); +/** + * Регистрация прокси-группы плейсхолдеров. + * + * Прокси-группа имеет собственный стек контекстов, но разделяет ключи, + * колбеки и хранилище значений с группой-источником. + * Это позволяет держать два независимых контекста одной логической группы + * одновременно — например, два разных игрока в одной строке. + * + * @note Регистрировать в форварде PCPH_OnRegisterProxyGroups, + * когда все обычные группы уже доступны. + * @note Регистрировать ключи и колбеки следует на группе-источнике, а не на прокси. + * + * @param prefix Уникальный префикс прокси-группы + * @param sourceGroup Хендлер группы-источника + * + * @return Хендлер зарегистрированной прокси-группы + */ +native T_PHGroup:PCPH_RegisterProxyGroup(const prefix[], const T_PHGroup:sourceGroup); + /** * Получение хендлера глобальной группы. * Плейсхолдеры глобальной группы имеют вид {key} (без префикса). From a34aeb0bc5bfb53fd10f22c01ed7b41071483557 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 4 May 2026 01:08:45 +0300 Subject: [PATCH 19/30] Add `PH-Template` param type --- .../DefaultObjects/ParamType/PHTemplate.inc | 16 +++++++++++++++ .../DefaultObjects/Registrar.inc | 2 ++ .../scripting/include/ParamsController.inc | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc new file mode 100644 index 0000000..50871b4 --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc @@ -0,0 +1,16 @@ +#include +#include +#include + +DefaultObjects_ParamType_PHTemplate_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_PH_TEMPLATE_NAME, "@DefaultObjects_ParamType_PHTemplate_OnRead"); +} + +bool:@DefaultObjects_ParamType_PHTemplate_OnRead(const JSON:valueJson) { + static src[PARAM_VALUE_MAX_LEN]; + json_get_string(valueJson, src, charsmax(src)); + + new T_PHTemplate:tmpl = PCPH_CompileTemplate(src); + + return ParamsController_SetCell(tmpl); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index 439c98a..58249af 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -18,6 +18,7 @@ #include "ParamsController/DefaultObjects/ParamType/WeekDay" #include "ParamsController/DefaultObjects/ParamType/Flags" #include "ParamsController/DefaultObjects/ParamType/Regexp" +#include "ParamsController/DefaultObjects/ParamType/PHTemplate" #include "ParamsController/DefaultObjects/Placeholder/Global" #include "ParamsController/DefaultObjects/Placeholder/Player" @@ -41,6 +42,7 @@ DefaultObjects_Register() { DefaultObjects_ParamType_TimeInterval_Register(); DefaultObjects_ParamType_WeekDay_Register(); DefaultObjects_ParamType_Regexp_Register(); + DefaultObjects_ParamType_PHTemplate_Register(); DefaultObjects_PH_Global_Register(); DefaultObjects_PH_Player_Register(); diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 5677fc6..c83f13b 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -34,6 +34,7 @@ stock const DEFAULT_PARAMS_TIME_NAME[] = "Time"; stock const DEFAULT_PARAMS_WEEK_DAY_NAME[] = "WeekDay"; stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; +stock const DEFAULT_PARAMS_PH_TEMPLATE_NAME[] = "PH-Template"; #define PARAMS_CONTROLLER_VERSION "1.4.0" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; @@ -944,6 +945,25 @@ stock Regex:PCSingle_Regexp(const JSON:valueJson, const Regex:def = Regex:REGEX_ return Regex:PCSingle_Cell(valueJson, DEFAULT_PARAMS_REGEXP_NAME, def, orFailKey); } +/** + * Чтение параметра типа "PH-Template" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Хендлер скомпилированного шаблона. + */ +stock T_PHTemplate:PCSingle_ObjPHTemplate(const JSON:objectJson, const key[], const T_PHTemplate:def = Invalid_PHTemplate, const bool:dotNot = false, const bool:orFail = false) { + return T_PHTemplate:PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_PH_TEMPLATE_NAME, def, dotNot, orFail); +} + +stock T_PHTemplate:PCSingle_PHTemplate(const JSON:valueJson, const T_PHTemplate:def = Invalid_PHTemplate, const orFailKey[] = "") { + return T_PHTemplate:PCSingle_Cell(valueJson, DEFAULT_PARAMS_PH_TEMPLATE_NAME, def, orFailKey); +} + // Getters stock any:PCGet_Cell(const Trie:p, const key[], const any:def = 0) { From e0389f43e8b307b7e53e896eae2d0f2f116d131b Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 4 May 2026 21:47:44 +0300 Subject: [PATCH 20/30] Fix --- .../DefaultObjects/ParamType/ChatMessage.inc | 2 +- .../DefaultObjects/ParamType/Dir.inc | 2 +- .../DefaultObjects/ParamType/File.inc | 2 +- .../DefaultObjects/ParamType/Flags.inc | 2 +- .../DefaultObjects/ParamType/Model.inc | 2 +- .../DefaultObjects/ParamType/PHTemplate.inc | 11 ++--- .../DefaultObjects/ParamType/RGB.inc | 2 +- .../DefaultObjects/ParamType/Regexp.inc | 2 +- .../DefaultObjects/ParamType/Resource.inc | 2 +- .../DefaultObjects/ParamType/Sound.inc | 2 +- .../DefaultObjects/ParamType/Time.inc | 2 +- .../DefaultObjects/ParamType/TimeInterval.inc | 2 +- .../DefaultObjects/ParamType/WeekDay.inc | 2 +- .../DefaultObjects/Placeholder/Player.inc | 21 ++++++-- .../DefaultObjects/Registrar.inc | 2 + .../Placeholders/Objects/PHGroup.inc | 9 ++-- .../scripting/include/ParamsController.inc | 48 +++++++++---------- 17 files changed, 64 insertions(+), 51 deletions(-) diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc index 51e238f..a643808 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_ChatMessage_Register() { bool:@DefaultObjects_ParamType_ChatMessage_OnRead(const JSON:valueJson) { static str[PARAM_CHAT_MESSAGE_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); replace_all(str, charsmax(str), "^^1", "^1"); replace_all(str, charsmax(str), "^^3", "^3"); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc index d93a396..291f384 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_Dir_Register() { bool:@DefaultObjects_ParamType_Dir_OnRead(const JSON:valueJson, const Trie:p, const key[]) { static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); + PCSingle_String(valueJson, path, charsmax(path)); if (!dir_exists(path)) { PCJson_LogForFile(valueJson, "WARNING", "Dir '%s' not found.", path); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc index 3fcf94b..42808fa 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_File_Register() { bool:@DefaultObjects_ParamType_File_OnRead(const JSON:valueJson, const Trie:p, const key[]) { static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)) + PCSingle_String(valueJson, path, charsmax(path)) if (!file_exists(path)) { PCJson_LogForFile(valueJson, "WARNING", "File '%s' not found.", path); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc index dd7eed1..25e3eeb 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_Flags_Register() { bool:@DefaultObjects_ParamType_Flags_OnRead(const JSON:valueJson) { static str[32]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); return ParamsController_SetCell(read_flags(str)); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc index 2b8f7ab..7d528b3 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_Model_Register() { bool:@DefaultObjects_ParamType_Model_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); + PCSingle_String(valueJson, path, charsmax(path)); if (!file_exists(path)) { PCJson_LogForFile(valueJson, "WARNING", "Model '%s' not found.", path); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc index 50871b4..f16385a 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc @@ -7,10 +7,9 @@ DefaultObjects_ParamType_PHTemplate_Register() { } bool:@DefaultObjects_ParamType_PHTemplate_OnRead(const JSON:valueJson) { - static src[PARAM_VALUE_MAX_LEN]; - json_get_string(valueJson, src, charsmax(src)); - - new T_PHTemplate:tmpl = PCPH_CompileTemplate(src); - - return ParamsController_SetCell(tmpl); + return ParamsController_SetCell( + PCPH_CompileTemplate( + PCSingle_iString(valueJson) + ) + ); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc index 14037f5..8b9a692 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc @@ -38,7 +38,7 @@ bool:@DefaultObjects_ParamType_RGB_OnRead(const JSON:valueJson, const Trie:p, co } } else if (json_is_string(valueJson)) { static str[32], colorStr[3][8]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); if (contain(str, ",")) { explode_string(str, ",", colorStr, 3, charsmax(colorStr[])); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc index 5238828..4971357 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc @@ -9,7 +9,7 @@ DefaultObjects_ParamType_Regexp_Register() { bool:@DefaultObjects_ParamType_Regexp_OnRead(const JSON:valueJson) { static pattern[PARAM_VALUE_MAX_LEN]; - json_get_string(valueJson, pattern, charsmax(pattern)); + PCSingle_String(valueJson, pattern, charsmax(pattern)); static error[256]; new ret; diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc index 8cdb9e5..e7b5134 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_Resource_Register() { bool:@DefaultObjects_ParamType_Resource_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); + PCSingle_String(valueJson, path, charsmax(path)); if (!file_exists(path)) { PCJson_LogForFile(valueJson, "WARNING", "Precaching file '%s' not found.", path); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc index 9e046c2..fd351ad 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc @@ -8,7 +8,7 @@ DefaultObjects_ParamType_Sound_Register() { bool:@DefaultObjects_ParamType_Sound_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); + PCSingle_String(valueJson, path, charsmax(path)); static fullPath[PLATFORM_MAX_PATH]; formatex(fullPath, charsmax(fullPath), "sound/%s", path); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc index b95ada0..4da033c 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc @@ -14,7 +14,7 @@ bool:@DefaultObjects_ParamType_Time_OnRead(const JSON:valueJson) { } case JSONString: { static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); new parts[3][3]; if (explode_string(str, ":", parts, 3, 3) > 3) { diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc index 62a85bb..14cc305 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc @@ -19,7 +19,7 @@ bool:@DefaultObjects_ParamType_TimeInterval_OnRead(const JSON:valueJson) { } case JSONString: { static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); // https://dev-cs.ru/threads/222/page-3#post-33411 new t, k; diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc index 0402e4d..2fdd75c 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc @@ -12,7 +12,7 @@ DefaultObjects_ParamType_WeekDay_Register() { bool:@DefaultObjects_ParamType_WeekDay_OnRead(const JSON:valueJson) { static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); + PCSingle_String(valueJson, str, charsmax(str)); new res; if (TrieGetCell(WeekDaysMap, str, res)) { diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc index f415b16..a8f829f 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -13,16 +13,27 @@ DefaultObjects_PH_Player_Register() { PCPH_Group_RegisterKey(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY); } -DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const authId[]) { - PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); - +DefaultObjects_PH_Player_OnClientPutInServer(const playerIndex) { static value[PH_VALUE_MAX_LEN]; get_user_name(playerIndex, value, charsmax(value)); PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_NAME_KEY, playerIndex, value); + PCPH_SetIntForInt(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY, playerIndex, get_user_userid(playerIndex)); +} + +DefaultObjects_PH_Player_OnClientDisconnected(const playerIndex) { + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, ""); + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_NAME_KEY, playerIndex, ""); + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_IP_KEY, playerIndex, ""); + PCPH_SetIntForInt(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY, playerIndex, -1); +} + +DefaultObjects_PH_Player_OnClientAuth(const playerIndex, const authId[]) { + PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_AUTHID_KEY, playerIndex, authId); + + static value[PH_VALUE_MAX_LEN]; + get_user_ip(playerIndex, value, charsmax(value)); PCPH_SetForInt(PlayerGroup, DEFAULT_PH_PLAYER_IP_KEY, playerIndex, value); - - PCPH_SetIntForInt(PlayerGroup, DEFAULT_PH_PLAYER_USERID_KEY, playerIndex, get_user_userid(playerIndex)); } diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index 58249af..641b569 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -54,8 +54,10 @@ DefaultObjects_OnClientAuth(const playerIndex, const authId[]) { DefaultObjects_OnClientPutInServer(const playerIndex) { DefaultObjects_PH_Global_OnClientPutInServer(playerIndex); + DefaultObjects_PH_Player_OnClientPutInServer(playerIndex); } DefaultObjects_OnClientDisconnected(const playerIndex) { DefaultObjects_PH_Global_OnClientDisconnected(playerIndex); + DefaultObjects_PH_Player_OnClientDisconnected(playerIndex); } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 5c8e3cb..54e96a3 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -81,10 +81,11 @@ T_PHGroup:PHGroup_ConstructProxy(const prefix[], const T_PHGroup:sourceGroup) { new T_PHGroup:group = T_PHGroup:ArrayPushArray(PHGroups, groupData); TrieSetCell(PHGroupsMap, prefix, group); - log_amx("[INFO] PH proxy group registered: prefix='%s' -> source='%s'", - prefix, - srcData[PHGroup_Prefix][0] != EOS ? srcData[PHGroup_Prefix] : "(global)" - ); + if (srcData[PHGroup_Prefix][0] != EOS) { + log_amx("[INFO] PH proxy group registered: prefix='%s' -> source='%s'", prefix, srcData[PHGroup_Prefix]); + } else { + log_amx("[INFO] PH proxy group registered: prefix='%s' -> source='(global)'", prefix); + } return group; } diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index c83f13b..caf3887 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -46,6 +46,30 @@ stock const PARAMS_CONTROLLER_TYPE_TAG_DELIMITER[] = ":"; enum T_Param { Invalid_Param = INVALID_HANDLE } enum T_ParamType { Invalid_ParamType = INVALID_HANDLE } +stock const DEFAULT_PH_GLOBAL_REAL_MAP_KEY[] = "real-map"; +stock const DEFAULT_PH_GLOBAL_MAP_KEY[] = "map"; +stock const DEFAULT_PH_GLOBAL_SERVER_NAME_KEY[] = "server-name"; +stock const DEFAULT_PH_GLOBAL_SERVER_IP_KEY[] = "server-ip"; +stock const DEFAULT_PH_GLOBAL_DATE_KEY[] = "date"; +stock const DEFAULT_PH_GLOBAL_TIME_KEY[] = "time"; +stock const DEFAULT_PH_GLOBAL_DATETIME_KEY[] = "datetime"; +stock const DEFAULT_PH_GLOBAL_PLAYERS_COUNT_KEY[] = "players-count"; +stock const DEFAULT_PH_GLOBAL_MAX_PLAYERS_COUNT_KEY[] = "max-players-count"; + +stock const DEFAULT_PH_PLAYER_GROUP_PREFIX[] = "p"; +stock const DEFAULT_PH_PLAYER_AUTHID_KEY[] = "authid"; +stock const DEFAULT_PH_PLAYER_NAME_KEY[] = "name"; +stock const DEFAULT_PH_PLAYER_IP_KEY[] = "ip"; +stock const DEFAULT_PH_PLAYER_USERID_KEY[] = "userid"; + +enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } + +/** + * Тип хендлера скомпилированного шаблона плейсхолдеров. + * Создаётся через PCPH_CompileTemplate, уничтожается через PCPH_DestroyTemplate. + */ +enum T_PHTemplate { Invalid_PHTemplate = INVALID_HANDLE } + enum _:E_NativeParam { ParamArg_sKey, ParamArg_sParamTypeName, @@ -1545,24 +1569,6 @@ stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { // --- Default placeholders --- -stock const DEFAULT_PH_GLOBAL_REAL_MAP_KEY[] = "real-map"; -stock const DEFAULT_PH_GLOBAL_MAP_KEY[] = "map"; -stock const DEFAULT_PH_GLOBAL_SERVER_NAME_KEY[] = "server-name"; -stock const DEFAULT_PH_GLOBAL_SERVER_IP_KEY[] = "server-ip"; -stock const DEFAULT_PH_GLOBAL_DATE_KEY[] = "date"; -stock const DEFAULT_PH_GLOBAL_TIME_KEY[] = "time"; -stock const DEFAULT_PH_GLOBAL_DATETIME_KEY[] = "datetime"; -stock const DEFAULT_PH_GLOBAL_PLAYERS_COUNT_KEY[] = "players-count"; -stock const DEFAULT_PH_GLOBAL_MAX_PLAYERS_COUNT_KEY[] = "max-players-count"; - -stock const DEFAULT_PH_PLAYER_GROUP_PREFIX[] = "p"; -stock const DEFAULT_PH_PLAYER_AUTHID_KEY[] = "authid"; -stock const DEFAULT_PH_PLAYER_NAME_KEY[] = "name"; -stock const DEFAULT_PH_PLAYER_IP_KEY[] = "ip"; -stock const DEFAULT_PH_PLAYER_USERID_KEY[] = "userid"; - -enum T_PHGroup { Invalid_PHGroup = INVALID_HANDLE } - /** * Вызывается когда контроллер готов к регистрации групп и ключей плейсхолдеров. * Вызывается до форварда *_OnInited. @@ -1797,12 +1803,6 @@ stock PCPH_SetFloat(const T_PHGroup:group, const key[], const Float:value) { */ native PCPH_Format(out[], const outLen, const format[]); -/** - * Тип хендлера скомпилированного шаблона плейсхолдеров. - * Создаётся через PCPH_CompileTemplate, уничтожается через PCPH_DestroyTemplate. - */ -enum T_PHTemplate { Invalid_PHTemplate = INVALID_HANDLE } - /** * Компилирует строку с плейсхолдерами в предварительно разобранный шаблон. * Кеширует хендлеры групп и форвардов — при каждом вызове PCPH_FormatTemplate From 8368599e73c723e34bde9a8c3c8e182b665dd8f3 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 17 May 2026 00:27:55 +0300 Subject: [PATCH 21/30] Add `PlayerModel` type --- .../DefaultObjects/ParamType/PlayerModel.inc | 23 +++++++++++++ .../DefaultObjects/Registrar.inc | 2 ++ .../scripting/include/ParamsController.inc | 34 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc new file mode 100644 index 0000000..c520f45 --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc @@ -0,0 +1,23 @@ +#include +#include +#include + +DefaultObjects_ParamType_PlayerModel_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_PLAYER_MODEL_NAME, "@DefaultObjects_ParamType_PlayerModel_OnRead"); +} + +bool:@DefaultObjects_ParamType_PlayerModel_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { + static modelName[PARAM_SHORT_STRING_MAX_LEN]; + PCSingle_ShortString(valueJson, modelName, charsmax(modelName)); + + static path[PLATFORM_MAX_PATH]; + formatex(path, charsmax(path), "models/player/%s/%s.mdl", modelName, modelName); + + if (!file_exists(path)) { + return PCJson_LogForFile(valueJson, "WARNING", "Player model '%s' (%s) not found.", modelName, path); + } + + precache_model(path); + + return ParamsController_SetString(path); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index 641b569..230e87b 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -8,6 +8,7 @@ #include "ParamsController/DefaultObjects/ParamType/String" #include "ParamsController/DefaultObjects/ParamType/RGB" #include "ParamsController/DefaultObjects/ParamType/Model" +#include "ParamsController/DefaultObjects/ParamType/PlayerModel" #include "ParamsController/DefaultObjects/ParamType/Sound" #include "ParamsController/DefaultObjects/ParamType/Resource" #include "ParamsController/DefaultObjects/ParamType/File" @@ -31,6 +32,7 @@ DefaultObjects_Register() { DefaultObjects_ParamType_RGB_Register(); DefaultObjects_ParamType_Model_Register(); + DefaultObjects_ParamType_PlayerModel_Register(); DefaultObjects_ParamType_Sound_Register(); DefaultObjects_ParamType_Resource_Register(); DefaultObjects_ParamType_File_Register(); diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index caf3887..fd6f841 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -24,6 +24,7 @@ stock const DEFAULT_PARAMS_SHORT_STR_NAME[] = "ShortString"; stock const DEFAULT_PARAMS_LONG_STR_NAME[] = "LongString"; stock const DEFAULT_PARAMS_RGB_NAME[] = "RGB"; stock const DEFAULT_PARAMS_MODEL_NAME[] = "Model"; +stock const DEFAULT_PARAMS_PLAYER_MODEL_NAME[] = "PlayerModel"; stock const DEFAULT_PARAMS_SOUND_NAME[] = "Sound"; stock const DEFAULT_PARAMS_RESOURCE_NAME[] = "Resource"; stock const DEFAULT_PARAMS_FILE_NAME[] = "File"; @@ -785,6 +786,39 @@ stock PCSingle_iModel(const JSON:valueJson, const def[] = "", const orFailKey[] return out; } +/** + * Чтение параметра типа "PlayerModel" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjPlayerModel(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjPlayerModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_PlayerModel(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iPlayerModel(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, orFailKey); + return out; +} + /** * Чтение параметра типа "Sound" из JSON-обьекта. * From 8d2af507b99ecf7d6b357ca66d9ea023eb3819e1 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 17 May 2026 00:38:30 +0300 Subject: [PATCH 22/30] Bump version --- amxmodx/scripting/include/ParamsController.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index fd6f841..199b598 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -37,7 +37,7 @@ stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; stock const DEFAULT_PARAMS_PH_TEMPLATE_NAME[] = "PH-Template"; -#define PARAMS_CONTROLLER_VERSION "1.4.0" +#define PARAMS_CONTROLLER_VERSION "1.4.1" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; From b621b71d8b6228920bc0eae6bd34bc9f2b725d91 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 17 May 2026 02:02:07 +0300 Subject: [PATCH 23/30] Fix player model type --- .../DefaultObjects/ParamType/PlayerModel.inc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc index c520f45..7981f12 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc @@ -12,12 +12,18 @@ bool:@DefaultObjects_ParamType_PlayerModel_OnRead(const JSON:valueJson, const Tr static path[PLATFORM_MAX_PATH]; formatex(path, charsmax(path), "models/player/%s/%s.mdl", modelName, modelName); - + if (!file_exists(path)) { return PCJson_LogForFile(valueJson, "WARNING", "Player model '%s' (%s) not found.", modelName, path); } - + precache_model(path); - return ParamsController_SetString(path); + static pathT[PLATFORM_MAX_PATH]; + formatex(pathT, charsmax(pathT), "models/player/%s/%sT.mdl", modelName, modelName); + if (file_exists(pathT)) { + precache_model(pathT); + } + + return ParamsController_SetString(modelName); } From 6fe40640a30fc202c231e0ee9b47450c9f2b2e18 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Thu, 21 May 2026 17:01:47 +0300 Subject: [PATCH 24/30] Add missing PCSingle_* helpers --- .../scripting/include/ParamsController.inc | 103 +- build/amxmodx/scripting/ParamsController.sma | 57 + .../ParamsController/API/General.inc | 33 + .../scripting/ParamsController/API/Param.inc | 104 + .../ParamsController/API/ParamType.inc | 30 + .../scripting/ParamsController/API/Utils.inc | 18 + .../DefaultObjects/ParamType/Boolean.inc | 48 + .../DefaultObjects/ParamType/ChatMessage.inc | 18 + .../DefaultObjects/ParamType/Dir.inc | 19 + .../DefaultObjects/ParamType/File.inc | 19 + .../DefaultObjects/ParamType/Flags.inc | 14 + .../DefaultObjects/ParamType/Float.inc | 27 + .../DefaultObjects/ParamType/Integer.inc | 27 + .../DefaultObjects/ParamType/Model.inc | 24 + .../DefaultObjects/ParamType/PlayerModel.inc | 29 + .../DefaultObjects/ParamType/RGB.inc | 64 + .../DefaultObjects/ParamType/Regexp.inc | 24 + .../DefaultObjects/ParamType/Resource.inc | 24 + .../DefaultObjects/ParamType/Sound.inc | 27 + .../DefaultObjects/ParamType/String.inc | 30 + .../DefaultObjects/ParamType/Time.inc | 35 + .../DefaultObjects/ParamType/TimeInterval.inc | 63 + .../DefaultObjects/ParamType/WeekDay.inc | 44 + .../DefaultObjects/Registrar.inc | 43 + .../scripting/ParamsController/Forwards.inc | 71 + .../ParamsController/Objects/Param.inc | 157 ++ .../ParamsController/Objects/ParamType.inc | 163 ++ .../scripting/include/ParamsController.inc | 1671 +++++++++++++++++ 28 files changed, 2985 insertions(+), 1 deletion(-) create mode 100644 build/amxmodx/scripting/ParamsController.sma create mode 100644 build/amxmodx/scripting/ParamsController/API/General.inc create mode 100644 build/amxmodx/scripting/ParamsController/API/Param.inc create mode 100644 build/amxmodx/scripting/ParamsController/API/ParamType.inc create mode 100644 build/amxmodx/scripting/ParamsController/API/Utils.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc create mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc create mode 100644 build/amxmodx/scripting/ParamsController/Forwards.inc create mode 100644 build/amxmodx/scripting/ParamsController/Objects/Param.inc create mode 100644 build/amxmodx/scripting/ParamsController/Objects/ParamType.inc create mode 100644 build/amxmodx/scripting/include/ParamsController.inc diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 199b598..188fd2d 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -37,7 +37,7 @@ stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; stock const DEFAULT_PARAMS_PH_TEMPLATE_NAME[] = "PH-Template"; -#define PARAMS_CONTROLLER_VERSION "1.4.1" +#define PARAMS_CONTROLLER_VERSION "1.4.2" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; @@ -1003,6 +1003,31 @@ stock Regex:PCSingle_Regexp(const JSON:valueJson, const Regex:def = Regex:REGEX_ return Regex:PCSingle_Cell(valueJson, DEFAULT_PARAMS_REGEXP_NAME, def, orFailKey); } +/** + * Чтение параметра типа "RGB" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер [3] для записи компонент цвета (R, G, B). + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + */ +stock PCSingle_ObjRGB(const JSON:objectJson, const key[], out[3], const bool:dotNot = false, const bool:orFail = false) { + new Trie:p = PCSingle_ReadFromObject(objectJson, key, DEFAULT_PARAMS_RGB_NAME, dotNot, orFail); + if (p != Invalid_Trie) { + TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); + TrieDestroy(p); + } +} + +stock PCSingle_RGB(const JSON:valueJson, out[3], const orFailKey[] = "") { + new Trie:p = PCSingle_Read(valueJson, DEFAULT_PARAMS_RGB_NAME, orFailKey); + if (p != Invalid_Trie) { + TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); + TrieDestroy(p); + } +} + /** * Чтение параметра типа "PH-Template" из JSON-обьекта. * @@ -1022,6 +1047,82 @@ stock T_PHTemplate:PCSingle_PHTemplate(const JSON:valueJson, const T_PHTemplate: return T_PHTemplate:PCSingle_Cell(valueJson, DEFAULT_PARAMS_PH_TEMPLATE_NAME, def, orFailKey); } +/** + * Чтение параметра типа "TimeInterval" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение в секундах. + */ +stock PCSingle_ObjTimeInterval(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, dotNot, orFail); +} + +stock PCSingle_TimeInterval(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Time" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (секунды от начала суток). + */ +stock PCSingle_ObjTime(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_NAME, def, dotNot, orFail); +} + +stock PCSingle_Time(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "WeekDay" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (0=Вс, 1=Пн, ..., 6=Сб). + */ +stock PCSingle_ObjWeekDay(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_WEEK_DAY_NAME, def, dotNot, orFail); +} + +stock PCSingle_WeekDay(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_WEEK_DAY_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Flags" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (битовая маска флагов). + */ +stock PCSingle_ObjFlags(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_FLAGS_NAME, def, dotNot, orFail); +} + +stock PCSingle_Flags(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_FLAGS_NAME, def, orFailKey); +} + // Getters stock any:PCGet_Cell(const Trie:p, const key[], const any:def = 0) { diff --git a/build/amxmodx/scripting/ParamsController.sma b/build/amxmodx/scripting/ParamsController.sma new file mode 100644 index 0000000..bf960f1 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController.sma @@ -0,0 +1,57 @@ +#include +#include + +#include "ParamsController/Forwards" + +#include "ParamsController/Objects/Param" +#include "ParamsController/DefaultObjects/Registrar" + +public stock const PluginName[] = "Params Controller"; +public stock const PluginVersion[] = PARAMS_CONTROLLER_VERSION; +public stock const PluginAuthor[] = "ArKaNeMaN"; +public stock const PluginURL[] = "https://github.com/AmxxModularEcosystem/ParamsController"; + +static bool:PluginInited = false; + +public plugin_precache() { + register_plugin(PluginName, PluginVersion, PluginAuthor); + register_library(PARAMS_CONTROLLER_LIBRARY); + PCCvar_Const(PARAMS_CONTROLLER_VERSION_CVAR_NAME, PluginVersion); +} + +PluginInit() { + if (PluginInited) { + return; + } + PluginInited = true; + + Forwards_Init(); + Param_Init(); + DefaultObjects_ParamType_Register(); + + // Тут регать типы + Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); + + register_srvcmd("params_controller_types", "@SrvCmd_Types"); + + // После этого можно юзать типы + Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); +} + +@SrvCmd_Types() { + ParamType_PrintListToConsole(); +} + +bool:IsPluginInited() { + return PluginInited; +} + +#include "ParamsController/API/General" +#include "ParamsController/API/Param" +#include "ParamsController/API/ParamType" + +public plugin_natives() { + API_General_Register(); + API_Param_Register(); + API_ParamType_Register(); +} diff --git a/build/amxmodx/scripting/ParamsController/API/General.inc b/build/amxmodx/scripting/ParamsController/API/General.inc new file mode 100644 index 0000000..4c2d39c --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/API/General.inc @@ -0,0 +1,33 @@ +#include +#include "ParamsController/API/Utils" + +API_General_Register() { + register_native("ParamsController_Init", "@API_Init"); + register_native("ParamsController_SetCell", "@API_SetCell"); + register_native("ParamsController_SetString", "@API_SetString"); +} + +@API_Init() { + PluginInit(); +} + +@API_SetCell() { + enum {Arg_Cell = 1} + + API_CheckPluginInited(); + API_CheckCurrentParams(); + + return TrieSetCell(g_tCurrentParams, g_sCurrentParamName, get_param(Arg_Cell)); +} + +@API_SetString() { + enum {Arg_String = 1} + + API_CheckPluginInited(); + API_CheckCurrentParams(); + + static sString[PARAM_LONG_STRING_MAX_LEN]; + get_string(Arg_String, sString, charsmax(sString)); + + return TrieSetString(g_tCurrentParams, g_sCurrentParamName, sString); +} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/Param.inc b/build/amxmodx/scripting/ParamsController/API/Param.inc new file mode 100644 index 0000000..e99cae3 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/API/Param.inc @@ -0,0 +1,104 @@ +#include +#include "ParamsController/API/Utils" + +API_Param_Register() { + register_native("ParamsController_Param_Construct", "@API_Param_Construct"); + register_native("ParamsController_Param_ReadList", "@API_Param_ReadList"); + register_native("ParamsController_Param_Read", "@API_Param_Read"); + register_native("ParamsController_Param_Free", "@API_Param_Free"); +} + +T_Param:@API_Param_Construct() { + enum {Arg_sKey = 1, Arg_sParamTypeName, Arg_bRequired} + + API_CheckPluginInited(); + + new sKey[PARAM_KEY_MAX_LEN]; + get_string(Arg_sKey, sKey, charsmax(sKey)); + + new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN + PARAM_TYPE_TAG_MAX_LEN + 1]; + get_string(Arg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); + + new bool:bRequired = !!get_param(Arg_bRequired); + + new sParamType[PARAM_TYPE_NAME_MAX_LEN], sParamTag[PARAM_TYPE_TAG_MAX_LEN]; + ParamType_GetTypeAndTag(sParamTypeName, + sParamType, charsmax(sParamType), + sParamTag, charsmax(sParamTag) + ); + new T_ParamType:iParamType = ParamType_Find(sParamType, .orFail = false); + + if (iParamType == Invalid_ParamType) { + if (bRequired) { + log_error(AMX_ERR_PARAMS, "Param '%s' is required, but type '%s' is not registered.", sKey, sParamType); + } + + return Param_Construct(sKey, iParamType, bRequired, sParamTypeName); + } + + return Param_Construct(sKey, iParamType, bRequired, sParamTag); +} + +// native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj, &Trie:tAppend = Invalid_Trie, &E_ParamsReadErrorType:iErrType, sErrParamName[], const iErrParamNameLen = 0); +Trie:@API_Param_ReadList() { + enum {Arg_aParams = 1, Arg_jObj, Arg_tAppend, Arg_iErrType, Arg_sErrParamName, Arg_iErrParamNameLen} + + API_CheckPluginInited(); + + new Array:aParams = Array:get_param(Arg_aParams); + new JSON:jObj = JSON:get_param(Arg_jObj); + new Trie:tAppend = Trie:get_param_byref(Arg_tAppend); + new E_ParamsReadErrorType:iErrType = E_ParamsReadErrorType:get_param_byref(Arg_iErrType); + new sErrParamName[PARAM_KEY_MAX_LEN]; + get_string(Arg_sErrParamName, sErrParamName, charsmax(sErrParamName)); + new iErrParamNameLen = get_param(Arg_iErrParamNameLen); + + if (!json_is_object(jObj)) { + log_error(AMX_ERR_PARAMS, "[ERROR] Param jObj must be an object. (%d)", jObj); + return Invalid_Trie; + } + + new Trie:tRet = Param_ReadList(aParams, jObj, tAppend, iErrType, sErrParamName, iErrParamNameLen); + + set_param_byref(Arg_iErrType, _:iErrType); + set_string(Arg_sErrParamName, sErrParamName, iErrParamNameLen); + + return tRet; +} + +bool:@API_Param_Read() { + enum {Arg_Param = 1, Arg_ValueJson, Arg_Dest, Arg_OrFailKey} + + API_CheckPluginInited(); + + new T_Param:param = T_Param:get_param(Arg_Param); + new JSON:valueJson = JSON:get_param(Arg_ValueJson); + new Trie:dest = Trie:get_param(Arg_Dest); + new orFailKey[MAX_FMT_LENGTH]; + get_string(Arg_OrFailKey, orFailKey, charsmax(orFailKey)); + + if (valueJson == Invalid_JSON) { + // log_error(AMX_ERR_PARAMS, "[ERROR] Param valueJson must be a valid json handler. (%d)", valueJson); + return false; + } + + new bool:ret = Param_Read(param, valueJson, dest); + + if (orFailKey[0] != EOS && !ret) { + PCJson_ErrorForFile(valueJson, "Field '%s' has invalid value for '%s' param type.", orFailKey, Param_iGetType(param)); + return false; + } + + return ret; +} + +T_Param:@API_Param_Free() { + enum {Arg_Param = 1} + + new T_Param:param = T_Param:get_param_byref(Arg_Param); + + Param_Free(param); + + set_param_byref(Arg_Param, _:Invalid_Param); + return Invalid_Param; +} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/ParamType.inc b/build/amxmodx/scripting/ParamsController/API/ParamType.inc new file mode 100644 index 0000000..3abf86a --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/API/ParamType.inc @@ -0,0 +1,30 @@ +#include +#include "ParamsController/API/Utils" + +API_ParamType_Register() { + register_native("ParamsController_ParamType_Register", "@API_ParamType_Register"); + register_native("ParamsController_ParamType_SetReadCallback", "@API_ParamType_SetReadCallback"); +} + +T_ParamType:@API_ParamType_Register() { + enum {Arg_Name = 1} + + API_CheckPluginInited(); + + new name[PARAM_TYPE_NAME_MAX_LEN]; + get_string(Arg_Name, name, charsmax(name)); + + return ParamType_Construct(name); +} + +@API_ParamType_SetReadCallback(const pluginIndex) { + enum {Arg_Type = 1, Arg_FunctionName} + + API_CheckPluginInited(); + + new T_ParamType:type = T_ParamType:get_param(Arg_Type); + new functionName[PARAM_TYPE_READ_CALLBACK_MAX_LEN]; + get_string(Arg_FunctionName, functionName, charsmax(functionName)); + + ParamType_SetReadCallback(type, functionName, pluginIndex); +} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/Utils.inc b/build/amxmodx/scripting/ParamsController/API/Utils.inc new file mode 100644 index 0000000..7eab564 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/API/Utils.inc @@ -0,0 +1,18 @@ +#if defined __pc_core__api_utils_included + #endinput +#endif +#define __pc_core__api_utils_included + +#include + +stock API_CheckPluginInited() { + if (!IsPluginInited()) { + abort(AMX_ERR_PARAMS, "Attempt to interact with params before init them."); + } +} + +stock API_CheckCurrentParams() { + if (g_tCurrentParams == Invalid_Trie) { + abort(AMX_ERR_PARAMS, "Attempt to set param value outside the read callback."); + } +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc new file mode 100644 index 0000000..3577ab8 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc @@ -0,0 +1,48 @@ +#include +#include +#include + +DefaultObjects_ParamType_Boolean_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_BOOL_NAME, "@DefaultObjects_ParamType_Boolean_OnRead"); +} + +bool:@DefaultObjects_ParamType_Boolean_OnRead(const JSON:valueJson) { + switch (json_get_type(valueJson)) { + case JSONNumber: { + return ParamsController_SetCell(json_get_number(valueJson) != 0); + } + case JSONString: { + static str[16]; + json_get_string(valueJson, str, charsmax(str)); + + if ( + equali(str, "yes") + || equali(str, "true") + || equali(str, "on") + || equali(str, "1") + || equali(str, "y") + ) { + return ParamsController_SetCell(true); + } + + if ( + equali(str, "no") + || equali(str, "false") + || equali(str, "off") + || equali(str, "0") + || equali(str, "n") + ) { + return ParamsController_SetCell(false); + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid string boolean value (%s).", str); + return false; + } + case JSONBoolean: { + return ParamsController_SetCell(json_get_bool(valueJson)); + } + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid boolean value."); + return false; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc new file mode 100644 index 0000000..51e238f --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc @@ -0,0 +1,18 @@ +#include +#include +#include + +DefaultObjects_ParamType_ChatMessage_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_CHAT_MESSAGE_NAME, "@DefaultObjects_ParamType_ChatMessage_OnRead"); +} + +bool:@DefaultObjects_ParamType_ChatMessage_OnRead(const JSON:valueJson) { + static str[PARAM_CHAT_MESSAGE_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + replace_all(str, charsmax(str), "^^1", "^1"); + replace_all(str, charsmax(str), "^^3", "^3"); + replace_all(str, charsmax(str), "^^4", "^4"); + + return ParamsController_SetString(str); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc new file mode 100644 index 0000000..d93a396 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc @@ -0,0 +1,19 @@ +#include +#include +#include + +DefaultObjects_ParamType_Dir_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_DIR_NAME, "@DefaultObjects_ParamType_Dir_OnRead"); +} + +bool:@DefaultObjects_ParamType_Dir_OnRead(const JSON:valueJson, const Trie:p, const key[]) { + static path[PLATFORM_MAX_PATH]; + json_get_string(valueJson, path, charsmax(path)); + + if (!dir_exists(path)) { + PCJson_LogForFile(valueJson, "WARNING", "Dir '%s' not found.", path); + return false; + } + + return ParamsController_SetString(path); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc new file mode 100644 index 0000000..3fcf94b --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc @@ -0,0 +1,19 @@ +#include +#include +#include + +DefaultObjects_ParamType_File_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_FILE_NAME, "@DefaultObjects_ParamType_File_OnRead"); +} + +bool:@DefaultObjects_ParamType_File_OnRead(const JSON:valueJson, const Trie:p, const key[]) { + static path[PLATFORM_MAX_PATH]; + json_get_string(valueJson, path, charsmax(path)) + + if (!file_exists(path)) { + PCJson_LogForFile(valueJson, "WARNING", "File '%s' not found.", path); + return false; + } + + return ParamsController_SetString(path); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc new file mode 100644 index 0000000..dd7eed1 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc @@ -0,0 +1,14 @@ +#include +#include +#include + +DefaultObjects_ParamType_Flags_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_FLAGS_NAME, "@DefaultObjects_ParamType_Flags_OnRead"); +} + +bool:@DefaultObjects_ParamType_Flags_OnRead(const JSON:valueJson) { + static str[32]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetCell(read_flags(str)); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc new file mode 100644 index 0000000..db0fe57 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc @@ -0,0 +1,27 @@ +#include +#include +#include + +DefaultObjects_ParamType_Float_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_FLOAT_NAME, "@DefaultObjects_ParamType_Float_OnRead"); +} + +bool:@DefaultObjects_ParamType_Float_OnRead(const JSON:valueJson) { + switch (json_get_type(valueJson)) { + case JSONNumber: { + return ParamsController_SetCell(json_get_real(valueJson)); + } + case JSONString: { + new str[16]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetCell(str_to_float(str)); + } + case JSONBoolean: { + return ParamsController_SetCell(json_get_bool(valueJson) ? 1 : 0); + } + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid float (real) value."); + return false; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc new file mode 100644 index 0000000..341e0e2 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc @@ -0,0 +1,27 @@ +#include +#include +#include + +DefaultObjects_ParamType_Integer_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_INT_NAME, "@DefaultObjects_ParamType_Integer_OnRead"); +} + +bool:@DefaultObjects_ParamType_Integer_OnRead(const JSON:valueJson) { + switch (json_get_type(valueJson)) { + case JSONNumber: { + return ParamsController_SetCell(json_get_number(valueJson)); + } + case JSONString: { + new str[16]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetCell(str_to_num(str)); + } + case JSONBoolean: { + return ParamsController_SetCell(json_get_bool(valueJson) ? 1 : 0); + } + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid integer value."); + return false; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc new file mode 100644 index 0000000..2b8f7ab --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc @@ -0,0 +1,24 @@ +#include +#include +#include + +DefaultObjects_ParamType_Model_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_MODEL_NAME, "@DefaultObjects_ParamType_Model_OnRead"); +} + +bool:@DefaultObjects_ParamType_Model_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { + static path[PLATFORM_MAX_PATH]; + json_get_string(valueJson, path, charsmax(path)); + + if (!file_exists(path)) { + PCJson_LogForFile(valueJson, "WARNING", "Model '%s' not found.", path); + return false; + } + + new index = precache_model(path); + if (equali(tag, "index")) { + return ParamsController_SetCell(index); + } + + return ParamsController_SetString(path); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc new file mode 100644 index 0000000..7981f12 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc @@ -0,0 +1,29 @@ +#include +#include +#include + +DefaultObjects_ParamType_PlayerModel_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_PLAYER_MODEL_NAME, "@DefaultObjects_ParamType_PlayerModel_OnRead"); +} + +bool:@DefaultObjects_ParamType_PlayerModel_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { + static modelName[PARAM_SHORT_STRING_MAX_LEN]; + PCSingle_ShortString(valueJson, modelName, charsmax(modelName)); + + static path[PLATFORM_MAX_PATH]; + formatex(path, charsmax(path), "models/player/%s/%s.mdl", modelName, modelName); + + if (!file_exists(path)) { + return PCJson_LogForFile(valueJson, "WARNING", "Player model '%s' (%s) not found.", modelName, path); + } + + precache_model(path); + + static pathT[PLATFORM_MAX_PATH]; + formatex(pathT, charsmax(pathT), "models/player/%s/%sT.mdl", modelName, modelName); + if (file_exists(pathT)) { + precache_model(pathT); + } + + return ParamsController_SetString(modelName); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc new file mode 100644 index 0000000..14037f5 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc @@ -0,0 +1,64 @@ +#include +#include +#include + +DefaultObjects_ParamType_RGB_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_RGB_NAME, "@DefaultObjects_ParamType_RGB_OnRead"); +} + +bool:@DefaultObjects_ParamType_RGB_OnRead(const JSON:valueJson, const Trie:p, const key[]) { + new color[3] = {0, ...}; + if (json_is_array(valueJson)) { + for (new i = 0, ii = min(json_array_get_count(valueJson), 3); i < ii; ++i) { + color[i] = json_array_get_number(valueJson, i); + } + } else if (json_is_object(valueJson)) { + if (json_object_has_value(valueJson, "R")) { + color[0] = json_object_get_number(valueJson, "R"); + } else if (json_object_has_value(valueJson, "Red")) { + color[0] = json_object_get_number(valueJson, "Red"); + } else { + color[0] = 0; + } + + if (json_object_has_value(valueJson, "G")) { + color[1] = json_object_get_number(valueJson, "G"); + } else if (json_object_has_value(valueJson, "Green")) { + color[1] = json_object_get_number(valueJson, "Green"); + } else { + color[1] = 0; + } + + if (json_object_has_value(valueJson, "B")) { + color[2] = json_object_get_number(valueJson, "B"); + } else if (json_object_has_value(valueJson, "Blue")) { + color[2] = json_object_get_number(valueJson, "Blue"); + } else { + color[2] = 0; + } + } else if (json_is_string(valueJson)) { + static str[32], colorStr[3][8]; + json_get_string(valueJson, str, charsmax(str)); + + if (contain(str, ",")) { + explode_string(str, ",", colorStr, 3, charsmax(colorStr[])); + for (new i = 0; i < 3; ++i) { + color[i] = str_to_num(colorStr[i]); + } + } else if (contain(str, " ")) { + explode_string(str, " ", colorStr, 3, charsmax(colorStr[])); + for (new i = 0; i < 3; ++i) { + color[i] = str_to_num(colorStr[i]); + } + } else { + PCJson_LogForFile(valueJson, "WARNING", "RGB value must be an array, object or string. String format is 'RRR,GGG,BBB' or 'RRR GGG BBB'."); + return false; + } + } else { + PCJson_LogForFile(valueJson, "WARNING", "RGB value must be an array, object or string."); + return false; + } + + TrieSetArray(p, key, color, 3); + return true; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc new file mode 100644 index 0000000..5238828 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +DefaultObjects_ParamType_Regexp_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_REGEXP_NAME, "@DefaultObjects_ParamType_Regexp_OnRead"); +} + +bool:@DefaultObjects_ParamType_Regexp_OnRead(const JSON:valueJson) { + static pattern[PARAM_VALUE_MAX_LEN]; + json_get_string(valueJson, pattern, charsmax(pattern)); + + static error[256]; + new ret; + new Regex:re = regex_compile(pattern, ret, error, charsmax(error)); + + if (re == REGEX_PATTERN_FAIL) { + PCJson_LogForFile(valueJson, "WARNING", "Invalid regexp pattern '%s': %s.", pattern, error); + return false; + } + + return ParamsController_SetCell(re); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc new file mode 100644 index 0000000..8cdb9e5 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc @@ -0,0 +1,24 @@ +#include +#include +#include + +DefaultObjects_ParamType_Resource_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_RESOURCE_NAME, "@DefaultObjects_ParamType_Resource_OnRead"); +} + +bool:@DefaultObjects_ParamType_Resource_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { + static path[PLATFORM_MAX_PATH]; + json_get_string(valueJson, path, charsmax(path)); + + if (!file_exists(path)) { + PCJson_LogForFile(valueJson, "WARNING", "Precaching file '%s' not found.", path); + return false; + } + + new index = precache_generic(path); + if (equali(tag, "index")) { + return ParamsController_SetCell(index); + } + + return ParamsController_SetString(path); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc new file mode 100644 index 0000000..9e046c2 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc @@ -0,0 +1,27 @@ +#include +#include +#include + +DefaultObjects_ParamType_Sound_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_SOUND_NAME, "@DefaultObjects_ParamType_Sound_OnRead"); +} + +bool:@DefaultObjects_ParamType_Sound_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { + static path[PLATFORM_MAX_PATH]; + json_get_string(valueJson, path, charsmax(path)); + + static fullPath[PLATFORM_MAX_PATH]; + formatex(fullPath, charsmax(fullPath), "sound/%s", path); + + if (!file_exists(fullPath)) { + PCJson_LogForFile(valueJson, "WARNING", "Sound '%s' not found.", fullPath); + return false; + } + + new index = precache_sound(path); + if (equali(tag, "index")) { + return ParamsController_SetCell(index); + } + + return ParamsController_SetString(path); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc new file mode 100644 index 0000000..afaa0da --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc @@ -0,0 +1,30 @@ +#include +#include +#include + +DefaultObjects_ParamType_String_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_SHORT_STR_NAME, "@DefaultObjects_ParamType_ShortString_OnRead"); + ParamsController_RegSimpleType(DEFAULT_PARAMS_STR_NAME, "@DefaultObjects_ParamType_String_OnRead"); + ParamsController_RegSimpleType(DEFAULT_PARAMS_LONG_STR_NAME, "@DefaultObjects_ParamType_LongString_OnRead"); +} + +bool:@DefaultObjects_ParamType_ShortString_OnRead(const JSON:valueJson) { + static str[PARAM_SHORT_STRING_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetString(str); +} + +bool:@DefaultObjects_ParamType_String_OnRead(const JSON:valueJson) { + static str[PARAM_VALUE_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetString(str); +} + +bool:@DefaultObjects_ParamType_LongString_OnRead(const JSON:valueJson) { + static str[PARAM_LONG_STRING_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + return ParamsController_SetString(str); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc new file mode 100644 index 0000000..b95ada0 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc @@ -0,0 +1,35 @@ +#include +#include +#include + +DefaultObjects_ParamType_Time_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_TIME_NAME, "@DefaultObjects_ParamType_Time_OnRead"); +} + +bool:@DefaultObjects_ParamType_Time_OnRead(const JSON:valueJson) { + switch (json_get_type(valueJson)) { + case JSONNumber: { + PCJson_LogForFile(valueJson, "WARNING", "Not recommended time format. Use string (^"HH[:MM[:SS]]^") instead numeric."); + return ParamsController_SetCell(json_get_number(valueJson)); + } + case JSONString: { + static str[PARAM_SHORT_STRING_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + new parts[3][3]; + if (explode_string(str, ":", parts, 3, 3) > 3) { + PCJson_LogForFile(valueJson, "WARNING", "Invalid time format. Use ^"HH[:MM[:SS]]^"."); + return false; + } + + new h = clamp(str_to_num(parts[0]), 0, 23) * 60 * 60; + new m = clamp(str_to_num(parts[1]), 0, 59) * 60; + new s = clamp(str_to_num(parts[2]), 0, 59); + + return ParamsController_SetCell(h + m + s); + } + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid time value. Expected string (^"HH[:MM[:SS]]^")."); + return false; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc new file mode 100644 index 0000000..62a85bb --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc @@ -0,0 +1,63 @@ +#include +#include +#include + +DefaultObjects_ParamType_TimeInterval_Register() { + ParamsController_RegSimpleType(DEFAULT_PARAMS_TIME_INTERVAL_NAME, "@DefaultObjects_ParamType_TimeInterval_OnRead"); +} + +bool:@DefaultObjects_ParamType_TimeInterval_OnRead(const JSON:valueJson) { + switch (json_get_type(valueJson)) { + case JSONNumber: { + new num = json_get_number(valueJson); + if (num < 0) { + PCJson_LogForFile(valueJson, "WARNING", "Invalid numeric time interval value. Expected >= 0."); + return false; + } + + return ParamsController_SetCell(num); + } + case JSONString: { + static str[PARAM_SHORT_STRING_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + // https://dev-cs.ru/threads/222/page-3#post-33411 + new t, k; + for (new i; str[i] != EOS; i++) { + switch (str[i]) { + case '0'..'9': { + t = (t * 10) + (str[i] - '0'); + } + case 'i': { + k += t * 60; + t = 0; + } + case 'h': { + k += t * 60 * 60; + t = 0; + } + case 'd': { + k += t * 60 * 60 * 24; + t = 0; + } + case 'm': { + k += t * 60 * 60 * 24 * 30; + t = 0; + } + case 'y': { + k += t * 60 * 60 * 24 * 30 * 12; + t = 0; + } + default: { + break; + } + } + } + + return ParamsController_SetCell(k + t); + } + } + + PCJson_LogForFile(valueJson, "WARNING", "Invalid time value. Expected string (^"1y2m3d4h5i6^")."); + return false; +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc new file mode 100644 index 0000000..0402e4d --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc @@ -0,0 +1,44 @@ +#include +#include +#include + +static Trie:WeekDaysMap = Invalid_Trie; + +DefaultObjects_ParamType_WeekDay_Register() { + WeekDaysMap = TrieCreate(); + + ParamsController_RegSimpleType(DEFAULT_PARAMS_WEEK_DAY_NAME, "@DefaultObjects_ParamType_WeekDay_OnRead"); +} + +bool:@DefaultObjects_ParamType_WeekDay_OnRead(const JSON:valueJson) { + static str[PARAM_SHORT_STRING_MAX_LEN]; + json_get_string(valueJson, str, charsmax(str)); + + new res; + if (TrieGetCell(WeekDaysMap, str, res)) { + return ParamsController_SetCell(res); + } + + if (equali("Вс", str) || equali("Воскресенье", str) || equali("Sun", str) || equali("Sunday", str)) { + res = 0; + } else if (equali("Пн", str) || equali("Понедельник", str) || equali("Mon", str) || equali("Monday", str)) { + res = 1; + } else if (equali("Вт", str) || equali("Вторник", str) || equali("Tue", str) || equali("Tuesday", str)) { + res = 2; + } else if (equali("Ср", str) || equali("Среда", str) || equali("Wed", str) || equali("Wednesday", str)) { + res = 3; + } else if (equali("Чт", str) || equali("Четверг", str) || equali("Thu", str) || equali("Thursday", str)) { + res = 4; + } else if (equali("Пт", str) || equali("Пятница", str) || equali("Fri", str) || equali("Friday", str)) { + res = 5; + } else if (equali("Сб", str) || equali("Суббота", str) || equali("Sat", str) || equali("Saturday", str)) { + res = 6; + } else { + PCJson_LogForFile(valueJson, "WARNING", "Week day '%s' not found.", str); + return false; + } + + TrieSetCell(WeekDaysMap, str, res); + + return ParamsController_SetCell(res); +} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc new file mode 100644 index 0000000..10d5210 --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "ParamsController/DefaultObjects/ParamType/Boolean" +#include "ParamsController/DefaultObjects/ParamType/Integer" +#include "ParamsController/DefaultObjects/ParamType/Float" +#include "ParamsController/DefaultObjects/ParamType/String" +#include "ParamsController/DefaultObjects/ParamType/RGB" +#include "ParamsController/DefaultObjects/ParamType/Model" +#include "ParamsController/DefaultObjects/ParamType/PlayerModel" +#include "ParamsController/DefaultObjects/ParamType/Sound" +#include "ParamsController/DefaultObjects/ParamType/Resource" +#include "ParamsController/DefaultObjects/ParamType/File" +#include "ParamsController/DefaultObjects/ParamType/Dir" +#include "ParamsController/DefaultObjects/ParamType/ChatMessage" +#include "ParamsController/DefaultObjects/ParamType/Time" +#include "ParamsController/DefaultObjects/ParamType/TimeInterval" +#include "ParamsController/DefaultObjects/ParamType/WeekDay" +#include "ParamsController/DefaultObjects/ParamType/Flags" +#include "ParamsController/DefaultObjects/ParamType/Regexp" + +DefaultObjects_ParamType_Register() { + DefaultObjects_ParamType_Boolean_Register(); + DefaultObjects_ParamType_Integer_Register(); + DefaultObjects_ParamType_Float_Register(); + DefaultObjects_ParamType_String_Register(); + + DefaultObjects_ParamType_RGB_Register(); + DefaultObjects_ParamType_Model_Register(); + DefaultObjects_ParamType_PlayerModel_Register(); + DefaultObjects_ParamType_Sound_Register(); + DefaultObjects_ParamType_Resource_Register(); + DefaultObjects_ParamType_File_Register(); + DefaultObjects_ParamType_Dir_Register(); + DefaultObjects_ParamType_ChatMessage_Register(); + DefaultObjects_ParamType_Flags_Register(); + + DefaultObjects_ParamType_Time_Register(); + DefaultObjects_ParamType_TimeInterval_Register(); + DefaultObjects_ParamType_WeekDay_Register(); + DefaultObjects_ParamType_Regexp_Register(); +} diff --git a/build/amxmodx/scripting/ParamsController/Forwards.inc b/build/amxmodx/scripting/ParamsController/Forwards.inc new file mode 100644 index 0000000..c64728b --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/Forwards.inc @@ -0,0 +1,71 @@ +#if defined __UTILS_forwards_included + #endinput +#endif +#define __UTILS_forwards_included + +/* +Author: ArKaNeMaN +Tg: t.me/arkaneman + +Functions & Macroses: + Forwards_Init(); + Forwards_Reg(const sName[], const iStopType, const ...?param_types); + Forwards_DefaultReturn(const iDefaultReturnValue); + Forwards_Call(const sName[]); + Forwards_CallP(const sName[], ...param_values); + Forwards_GetHandler(const sName[]); + Forwards_RegAndCallP(const sName[], const iStopType, [...param_types], [...param_values]); + Forwards_RegAndCall(const sName[], const iStopType); +*/ + +#include + +stock Trie:Forwards_gMap = Invalid_Trie; + +stock Forwards_Init() { + Forwards_gMap = TrieCreate(); +} + +// Forwards_Reg(const sName[], const iStopType, const ...?param_types); +#define Forwards_Reg(%1,%2) \ + TrieSetCell(Forwards_gMap, %1, CreateMultiForward(%1, %2), false) + +stock __Forwards_Call_ret; + +// Forwards_DefaultReturn(const iDefaultReturnValue); +#define Forwards_DefaultReturn(%1) \ + __Forwards_Call_ret = %1 + +// Forwards_CallP(const sName[], ...param_values); +#define Forwards_CallP(%1,%2) \ + (ExecuteForward(Forwards_GetHandler(%1), __Forwards_Call_ret, %2) ? __Forwards_Call_ret : __Forwards_Call_ret) + +// Forwards_Call(const sName[]); +#define Forwards_Call(%1) \ + (ExecuteForward(Forwards_GetHandler(%1), __Forwards_Call_ret) ? __Forwards_Call_ret : __Forwards_Call_ret) + +stock Forwards_GetHandler(const sName[]) { + new iHandler; + TrieGetCell(Forwards_gMap, sName, iHandler); + return iHandler; +} + +stock __Forwards_RegAndCall_handler; + +// Forwards_RegAndCallP(const sName[], const iStopType, [...param_types], [...param_values]); +#define Forwards_RegAndCallP(%1,%2,[%3],[%4]) \ + (DestroyForward( \ + ExecuteForward( \ + __Forwards_RegAndCall_handler = CreateMultiForward(%1, %2, %3), \ + __Forwards_Call_ret, %4 \ + ) ? __Forwards_RegAndCall_handler : __Forwards_RegAndCall_handler \ + ) ? __Forwards_Call_ret : __Forwards_Call_ret) + +// Forwards_RegAndCall(const sName[], const iStopType); +#define Forwards_RegAndCall(%1,%2) \ + (DestroyForward( \ + ExecuteForward( \ + __Forwards_RegAndCall_handler = CreateMultiForward(%1, %2), \ + __Forwards_Call_ret \ + ) ? __Forwards_RegAndCall_handler : __Forwards_RegAndCall_handler \ + ) ? __Forwards_Call_ret : __Forwards_Call_ret) diff --git a/build/amxmodx/scripting/ParamsController/Objects/Param.inc b/build/amxmodx/scripting/ParamsController/Objects/Param.inc new file mode 100644 index 0000000..1930fcc --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/Objects/Param.inc @@ -0,0 +1,157 @@ +#if defined __params_controller_src_param_included + #endinput +#endif +#define __params_controller_src_param_included + +#include +#include +#include +#include "ParamsController/Objects/ParamType" + +enum _:S_Param { + Param_Key[PARAM_KEY_MAX_LEN], + T_ParamType:Param_Type, + bool:Param_Required, + Param_Tag[PARAM_TYPE_TAG_MAX_LEN], + bool:Param_IsUnknownType, +} + +static Array:Params = Invalid_Array; +static Stack:FreeParamHandlers = Invalid_Stack; + +Param_Init() { + ParamType_Init(); + + if (Params == Invalid_Array) { + Params = ArrayCreate(S_Param, 1); + } + + if (FreeParamHandlers == Invalid_Stack) { + FreeParamHandlers = CreateStack(); + } +} + +T_Param:Param_Construct( + const key[], + const T_ParamType:type, + const bool:isRequired = true, + const tag[] = "" +) { + new paramObject[S_Param]; + + copy(paramObject[Param_Key], charsmax(paramObject[Param_Key]), key); + paramObject[Param_Type] = type; + paramObject[Param_Required] = isRequired; + copy(paramObject[Param_Tag], charsmax(paramObject[Param_Tag]), tag); + paramObject[Param_IsUnknownType] = (type == Invalid_ParamType); + + if (IsStackEmpty(FreeParamHandlers)) { + return T_Param:ArrayPushArray(Params, paramObject); + } else { + new T_Param:param; + PopStackCell(FreeParamHandlers, param); + ArraySetArray(Params, _:param, paramObject); + return param; + } +} + +static Param_Get(const T_Param:param, paramObject[S_Param]) { + ArrayGetArray(Params, _:param, paramObject); + if (paramObject[Param_Type] == Invalid_ParamType && !paramObject[Param_IsUnknownType]) { + abort(AMX_ERR_PARAMS, "Invalid param type handler (%d).", paramObject[Param_Type]); + } +} + +Param_iGetType(const T_Param:param) { + new paramObject[S_Param]; + Param_Get(param, paramObject); + + new typeName[PARAM_TYPE_NAME_MAX_LEN]; + if (paramObject[Param_IsUnknownType]) { + copy(typeName, charsmax(typeName), paramObject[Param_Tag]); + } else { + ParamType_GetName(paramObject[Param_Type], typeName, charsmax(typeName)); + } + + return typeName; +} + +T_Param:Param_Free(&T_Param:param) { + ArraySetCell(Params, _:param, Invalid_ParamType, Param_Type); + ArraySetCell(Params, _:param, false, Param_IsUnknownType); + PushStackCell(FreeParamHandlers, param); + return param = Invalid_Param; +} + +bool:Param_Read(const T_Param:param, const JSON:valueJson, const Trie:p) { + new paramObject[S_Param]; + Param_Get(param, paramObject); + + if (paramObject[Param_IsUnknownType]) { + return false; + } + + return ParamType_Read(paramObject[Param_Type], valueJson, p, paramObject[Param_Key], paramObject[Param_Tag]); +} + +Trie:Param_ReadList( + const Array:params, + const JSON:objectJson, + &Trie:p = Invalid_Trie, + &E_ParamsReadErrorType:iErrType = ParamsReadError_None, + sErrParamName[] = "", + const iErrParamNameLen = 0 +) { + if (p == Invalid_Trie) { + p = TrieCreate(); + } + + if (params == Invalid_Array) { + return p; + } + + for (new i = 0, ii = ArraySize(params); i < ii; ++i) { + new T_Param:param = ArrayGetCell(params, i); + + new paramObject[S_Param]; + Param_Get(param, paramObject); + + if (iErrParamNameLen > 0) { + copy(sErrParamName, iErrParamNameLen, paramObject[Param_Key]); + } + + if (paramObject[Param_IsUnknownType]) { + if (paramObject[Param_Required]) { + iErrType = ParamsReadError_UnknownParamType; + return p; + } + + PCJson_LogForFile(objectJson, "WARNING", "Param '%s' has unknown type '%s', skipping.", paramObject[Param_Key], paramObject[Param_Tag]); + continue; + } + + if (!json_object_has_value(objectJson, paramObject[Param_Key])) { + if (paramObject[Param_Required]) { + iErrType = ParamsReadError_RequiredParamNotPresented; + return p; + } + + continue; + } + + new JSON:valueJson = json_object_get_value(objectJson, paramObject[Param_Key]); + new bool:ret = Param_Read(param, valueJson, p); + json_free(valueJson); + + if (!ret) { + iErrType = ParamsReadError_ParamValueIsInvalid; + return p; + } + + if (!TrieKeyExists(p, paramObject[Param_Key])) { + abort(AMX_ERR_GENERAL, "Param type '%s' dont write value with key '%s' to trie.", ParamType_iGetName(paramObject[Param_Type]), paramObject[Param_Key]); + } + } + + return p; +} diff --git a/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc b/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc new file mode 100644 index 0000000..7b48eae --- /dev/null +++ b/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc @@ -0,0 +1,163 @@ +#if defined __params_controller_src_param_type_included + #endinput +#endif +#define __params_controller_src_param_type_included + +#include +#include + +enum _:S_ParamType { + ParamType_Name[PARAM_TYPE_NAME_MAX_LEN], + ParamType_ReadCallback, +} + +static Array:ParamTypes = Invalid_Array; +static Trie:ParamTypesMap = Invalid_Trie; + +ParamType_Init() { + if (ParamTypes == Invalid_Array) { + ParamTypes = ArrayCreate(S_ParamType, 1); + } + + if (ParamTypesMap == Invalid_Trie) { + ParamTypesMap = TrieCreate(); + } +} + +T_ParamType:ParamType_Construct(const name[]) { + new typeObject[S_ParamType]; + + copy(typeObject[ParamType_Name], charsmax(typeObject[ParamType_Name]), name); + typeObject[ParamType_ReadCallback] = INVALID_HANDLE; + + new T_ParamType:type = T_ParamType:ArrayPushArray(ParamTypes, typeObject); + TrieSetCell(ParamTypesMap, name, type); + + return type; +} + +T_ParamType:ParamType_Find(const name[], const bool:orFail = false) { + new T_ParamType:type = Invalid_ParamType; + TrieGetCell(ParamTypesMap, name, type); + + if (orFail && type == Invalid_ParamType) { + abort(AMX_ERR_PARAMS, "Param type '%s' not found.", name); + } + + return type; +} + +static ParamType_Get(const T_ParamType:type, typeObject[S_ParamType]) { + ArrayGetArray(ParamTypes, _:type, typeObject); +} + +static ParamType_Update(const typeObject[S_ParamType]) { + new T_ParamType:type = ParamType_Find(typeObject[ParamType_Name], .orFail = true); + ArraySetArray(ParamTypes, _:type, typeObject); +} + +ParamType_iGetName(const T_ParamType:type) { + new typeObject[S_ParamType]; + ParamType_Get(type, typeObject); + + return typeObject[ParamType_Name]; +} + +ParamType_GetName(const T_ParamType:type, out[], const outLen) { + new typeObject[S_ParamType]; + ParamType_Get(type, typeObject); + + return copy(out, outLen, typeObject[ParamType_Name]); +} + +ParamType_SetReadCallback(const T_ParamType:type, const functionName[], const pluginIndex) { + new typeObject[S_ParamType]; + ParamType_Get(type, typeObject); + + // bool:(const JSON:valueJson, const Trie:p, const key[], const tag[]) + typeObject[ParamType_ReadCallback] = CreateOneForward(pluginIndex, functionName, FP_CELL, FP_CELL, FP_STRING, FP_STRING); + if (typeObject[ParamType_ReadCallback] == INVALID_HANDLE) { + abort(AMX_ERR_GENERAL, "Can't create callback by '%s' func of #%d plugin.", functionName, pluginIndex); + return; + } + + ParamType_Update(typeObject); +} + +enum _:S_CurrentParam { + Trie:CurrentParam_Params, + CurrentParam_Name[PARAM_KEY_MAX_LEN], +} +new Stack:g_stCurrentParams = Invalid_Stack; +new Trie:g_tCurrentParams = Invalid_Trie; +new g_sCurrentParamName[PARAM_KEY_MAX_LEN] = ""; + +static PushCurrentParam(const Trie:tParams, const sParamName[]) { + if (g_stCurrentParams == Invalid_Stack) { + g_stCurrentParams = CreateStack(S_CurrentParam); + } + + if (g_tCurrentParams != Invalid_Trie) { + new CurrentParam[S_CurrentParam]; + copy(CurrentParam[CurrentParam_Name], charsmax(CurrentParam[CurrentParam_Name]), g_sCurrentParamName); + CurrentParam[CurrentParam_Params] = g_tCurrentParams; + PushStackArray(g_stCurrentParams, CurrentParam); + } + + g_tCurrentParams = tParams; + copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), sParamName); +} + +static PopCurrentParam() { + if ( + g_stCurrentParams != Invalid_Stack + && !IsStackEmpty(g_stCurrentParams) + ) { + new CurrentParam[S_CurrentParam]; + PopStackArray(g_stCurrentParams, CurrentParam); + copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), CurrentParam[CurrentParam_Name]); + g_tCurrentParams = CurrentParam[CurrentParam_Params]; + } else { + g_tCurrentParams = Invalid_Trie; + g_sCurrentParamName[0] = 0; + } +} + +bool:ParamType_Read( + const T_ParamType:type, + const JSON:valueJson, + const Trie:p, + const key[], + const tag[] = "" +) { + new typeObject[S_ParamType]; + ParamType_Get(type, typeObject); + + new JSON:linkedJson; + PCJson_HandleLinkedValue(valueJson, linkedJson); + + PushCurrentParam(p, key); + new bool:ret; + ExecuteForward(typeObject[ParamType_ReadCallback], ret, linkedJson, p, key, tag); + PopCurrentParam(); + + PCJson_FreeLinked(linkedJson, valueJson); + + return ret; +} + +ParamType_PrintListToConsole() { + // TODO: Сделать красиво табличкой как в vipm :) + + server_print("Registered param types:"); + for (new i = 0, ii = ArraySize(ParamTypes); i < ii; ++i) { + new typeObject[S_ParamType]; + ArrayGetArray(ParamTypes, i, typeObject); + + server_print(" - %s", typeObject[ParamType_Name]); + } +} + +ParamType_GetTypeAndTag(const str[], sParamType[], const iParamTypeLen, sParamTag[], const iParamTagLen) { + return strtok2(str, sParamType, iParamTypeLen, sParamTag, iParamTagLen, PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR); +} diff --git a/build/amxmodx/scripting/include/ParamsController.inc b/build/amxmodx/scripting/include/ParamsController.inc new file mode 100644 index 0000000..4ee0470 --- /dev/null +++ b/build/amxmodx/scripting/include/ParamsController.inc @@ -0,0 +1,1671 @@ +#if defined __params_controller_included + #endinput +#endif +#define __params_controller_included + +#include +#include +#include + +#define PARAM_TYPE_NAME_MAX_LEN 64 +#define PARAM_KEY_MAX_LEN 64 +#define PARAM_VALUE_MAX_LEN 512 +#define PARAM_TYPE_READ_CALLBACK_MAX_LEN 64 +#define PARAM_TYPE_TAG_MAX_LEN 64 +#define PARAM_SHORT_STRING_MAX_LEN 64 +#define PARAM_LONG_STRING_MAX_LEN 4096 +#define PARAM_CHAT_MESSAGE_MAX_LEN 190 + +stock const DEFAULT_PARAMS_BOOL_NAME[] = "Boolean"; +stock const DEFAULT_PARAMS_INT_NAME[] = "Integer"; +stock const DEFAULT_PARAMS_FLOAT_NAME[] = "Float"; +stock const DEFAULT_PARAMS_STR_NAME[] = "String"; +stock const DEFAULT_PARAMS_SHORT_STR_NAME[] = "ShortString"; +stock const DEFAULT_PARAMS_LONG_STR_NAME[] = "LongString"; +stock const DEFAULT_PARAMS_RGB_NAME[] = "RGB"; +stock const DEFAULT_PARAMS_MODEL_NAME[] = "Model"; +stock const DEFAULT_PARAMS_PLAYER_MODEL_NAME[] = "PlayerModel"; +stock const DEFAULT_PARAMS_SOUND_NAME[] = "Sound"; +stock const DEFAULT_PARAMS_RESOURCE_NAME[] = "Resource"; +stock const DEFAULT_PARAMS_FILE_NAME[] = "File"; +stock const DEFAULT_PARAMS_DIR_NAME[] = "Dir"; +stock const DEFAULT_PARAMS_CHAT_MESSAGE_NAME[] = "ChatMessage"; +stock const DEFAULT_PARAMS_TIME_INTERVAL_NAME[] = "TimeInterval"; +stock const DEFAULT_PARAMS_TIME_NAME[] = "Time"; +stock const DEFAULT_PARAMS_WEEK_DAY_NAME[] = "WeekDay"; +stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; +stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; + +#define PARAMS_CONTROLLER_VERSION "1.4.2" +stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; +stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; + +stock const PARAMS_CONTROLLER_TYPE_TAG_DELIMITER[] = ":"; +#define PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR ':' + +enum T_Param { Invalid_Param = INVALID_HANDLE } +enum T_ParamType { Invalid_ParamType = INVALID_HANDLE } + +enum _:E_NativeParam { + ParamArg_sKey, + ParamArg_sParamTypeName, + ParamArg_bRequired, +} + +enum E_ParamsReadErrorType { + ParamsReadError_None, + + /** + * Обязательный параметр отсутствует в переданном JSON-обьекте. + * + * Возникает только для обязательных параметров. + */ + ParamsReadError_RequiredParamNotPresented, + + /** + * Читаемое значение параметра некорректно. + * + * Значит что колбек чтения параметра вернул false. + * Может возникнуть даже для необязательных параметров. + */ + ParamsReadError_ParamValueIsInvalid, + + /** + * Тип параметра не был зарегистрирован на момент создания параметра. + * + * Возникает только для обязательных параметров с неизвестным типом. + * Для необязательных параметров с неизвестным типом значение тихо пропускается. + */ + ParamsReadError_UnknownParamType, +} + +/** + * Вызывается в тот момент, когда контроллер готов регистрировать новые типы параметров + * + * @noreturn + */ +forward ParamsController_OnRegisterTypes(); + +/** + * Вызывается после полной иницализации контроллера и регистрации всех типов + * + * @noreturn + */ +forward ParamsController_OnInited(); + +/** + * Принудительная инициализация контроллера параметров + * + * @note В ходе обработки этого натива будут вызваны форварды *_OnRegisterTypes и *_OnInited, + * а значит после его вызова можно создавать новые параметры (*_Param_Construct) + * + * @noreturn + */ +native ParamsController_Init(); + +/** + * Сохранение прочитанного значения параметра (число, float, bool, и т.д.) + * + * @note Можно вызывать только в рамках колбека чтения параметра + * + * @param iCell Сохраняемое значение + * + * @return true, если значение было установлено успешно, иначе false + */ +native bool:ParamsController_SetCell(const any:iCell); + +/** + * Сохранение прочитанного строкового значения параметра + * + * @note Можно вызывать только в рамках колбека чтения параметра + * @note Буфер для сохраняемой строки имеет размер 4096 ячеек (PARAM_LONG_STRING_MAX_LEN) + * Если требуется сохранить бОльшую строку, то сохраняйте напрямую + * + * @param sString Сохраняемая строка + * + * @return true, если значение было установлено успешно, иначе false + */ +native bool:ParamsController_SetString(const sString[]); + +/** + * Регистрация типа параметров + * + * @note Можно вызывать только после вызова форварда *_OnRegisterTypes + * + * @param sParamTypeName Название типа параметра (будет указываться при создании параметра) + * + * @return Хендлер зарегистрированного типа + */ +native T_ParamType:ParamsController_ParamType_Register(const sParamTypeName[]); + +/** + * Установка колбека для чтения параметра указанного типа + * + * @param iParamType Хендлер типа параметров, для которого будет установлен колбека + * @param sCallback Название функции-колбека + * + * @note Сигнатура колбека: bool:(const JSON:jValue, const Trie:tParams, const sParamKey[], const sParamTag[]) + * jValue: JSON-значение читаемого параметра + * tParams: гарантированно валидный хендлер Trie-структуры + * sParamKey: ключ читаемого параметра + * Колбек должен либо вернуть false, либо записать в tParams какое-либо значение и вернуть true + * В противном случае контроллер выведет в консоль соответствующую ошибку. + * + * @noreturn + */ +native ParamsController_ParamType_SetReadCallback(const T_ParamType:iParamType, const sCallback[]); + +/** + * Регистрация типа параметров с установкой колбека для чтения + * + * @param sParamTypeName Название типа параметра (будет указываться при создании параметра) + * @param sCallback Название функции-колбека + * + * @return Хендлер зарегистрированного типа + */ +stock T_ParamType:ParamsController_RegSimpleType(const sParamTypeName[], const sCallback[]) { + // Не то чтобы сейчас есть какие-то "сложные" типы, + // но пусть пока будет так) + new T_ParamType:iParamType = ParamsController_ParamType_Register(sParamTypeName); + ParamsController_ParamType_SetReadCallback(iParamType, sCallback); + + return iParamType; +} + +/** + * Создание экземпляра параметра. + * + * @param sKey Ключ параметра (по этому ключу будет читаться параметр из JSON-обьекта и по нему же значение будет заноситься в trie) + * @param sParamTypeName Название типа параметра + * @param bRequired Является ли параметр обязательным + * + * @note Если параметр был создан для разового использования, он может быть удалён нативом ParamsController_Param_Free(). + * @note Если тип sParamTypeName не зарегистрирован, параметр всё равно создаётся, но помечается как с неизвестным типом. + * При чтении такого параметра через ReadList: + * - обязательный → ошибка ParamsReadError_UnknownParamType (и log_error при вызове этого натива); + * - необязательный → тихо пропускается, даже если поле есть в JSON. + * + * @return Хендлер созданного параметра + */ +native T_Param:ParamsController_Param_Construct(const sKey[], const sParamTypeName[], const bool:bRequired = true); + +/** + * Удаление экземпляра параметра (очистка хендлера). + * + * @param param Хендлер экземпляра параметра, который нужно удалить (очистить). Всегда заменяется на Invalid_Param. + * + * @return Всегда Invalid_Param. + */ +native T_Param:ParamsController_Param_Free(&T_Param:param); + +stock Array:ParamsController_Param_FreeList(&Array:params) { + if (params == Invalid_Array) { + return params; + } + + for (new i = 0, ii = ArraySize(params); i < ii; ++i) { + ParamsController_Param_Free(ArrayGetCell(params, i)); + } + ArrayDestroy(params); + + return params; +} + +/** + * Чтение массива параметров из JSON-обьекта + * + * @param aParams Массив параметров + * @param jObj JSON-обьект + * @param tAppend trie-хендлер, в который будут записываться прочитанные параметры (по умолчанию создаётся новый) + * @param iErrType Переменная для записи типа ошибки + * @param sErrParamName Буфер для записи ключа параметра, вызвавшего ошибку + * @param iErrParamNameLen Длина буфера + * + * @note В случае возникновения ошибки функция вернёт trie-хендлер только с теми параметрами, + * которые были прочитаны до возникновения ошибки. Если в таком случае хендлер не нужен, + * то нужно его освободить при помощи TrieDestroy(). + * + * @return Валидный trie-хендлер с прочитанными параметрами. Если был передан параметр tAppend, то будет возвращён тот же хендлер + */ +native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj, &Trie:tAppend = Invalid_Trie, &E_ParamsReadErrorType:iErrType = ParamsReadError_None,sErrParamName[] = "", const iErrParamNameLen = 0); + +/** + * Чтение одного параметра из JSON-значения. + * + * @param param Хендлер параметра. + * @param valueJson JSON-значение. + * @param dest Хендлер Trie, куда будет записан прочитанный параметр. + * @param orFailKey Название поля, для которого выводить ошибку в случае её возникновения. Если пусто - ошибка не будет выброшена. + * + * @return true, если параметр прочитан успешно, false в случае ошибки. + */ +native bool:ParamsController_Param_Read(const T_Param:param, const JSON:valueJson, const Trie:dest, const orFailKey[] = ""); + +// Маркеры формата PC-аргументов натива. +// Первая ячейка всегда '$', вторая определяет тип содержимого. +#define PCARG_MARKER '$' +#define PCARG_TYPE_PARAM 'P' // одиночный T_Param хендл +#define PCARG_TYPE_ARRAY 'A' // Array: хендл со списком T_Param + +/** + * Конструктор параметра для передачи через аргументы натива. + * Компактная альтернатива тройке ("ключ", "тип", required). + * + * @note Возвращаемое значение передаётся как ОДИН аргумент натива вместо трёх. + * Совместимо с ParamsController_Param_ListFromNativeParams — оба стиля можно + * смешивать в одном вызове. + * + * @param key Ключ параметра + * @param typeName Название типа параметра + * @param required Является ли параметр обязательным + * + * @return Закодированный хендлер параметра (строка-маркер для передачи в натив) + */ +stock PCParam(const key[], const typeName[], const bool:required = false) { + // Формат: [PCARG_MARKER, PCARG_TYPE_PARAM, handle + 1, EOS] + // +1 чтобы handle=0 не читался как EOS при get_string на стороне сервера. + new buf[4]; + buf[0] = PCARG_MARKER; + buf[1] = PCARG_TYPE_PARAM; + buf[2] = _:ParamsController_Param_Construct(key, typeName, required) + 1; + buf[3] = EOS; + return buf; +} + +/** + * Упаковка набора PCParam()-значений в один аргумент натива. + * + * @note Возвращаемое значение передаётся как ОДИН аргумент натива. + * Совместимо с ParamsController_Param_ListFromNativeParams. + * Временный массив уничтожается внутри ListFromNativeParams. + * + * @param ... Результаты вызовов PCParam() + * + * @return Закодированный хендлер массива параметров (строка-маркер для передачи в натив) + */ +stock PCParams(any:...) { + new Array:arr = ArrayCreate(1, 1); + + for (new i = 0, count = numargs(); i < count; i++) { + if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { + ArrayPushCell(arr, T_Param:(getarg(i, 2) - 1)); + } + } + + // Формат: [PCARG_MARKER, PCARG_TYPE_ARRAY, array_handle + 1, EOS] + new buf[4]; + buf[0] = PCARG_MARKER; + buf[1] = PCARG_TYPE_ARRAY; + buf[2] = _:arr + 1; + buf[3] = EOS; + return buf; +} + +/** + * Создание Array: с хендлерами параметров из набора PCParam()-значений. + * + * @param ... Результаты вызовов PCParam() + * + * @return Хендлер динамического массива с параметрами + */ +stock Array:PCParamsArray(any:...) { + new Array:result = ArrayCreate(1, 1); + + for (new i = 0, count = numargs(); i < count; i++) { + if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { + ArrayPushCell(result, T_Param:(getarg(i, 2) - 1)); + } + } + + return result; +} + +/** + * Создание параметра из параметров натива + * + * @note Должно вызываться только в рамках обработки вызова натива + * + * @param iParamOffset Номер параметра, с которого начинать читать + * + * @note Параметр создаётся по трём параметрам натива: + * - Ключ параметра + * - Название типа параметра + * - true/false является ли параметр обязательным + * + * @return Хендлер созданного параметра + */ +stock T_Param:ParamsController_Param_FromNativeParams(const iParamOffset) { + new sKey[PARAM_KEY_MAX_LEN]; + get_string(iParamOffset + ParamArg_sKey, sKey, charsmax(sKey)); + + new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN]; + get_string(iParamOffset + ParamArg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); + + new bool:bRequired = bool:get_param_byref(iParamOffset + ParamArg_bRequired); + + return ParamsController_Param_Construct(sKey, sParamTypeName, bRequired); +} + +/** + * Создание списка параметров из параметров натива. + * + * @note Должно вызываться только в рамках обработки вызова натива. + * + * @note Поддерживает несколько стилей передачи параметров (можно смешивать): + * - Классический: три аргумента натива на параметр ("ключ", "тип", required) + * - PCParam(): один аргумент натива — одиночный параметр + * - PCParams(): один аргумент натива — массив параметров + * + * @param iParamOffset Номер аргумента натива, с которого начинать читать + * @param iParamsCount Общее количество аргументов натива (первый параметр обработчика) + * @param aAppend Массив, в который дописывать параметры (по умолчанию создаётся новый) + * + * @return Хендлер динамического массива с прочитанными параметрами + */ +stock Array:ParamsController_Param_ListFromNativeParams(const iParamOffset, const iParamsCount, &Array:aAppend = Invalid_Array) { + if (aAppend == Invalid_Array) { + aAppend = ArrayCreate(1, 1); + } + + new i = iParamOffset; + new val[4]; + while (i <= iParamsCount) { + get_string(i, val, charsmax(val)); + if (val[0] == PCARG_MARKER) { + if (val[1] == PCARG_TYPE_PARAM) { + // PCParam(): одиночный параметр + ArrayPushCell(aAppend, T_Param:(val[2] - 1)); + } else if (val[1] == PCARG_TYPE_ARRAY) { + // PCParams(): временный массив параметров + new Array:arr = Array:(val[2] - 1); + for (new j = 0, jj = ArraySize(arr); j < jj; j++) { + ArrayPushCell(aAppend, ArrayGetCell(arr, j)); + } + ArrayDestroy(arr); + } + i += 1; + } else { + // Классический стиль: тройка ("ключ", "тип", required) + ArrayPushCell(aAppend, ParamsController_Param_FromNativeParams(i)); + i += E_NativeParam; + } + } + + return aAppend; +} + +// Single + +stock const PCSINGLE_PARAM_KEY[] = "__SINGLE"; + +/** + * Чтение одного параметра из JSON-значения. + * + * @param valueJson JSON-значение. + * @param type Название типа параметра. Может cодержать тэг. + * @param orFailKey Название поля, для которого выводить ошибку в случае её возникновения. Если пусто - ошибка не будет выброшена. + * + * @return Хендлер Trie с прочитанным параметром по ключу "__SINGLE". Даже в случае ошибки хендлер будет создан. + */ +stock Trie:PCSingle_Read(const JSON:valueJson, const type[], const orFailKey[] = "") { + new T_Param:param = ParamsController_Param_Construct(PCSINGLE_PARAM_KEY, type, true); + + new Trie:p = TrieCreate(); + ParamsController_Param_Read(param, valueJson, p, orFailKey); + + ParamsController_Param_Free(param); + return p; +} + +/** + * Чтение одного параметра из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param type Название типа параметра. Может cодержать тэг. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Хендлер Trie с прочитанным параметром по ключу "__SINGLE". Даже в случае ошибки хендлер будет создан. + */ +stock Trie:PCSingle_ReadFromObject(const JSON:objectJson, const key[], const type[], const bool:dotNot = false, const bool:orFail = false) { + if (!json_is_object(objectJson)) { + PCJson_ErrorForFile(objectJson, "Json value must be an object. (%d)", objectJson); + return Invalid_Trie; + } + + if (!json_object_has_value(objectJson, key, .dot_not = dotNot)) { + if (orFail) { + PCJson_ErrorForFile(objectJson, "Object must contain '%s' field.", key); + } + return Invalid_Trie; + } + + new JSON:valueJson = json_object_get_value(objectJson, key, dotNot); + new Trie:ret = PCSingle_Read(valueJson, type, orFail ? key : ""); + json_free(valueJson); + + return ret; +} + +/** + * Чтение числового типа параметра из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param type Название типа параметра. Может cодержать тэг. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock any:PCSingle_ObjCell(const JSON:objectJson, const key[], const type[], const any:def = 0, const bool:dotNot = false, const bool:orFail = false) { + new Trie:p = PCSingle_ReadFromObject(objectJson, key, type, dotNot, orFail); + + new any:value = def; + + if (p != Invalid_Trie) { + TrieGetCell(p, PCSINGLE_PARAM_KEY, value); + } + TrieDestroy(p); + + return value; +} + +/** + * Чтение числового типа параметра из JSON-значения. + * + * @param valueJson JSON-обьект. + * @param type Название типа параметра. Может cодержать тэг. + * @param def Значение по умолчанию, в случае ошибки. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock any:PCSingle_Cell(const JSON:valueJson, const type[], const any:def = 0, const orFailKey[] = "") { + new Trie:p = PCSingle_Read(valueJson, type, orFailKey); + + new any:value = def; + + if (p != Invalid_Trie) { + TrieGetCell(p, PCSINGLE_PARAM_KEY, value); + } + TrieDestroy(p); + + return value; +} + +/** + * Чтение параметра типа "Integer" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock PCSingle_ObjInt(const JSON:objectJson, const key[], const any:def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_INT_NAME, def, dotNot, orFail); +} + +stock PCSingle_Int(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_INT_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Float" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock Float:PCSingle_ObjFloat(const JSON:objectJson, const key[], const Float:def = 0.0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_FLOAT_NAME, def, dotNot, orFail); +} + +stock Float:PCSingle_Float(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_FLOAT_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Bool" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock bool:PCSingle_ObjBool(const JSON:objectJson, const key[], const bool:def = false, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_BOOL_NAME, def, dotNot, orFail); +} + +stock bool:PCSingle_Bool(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_BOOL_NAME, def, orFailKey); +} + +/** + * Чтение строкового типа параметра из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param type Название типа параметра. Может cодержать тэг. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjStr(const JSON:objectJson, const key[], const type[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new Trie:p = PCSingle_ReadFromObject(objectJson, key, type, dotNot, orFail); + + new ret; + if (p != Invalid_Trie && TrieKeyExists(p, PCSINGLE_PARAM_KEY)) { + ret = TrieGetString(p, PCSINGLE_PARAM_KEY, out, outLen); + } else { + ret = copy(out, outLen, def); + } + TrieDestroy(p); + + return ret; +} + +/** + * Чтение строкового типа параметра из JSON-обьекта. Inline вариант функуции PCSingle_ObjStr. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param type Название типа параметра. Может cодержать тэг. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock PCSingle_iObjStr(const JSON:objectJson, const key[], const type[] = DEFAULT_PARAMS_STR_NAME, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_VALUE_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, type, out, charsmax(out), def, dotNot, orFail); + return out; +} + +/** + * Чтение строкового типа параметра из JSON-значения. + * + * @param valueJson JSON-обьект. + * @param type Название типа параметра. Может cодержать тэг. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_Str(const JSON:valueJson, const type[], out[], const outLen, const def[] = "", const orFailKey[] = "") { + new Trie:p = PCSingle_Read(valueJson, type, orFailKey); + + new ret; + if (p != Invalid_Trie && TrieKeyExists(p, PCSINGLE_PARAM_KEY)) { + ret = TrieGetString(p, PCSINGLE_PARAM_KEY, out, outLen); + } else { + ret = copy(out, outLen, def); + } + TrieDestroy(p); + + return ret; +} + +/** + * Чтение строкового типа параметра из JSON-значения. Inline вариант функуции PCSingle_Str. + * + * @param valueJson JSON-обьект. + * @param type Название типа параметра. Может cодержать тэг. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение. + */ +stock PCSingle_iStr(const JSON:valueJson, const type[] = DEFAULT_PARAMS_STR_NAME, const def[] = "", const orFailKey[] = "") { + new out[PARAM_VALUE_MAX_LEN]; + PCSingle_Str(valueJson, type, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "String" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_VALUE_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_String(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_STR_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PARAM_VALUE_MAX_LEN]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_STR_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "ShortString" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjShortString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjShortString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_SHORT_STRING_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_ShortString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iShortString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PARAM_SHORT_STRING_MAX_LEN]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_SHORT_STR_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "LongString" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjLongString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjLongString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_LONG_STRING_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_LongString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iLongString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PARAM_LONG_STRING_MAX_LEN]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_LONG_STR_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "Model" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjModel(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_Model(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iModel(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_MODEL_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "PlayerModel" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjPlayerModel(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjPlayerModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_PlayerModel(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iPlayerModel(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "Sound" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjSound(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjSound(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_Sound(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iSound(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_SOUND_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "Resource" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjResource(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjResource(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_Resource(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iResource(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_RESOURCE_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "File" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjFile(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjFile(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_File(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iFile(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_FILE_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "Dir" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjDir(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjDir(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PLATFORM_MAX_PATH]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_Dir(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, orFailKey); +} + +stock PCSingle_iDir(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { + new out[PLATFORM_MAX_PATH]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_DIR_NAME, out, charsmax(out), def, orFailKey); + return out; +} + +/** + * Чтение параметра типа "СhatMessage" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер для записи прочитанного значения. + * @param outLen Размер буфера. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Кол-во записанных в буфер ячеек. + */ +stock PCSingle_ObjChatMessage(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, dotNot, orFail); +} + +stock PCSingle_iObjChatMessage(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { + new out[PARAM_CHAT_MESSAGE_MAX_LEN]; + PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, charsmax(out), def, dotNot, orFail); + return out; +} + +stock PCSingle_ChatMessage(const JSON:valueJson, out[], const outLen, const def[] = "", const bool:orFail = false) { + return PCSingle_Str(valueJson, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, orFail); +} + +stock PCSingle_iChatMessage(const JSON:valueJson, const def[] = "", const bool:orFail = false) { + new out[PARAM_CHAT_MESSAGE_MAX_LEN]; + PCSingle_Str(valueJson, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, charsmax(out), def, orFail); + return out; +} + +/** + * Чтение параметра типа "Regexp" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Хендлер скомпилированного выражения. + */ +stock Regex:PCSingle_ObjRegexp(const JSON:objectJson, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL, const bool:dotNot = false, const bool:orFail = false) { + return Regex:PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_REGEXP_NAME, def, dotNot, orFail); +} + +stock Regex:PCSingle_Regexp(const JSON:valueJson, const Regex:def = Regex:REGEX_PATTERN_FAIL, const orFailKey[] = "") { + return Regex:PCSingle_Cell(valueJson, DEFAULT_PARAMS_REGEXP_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "RGB" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param out Буфер [3] для записи компонент цвета (R, G, B). + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + */ +stock PCSingle_ObjRGB(const JSON:objectJson, const key[], out[3], const bool:dotNot = false, const bool:orFail = false) { + new Trie:p = PCSingle_ReadFromObject(objectJson, key, DEFAULT_PARAMS_RGB_NAME, dotNot, orFail); + if (p != Invalid_Trie) { + TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); + TrieDestroy(p); + } +} + +stock PCSingle_RGB(const JSON:valueJson, out[3], const orFailKey[] = "") { + new Trie:p = PCSingle_Read(valueJson, DEFAULT_PARAMS_RGB_NAME, orFailKey); + if (p != Invalid_Trie) { + TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); + TrieDestroy(p); + } +} + +/** + * Чтение параметра типа "TimeInterval" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение в секундах. + */ +stock PCSingle_ObjTimeInterval(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, dotNot, orFail); +} + +stock PCSingle_TimeInterval(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Time" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (секунды от начала суток). + */ +stock PCSingle_ObjTime(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_NAME, def, dotNot, orFail); +} + +stock PCSingle_Time(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "WeekDay" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (0=Вс, 1=Пн, ..., 6=Сб). + */ +stock PCSingle_ObjWeekDay(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_WEEK_DAY_NAME, def, dotNot, orFail); +} + +stock PCSingle_WeekDay(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_WEEK_DAY_NAME, def, orFailKey); +} + +/** + * Чтение параметра типа "Flags" из JSON-обьекта. + * + * @param objectJson JSON-обьект. + * @param key Ключ, значение по которому нужно прочитать. + * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. + * @param dotNot Использовать ли вложенные ключи через точку. + * @param orFail Выбрасывать ли ошибку в случае её возникновения. + * + * @return Прочитанное значение (битовая маска флагов). + */ +stock PCSingle_ObjFlags(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { + return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_FLAGS_NAME, def, dotNot, orFail); +} + +stock PCSingle_Flags(const JSON:valueJson, const def = 0, const orFailKey[] = "") { + return PCSingle_Cell(valueJson, DEFAULT_PARAMS_FLAGS_NAME, def, orFailKey); +} + +// Getters + +stock any:PCGet_Cell(const Trie:p, const key[], const any:def = 0) { + new any:cell = def; + if (p != Invalid_Trie) { + TrieGetCell(p, key, cell); + } + return cell; +} + +stock PCGet_Str(const Trie:p, const key[], out[], const outLen, const def[] = "") { + if (p != Invalid_Trie && TrieKeyExists(p, key)) { + return TrieGetString(p, key, out, outLen); + } else { + return copy(out, outLen, def); + } +} + +stock PCGet_iStr(const Trie:p, const key[], const def[] = "") { + new str[PARAM_VALUE_MAX_LEN]; + PCGet_Str(p, key, str, charsmax(str), def); + return str; +} + +stock Regex:PCGet_Regexp(const Trie:p, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL) { + return Regex:PCGet_Cell(p, key, def); +} + +stock bool:PCGet_RegexpMatch(const Trie:p, const key[], const string[]) { + new Regex:re = PCGet_Regexp(p, key); + if (re == Regex:REGEX_PATTERN_FAIL) { + return false; + } + + return regex_match_c(string, re) > 0; +} + +stock PCGet_iChatMessage(const Trie:p, const key[], const def[] = "") { + new str[PARAM_CHAT_MESSAGE_MAX_LEN]; + PCGet_Str(p, key, str, charsmax(str), def); + return str; +} + +stock bool:PCGet_ChatMessagePrint(const playerIndex, const Trie:p, const key[], const def[] = "", const sender = print_team_default) { + new str[PARAM_CHAT_MESSAGE_MAX_LEN]; + PCGet_Str(p, key, str, charsmax(str), def); + + if (!def[0] == EOS) { + return false; + } + + client_print_color(sender, playerIndex, "%s", str); + + return true; +} + +stock PCGet_Int(const Trie:p, const key[], const def = 0) { + return PCGet_Cell(p, key, def); +} + +stock Float:PCGet_Float(const Trie:p, const key[], const Float:def = 0.0) { + return PCGet_Cell(p, key, def); +} + +stock bool:PCGet_Bool(const Trie:p, const key[], const bool:def = false) { + return PCGet_Cell(p, key, def); +} + +stock PCGet_RGB(const Trie:p, const key[]) { + new color[3]; + TrieGetArray(p, key, color, 3); + + return color; +} + +stock PCGet_RGB_Red(const Trie:p, const key[]) { + return PCGet_RGB(p, key)[0]; +} + +stock PCGet_RGB_Green(const Trie:p, const key[]) { + return PCGet_RGB(p, key)[1]; +} + +stock PCGet_RGB_Blue(const Trie:p, const key[]) { + return PCGet_RGB(p, key)[2]; +} + +// Path Utils + +stock PCPath_IsPathSeparator(const ch) { + return ch == '/' || ch == '\'; +} + +stock PCPath_Normalize(out[], const outLen) { + replace_string(out, outLen, "\", "/"); + replace_string(out, outLen, "//", "/"); + replace_string(out, outLen, "./", ""); +} + +stock PCPath_MakePath(const path[], out[], const outLen, const absolutePrefix[] = "", const forceFileExtension[] = "") { + static __amxx_configsdir[PLATFORM_MAX_PATH]; + if (!__amxx_configsdir[0]) { + get_localinfo("amxx_configsdir", __amxx_configsdir, charsmax(__amxx_configsdir)); + } + + if (PCPath_IsPathSeparator(path[0])) { + formatex(out, outLen, "%s/%s", __amxx_configsdir, path) + } else { + if (absolutePrefix[0] != EOS) { + formatex(out, outLen, "%s/%s", absolutePrefix, path); + } else { + formatex(out, outLen, "%s/plugins/%s", __amxx_configsdir, path); + } + } + + PCPath_Normalize(out, outLen); + + if (forceFileExtension[0] != EOS) { + static ext[PLATFORM_MAX_PATH]; + formatex(ext, charsmax(ext), ".%s", forceFileExtension); + + if (strfind(out, ext, .ignorecase = true, .pos = strlen(out) - strlen(ext)) < 0) { + formatex(out, outLen, "%s%s", out, ext); + } + } +} + +stock PCPath_iMakePath(const path[], const absolutePrefix[] = "", const forceFileExtension[] = "") { + new out[PLATFORM_MAX_PATH]; + PCPath_MakePath(path, out, charsmax(out), absolutePrefix, forceFileExtension); + return out; +} + +stock bool:PCPath_GetFileName(const path[], out[], const outLen) { + new i = strlen(path) - 1; + + if (i < 0 || PCPath_IsPathSeparator(path[i])) { + return false; + } + + while (i > 0 && !PCPath_IsPathSeparator(path[i - 1])) { + --i; + } + + new j = 0, lastDot = -1; + while (j < outLen && path[i + j]) { + out[j] = path[i + j]; + if (out[j] == '.') { + lastDot = j; + } + + j++; + } + + if (lastDot >= 0 && lastDot != j - 1) { + out[lastDot] = EOS; + } + + return true; +} + +stock PCPath_iGetFileName(const path[]) { + new out[PLATFORM_MAX_PATH]; + PCPath_GetFileName(path, out, charsmax(out)); + return out; +} + +// Json Wrapper + +stock const PC_JSON_WRAPPER_FIELD_ROOT[] = "___PC_JSON_WRAPPER_FIELD_ROOT___dOnOtUsEtHiSkEyFoRrEaLdAtA___"; +stock const PC_JSON_WRAPPER_FIELD_HANDLER[] = "Handler"; +stock const PC_JSON_WRAPPER_FIELD_WORKDIR[] = "Workdir"; +stock const PC_JSON_WRAPPER_FIELD_FILE[] = "File"; +stock const PC_JSON_WRAPPER_FIELD_VALUE[] = "Value"; +stock const PC_JSON_WRAPPER_FIELD_LINKED[] = "Linked"; +#define PC_JSON_WRAPPER_MESSAGE_MAX_LEN 256 + +stock const PC_JSON_WRAPPER_LINK_PREFIX_FILE[] = "File:"; + +stock JSON:PCJson_ParseFile(const path[], const workdir[] = "") { + if (!file_exists(path)) { + abort(AMX_ERR_GENERAL, "[JSON][ERROR] File '%s' not found.", path); + return Invalid_JSON; + } + + new JSON:value = json_parse(path, .is_file = true, .with_comments = true); + if (value == Invalid_JSON) { + abort(AMX_ERR_GENERAL, "[JSON][ERROR] File '%s' contains invalid JSON value.", path); + return Invalid_JSON; + } + + new JSON:wrapper = json_init_object(); + json_object_set_bool(wrapper, PC_JSON_WRAPPER_FIELD_ROOT, true); + json_object_set_number(wrapper, PC_JSON_WRAPPER_FIELD_HANDLER, _:wrapper); + json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_FILE, path); + json_object_set_value(wrapper, PC_JSON_WRAPPER_FIELD_VALUE, value); + + if (workdir[0] != EOS) { + json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, workdir); + } else { + static autoWorkdir[PLATFORM_MAX_PATH]; + copy(autoWorkdir, charsmax(autoWorkdir), path); + for (new i = strlen(autoWorkdir) - 1; i >= 0; --i) { + if (PCPath_IsPathSeparator(autoWorkdir[i]) || i == 0) { + autoWorkdir[i] = EOS; + break; + } + } + json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, autoWorkdir); + } + + return value; +} + +stock JSON:PCJson_Free(&JSON:value, const bool:rootOnly = false) { + if (value == Invalid_JSON) { + return Invalid_JSON; + } + + if (PCJson_IsWrapper(value)) { + json_free(value); + return Invalid_JSON; + } + + if (!PCJson_IsWrappedRoot(value)) { + if (rootOnly && !PCJson_IsRoot(value)) { + return value; + } else { + json_free(value); + return Invalid_JSON; + } + } + + new JSON:wrapper = PCJson_FindWrapper(value); + PCJson_FreeWrapper(wrapper); + + json_free(value); + return Invalid_JSON; +} + +stock JSON:PCJson_FreeWrapper(&JSON:value) { + if (!PCJson_IsWrapper(value)) { + abort(AMX_ERR_PARAMS, "Attempt to free not a wrapper via PCJson_FreeWrapper() function."); + return Invalid_JSON; + } + + new JSON:mainWrapperHandler = JSON:json_object_get_number(value, PC_JSON_WRAPPER_FIELD_HANDLER); + json_free(value); + json_free(mainWrapperHandler); + + return Invalid_JSON; +} + +stock bool:PCJson_IsWrapper(const JSON:value) { + if (value == Invalid_JSON) { + return false; + } + + return json_is_object(value) + && PCJson_IsRoot(value) + && json_object_has_value(value, PC_JSON_WRAPPER_FIELD_ROOT, JSONBoolean); +} + +stock bool:PCJson_IsRoot(const JSON:value) { + if (value == Invalid_JSON) { + return false; + } + + new JSON:parent = json_get_parent(value); + new bool:ret = parent == Invalid_JSON; + if (parent != Invalid_JSON) { + json_free(parent); + } + return ret; +} + +stock bool:PCJson_IsWrappedRoot(const JSON:value) { + if (value == Invalid_JSON) { + return false; + } + + new JSON:parent = json_get_parent(value); + new bool:ret = PCJson_IsWrapper(parent); + if (parent != Invalid_JSON) { + json_free(parent); + } + return ret; +} + +stock bool:PCJson_IsLink(const JSON:value) { + if (value == Invalid_JSON || !json_is_string(value)) { + return false; + } + + static str[sizeof PC_JSON_WRAPPER_LINK_PREFIX_FILE]; + json_get_string(value, str, charsmax(str)); + + return bool:equali(str, PC_JSON_WRAPPER_LINK_PREFIX_FILE, charsmax(str)); +} + +stock JSON:PCJson_FindRoot(const JSON:value, const Array:trace = Invalid_Array) { + if (value == Invalid_JSON) { + return Invalid_JSON; + } + + if (trace != Invalid_Array) { + ArrayPushCell(trace, json_get_type(value)); + } + + new JSON:parent; + new JSON:iter = json_get_parent(value); + if (iter == Invalid_JSON) { + return value; + } + + while (iter != Invalid_JSON) { + if (trace != Invalid_Array) { + ArrayPushCell(trace, json_get_type(iter)); + } + + parent = json_get_parent(iter); + + if (parent == Invalid_JSON) { + break; + } + json_free(iter); + iter = parent; + } + + return iter; +} + +stock JSON:PCJson_FindWrapper(const JSON:value, const Array:trace = Invalid_Array) { + new JSON:root = PCJson_FindRoot(value, .trace = trace); + + if (!PCJson_IsWrapper(root)) { + if (value != root) { + json_free(root); + } + return Invalid_JSON; + } + + return root; +} + +stock PCJson_GetWrapperWorkdir(const JSON:value, out[] = "", const outLen = 0, const def[] = "") { + new path[PLATFORM_MAX_PATH]; + + new JSON:wrapper = PCJson_FindWrapper(value); + if (wrapper != Invalid_JSON) { + json_object_get_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, path, charsmax(path)); + } + + if (wrapper != value) { + json_free(wrapper); + } + + if (path[0] == EOS) { + copy(path, charsmax(path), def); + } + + if (outLen > 0) { + copy(out, outLen, path); + } + + return path; +} + +stock PCJson_GetWrapperFile(const JSON:value, out[] = "", const outLen = 0, const def[] = "", const Array:trace = Invalid_Array) { + new path[PLATFORM_MAX_PATH]; + + new JSON:wrapper = PCJson_FindWrapper(value, .trace = trace); + if (wrapper != Invalid_JSON) { + json_object_get_string(wrapper, PC_JSON_WRAPPER_FIELD_FILE, path, charsmax(path)); + } + + if (wrapper != value) { + json_free(wrapper); + } + + if (path[0] == EOS) { + copy(path, charsmax(path), def); + } + + if (outLen > 0) { + copy(out, outLen, path); + } + + return path; +} + +stock PCJson_ExtractLinkPath(const JSON:link, out[], const outLen) { + out[0] = EOS; + if (!PCJson_IsLink(link)) { + return 0; + } + + json_get_string(link, out, outLen); + return replace(out, outLen, PC_JSON_WRAPPER_LINK_PREFIX_FILE, ""); +} + +stock bool:PCJson_HandleLinkedValue(const JSON:value, &JSON:linkedOrSameValue) { + linkedOrSameValue = value; + if (!PCJson_IsLink(value)) { + return false; + } + + new JSON:wrapper = PCJson_FindWrapper(value); + if (wrapper == Invalid_JSON) { + return false; + } + + static linkPath[PLATFORM_MAX_PATH]; + PCJson_ExtractLinkPath(value, linkPath, charsmax(linkPath)); + + static workdir[PLATFORM_MAX_PATH]; + if (linkPath[0] == '.' && linkPath[1] == '/') { + workdir[0] = EOS; + } else { + PCJson_GetWrapperWorkdir(wrapper, workdir, charsmax(workdir)); + } + + json_free(wrapper); + + static path[PLATFORM_MAX_PATH]; + PCPath_MakePath( + linkPath, path, charsmax(path), + .absolutePrefix = workdir, + .forceFileExtension = "json" + ); + + new JSON:linkedValue = PCJson_ParseFile(path, workdir); + + new JSON:deepLinkedValue; + if (PCJson_HandleLinkedValue(linkedValue, deepLinkedValue)) { + PCJson_Free(linkedValue); + linkedOrSameValue = deepLinkedValue; + return true; + } else { + linkedOrSameValue = linkedValue; + return true; + } +} + +enum _:E_PCJson_CachedLinkedValueState { + PCJson_CachedLinkedValue_NotLinked = 0, + PCJson_CachedLinkedValue_Linked = 1, + PCJson_CachedLinkedValue_Cached = 2, +} + +stock PCJson_HandleCachedLinkedValue(const Trie:cache, const JSON:value, &JSON:linkedOrSameValue, &any:cachedCell, linkPath[], const linkPathLen) { + linkedOrSameValue = value; + + if (cache == Invalid_Trie) { + return _:PCJson_HandleLinkedValue(value, linkedOrSameValue); + } + + PCJson_ExtractLinkPath(value, linkPath, linkPathLen); + if (linkPath[0] == EOS) { + return PCJson_CachedLinkedValue_NotLinked; + } + + if (TrieGetCell(cache, linkPath, cachedCell)) { + return PCJson_CachedLinkedValue_Cached; + } + + return _:PCJson_HandleLinkedValue(value, linkedOrSameValue); +} + +#pragma deprecated Dont use example function, please... +stock someTag:USAGE_EXAMPLE__PCJson_HandleCachedLinkedValue(const JSON:value) { + // global + new Trie:cache; // linkPath[] => any:cachedCell + + // func + new JSON:linked, someTag:cachedCell = someTag:-1, linkPath[PLATFORM_MAX_PATH]; + if (PCJson_HandleCachedLinkedValue(cache, value, linked, cachedCell, linkPath, charsmax(linkPath)) == PCJson_CachedLinkedValue_Cached) { + return cachedCell; + } + + // cachedCell = YourJsonReadFunc(linked); + + if (linkPath[0] != EOS && cachedCell >= 0) { + TrieSetCell(cache, linkPath, cachedCell); + } + + PCJson_FreeLinked(linked, value); + return cachedCell; +} + +stock JSON:PCJson_FreeLinked(&JSON:value, const JSON:sourceJson = Invalid_JSON) { + if (value == sourceJson) { + return value; + } + + return PCJson_Free(value, .rootOnly = true); +} + +stock bool:PCJson_LogForFile(const JSON:value, const prefix[], const messageFormat[], any:...) { + static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; + vformat(msg, charsmax(msg), messageFormat, 4); + + new Array:trace = ArrayCreate(1, 1); + + static path[PLATFORM_MAX_PATH]; + PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*", .trace = trace); + + log_amx("[JSON][%s] File: %s", prefix, path); + // log_amx("[JSON][%s] Trace: %s", prefix, PCJSon_TraceToString(trace, .reverse = true)); // Экспериментальное + log_amx("[JSON][%s] Log: %s", prefix, msg); + + ArrayDestroy(trace); + + return false; +} + +stock bool:PCJson_ErrorForFile(const JSON:value, const messageFormat[], any:...) { + static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; + vformat(msg, charsmax(msg), messageFormat, 3); + + static path[PLATFORM_MAX_PATH]; + PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*"); + + abort(AMX_ERR_GENERAL, "[JSON][ERROR] %s (File '%s')", msg, path); + + return false; +} +#define PCJson_AbortForFile PCJson_ErrorForFile + +stock bool:PCJson_FailForFile(const JSON:value, const messageFormat[], any:...) { + static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; + vformat(msg, charsmax(msg), messageFormat, 3); + + static path[PLATFORM_MAX_PATH]; + PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*"); + + set_fail_state("[JSON][FATAL ERROR] %s (File '%s')", msg, path); + + return false; +} + +stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { + new str[256]; + for (new i = 0, ii = ArraySize(trace); i < ii; ++i) { + if (i > 0) { + add(str, charsmax(str), "->"); + } + + switch (ArrayGetCell(trace, reverse ? ii - i - 1 : i)) { + case JSONError: + // Хз зачем, но пусть будет) + add(str, charsmax(str), "err"); + case JSONNull: + add(str, charsmax(str), "null"); + case JSONString: + add(str, charsmax(str), "str"); + case JSONNumber: + add(str, charsmax(str), "num"); + case JSONObject: + add(str, charsmax(str), "obj"); + case JSONArray: + add(str, charsmax(str), "arr"); + case JSONBoolean: + add(str, charsmax(str), "bool"); + } + } + + return str; +} + +// Cvars + +stock PCCvar_Const(const name[], const value[]) { + new p = create_cvar(name, value); + set_pcvar_string(p, value); + + return p; +} From d1b866cf20d27154acdb2910bed766d8d4c45bfd Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sun, 31 May 2026 21:04:22 +0300 Subject: [PATCH 25/30] Use amxx-builder --- .build-config | 8 --- .github/workflows/CI.yml | 111 +++++++++------------------------------ .gitignore | 6 ++- amxbuild.yml | 6 +++ build.bat | 93 +------------------------------- config.bat | 84 ----------------------------- 6 files changed, 36 insertions(+), 272 deletions(-) delete mode 100644 .build-config create mode 100644 amxbuild.yml delete mode 100644 config.bat diff --git a/.build-config b/.build-config deleted file mode 100644 index 3dc0ee3..0000000 --- a/.build-config +++ /dev/null @@ -1,8 +0,0 @@ -PACKAGE_NAME=ParamsController - -PACKAGE_COMPILED_PLUGINS_USE=1 -PACKAGE_COMPILED_PLUGINS_SAVE=0 - -PACKAGE_README_USE=0 -PACKAGE_PLUINGS_LIST_USE=0 -PACKAGE_ASSETS_USE=0 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cee241f..d8cedf3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,97 +2,41 @@ name: CI on: push: - branches: [master] + branches: [master, feature/**, fix/**] paths-ignore: - "**.md" - - "**.bat" - pull_request: types: [opened, reopened, synchronize] release: types: [published] -env: - PACKAGE_NAME: ParamsController - STORE_READMY: 0 - jobs: build: - name: "Build" + name: Build runs-on: ubuntu-latest outputs: - COMMIT_SHA: ${{ steps.declare_sha.outputs.COMMIT_SHA }} - SEMVER: ${{ steps.declare_sha.outputs.SEMVER }} + sha: ${{ steps.sha.outputs.SHORT }} + name: ${{ steps.build.outputs.name }} steps: - - name: Checkout - uses: actions/checkout@v4.1.1 - with: - fetch-depth: 0 - - - name: Parse SemVer string (release) - id: semver_parser - if: | - github.event_name == 'release' && - github.event.action == 'published' && - startsWith(github.ref, 'refs/tags/') - uses: booxmedialtd/ws-action-parse-semver@v1.4.7 - with: - input_string: ${{ github.ref }} - version_extractor_regex: 'refs\/tags\/(.*)$' + - uses: actions/checkout@v4 - - name: Declare SHA & package name - id: declare_sha - shell: bash - run: | - SHA=$(git rev-parse --short HEAD) - echo "COMMIT_SHA=$SHA" >> $GITHUB_OUTPUT - echo "SEMVER=${{ steps.semver_parser.outputs.fullversion }}" >> $GITHUB_OUTPUT + - id: sha + run: echo "SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Setup AMXXPawn Compiler - uses: wopox1337/setup-amxxpawn@v1.1.0 + - id: build + uses: AmxxModularEcosystem/amxx-builder@v1 with: - version: "1.10.5428" - - - name: Compile plugins - working-directory: amxmodx/scripting/ - env: - REAPI_INCLUDE: ${{ env.REAPI_INCLUDE_PATH }} - run: | - compile() { - sourcefile=$1 - amxxfile="$(echo $sourcefile | sed -e 's/\.sma$/.amxx/')" - output_path="../plugins/$amxxfile" + set: | + output.pack=false + output.dir=./artifact - mkdir -p $(dirname $output_path) - - echo -n "Compiling $sourcefile ... " - amxxpc $sourcefile -o"$output_path" \ - -i"include" \ - -i"$REAPI_INCLUDE" - } - export -f compile - - find . -type f -name "*.sma" -exec bash -c 'compile "$0"' {} \; - - - name: Move files - env: - STORE_READMY: ${{ env.STORE_READMY }} - run: | - mkdir -p publish/${{ env.PACKAGE_NAME }}/addons - mv amxmodx/ publish/${{ env.PACKAGE_NAME }}/addons/ - - if [ ! -z "${STORE_READMY}" ]; then - mv README.md publish/ - fi - - - name: Deploy artifact - uses: actions/upload-artifact@v4.3.1 + - uses: actions/upload-artifact@v4 with: - name: ${{ env.PACKAGE_NAME }}-${{ steps.declare_sha.outputs.COMMIT_SHA }}-dev - path: publish/* + name: ${{ steps.build.outputs.name }}-${{ steps.sha.outputs.SHORT }}-dev + path: artifact/ publish: - name: "Publish release" + name: Publish release runs-on: ubuntu-latest needs: [build] if: | @@ -100,21 +44,16 @@ jobs: github.event.action == 'published' && startsWith(github.ref, 'refs/tags/') steps: - - name: Download artifact - uses: actions/download-artifact@v4.1.7 + - uses: actions/download-artifact@v4 with: - name: ${{ env.PACKAGE_NAME }}-${{ needs.build.outputs.COMMIT_SHA }}-dev + name: ${{ needs.build.outputs.name }}-${{ needs.build.outputs.sha }}-dev + path: artifact/ - - name: Packaging binaries - id: packaging - run: 7z a -mm=Deflate -mfb=258 -mpass=15 -r ${{ env.PACKAGE_NAME }}-${{ needs.build.outputs.SEMVER }}.zip + - name: Package for release + run: | + cd artifact + zip -r "../${{ needs.build.outputs.name }}-${{ github.ref_name }}.zip" . - - name: Publish artifacts - uses: softprops/action-gh-release@v2.0.4 - id: publish-job - if: | - startsWith(github.ref, 'refs/tags/') && - steps.packaging.outcome == 'success' + - uses: softprops/action-gh-release@v2 with: - files: | - *.zip + files: "${{ needs.build.outputs.name }}-*.zip" diff --git a/.gitignore b/.gitignore index 40b0a40..3ac5806 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.amxx *.zip -.build -plugins-*.ini +.env +.amxb-cache .claude +build +dist diff --git a/amxbuild.yml b/amxbuild.yml new file mode 100644 index 0000000..04f19af --- /dev/null +++ b/amxbuild.yml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/AmxxModularEcosystem/amxx-builder/master/schema/amxbuild.schema.json + +name: ParamsController + +amxmodx: + version: "1.10.5428" diff --git a/build.bat b/build.bat index e1a9fd9..a7bac20 100644 --- a/build.bat +++ b/build.bat @@ -1,94 +1,3 @@ @echo off -call config - -echo Copy includes to compiler... -if exist "%INCLUDES_PATH%" ( - call :copy %INCLUDES_PATH%\*, %AMXX_COMPILER_INCLUDES_PATH% -) - -echo Cleanup old compiled plugins... -call :deldir %COMPILER_OUTPUT_PATH% -call :deldir %AMXMODX_PATH%\plugins - -if not "%PACKAGE_COMPILED_PLUGINS_USE%" == "1" goto after-compile - -echo Prepare for compiling plugins... - -call :del %PLUGINS_LIST% -if "%PACKAGE_PLUINGS_LIST_USE%" == "1" ( - call :makedir %PLUGINS_LIST_PATH% - echo. 2>%PLUGINS_LIST% -) - -echo Compile plugins... - -call :makedir %COMPILER_OUTPUT_PATH% -cd %COMPILER_OUTPUT_PATH% -for /R %AMXMODX_PATH%\scripting %%F in (*.sma) do ( - echo. - echo Compile %%~nF: - - if "%PACKAGE_DEBUG%" == "1" ( - %AMXX_COMPILER_EXECUTABLE_PATH% DEBUG=1 %%F - ) else ( - %AMXX_COMPILER_EXECUTABLE_PATH% %%F - ) - - if errorlevel 1 ( - echo. - echo Plugin %%~nF compiled with error. - set /p q= - exit /b %errorlevel% - ) - - if "%PACKAGE_PLUINGS_LIST_USE%" == "1" ( - echo %%~nF.amxx>>%PLUGINS_LIST% - ) -) -cd %ROOT_PATH% -:after-compile - -echo Prepare files... -call :makedir %BUILD_AMXMODX_PATH% -call :copy "%AMXMODX_PATH%\*", "%BUILD_AMXMODX_PATH%" - -if "%PACKAGE_README_USE%" == "1" ( - call :copy "%README_FILE%", "%BUILD_ROOT_PATH%" -) - -if "%PACKAGE_ASSETS_USE%" == "1" ( - call :makedir %BUILD_ASSETS_PATH% - call :copy %ASSETS_PATH%\* %BUILD_ASSETS_PATH% -) - -echo Compress files to ZIP archive... -call :del %ZIP_FILE% -call :zip %BUILD_ROOT_PATH%/*, %ZIP_FILE% - -echo Cleanup temp files... -call :deldir %BUILD_ROOT_PATH% - -echo Build finished. - -exit 0 - -:makedir - if not exist %~1 mkdir %~1 -exit /b - -:deldir - if exist %~1 rd /S /q %~1 -exit /b - -:del - if exist %~1 del %~1 -exit /b - -:copy - powershell Copy-Item -Path %~1 -Destination %~2 -Recurse -Force -exit /b - -:zip - powershell Compress-Archive %~1 %~2 -Force -exit /b \ No newline at end of file +amxb build diff --git a/config.bat b/config.bat deleted file mode 100644 index 36b19b9..0000000 --- a/config.bat +++ /dev/null @@ -1,84 +0,0 @@ -@echo off - -echo Read build config: -set PACKAGE_NAME=Unnamed -set PACKAGE_AMXMODX_FOLDER=amxmodx -set PACKAGE_README_USE=0 - -set PACKAGE_ASSETS_USE=0 -set PACKAGE_ASSETS_FOLDER=assets - -set PACKAGE_COMPILED_PLUGINS_USE=1 -set PACKAGE_COMPILED_PLUGINS_SAVE=1 - -set PACKAGE_PLUINGS_LIST_USE=0 -set PACKAGE_PLUINGS_LIST_POSTFIX=unnamed -set PACKAGE_PLUINGS_LIST_SAVE=0 - -if not defined AMXX_COMPILER_DIR ( - set AMXX_COMPILER_DIR=C:\AmxModX\1.9.0 -) -if not defined AMXX_COMPILER_EXECUTABLE ( - set AMXX_COMPILER_EXECUTABLE=amxx190 -) -if not defined PACKAGE_DEBUG ( - set PACKAGE_DEBUG=0 -) - -for /f "delims== tokens=1,2" %%G in (.build-config) do set %%G=%%H -if exist (local.build-config) ( - for /f "delims== tokens=1,2" %%G in (local.build-config) do set %%G=%%H -) - -if "%PACKAGE_RELEASE%" == "1" ( - set PACKAGE_DEBUG=0 - set PACKAGE_COMPILED_PLUGINS_USE=0 - set PACKAGE_NAME=%PACKAGE_NAME%-release -) else if "%PACKAGE_DEBUG%" == "1" ( - set PACKAGE_NAME=%PACKAGE_NAME%-debug -) - -set AMXX_COMPILER_EXECUTABLE_PATH=%AMXX_COMPILER_DIR%\%AMXX_COMPILER_EXECUTABLE% - -set ROOT_PATH=%CD% -set AMXMODX_PATH=%ROOT_PATH%\%PACKAGE_AMXMODX_FOLDER% -set INCLUDES_PATH=%AMXMODX_PATH%\scripting\include -set README_FILE=%ROOT_PATH%\README.md -set ASSETS_PATH=%ROOT_PATH%\%PACKAGE_ASSETS_FOLDER% - -set BUILD_ROOT_PATH=%ROOT_PATH%\.build -set BUILD_PACKAGE_PATH=%BUILD_ROOT_PATH%\%PACKAGE_NAME% -set BUILD_AMXMODX_PATH=%BUILD_PACKAGE_PATH%\addons\amxmodx -set BUILD_ASSETS_PATH=%BUILD_PACKAGE_PATH% - -set AMXX_COMPILER_INCLUDES_PATH=%AMXX_COMPILER_DIR%\include - -set PLUGINS_LIST_PATH=%BUILD_AMXMODX_PATH%\configs -if "%PACKAGE_PLUINGS_LIST_SAVE%" == "1" ( - set PLUGINS_LIST_PATH=%AMXMODX_PATH%\configs -) -set PLUGINS_LIST=%PLUGINS_LIST_PATH%\plugins-%PACKAGE_PLUINGS_LIST_POSTFIX%.ini - -set COMPILER_OUTPUT_PATH=%BUILD_AMXMODX_PATH%\plugins -if "%PACKAGE_COMPILED_PLUGINS_SAVE%" == "1" ( - set COMPILER_OUTPUT_PATH=%AMXMODX_PATH%\plugins -) - -set ZIP_FILE=%ROOT_PATH%\%PACKAGE_NAME%.zip - - -echo - PACKAGE_NAME = %PACKAGE_NAME% -echo - PACKAGE_README_USE = %PACKAGE_PLUINGS_LIST_USE% - -echo - PACKAGE_COMPILED_PLUGINS_USE = %PACKAGE_COMPILED_PLUGINS_USE% -echo - PACKAGE_COMPILED_PLUGINS_SAVE = %PACKAGE_COMPILED_PLUGINS_SAVE% - -echo - PACKAGE_PLUINGS_LIST_USE = %PACKAGE_PLUINGS_LIST_USE% -echo - PACKAGE_PLUINGS_LIST_POSTFIX = %PACKAGE_PLUINGS_LIST_POSTFIX% -echo - PACKAGE_PLUINGS_LIST_SAVE = %PACKAGE_PLUINGS_LIST_SAVE% - -echo - AMXX_COMPILER_DIR = %AMXX_COMPILER_DIR% -echo - AMXX_COMPILER_EXECUTABLE = %AMXX_COMPILER_EXECUTABLE% -echo. -echo - PACKAGE_DEBUG = %PACKAGE_DEBUG% -echo. From c31d8f94c53b76fd38360ca32e6f9c261e3e2f16 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 1 Jun 2026 01:13:33 +0300 Subject: [PATCH 26/30] Add unknown param type handling --- .../scripting/ParamsController/API/Param.inc | 21 ++++++++----- .../ParamsController/Objects/Param.inc | 31 ++++++++++++++----- .../scripting/include/ParamsController.inc | 14 ++++++++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/amxmodx/scripting/ParamsController/API/Param.inc b/amxmodx/scripting/ParamsController/API/Param.inc index 2e621c3..8e995ee 100644 --- a/amxmodx/scripting/ParamsController/API/Param.inc +++ b/amxmodx/scripting/ParamsController/API/Param.inc @@ -19,14 +19,21 @@ T_Param:@API_Param_Construct() { new paramTypeName[PARAM_TYPE_NAME_MAX_LEN + PARAM_TYPE_TAG_MAX_LEN + 1]; get_string(Arg_ParamTypeName, paramTypeName, charsmax(paramTypeName)); - new bool:required = !!get_param(Arg_Required); - - new typeName[PARAM_TYPE_NAME_MAX_LEN], paramTag[PARAM_TYPE_TAG_MAX_LEN]; - ParamType_GetTypeAndTag(paramTypeName, - typeName, charsmax(typeName), - paramTag, charsmax(paramTag) + new bool:bRequired = !!get_param(Arg_Required); + new sParamType[PARAM_TYPE_NAME_MAX_LEN], sParamTag[PARAM_TYPE_TAG_MAX_LEN]; + ParamType_GetTypeAndTag(sParamTypeName, + sParamType, charsmax(sParamType), + sParamTag, charsmax(sParamTag) ); - new T_ParamType:paramType = ParamType_Find(typeName, .orFail = true); + new T_ParamType:iParamType = ParamType_Find(sParamType, .orFail = false); + + if (iParamType == Invalid_ParamType) { + if (bRequired) { + log_error(AMX_ERR_PARAMS, "Param '%s' is required, but type '%s' is not registered.", sKey, sParamType); + } + + return Param_Construct(sKey, iParamType, bRequired, sParamTypeName); + } return Param_Construct(key, paramType, required, paramTag); } diff --git a/amxmodx/scripting/ParamsController/Objects/Param.inc b/amxmodx/scripting/ParamsController/Objects/Param.inc index a34bcaf..dc34be9 100644 --- a/amxmodx/scripting/ParamsController/Objects/Param.inc +++ b/amxmodx/scripting/ParamsController/Objects/Param.inc @@ -13,6 +13,7 @@ enum _:S_Param { T_ParamType:Param_Type, bool:Param_Required, Param_Tag[PARAM_TYPE_TAG_MAX_LEN], + bool:Param_IsUnknownType, } static Array:Params = Invalid_Array; @@ -38,15 +39,11 @@ T_Param:Param_Construct( ) { new paramObject[S_Param]; - if (type == Invalid_ParamType) { - abort(AMX_ERR_PARAMS, "Invalid param type handler (%d).", type); - return Invalid_Param; - } - copy(paramObject[Param_Key], charsmax(paramObject[Param_Key]), key); paramObject[Param_Type] = type; paramObject[Param_Required] = isRequired; copy(paramObject[Param_Tag], charsmax(paramObject[Param_Tag]), tag); + paramObject[Param_IsUnknownType] = (type == Invalid_ParamType); if (IsStackEmpty(FreeParamHandlers)) { return T_Param:ArrayPushArray(Params, paramObject); @@ -60,7 +57,7 @@ T_Param:Param_Construct( static Param_Get(const T_Param:param, paramObject[S_Param]) { ArrayGetArray(Params, _:param, paramObject); - if (paramObject[Param_Type] == Invalid_ParamType) { + if (paramObject[Param_Type] == Invalid_ParamType && !paramObject[Param_IsUnknownType]) { abort(AMX_ERR_PARAMS, "Invalid param type handler (%d).", paramObject[Param_Type]); } } @@ -70,13 +67,18 @@ Param_iGetType(const T_Param:param) { Param_Get(param, paramObject); new typeName[PARAM_TYPE_NAME_MAX_LEN]; - ParamType_GetName(paramObject[Param_Type], typeName, charsmax(typeName)); + if (paramObject[Param_IsUnknownType]) { + copy(typeName, charsmax(typeName), paramObject[Param_Tag]); + } else { + ParamType_GetName(paramObject[Param_Type], typeName, charsmax(typeName)); + } return typeName; } T_Param:Param_Free(&T_Param:param) { ArraySetCell(Params, _:param, Invalid_ParamType, Param_Type); + ArraySetCell(Params, _:param, false, Param_IsUnknownType); PushStackCell(FreeParamHandlers, param); return param = Invalid_Param; } @@ -84,7 +86,11 @@ T_Param:Param_Free(&T_Param:param) { bool:Param_Read(const T_Param:param, const JSON:valueJson, const Trie:p) { new paramObject[S_Param]; Param_Get(param, paramObject); - + + if (paramObject[Param_IsUnknownType]) { + return false; + } + return ParamType_Read(paramObject[Param_Type], valueJson, p, paramObject[Param_Key], paramObject[Param_Tag]); } @@ -114,6 +120,15 @@ Trie:Param_ReadList( copy(errParamName, errParamNameLen, paramObject[Param_Key]); } + if (paramObject[Param_IsUnknownType]) { + if (paramObject[Param_Required]) { + iErrType = ParamsReadError_UnknownParamType; + return p; + } + + continue; + } + if (!json_object_has_value(objectJson, paramObject[Param_Key])) { if (paramObject[Param_Required]) { errType = ParamsReadError_RequiredParamNotPresented; diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index 188fd2d..e77f330 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -94,6 +94,14 @@ enum E_ParamsReadErrorType { * Может возникнуть даже для необязательных параметров. */ ParamsReadError_ParamValueIsInvalid, + + /** + * Тип параметра не был зарегистрирован на момент создания параметра. + * + * Возникает только для обязательных параметров с неизвестным типом. + * Для необязательных параметров с неизвестным типом значение тихо пропускается. + */ + ParamsReadError_UnknownParamType, } /** @@ -195,8 +203,12 @@ stock T_ParamType:ParamsController_RegSimpleType(const sParamTypeName[], const s * @param sKey Ключ параметра (по этому ключу будет читаться параметр из JSON-обьекта и по нему же значение будет заноситься в trie) * @param sParamTypeName Название типа параметра * @param bRequired Является ли параметр обязательным - * + * * @note Если параметр был создан для разового использования, он может быть удалён нативом ParamsController_Param_Free(). + * @note Если тип sParamTypeName не зарегистрирован, параметр всё равно создаётся, но помечается как с неизвестным типом. + * При чтении такого параметра через ReadList: + * - обязательный → ошибка ParamsReadError_UnknownParamType (и log_error при вызове этого натива); + * - необязательный → тихо пропускается, даже если поле есть в JSON. * * @return Хендлер созданного параметра */ From 7057fb4ce881a7a5f218561fe6393a4c927d668e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 1 Jun 2026 01:20:40 +0300 Subject: [PATCH 27/30] Show warn when param with unknown param is specified --- amxmodx/scripting/ParamsController/Objects/Param.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/amxmodx/scripting/ParamsController/Objects/Param.inc b/amxmodx/scripting/ParamsController/Objects/Param.inc index dc34be9..bf45f36 100644 --- a/amxmodx/scripting/ParamsController/Objects/Param.inc +++ b/amxmodx/scripting/ParamsController/Objects/Param.inc @@ -126,6 +126,7 @@ Trie:Param_ReadList( return p; } + PCJson_LogForFile(objectJson, "WARNING", "Param '%s' has unknown type '%s', skipping.", paramObject[Param_Key], paramObject[Param_Tag]); continue; } From 6e909e5f3f67056bfa3167ff56bcac539b633659 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Mon, 1 Jun 2026 01:22:08 +0300 Subject: [PATCH 28/30] Bump version --- amxmodx/scripting/include/ParamsController.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index e77f330..8079f81 100644 --- a/amxmodx/scripting/include/ParamsController.inc +++ b/amxmodx/scripting/include/ParamsController.inc @@ -37,7 +37,7 @@ stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; stock const DEFAULT_PARAMS_PH_TEMPLATE_NAME[] = "PH-Template"; -#define PARAMS_CONTROLLER_VERSION "1.4.2" +#define PARAMS_CONTROLLER_VERSION "1.4.3" stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; From 31dbbc02527a808bfcf9bafa55fc61ce6374b9eb Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Thu, 27 Aug 2026 23:55:56 +0300 Subject: [PATCH 29/30] Add OC cfg with amxb --- .opencode/opencode.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .opencode/opencode.json diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 0000000..2044b26 --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "amxx-dep-resolver": { + "type": "local", + "command": [ + "amxb", + "mcp" + ], + "enabled": true + } + } +} From c430afd7ba2e91f01b0f0e95f9450b3c57a44113 Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Fri, 28 Aug 2026 02:48:58 +0300 Subject: [PATCH 30/30] Fix placeholders and cleanup repo --- .gitignore | 1 + AGENTS.md | 73 + CLAUDE.md | 42 +- .../scripting/ParamsController/API/Param.inc | 8 +- .../DefaultObjects/Placeholder/Global.inc | 5 +- .../DefaultObjects/Placeholder/Player.inc | 1 - .../ParamsController/Objects/Param.inc | 2 +- .../Placeholders/Objects/PHGroup.inc | 128 +- .../Placeholders/Objects/PHTemplate.inc | 53 +- .../ParamsController/Placeholders/PHParse.inc | 68 + build/amxmodx/scripting/ParamsController.sma | 57 - .../ParamsController/API/General.inc | 33 - .../scripting/ParamsController/API/Param.inc | 104 - .../ParamsController/API/ParamType.inc | 30 - .../scripting/ParamsController/API/Utils.inc | 18 - .../DefaultObjects/ParamType/Boolean.inc | 48 - .../DefaultObjects/ParamType/ChatMessage.inc | 18 - .../DefaultObjects/ParamType/Dir.inc | 19 - .../DefaultObjects/ParamType/File.inc | 19 - .../DefaultObjects/ParamType/Flags.inc | 14 - .../DefaultObjects/ParamType/Float.inc | 27 - .../DefaultObjects/ParamType/Integer.inc | 27 - .../DefaultObjects/ParamType/Model.inc | 24 - .../DefaultObjects/ParamType/PlayerModel.inc | 29 - .../DefaultObjects/ParamType/RGB.inc | 64 - .../DefaultObjects/ParamType/Regexp.inc | 24 - .../DefaultObjects/ParamType/Resource.inc | 24 - .../DefaultObjects/ParamType/Sound.inc | 27 - .../DefaultObjects/ParamType/String.inc | 30 - .../DefaultObjects/ParamType/Time.inc | 35 - .../DefaultObjects/ParamType/TimeInterval.inc | 63 - .../DefaultObjects/ParamType/WeekDay.inc | 44 - .../DefaultObjects/Registrar.inc | 43 - .../scripting/ParamsController/Forwards.inc | 71 - .../ParamsController/Objects/Param.inc | 157 -- .../ParamsController/Objects/ParamType.inc | 163 -- .../scripting/include/ParamsController.inc | 1671 ----------------- 37 files changed, 234 insertions(+), 3030 deletions(-) create mode 100644 AGENTS.md create mode 100644 amxmodx/scripting/ParamsController/Placeholders/PHParse.inc delete mode 100644 build/amxmodx/scripting/ParamsController.sma delete mode 100644 build/amxmodx/scripting/ParamsController/API/General.inc delete mode 100644 build/amxmodx/scripting/ParamsController/API/Param.inc delete mode 100644 build/amxmodx/scripting/ParamsController/API/ParamType.inc delete mode 100644 build/amxmodx/scripting/ParamsController/API/Utils.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc delete mode 100644 build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc delete mode 100644 build/amxmodx/scripting/ParamsController/Forwards.inc delete mode 100644 build/amxmodx/scripting/ParamsController/Objects/Param.inc delete mode 100644 build/amxmodx/scripting/ParamsController/Objects/ParamType.inc delete mode 100644 build/amxmodx/scripting/include/ParamsController.inc diff --git a/.gitignore b/.gitignore index 3ac5806..41ebc4a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .claude build dist +.omo diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6d54eb1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md + +Guidance for AI agents working with code in this repository. + +## Build + +**Requirements:** [amxx-builder](https://github.com/AmxxModularEcosystem/amxx-builder) (`amxb`). The manifest is `amxbuild.yml` — it pins the AMXX compiler version (`amxmodx.version`) and declares include dependencies (`deps`). + +```bat +build.bat :: local wrapper around `amxb build` +amxb build :: actual build command +``` + +Output goes to `dist/` (or `ParamsController.zip` at repo root when packaging). Build settings live in `amxbuild.yml`. No `deps` are currently declared — the plugin intentionally has no third-party include dependencies (ReAPI was removed; default placeholder groups use only AMXX core natives). + +There are no test or lint commands — the CI pipeline (`.github/workflows/CI.yml`) runs the same `amxb build` as the validation step. + +> The plugin must compile without warnings that are errors — CI treats compiler output as the gate. Run `amxb build` before pushing. + +## Architecture + +ParamsController is an AmxModX plugin library (written in PAWN) that provides: + +1. **Parameter parsing** — a JSON-based typed parameter framework. A consumer plugin calls `ParamsController_Init()`, constructs typed parameters with `ParamsController_Param_Construct()`, then reads a JSON object via `ParamsController_Param_ReadList()`. Each parameter is validated against its registered type handler. +2. **Placeholder system** — a global/per-player string substitution framework (groups, keys, contexts, proxy groups, compiled templates). Full design is documented in `PLACEHOLDERS.md`. + +### Layer structure + +| Layer | Path | Role | +|---|---|---| +| Public API | `amxmodx/scripting/include/ParamsController.inc` | All native/forward declarations + stock helpers; this is what consumers include | +| Main plugin | `amxmodx/scripting/ParamsController.sma` | Entry point, library registration, init flow, forward registration | +| Params API impl | `ParamsController/API/` | Native implementations (General, Param, ParamType, Utils) | +| Placeholder API impl | `ParamsController/Placeholders/API/` | `PCPH_*` native implementations (General, Group, Key) | +| Object model | `ParamsController/Objects/` | `Param.inc` (Array + free-list storage), `ParamType.inc` (Trie-keyed type registry, current-param stack) | +| Placeholder objects | `ParamsController/Placeholders/Objects/` | `PHGroup.inc` (group registry, contexts, values, callback stack), `PHTemplate.inc` (compiled template cache) | +| Placeholder utils | `ParamsController/Placeholders/PHParse.inc` | Shared placeholder parsing (`PHParse_FindClosing`, `PHParse_Placeholder`) used by inline formatting and template compilation | +| Built-in types | `ParamsController/DefaultObjects/ParamType/` | 18 files registering 20 type names: Boolean, Integer, Float, String/ShortString/LongString, RGB, Model, PlayerModel, Sound, Resource, File, Dir, ChatMessage, Time, TimeInterval, WeekDay, Flags, Regexp, PH-Template | +| Default placeholders | `ParamsController/DefaultObjects/Placeholder/` | `Global.inc`, `Player.inc` — built-in groups (`{map}`, `{real-map}`, `{server-name}`, `{server-ip}`, `{date}`, `{time}`, `{datetime}`, `{players-count}`, `{max-players-count}`; `{p:authid}`, `{p:name}`, `{p:ip}`, `{p:userid}`) | +| Registrar | `ParamsController/DefaultObjects/Registrar.inc` | Wires up all default types and placeholder groups on plugin load | +| Forwards | `ParamsController/Forwards.inc` | Generic forward registration utility used throughout | + +### Key design patterns + +- **Parameter storage:** Array-based with a free-list for handle reuse (`Objects/Param.inc`) +- **Type registry:** Trie-keyed by type name string (`Objects/ParamType.inc`); read callbacks are `CreateOneForward`-based, so plugin-owned +- **Current param context:** `CurrentParams`/`CurrentParamName` globals + a save/restore stack, so read callbacks can nest (types that internally read other types via `PCSingle_*`) +- **Placeholder groups:** Array-backed, Trie-keyed by prefix (`Placeholders/Objects/PHGroup.inc`); values stored as `"key:contextKey"` strings; contexts are per-group stacks +- **Proxy groups:** alias a source group's keys/callbacks/values but keep an independent context stack (two players in one string) +- **Callback reentrancy:** the currently-active callback (group/key/context) is kept on a stack (`S_PHCurrentCallback`), so `PCPH_Format`/`PCPH_Cb_Set*` can be called from inside a callback — nested calls restore the outer state +- **Compiled templates:** `PCPH_CompileTemplate` pre-parses a format string into parts and caches group/forward handles (`Placeholders/Objects/PHTemplate.inc`) +- **Extensibility:** consumers can register custom parameter types (`ParamsController_ParamType_Register` / `ParamsController_RegSimpleType`) and placeholder groups/keys via `PCPH_*` natives +- **Error reporting:** `E_ParamsReadErrorType` enum returned from `ReadList`; error details (param name, expected/got type) accessible via out-params of `ParamsController_Param_ReadList` + +### Init flow (`PluginInit` in `ParamsController.sma`) + +1. `Forwards_Init()`, `Param_Init()`, `PHGroup_Init()` — allocate storages +2. `DefaultObjects_Register()` — register built-in param types + default placeholder groups +3. Forward `ParamsController_OnRegisterTypes` — consumers register custom param types +4. Forward `PCPH_OnRegisterGroups` — consumers register placeholder groups/keys +5. Forward `PCPH_OnRegisterProxyGroups` — consumers register proxy groups (all normal groups already exist) +6. Forward `ParamsController_OnInited` — everything is ready + +### Language notes + +PAWN uses `#include` for all modular decomposition. `.inc` files are not independently compiled — they are all included into `ParamsController.sma`. The public header (`ParamsController.inc`) only declares natives/forwards and stock helpers; it contains no plugin state or implementation logic. + +## Conventions + +- Natives are named `ParamsController_*` (params) and `PCPH_*` (placeholders); implementation handlers use the `@` prefix (e.g. `@API_Param_Construct`) and `register_native(name, "@handler")`. +- All buffers that cross the callback boundary should be local (`new`), not `static` — placeholder callbacks can re-enter the formatter. +- Keep the manifest free of undeclared includes: any `#include <...>` beyond AMXX stdlib must be declared in `amxbuild.yml` `deps` (or removed). +- Do not commit build output — `dist/`, `build/`, `.amxb-cache/` are git-ignored. diff --git a/CLAUDE.md b/CLAUDE.md index 8c3051f..341212e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,44 +2,4 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Build - -**Requirements:** AmxModX 1.9.0+, AMxModX compiler configured as `amxx190` in `config.bat` - -```bat -build.bat -``` - -Output goes to `.build/` directory, final artifact is a ZIP package. Build settings are in `.build-config`. Нужен только для локальной сборки. - -There are no test or lint commands — the CI pipeline (`.github/workflows/CI.yml`) compiles all `.sma` files as the validation step. - -## Architecture - -ParamsController is an AmxModX plugin library (written in PAWN) that provides a JSON-based parameter parsing framework for other plugins. - -**Core flow:** A consumer plugin calls `ParamsController_Init()`, constructs typed parameters with `ParamsController_Param_Construct()`, then reads a JSON object using `ParamsController_Param_ReadList()`. Each parameter is validated against its registered type handler. - -### Layer structure - -| Layer | Path | Role | -|---|---|---| -| Public API | `amxmodx/scripting/include/ParamsController.inc` | All native/forward declarations; this is what consumers include | -| Main plugin | `amxmodx/scripting/ParamsController.sma` | Entry point, library registration, forward init | -| API impl | `ParamsController/API/` | Native implementations (General, Param, ParamType, Utils) | -| Object model | `ParamsController/Objects/` | `Param.inc` (Array + free-list storage), `ParamType.inc` (Trie-keyed type registry) | -| Built-in types | `ParamsController/DefaultObjects/ParamType/` | 14 type handlers: Boolean, Integer, Float, String, RGB, Model, Sound, Resource, File, Dir, ChatMessage, Time, TimeInterval, WeekDay, Flags | -| Placeholders | `ParamsController/Placeholders/` | Global and per-player placeholder substitution system | -| Registrars | `ParamsController/DefaultObjects/Registrar.inc`, `PlaceholderRegistrar.inc` | Wire up default types and placeholders on plugin load | -| Forwards | `ParamsController/Forwards.inc` | Generic forward registration utility used throughout | - -### Key design patterns - -- **Parameter storage:** Array-based with a free-list for handle reuse (`Param.inc`) -- **Type registry:** Trie-keyed by type name string (`ParamType.inc`) -- **Extensibility:** `CreateMultiForward` / `ExecuteForward` — consumers can register custom parameter types and placeholder groups via natives -- **Error reporting:** `E_ParamsReadErrorType` enum returned from `ReadList`; error details (param name, expected/got type) accessible via `ParamsController_GetErrorInfo()` - -### Language notes - -PAWN uses `#include` for all modular decomposition. `.inc` files are not independently compiled — they are all included into `ParamsController.sma`. The public header (`ParamsController.inc`) only declares natives/forwards; it contains no implementation. +All repository guidance lives in [AGENTS.md](AGENTS.md) — build instructions, architecture, conventions, and language notes. Read it before working with the code. diff --git a/amxmodx/scripting/ParamsController/API/Param.inc b/amxmodx/scripting/ParamsController/API/Param.inc index 8e995ee..c762e0a 100644 --- a/amxmodx/scripting/ParamsController/API/Param.inc +++ b/amxmodx/scripting/ParamsController/API/Param.inc @@ -21,7 +21,7 @@ T_Param:@API_Param_Construct() { new bool:bRequired = !!get_param(Arg_Required); new sParamType[PARAM_TYPE_NAME_MAX_LEN], sParamTag[PARAM_TYPE_TAG_MAX_LEN]; - ParamType_GetTypeAndTag(sParamTypeName, + ParamType_GetTypeAndTag(paramTypeName, sParamType, charsmax(sParamType), sParamTag, charsmax(sParamTag) ); @@ -29,13 +29,13 @@ T_Param:@API_Param_Construct() { if (iParamType == Invalid_ParamType) { if (bRequired) { - log_error(AMX_ERR_PARAMS, "Param '%s' is required, but type '%s' is not registered.", sKey, sParamType); + log_error(AMX_ERR_PARAMS, "Param '%s' is required, but type '%s' is not registered.", key, sParamType); } - return Param_Construct(sKey, iParamType, bRequired, sParamTypeName); + return Param_Construct(key, iParamType, bRequired, paramTypeName); } - return Param_Construct(key, paramType, required, paramTag); + return Param_Construct(key, iParamType, bRequired, sParamTag); } // native Trie:ParamsController_Param_ReadList(const Array:params, const JSON:objectJson, &Trie:append = Invalid_Trie, &E_ParamsReadErrorType:errType, errParamName[], const errParamNameLen = 0); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc index 592ab1d..80efce3 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc @@ -1,14 +1,13 @@ #include -#include #include DefaultObjects_PH_Global_Register() { new value[PH_VALUE_MAX_LEN]; - rh_get_mapname(value, charsmax(value), MNT_SET); + get_mapname(value, charsmax(value)); PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_MAP_KEY, value); - rh_get_mapname(value, charsmax(value), MNT_TRUE); + get_mapname(value, charsmax(value)); PCPH_Gl_ConstKey(DEFAULT_PH_GLOBAL_REAL_MAP_KEY, value); get_user_name(0, value, charsmax(value)); diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc index a8f829f..4a7d71a 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -1,5 +1,4 @@ #include -#include #include static T_PHGroup:PlayerGroup = Invalid_PHGroup; diff --git a/amxmodx/scripting/ParamsController/Objects/Param.inc b/amxmodx/scripting/ParamsController/Objects/Param.inc index bf45f36..3ef80f8 100644 --- a/amxmodx/scripting/ParamsController/Objects/Param.inc +++ b/amxmodx/scripting/ParamsController/Objects/Param.inc @@ -122,7 +122,7 @@ Trie:Param_ReadList( if (paramObject[Param_IsUnknownType]) { if (paramObject[Param_Required]) { - iErrType = ParamsReadError_UnknownParamType; + errType = ParamsReadError_UnknownParamType; return p; } diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc index 54e96a3..af20fa6 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -5,6 +5,7 @@ #include #include +#include "ParamsController/Placeholders/PHParse" enum _:S_PHGroup { PHGroup_Prefix[PH_GROUP_PREFIX_MAX_LEN], @@ -23,6 +24,50 @@ static T_PHGroup:CurrentPHGroup = Invalid_PHGroup; static CurrentPHKey[PH_KEY_MAX_LEN]; static CurrentPHContext[PH_CONTEXT_KEY_MAX_LEN]; +// Стек состояния текущего колбека. Позволяет вызывать PCPH_Format (и другие +// колбеки) рекурсивно изнутри колбека ключа: после вложенного вызова состояние +// внешнего колбека (группа/ключ/контекст) восстанавливается, и PCPH_Cb_Set* +// во внешнем колбеке продолжает работать с корректными значениями. +enum _:S_PHCurrentCallback { + T_PHGroup:PHCurrentCallback_Group, + PHCurrentCallback_Key[PH_KEY_MAX_LEN], + PHCurrentCallback_Context[PH_CONTEXT_KEY_MAX_LEN], +} +static Stack:CurrentPHCallbacks = Invalid_Stack; + +static PHGroup_PushCurrentCallback(const T_PHGroup:group, const key[], const contextKey[]) { + if (CurrentPHCallbacks == Invalid_Stack) { + CurrentPHCallbacks = CreateStack(S_PHCurrentCallback); + } + + new entry[S_PHCurrentCallback]; + entry[PHCurrentCallback_Group] = CurrentPHGroup; + copy(entry[PHCurrentCallback_Key], charsmax(entry[PHCurrentCallback_Key]), CurrentPHKey); + copy(entry[PHCurrentCallback_Context], charsmax(entry[PHCurrentCallback_Context]), CurrentPHContext); + PushStackArray(CurrentPHCallbacks, entry); + + CurrentPHGroup = group; + copy(CurrentPHKey, charsmax(CurrentPHKey), key); + copy(CurrentPHContext, charsmax(CurrentPHContext), contextKey); +} + +static PHGroup_PopCurrentCallback() { + new entry[S_PHCurrentCallback]; + if ( + CurrentPHCallbacks != Invalid_Stack + && !IsStackEmpty(CurrentPHCallbacks) + ) { + PopStackArray(CurrentPHCallbacks, entry); + CurrentPHGroup = T_PHGroup:entry[PHCurrentCallback_Group]; + copy(CurrentPHKey, charsmax(CurrentPHKey), entry[PHCurrentCallback_Key]); + copy(CurrentPHContext, charsmax(CurrentPHContext), entry[PHCurrentCallback_Context]); + } else { + CurrentPHGroup = Invalid_PHGroup; + CurrentPHKey[0] = EOS; + CurrentPHContext[0] = EOS; + } +} + PHGroup_Init() { if (PHGroups == Invalid_Array) { PHGroups = ArrayCreate(S_PHGroup, 1); @@ -100,6 +145,12 @@ T_PHGroup:PHGroup_GetGlobal() { return PHGlobalGroup; } +// Префикс → группа: пустой префикс — глобальная группа. +// Общий хелпер для инлайн-форматирования и компиляции шаблонов. +T_PHGroup:PHGroup_ResolveByPrefix(const prefix[]) { + return (prefix[0] == EOS) ? PHGlobalGroup : PHGroup_Find(prefix); +} + static PHGroup_Get(const T_PHGroup:group, groupData[S_PHGroup]) { ArrayGetArray(PHGroups, _:group, groupData); } @@ -229,16 +280,12 @@ static PHGroup_CallCallback(const T_PHGroup:group, const key[], const contextKey return; } - CurrentPHGroup = group; - copy(CurrentPHKey, charsmax(CurrentPHKey), key); - copy(CurrentPHContext, charsmax(CurrentPHContext), contextKey); + PHGroup_PushCurrentCallback(group, key, contextKey); new ret; ExecuteForward(fwd, ret, key, contextKey); - CurrentPHGroup = Invalid_PHGroup; - CurrentPHKey[0] = EOS; - CurrentPHContext[0] = EOS; + PHGroup_PopCurrentCallback(); } PHGroup_FormatString(const src[], out[], const outLen) { @@ -256,55 +303,25 @@ PHGroup_FormatString(const src[], out[], const outLen) { continue; } - new closing = inPos + 1; - while (closing < srcLen && src[closing] != '}') { - ++closing; - } - - if (closing >= srcLen) { + new closing; + if (!PHParse_FindClosing(src, srcLen, inPos, closing)) { // Незакрытая скобка — выводим как есть out[outPos++] = src[inPos++]; continue; } - static placeholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; - new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); + new prefix[PH_GROUP_PREFIX_MAX_LEN], key[PH_KEY_MAX_LEN]; + new bool:isPlaceholder = PHParse_Placeholder(src, inPos, closing, prefix, charsmax(prefix), key, charsmax(key)); - for (new k = 0; k < placeholderLen; ++k) { - placeholder[k] = src[inPos + 1 + k]; + new T_PHGroup:group = Invalid_PHGroup; + if (isPlaceholder) { + group = PHGroup_ResolveByPrefix(prefix); } - placeholder[placeholderLen] = 0; - - static prefix[PH_GROUP_PREFIX_MAX_LEN]; - static key[PH_KEY_MAX_LEN]; - new colon = -1; - for (new k = 0; k < placeholderLen; ++k) { - if (placeholder[k] == ':') { - colon = k; - break; - } - } - - if (colon < 0) { - prefix[0] = 0; - copy(key, charsmax(key), placeholder); - } else { - new prefixLen = min(colon, charsmax(prefix)); - for (new k = 0; k < prefixLen; ++k) { - prefix[k] = placeholder[k]; - } - prefix[prefixLen] = 0; - copy(key, charsmax(key), placeholder[colon + 1]); - } - - new T_PHGroup:group = (prefix[0] == 0) - ? PHGlobalGroup - : PHGroup_Find(prefix); - - if (group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { - // Захватываем контекст до вызова колбека - static currentContext[PH_CONTEXT_KEY_MAX_LEN]; + if (isPlaceholder && group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { + // Захватываем контекст до вызова колбека (локальный буфер — + // вложенный вызов PCPH_Format из колбека не должен его затирать) + new currentContext[PH_CONTEXT_KEY_MAX_LEN]; new groupData[S_PHGroup]; PHGroup_Get(group, groupData); copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); @@ -319,10 +336,11 @@ PHGroup_FormatString(const src[], out[], const outLen) { } } } else { - // Неизвестная группа или незарегистрированный ключ — сохраняем как есть + // Слишком длинный, неизвестная группа или незарегистрированный ключ — + // сохраняем как есть (литеральный текст с фигурными скобками) out[outPos++] = '{'; - for (new k = 0; k < placeholderLen && outPos < outLen - 1; ++k) { - out[outPos++] = placeholder[k]; + for (new k = 0; k < closing - inPos - 1 && outPos < outLen - 1; ++k) { + out[outPos++] = src[inPos + 1 + k]; } if (outPos < outLen - 1) { out[outPos++] = '}'; @@ -348,22 +366,18 @@ PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { // Вызывает колбек (если fwd != INVALID_HANDLE) и записывает текущее значение ключа в out. // Используется скомпилированными шаблонами: группа и форвард уже известны, контекст берётся в рантайме. bool:PHGroup_FillValue(const T_PHGroup:group, const key[], const fwd, out[], const outLen) { - static currentContext[PH_CONTEXT_KEY_MAX_LEN]; + new currentContext[PH_CONTEXT_KEY_MAX_LEN]; new groupData[S_PHGroup]; PHGroup_Get(group, groupData); copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); if (fwd != INVALID_HANDLE) { - CurrentPHGroup = group; - copy(CurrentPHKey, charsmax(CurrentPHKey), key); - copy(CurrentPHContext, charsmax(CurrentPHContext), currentContext); + PHGroup_PushCurrentCallback(group, key, currentContext); new ret; ExecuteForward(fwd, ret, key, currentContext); - CurrentPHGroup = Invalid_PHGroup; - CurrentPHKey[0] = EOS; - CurrentPHContext[0] = EOS; + PHGroup_PopCurrentCallback(); } return PHGroup_GetValue(group, key, currentContext, out, outLen); diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc index 1aa5cca..3bdd088 100644 --- a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc @@ -6,6 +6,7 @@ #include #include #include "ParamsController/Placeholders/Objects/PHGroup" +#include "ParamsController/Placeholders/PHParse" enum E_PHTemplatePartType { PHTemplatePartType_Text, @@ -47,7 +48,6 @@ T_PHTemplate:PHTemplate_Compile(const src[]) { static text[PH_FORMAT_MAX_LEN]; new textLen = 0; - static placeholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; static prefix[PH_GROUP_PREFIX_MAX_LEN]; static key[PH_KEY_MAX_LEN]; @@ -60,12 +60,8 @@ T_PHTemplate:PHTemplate_Compile(const src[]) { continue; } - new closing = inPos + 1; - while (closing < srcLen && src[closing] != '}') { - ++closing; - } - - if (closing >= srcLen) { + new closing; + if (!PHParse_FindClosing(src, srcLen, inPos, closing)) { // Незакрытая скобка — выводим как есть if (textLen < charsmax(text)) { text[textLen++] = src[inPos]; @@ -79,37 +75,14 @@ T_PHTemplate:PHTemplate_Compile(const src[]) { textLen = 0; } - new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); - for (new k = 0; k < placeholderLen; ++k) { - placeholder[k] = src[inPos + 1 + k]; - } - placeholder[placeholderLen] = EOS; + new bool:isPlaceholder = PHParse_Placeholder(src, inPos, closing, prefix, charsmax(prefix), key, charsmax(key)); - new colon = -1; - for (new k = 0; k < placeholderLen; ++k) { - if (placeholder[k] == ':') { - colon = k; - break; - } + new T_PHGroup:group = Invalid_PHGroup; + if (isPlaceholder) { + group = PHGroup_ResolveByPrefix(prefix); } - if (colon < 0) { - prefix[0] = EOS; - copy(key, charsmax(key), placeholder); - } else { - new prefixLen = min(colon, charsmax(prefix)); - for (new k = 0; k < prefixLen; ++k) { - prefix[k] = placeholder[k]; - } - prefix[prefixLen] = EOS; - copy(key, charsmax(key), placeholder[colon + 1]); - } - - new T_PHGroup:group = (prefix[0] == EOS) - ? PHGroup_GetGlobal() - : PHGroup_Find(prefix); - - if (group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { + if (isPlaceholder && group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { static partData[S_PHTemplatePart]; partData[PHTemplatePart_Type] = PHTemplatePartType_Placeholder; copy(partData[PHTemplatePart_Content], charsmax(partData[PHTemplatePart_Content]), key); @@ -117,11 +90,11 @@ T_PHTemplate:PHTemplate_Compile(const src[]) { partData[PHTemplatePart_Forward] = PHGroup_GetKeyForward(group, key); ArrayPushArray(parts, partData); } else { - // Неизвестный плейсхолдер — сохраняем как текст с фигурными скобками - text[0] = '{'; - textLen = 1; - for (new k = 0; k < placeholderLen && textLen < charsmax(text); ++k) { - text[textLen++] = placeholder[k]; + // Слишком длинный, неизвестная группа или незарегистрированный ключ — + // фиксируем как текст с фигурными скобками + text[textLen++] = '{'; + for (new k = 0; k < closing - inPos - 1 && textLen < charsmax(text); ++k) { + text[textLen++] = src[inPos + 1 + k]; } if (textLen < charsmax(text)) { text[textLen++] = '}'; diff --git a/amxmodx/scripting/ParamsController/Placeholders/PHParse.inc b/amxmodx/scripting/ParamsController/Placeholders/PHParse.inc new file mode 100644 index 0000000..ae47ca0 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/PHParse.inc @@ -0,0 +1,68 @@ +#if defined __pc_src_phparse_included + #endinput +#endif +#define __pc_src_phparse_included + +#include +#include + +// Общие утилиты парсинга плейсхолдеров вида {key} / {prefix:key}. +// Используются как инлайн-форматированием (PHGroup_FormatString), так и +// компиляцией шаблонов (PHTemplate_Compile), чтобы логика не дублировалась. + +// Максимальная длина содержимого плейсхолдера "prefix:key" (без фигурных скобок). +// Содержимое длиннее этого значения не является валидным плейсхолдером +// и должно выводиться как есть (литеральный текст, без обрезки). +#define PH_PLACEHOLDER_CONTENT_MAX_LEN (PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 1) + +// Ищет закрывающую '}' для '{' на позиции openPos. +// @return true — найдена (closePos — позиция '}'); false — скобка не закрыта. +stock bool:PHParse_FindClosing(const src[], const srcLen, const openPos, &closePos) { + closePos = openPos + 1; + while (closePos < srcLen && src[closePos] != '}') { + ++closePos; + } + return closePos < srcLen; +} + +// Разбирает содержимое плейсхолдера между '{' (openPos) и '}' (closePos): +// извлекает текст и разбивает на "prefix:key" (без ':' — ключ глобальной группы). +// +// @return true — содержимое валидно; prefix и key заполнены +// (пустой prefix = глобальная группа); +// false — содержимое длиннее PH_PLACEHOLDER_CONTENT_MAX_LEN, +// это не плейсхолдер (вызывающий должен вывести его как есть). +stock bool:PHParse_Placeholder(const src[], const openPos, const closePos, prefix[], const prefixLen, key[], const keyLen) { + new contentLen = closePos - openPos - 1; + if (contentLen > PH_PLACEHOLDER_CONTENT_MAX_LEN) { + return false; + } + + new content[PH_PLACEHOLDER_CONTENT_MAX_LEN + 1]; + for (new k = 0; k < contentLen; ++k) { + content[k] = src[openPos + 1 + k]; + } + content[contentLen] = EOS; + + new colon = -1; + for (new k = 0; k < contentLen; ++k) { + if (content[k] == ':') { + colon = k; + break; + } + } + + if (colon < 0) { + prefix[0] = EOS; + copy(key, keyLen, content); + } else { + new copyLen = min(colon, prefixLen); + for (new k = 0; k < copyLen; ++k) { + prefix[k] = content[k]; + } + prefix[copyLen] = EOS; + copy(key, keyLen, content[colon + 1]); + } + + return true; +} diff --git a/build/amxmodx/scripting/ParamsController.sma b/build/amxmodx/scripting/ParamsController.sma deleted file mode 100644 index bf960f1..0000000 --- a/build/amxmodx/scripting/ParamsController.sma +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include - -#include "ParamsController/Forwards" - -#include "ParamsController/Objects/Param" -#include "ParamsController/DefaultObjects/Registrar" - -public stock const PluginName[] = "Params Controller"; -public stock const PluginVersion[] = PARAMS_CONTROLLER_VERSION; -public stock const PluginAuthor[] = "ArKaNeMaN"; -public stock const PluginURL[] = "https://github.com/AmxxModularEcosystem/ParamsController"; - -static bool:PluginInited = false; - -public plugin_precache() { - register_plugin(PluginName, PluginVersion, PluginAuthor); - register_library(PARAMS_CONTROLLER_LIBRARY); - PCCvar_Const(PARAMS_CONTROLLER_VERSION_CVAR_NAME, PluginVersion); -} - -PluginInit() { - if (PluginInited) { - return; - } - PluginInited = true; - - Forwards_Init(); - Param_Init(); - DefaultObjects_ParamType_Register(); - - // Тут регать типы - Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); - - register_srvcmd("params_controller_types", "@SrvCmd_Types"); - - // После этого можно юзать типы - Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); -} - -@SrvCmd_Types() { - ParamType_PrintListToConsole(); -} - -bool:IsPluginInited() { - return PluginInited; -} - -#include "ParamsController/API/General" -#include "ParamsController/API/Param" -#include "ParamsController/API/ParamType" - -public plugin_natives() { - API_General_Register(); - API_Param_Register(); - API_ParamType_Register(); -} diff --git a/build/amxmodx/scripting/ParamsController/API/General.inc b/build/amxmodx/scripting/ParamsController/API/General.inc deleted file mode 100644 index 4c2d39c..0000000 --- a/build/amxmodx/scripting/ParamsController/API/General.inc +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "ParamsController/API/Utils" - -API_General_Register() { - register_native("ParamsController_Init", "@API_Init"); - register_native("ParamsController_SetCell", "@API_SetCell"); - register_native("ParamsController_SetString", "@API_SetString"); -} - -@API_Init() { - PluginInit(); -} - -@API_SetCell() { - enum {Arg_Cell = 1} - - API_CheckPluginInited(); - API_CheckCurrentParams(); - - return TrieSetCell(g_tCurrentParams, g_sCurrentParamName, get_param(Arg_Cell)); -} - -@API_SetString() { - enum {Arg_String = 1} - - API_CheckPluginInited(); - API_CheckCurrentParams(); - - static sString[PARAM_LONG_STRING_MAX_LEN]; - get_string(Arg_String, sString, charsmax(sString)); - - return TrieSetString(g_tCurrentParams, g_sCurrentParamName, sString); -} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/Param.inc b/build/amxmodx/scripting/ParamsController/API/Param.inc deleted file mode 100644 index e99cae3..0000000 --- a/build/amxmodx/scripting/ParamsController/API/Param.inc +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include "ParamsController/API/Utils" - -API_Param_Register() { - register_native("ParamsController_Param_Construct", "@API_Param_Construct"); - register_native("ParamsController_Param_ReadList", "@API_Param_ReadList"); - register_native("ParamsController_Param_Read", "@API_Param_Read"); - register_native("ParamsController_Param_Free", "@API_Param_Free"); -} - -T_Param:@API_Param_Construct() { - enum {Arg_sKey = 1, Arg_sParamTypeName, Arg_bRequired} - - API_CheckPluginInited(); - - new sKey[PARAM_KEY_MAX_LEN]; - get_string(Arg_sKey, sKey, charsmax(sKey)); - - new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN + PARAM_TYPE_TAG_MAX_LEN + 1]; - get_string(Arg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); - - new bool:bRequired = !!get_param(Arg_bRequired); - - new sParamType[PARAM_TYPE_NAME_MAX_LEN], sParamTag[PARAM_TYPE_TAG_MAX_LEN]; - ParamType_GetTypeAndTag(sParamTypeName, - sParamType, charsmax(sParamType), - sParamTag, charsmax(sParamTag) - ); - new T_ParamType:iParamType = ParamType_Find(sParamType, .orFail = false); - - if (iParamType == Invalid_ParamType) { - if (bRequired) { - log_error(AMX_ERR_PARAMS, "Param '%s' is required, but type '%s' is not registered.", sKey, sParamType); - } - - return Param_Construct(sKey, iParamType, bRequired, sParamTypeName); - } - - return Param_Construct(sKey, iParamType, bRequired, sParamTag); -} - -// native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj, &Trie:tAppend = Invalid_Trie, &E_ParamsReadErrorType:iErrType, sErrParamName[], const iErrParamNameLen = 0); -Trie:@API_Param_ReadList() { - enum {Arg_aParams = 1, Arg_jObj, Arg_tAppend, Arg_iErrType, Arg_sErrParamName, Arg_iErrParamNameLen} - - API_CheckPluginInited(); - - new Array:aParams = Array:get_param(Arg_aParams); - new JSON:jObj = JSON:get_param(Arg_jObj); - new Trie:tAppend = Trie:get_param_byref(Arg_tAppend); - new E_ParamsReadErrorType:iErrType = E_ParamsReadErrorType:get_param_byref(Arg_iErrType); - new sErrParamName[PARAM_KEY_MAX_LEN]; - get_string(Arg_sErrParamName, sErrParamName, charsmax(sErrParamName)); - new iErrParamNameLen = get_param(Arg_iErrParamNameLen); - - if (!json_is_object(jObj)) { - log_error(AMX_ERR_PARAMS, "[ERROR] Param jObj must be an object. (%d)", jObj); - return Invalid_Trie; - } - - new Trie:tRet = Param_ReadList(aParams, jObj, tAppend, iErrType, sErrParamName, iErrParamNameLen); - - set_param_byref(Arg_iErrType, _:iErrType); - set_string(Arg_sErrParamName, sErrParamName, iErrParamNameLen); - - return tRet; -} - -bool:@API_Param_Read() { - enum {Arg_Param = 1, Arg_ValueJson, Arg_Dest, Arg_OrFailKey} - - API_CheckPluginInited(); - - new T_Param:param = T_Param:get_param(Arg_Param); - new JSON:valueJson = JSON:get_param(Arg_ValueJson); - new Trie:dest = Trie:get_param(Arg_Dest); - new orFailKey[MAX_FMT_LENGTH]; - get_string(Arg_OrFailKey, orFailKey, charsmax(orFailKey)); - - if (valueJson == Invalid_JSON) { - // log_error(AMX_ERR_PARAMS, "[ERROR] Param valueJson must be a valid json handler. (%d)", valueJson); - return false; - } - - new bool:ret = Param_Read(param, valueJson, dest); - - if (orFailKey[0] != EOS && !ret) { - PCJson_ErrorForFile(valueJson, "Field '%s' has invalid value for '%s' param type.", orFailKey, Param_iGetType(param)); - return false; - } - - return ret; -} - -T_Param:@API_Param_Free() { - enum {Arg_Param = 1} - - new T_Param:param = T_Param:get_param_byref(Arg_Param); - - Param_Free(param); - - set_param_byref(Arg_Param, _:Invalid_Param); - return Invalid_Param; -} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/ParamType.inc b/build/amxmodx/scripting/ParamsController/API/ParamType.inc deleted file mode 100644 index 3abf86a..0000000 --- a/build/amxmodx/scripting/ParamsController/API/ParamType.inc +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include "ParamsController/API/Utils" - -API_ParamType_Register() { - register_native("ParamsController_ParamType_Register", "@API_ParamType_Register"); - register_native("ParamsController_ParamType_SetReadCallback", "@API_ParamType_SetReadCallback"); -} - -T_ParamType:@API_ParamType_Register() { - enum {Arg_Name = 1} - - API_CheckPluginInited(); - - new name[PARAM_TYPE_NAME_MAX_LEN]; - get_string(Arg_Name, name, charsmax(name)); - - return ParamType_Construct(name); -} - -@API_ParamType_SetReadCallback(const pluginIndex) { - enum {Arg_Type = 1, Arg_FunctionName} - - API_CheckPluginInited(); - - new T_ParamType:type = T_ParamType:get_param(Arg_Type); - new functionName[PARAM_TYPE_READ_CALLBACK_MAX_LEN]; - get_string(Arg_FunctionName, functionName, charsmax(functionName)); - - ParamType_SetReadCallback(type, functionName, pluginIndex); -} \ No newline at end of file diff --git a/build/amxmodx/scripting/ParamsController/API/Utils.inc b/build/amxmodx/scripting/ParamsController/API/Utils.inc deleted file mode 100644 index 7eab564..0000000 --- a/build/amxmodx/scripting/ParamsController/API/Utils.inc +++ /dev/null @@ -1,18 +0,0 @@ -#if defined __pc_core__api_utils_included - #endinput -#endif -#define __pc_core__api_utils_included - -#include - -stock API_CheckPluginInited() { - if (!IsPluginInited()) { - abort(AMX_ERR_PARAMS, "Attempt to interact with params before init them."); - } -} - -stock API_CheckCurrentParams() { - if (g_tCurrentParams == Invalid_Trie) { - abort(AMX_ERR_PARAMS, "Attempt to set param value outside the read callback."); - } -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc deleted file mode 100644 index 3577ab8..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Boolean.inc +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Boolean_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_BOOL_NAME, "@DefaultObjects_ParamType_Boolean_OnRead"); -} - -bool:@DefaultObjects_ParamType_Boolean_OnRead(const JSON:valueJson) { - switch (json_get_type(valueJson)) { - case JSONNumber: { - return ParamsController_SetCell(json_get_number(valueJson) != 0); - } - case JSONString: { - static str[16]; - json_get_string(valueJson, str, charsmax(str)); - - if ( - equali(str, "yes") - || equali(str, "true") - || equali(str, "on") - || equali(str, "1") - || equali(str, "y") - ) { - return ParamsController_SetCell(true); - } - - if ( - equali(str, "no") - || equali(str, "false") - || equali(str, "off") - || equali(str, "0") - || equali(str, "n") - ) { - return ParamsController_SetCell(false); - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid string boolean value (%s).", str); - return false; - } - case JSONBoolean: { - return ParamsController_SetCell(json_get_bool(valueJson)); - } - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid boolean value."); - return false; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc deleted file mode 100644 index 51e238f..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/ChatMessage.inc +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_ChatMessage_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_CHAT_MESSAGE_NAME, "@DefaultObjects_ParamType_ChatMessage_OnRead"); -} - -bool:@DefaultObjects_ParamType_ChatMessage_OnRead(const JSON:valueJson) { - static str[PARAM_CHAT_MESSAGE_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - replace_all(str, charsmax(str), "^^1", "^1"); - replace_all(str, charsmax(str), "^^3", "^3"); - replace_all(str, charsmax(str), "^^4", "^4"); - - return ParamsController_SetString(str); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc deleted file mode 100644 index d93a396..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Dir.inc +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Dir_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_DIR_NAME, "@DefaultObjects_ParamType_Dir_OnRead"); -} - -bool:@DefaultObjects_ParamType_Dir_OnRead(const JSON:valueJson, const Trie:p, const key[]) { - static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); - - if (!dir_exists(path)) { - PCJson_LogForFile(valueJson, "WARNING", "Dir '%s' not found.", path); - return false; - } - - return ParamsController_SetString(path); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc deleted file mode 100644 index 3fcf94b..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/File.inc +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_File_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_FILE_NAME, "@DefaultObjects_ParamType_File_OnRead"); -} - -bool:@DefaultObjects_ParamType_File_OnRead(const JSON:valueJson, const Trie:p, const key[]) { - static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)) - - if (!file_exists(path)) { - PCJson_LogForFile(valueJson, "WARNING", "File '%s' not found.", path); - return false; - } - - return ParamsController_SetString(path); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc deleted file mode 100644 index dd7eed1..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Flags.inc +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Flags_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_FLAGS_NAME, "@DefaultObjects_ParamType_Flags_OnRead"); -} - -bool:@DefaultObjects_ParamType_Flags_OnRead(const JSON:valueJson) { - static str[32]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetCell(read_flags(str)); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc deleted file mode 100644 index db0fe57..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Float.inc +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Float_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_FLOAT_NAME, "@DefaultObjects_ParamType_Float_OnRead"); -} - -bool:@DefaultObjects_ParamType_Float_OnRead(const JSON:valueJson) { - switch (json_get_type(valueJson)) { - case JSONNumber: { - return ParamsController_SetCell(json_get_real(valueJson)); - } - case JSONString: { - new str[16]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetCell(str_to_float(str)); - } - case JSONBoolean: { - return ParamsController_SetCell(json_get_bool(valueJson) ? 1 : 0); - } - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid float (real) value."); - return false; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc deleted file mode 100644 index 341e0e2..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Integer.inc +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Integer_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_INT_NAME, "@DefaultObjects_ParamType_Integer_OnRead"); -} - -bool:@DefaultObjects_ParamType_Integer_OnRead(const JSON:valueJson) { - switch (json_get_type(valueJson)) { - case JSONNumber: { - return ParamsController_SetCell(json_get_number(valueJson)); - } - case JSONString: { - new str[16]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetCell(str_to_num(str)); - } - case JSONBoolean: { - return ParamsController_SetCell(json_get_bool(valueJson) ? 1 : 0); - } - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid integer value."); - return false; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc deleted file mode 100644 index 2b8f7ab..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Model.inc +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Model_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_MODEL_NAME, "@DefaultObjects_ParamType_Model_OnRead"); -} - -bool:@DefaultObjects_ParamType_Model_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { - static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); - - if (!file_exists(path)) { - PCJson_LogForFile(valueJson, "WARNING", "Model '%s' not found.", path); - return false; - } - - new index = precache_model(path); - if (equali(tag, "index")) { - return ParamsController_SetCell(index); - } - - return ParamsController_SetString(path); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc deleted file mode 100644 index 7981f12..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PlayerModel.inc +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_PlayerModel_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_PLAYER_MODEL_NAME, "@DefaultObjects_ParamType_PlayerModel_OnRead"); -} - -bool:@DefaultObjects_ParamType_PlayerModel_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { - static modelName[PARAM_SHORT_STRING_MAX_LEN]; - PCSingle_ShortString(valueJson, modelName, charsmax(modelName)); - - static path[PLATFORM_MAX_PATH]; - formatex(path, charsmax(path), "models/player/%s/%s.mdl", modelName, modelName); - - if (!file_exists(path)) { - return PCJson_LogForFile(valueJson, "WARNING", "Player model '%s' (%s) not found.", modelName, path); - } - - precache_model(path); - - static pathT[PLATFORM_MAX_PATH]; - formatex(pathT, charsmax(pathT), "models/player/%s/%sT.mdl", modelName, modelName); - if (file_exists(pathT)) { - precache_model(pathT); - } - - return ParamsController_SetString(modelName); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc deleted file mode 100644 index 14037f5..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/RGB.inc +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_RGB_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_RGB_NAME, "@DefaultObjects_ParamType_RGB_OnRead"); -} - -bool:@DefaultObjects_ParamType_RGB_OnRead(const JSON:valueJson, const Trie:p, const key[]) { - new color[3] = {0, ...}; - if (json_is_array(valueJson)) { - for (new i = 0, ii = min(json_array_get_count(valueJson), 3); i < ii; ++i) { - color[i] = json_array_get_number(valueJson, i); - } - } else if (json_is_object(valueJson)) { - if (json_object_has_value(valueJson, "R")) { - color[0] = json_object_get_number(valueJson, "R"); - } else if (json_object_has_value(valueJson, "Red")) { - color[0] = json_object_get_number(valueJson, "Red"); - } else { - color[0] = 0; - } - - if (json_object_has_value(valueJson, "G")) { - color[1] = json_object_get_number(valueJson, "G"); - } else if (json_object_has_value(valueJson, "Green")) { - color[1] = json_object_get_number(valueJson, "Green"); - } else { - color[1] = 0; - } - - if (json_object_has_value(valueJson, "B")) { - color[2] = json_object_get_number(valueJson, "B"); - } else if (json_object_has_value(valueJson, "Blue")) { - color[2] = json_object_get_number(valueJson, "Blue"); - } else { - color[2] = 0; - } - } else if (json_is_string(valueJson)) { - static str[32], colorStr[3][8]; - json_get_string(valueJson, str, charsmax(str)); - - if (contain(str, ",")) { - explode_string(str, ",", colorStr, 3, charsmax(colorStr[])); - for (new i = 0; i < 3; ++i) { - color[i] = str_to_num(colorStr[i]); - } - } else if (contain(str, " ")) { - explode_string(str, " ", colorStr, 3, charsmax(colorStr[])); - for (new i = 0; i < 3; ++i) { - color[i] = str_to_num(colorStr[i]); - } - } else { - PCJson_LogForFile(valueJson, "WARNING", "RGB value must be an array, object or string. String format is 'RRR,GGG,BBB' or 'RRR GGG BBB'."); - return false; - } - } else { - PCJson_LogForFile(valueJson, "WARNING", "RGB value must be an array, object or string."); - return false; - } - - TrieSetArray(p, key, color, 3); - return true; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc deleted file mode 100644 index 5238828..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Regexp.inc +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include -#include - -DefaultObjects_ParamType_Regexp_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_REGEXP_NAME, "@DefaultObjects_ParamType_Regexp_OnRead"); -} - -bool:@DefaultObjects_ParamType_Regexp_OnRead(const JSON:valueJson) { - static pattern[PARAM_VALUE_MAX_LEN]; - json_get_string(valueJson, pattern, charsmax(pattern)); - - static error[256]; - new ret; - new Regex:re = regex_compile(pattern, ret, error, charsmax(error)); - - if (re == REGEX_PATTERN_FAIL) { - PCJson_LogForFile(valueJson, "WARNING", "Invalid regexp pattern '%s': %s.", pattern, error); - return false; - } - - return ParamsController_SetCell(re); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc deleted file mode 100644 index 8cdb9e5..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Resource.inc +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Resource_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_RESOURCE_NAME, "@DefaultObjects_ParamType_Resource_OnRead"); -} - -bool:@DefaultObjects_ParamType_Resource_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { - static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); - - if (!file_exists(path)) { - PCJson_LogForFile(valueJson, "WARNING", "Precaching file '%s' not found.", path); - return false; - } - - new index = precache_generic(path); - if (equali(tag, "index")) { - return ParamsController_SetCell(index); - } - - return ParamsController_SetString(path); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc deleted file mode 100644 index 9e046c2..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Sound.inc +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Sound_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_SOUND_NAME, "@DefaultObjects_ParamType_Sound_OnRead"); -} - -bool:@DefaultObjects_ParamType_Sound_OnRead(const JSON:valueJson, const Trie:p, const key[], const tag[]) { - static path[PLATFORM_MAX_PATH]; - json_get_string(valueJson, path, charsmax(path)); - - static fullPath[PLATFORM_MAX_PATH]; - formatex(fullPath, charsmax(fullPath), "sound/%s", path); - - if (!file_exists(fullPath)) { - PCJson_LogForFile(valueJson, "WARNING", "Sound '%s' not found.", fullPath); - return false; - } - - new index = precache_sound(path); - if (equali(tag, "index")) { - return ParamsController_SetCell(index); - } - - return ParamsController_SetString(path); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc deleted file mode 100644 index afaa0da..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/String.inc +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_String_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_SHORT_STR_NAME, "@DefaultObjects_ParamType_ShortString_OnRead"); - ParamsController_RegSimpleType(DEFAULT_PARAMS_STR_NAME, "@DefaultObjects_ParamType_String_OnRead"); - ParamsController_RegSimpleType(DEFAULT_PARAMS_LONG_STR_NAME, "@DefaultObjects_ParamType_LongString_OnRead"); -} - -bool:@DefaultObjects_ParamType_ShortString_OnRead(const JSON:valueJson) { - static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetString(str); -} - -bool:@DefaultObjects_ParamType_String_OnRead(const JSON:valueJson) { - static str[PARAM_VALUE_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetString(str); -} - -bool:@DefaultObjects_ParamType_LongString_OnRead(const JSON:valueJson) { - static str[PARAM_LONG_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - return ParamsController_SetString(str); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc deleted file mode 100644 index b95ada0..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/Time.inc +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_Time_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_TIME_NAME, "@DefaultObjects_ParamType_Time_OnRead"); -} - -bool:@DefaultObjects_ParamType_Time_OnRead(const JSON:valueJson) { - switch (json_get_type(valueJson)) { - case JSONNumber: { - PCJson_LogForFile(valueJson, "WARNING", "Not recommended time format. Use string (^"HH[:MM[:SS]]^") instead numeric."); - return ParamsController_SetCell(json_get_number(valueJson)); - } - case JSONString: { - static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - new parts[3][3]; - if (explode_string(str, ":", parts, 3, 3) > 3) { - PCJson_LogForFile(valueJson, "WARNING", "Invalid time format. Use ^"HH[:MM[:SS]]^"."); - return false; - } - - new h = clamp(str_to_num(parts[0]), 0, 23) * 60 * 60; - new m = clamp(str_to_num(parts[1]), 0, 59) * 60; - new s = clamp(str_to_num(parts[2]), 0, 59); - - return ParamsController_SetCell(h + m + s); - } - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid time value. Expected string (^"HH[:MM[:SS]]^")."); - return false; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc deleted file mode 100644 index 62a85bb..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/TimeInterval.inc +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include - -DefaultObjects_ParamType_TimeInterval_Register() { - ParamsController_RegSimpleType(DEFAULT_PARAMS_TIME_INTERVAL_NAME, "@DefaultObjects_ParamType_TimeInterval_OnRead"); -} - -bool:@DefaultObjects_ParamType_TimeInterval_OnRead(const JSON:valueJson) { - switch (json_get_type(valueJson)) { - case JSONNumber: { - new num = json_get_number(valueJson); - if (num < 0) { - PCJson_LogForFile(valueJson, "WARNING", "Invalid numeric time interval value. Expected >= 0."); - return false; - } - - return ParamsController_SetCell(num); - } - case JSONString: { - static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - // https://dev-cs.ru/threads/222/page-3#post-33411 - new t, k; - for (new i; str[i] != EOS; i++) { - switch (str[i]) { - case '0'..'9': { - t = (t * 10) + (str[i] - '0'); - } - case 'i': { - k += t * 60; - t = 0; - } - case 'h': { - k += t * 60 * 60; - t = 0; - } - case 'd': { - k += t * 60 * 60 * 24; - t = 0; - } - case 'm': { - k += t * 60 * 60 * 24 * 30; - t = 0; - } - case 'y': { - k += t * 60 * 60 * 24 * 30 * 12; - t = 0; - } - default: { - break; - } - } - } - - return ParamsController_SetCell(k + t); - } - } - - PCJson_LogForFile(valueJson, "WARNING", "Invalid time value. Expected string (^"1y2m3d4h5i6^")."); - return false; -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc deleted file mode 100644 index 0402e4d..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/WeekDay.inc +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include - -static Trie:WeekDaysMap = Invalid_Trie; - -DefaultObjects_ParamType_WeekDay_Register() { - WeekDaysMap = TrieCreate(); - - ParamsController_RegSimpleType(DEFAULT_PARAMS_WEEK_DAY_NAME, "@DefaultObjects_ParamType_WeekDay_OnRead"); -} - -bool:@DefaultObjects_ParamType_WeekDay_OnRead(const JSON:valueJson) { - static str[PARAM_SHORT_STRING_MAX_LEN]; - json_get_string(valueJson, str, charsmax(str)); - - new res; - if (TrieGetCell(WeekDaysMap, str, res)) { - return ParamsController_SetCell(res); - } - - if (equali("Вс", str) || equali("Воскресенье", str) || equali("Sun", str) || equali("Sunday", str)) { - res = 0; - } else if (equali("Пн", str) || equali("Понедельник", str) || equali("Mon", str) || equali("Monday", str)) { - res = 1; - } else if (equali("Вт", str) || equali("Вторник", str) || equali("Tue", str) || equali("Tuesday", str)) { - res = 2; - } else if (equali("Ср", str) || equali("Среда", str) || equali("Wed", str) || equali("Wednesday", str)) { - res = 3; - } else if (equali("Чт", str) || equali("Четверг", str) || equali("Thu", str) || equali("Thursday", str)) { - res = 4; - } else if (equali("Пт", str) || equali("Пятница", str) || equali("Fri", str) || equali("Friday", str)) { - res = 5; - } else if (equali("Сб", str) || equali("Суббота", str) || equali("Sat", str) || equali("Saturday", str)) { - res = 6; - } else { - PCJson_LogForFile(valueJson, "WARNING", "Week day '%s' not found.", str); - return false; - } - - TrieSetCell(WeekDaysMap, str, res); - - return ParamsController_SetCell(res); -} diff --git a/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc deleted file mode 100644 index 10d5210..0000000 --- a/build/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "ParamsController/DefaultObjects/ParamType/Boolean" -#include "ParamsController/DefaultObjects/ParamType/Integer" -#include "ParamsController/DefaultObjects/ParamType/Float" -#include "ParamsController/DefaultObjects/ParamType/String" -#include "ParamsController/DefaultObjects/ParamType/RGB" -#include "ParamsController/DefaultObjects/ParamType/Model" -#include "ParamsController/DefaultObjects/ParamType/PlayerModel" -#include "ParamsController/DefaultObjects/ParamType/Sound" -#include "ParamsController/DefaultObjects/ParamType/Resource" -#include "ParamsController/DefaultObjects/ParamType/File" -#include "ParamsController/DefaultObjects/ParamType/Dir" -#include "ParamsController/DefaultObjects/ParamType/ChatMessage" -#include "ParamsController/DefaultObjects/ParamType/Time" -#include "ParamsController/DefaultObjects/ParamType/TimeInterval" -#include "ParamsController/DefaultObjects/ParamType/WeekDay" -#include "ParamsController/DefaultObjects/ParamType/Flags" -#include "ParamsController/DefaultObjects/ParamType/Regexp" - -DefaultObjects_ParamType_Register() { - DefaultObjects_ParamType_Boolean_Register(); - DefaultObjects_ParamType_Integer_Register(); - DefaultObjects_ParamType_Float_Register(); - DefaultObjects_ParamType_String_Register(); - - DefaultObjects_ParamType_RGB_Register(); - DefaultObjects_ParamType_Model_Register(); - DefaultObjects_ParamType_PlayerModel_Register(); - DefaultObjects_ParamType_Sound_Register(); - DefaultObjects_ParamType_Resource_Register(); - DefaultObjects_ParamType_File_Register(); - DefaultObjects_ParamType_Dir_Register(); - DefaultObjects_ParamType_ChatMessage_Register(); - DefaultObjects_ParamType_Flags_Register(); - - DefaultObjects_ParamType_Time_Register(); - DefaultObjects_ParamType_TimeInterval_Register(); - DefaultObjects_ParamType_WeekDay_Register(); - DefaultObjects_ParamType_Regexp_Register(); -} diff --git a/build/amxmodx/scripting/ParamsController/Forwards.inc b/build/amxmodx/scripting/ParamsController/Forwards.inc deleted file mode 100644 index c64728b..0000000 --- a/build/amxmodx/scripting/ParamsController/Forwards.inc +++ /dev/null @@ -1,71 +0,0 @@ -#if defined __UTILS_forwards_included - #endinput -#endif -#define __UTILS_forwards_included - -/* -Author: ArKaNeMaN -Tg: t.me/arkaneman - -Functions & Macroses: - Forwards_Init(); - Forwards_Reg(const sName[], const iStopType, const ...?param_types); - Forwards_DefaultReturn(const iDefaultReturnValue); - Forwards_Call(const sName[]); - Forwards_CallP(const sName[], ...param_values); - Forwards_GetHandler(const sName[]); - Forwards_RegAndCallP(const sName[], const iStopType, [...param_types], [...param_values]); - Forwards_RegAndCall(const sName[], const iStopType); -*/ - -#include - -stock Trie:Forwards_gMap = Invalid_Trie; - -stock Forwards_Init() { - Forwards_gMap = TrieCreate(); -} - -// Forwards_Reg(const sName[], const iStopType, const ...?param_types); -#define Forwards_Reg(%1,%2) \ - TrieSetCell(Forwards_gMap, %1, CreateMultiForward(%1, %2), false) - -stock __Forwards_Call_ret; - -// Forwards_DefaultReturn(const iDefaultReturnValue); -#define Forwards_DefaultReturn(%1) \ - __Forwards_Call_ret = %1 - -// Forwards_CallP(const sName[], ...param_values); -#define Forwards_CallP(%1,%2) \ - (ExecuteForward(Forwards_GetHandler(%1), __Forwards_Call_ret, %2) ? __Forwards_Call_ret : __Forwards_Call_ret) - -// Forwards_Call(const sName[]); -#define Forwards_Call(%1) \ - (ExecuteForward(Forwards_GetHandler(%1), __Forwards_Call_ret) ? __Forwards_Call_ret : __Forwards_Call_ret) - -stock Forwards_GetHandler(const sName[]) { - new iHandler; - TrieGetCell(Forwards_gMap, sName, iHandler); - return iHandler; -} - -stock __Forwards_RegAndCall_handler; - -// Forwards_RegAndCallP(const sName[], const iStopType, [...param_types], [...param_values]); -#define Forwards_RegAndCallP(%1,%2,[%3],[%4]) \ - (DestroyForward( \ - ExecuteForward( \ - __Forwards_RegAndCall_handler = CreateMultiForward(%1, %2, %3), \ - __Forwards_Call_ret, %4 \ - ) ? __Forwards_RegAndCall_handler : __Forwards_RegAndCall_handler \ - ) ? __Forwards_Call_ret : __Forwards_Call_ret) - -// Forwards_RegAndCall(const sName[], const iStopType); -#define Forwards_RegAndCall(%1,%2) \ - (DestroyForward( \ - ExecuteForward( \ - __Forwards_RegAndCall_handler = CreateMultiForward(%1, %2), \ - __Forwards_Call_ret \ - ) ? __Forwards_RegAndCall_handler : __Forwards_RegAndCall_handler \ - ) ? __Forwards_Call_ret : __Forwards_Call_ret) diff --git a/build/amxmodx/scripting/ParamsController/Objects/Param.inc b/build/amxmodx/scripting/ParamsController/Objects/Param.inc deleted file mode 100644 index 1930fcc..0000000 --- a/build/amxmodx/scripting/ParamsController/Objects/Param.inc +++ /dev/null @@ -1,157 +0,0 @@ -#if defined __params_controller_src_param_included - #endinput -#endif -#define __params_controller_src_param_included - -#include -#include -#include -#include "ParamsController/Objects/ParamType" - -enum _:S_Param { - Param_Key[PARAM_KEY_MAX_LEN], - T_ParamType:Param_Type, - bool:Param_Required, - Param_Tag[PARAM_TYPE_TAG_MAX_LEN], - bool:Param_IsUnknownType, -} - -static Array:Params = Invalid_Array; -static Stack:FreeParamHandlers = Invalid_Stack; - -Param_Init() { - ParamType_Init(); - - if (Params == Invalid_Array) { - Params = ArrayCreate(S_Param, 1); - } - - if (FreeParamHandlers == Invalid_Stack) { - FreeParamHandlers = CreateStack(); - } -} - -T_Param:Param_Construct( - const key[], - const T_ParamType:type, - const bool:isRequired = true, - const tag[] = "" -) { - new paramObject[S_Param]; - - copy(paramObject[Param_Key], charsmax(paramObject[Param_Key]), key); - paramObject[Param_Type] = type; - paramObject[Param_Required] = isRequired; - copy(paramObject[Param_Tag], charsmax(paramObject[Param_Tag]), tag); - paramObject[Param_IsUnknownType] = (type == Invalid_ParamType); - - if (IsStackEmpty(FreeParamHandlers)) { - return T_Param:ArrayPushArray(Params, paramObject); - } else { - new T_Param:param; - PopStackCell(FreeParamHandlers, param); - ArraySetArray(Params, _:param, paramObject); - return param; - } -} - -static Param_Get(const T_Param:param, paramObject[S_Param]) { - ArrayGetArray(Params, _:param, paramObject); - if (paramObject[Param_Type] == Invalid_ParamType && !paramObject[Param_IsUnknownType]) { - abort(AMX_ERR_PARAMS, "Invalid param type handler (%d).", paramObject[Param_Type]); - } -} - -Param_iGetType(const T_Param:param) { - new paramObject[S_Param]; - Param_Get(param, paramObject); - - new typeName[PARAM_TYPE_NAME_MAX_LEN]; - if (paramObject[Param_IsUnknownType]) { - copy(typeName, charsmax(typeName), paramObject[Param_Tag]); - } else { - ParamType_GetName(paramObject[Param_Type], typeName, charsmax(typeName)); - } - - return typeName; -} - -T_Param:Param_Free(&T_Param:param) { - ArraySetCell(Params, _:param, Invalid_ParamType, Param_Type); - ArraySetCell(Params, _:param, false, Param_IsUnknownType); - PushStackCell(FreeParamHandlers, param); - return param = Invalid_Param; -} - -bool:Param_Read(const T_Param:param, const JSON:valueJson, const Trie:p) { - new paramObject[S_Param]; - Param_Get(param, paramObject); - - if (paramObject[Param_IsUnknownType]) { - return false; - } - - return ParamType_Read(paramObject[Param_Type], valueJson, p, paramObject[Param_Key], paramObject[Param_Tag]); -} - -Trie:Param_ReadList( - const Array:params, - const JSON:objectJson, - &Trie:p = Invalid_Trie, - &E_ParamsReadErrorType:iErrType = ParamsReadError_None, - sErrParamName[] = "", - const iErrParamNameLen = 0 -) { - if (p == Invalid_Trie) { - p = TrieCreate(); - } - - if (params == Invalid_Array) { - return p; - } - - for (new i = 0, ii = ArraySize(params); i < ii; ++i) { - new T_Param:param = ArrayGetCell(params, i); - - new paramObject[S_Param]; - Param_Get(param, paramObject); - - if (iErrParamNameLen > 0) { - copy(sErrParamName, iErrParamNameLen, paramObject[Param_Key]); - } - - if (paramObject[Param_IsUnknownType]) { - if (paramObject[Param_Required]) { - iErrType = ParamsReadError_UnknownParamType; - return p; - } - - PCJson_LogForFile(objectJson, "WARNING", "Param '%s' has unknown type '%s', skipping.", paramObject[Param_Key], paramObject[Param_Tag]); - continue; - } - - if (!json_object_has_value(objectJson, paramObject[Param_Key])) { - if (paramObject[Param_Required]) { - iErrType = ParamsReadError_RequiredParamNotPresented; - return p; - } - - continue; - } - - new JSON:valueJson = json_object_get_value(objectJson, paramObject[Param_Key]); - new bool:ret = Param_Read(param, valueJson, p); - json_free(valueJson); - - if (!ret) { - iErrType = ParamsReadError_ParamValueIsInvalid; - return p; - } - - if (!TrieKeyExists(p, paramObject[Param_Key])) { - abort(AMX_ERR_GENERAL, "Param type '%s' dont write value with key '%s' to trie.", ParamType_iGetName(paramObject[Param_Type]), paramObject[Param_Key]); - } - } - - return p; -} diff --git a/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc b/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc deleted file mode 100644 index 7b48eae..0000000 --- a/build/amxmodx/scripting/ParamsController/Objects/ParamType.inc +++ /dev/null @@ -1,163 +0,0 @@ -#if defined __params_controller_src_param_type_included - #endinput -#endif -#define __params_controller_src_param_type_included - -#include -#include - -enum _:S_ParamType { - ParamType_Name[PARAM_TYPE_NAME_MAX_LEN], - ParamType_ReadCallback, -} - -static Array:ParamTypes = Invalid_Array; -static Trie:ParamTypesMap = Invalid_Trie; - -ParamType_Init() { - if (ParamTypes == Invalid_Array) { - ParamTypes = ArrayCreate(S_ParamType, 1); - } - - if (ParamTypesMap == Invalid_Trie) { - ParamTypesMap = TrieCreate(); - } -} - -T_ParamType:ParamType_Construct(const name[]) { - new typeObject[S_ParamType]; - - copy(typeObject[ParamType_Name], charsmax(typeObject[ParamType_Name]), name); - typeObject[ParamType_ReadCallback] = INVALID_HANDLE; - - new T_ParamType:type = T_ParamType:ArrayPushArray(ParamTypes, typeObject); - TrieSetCell(ParamTypesMap, name, type); - - return type; -} - -T_ParamType:ParamType_Find(const name[], const bool:orFail = false) { - new T_ParamType:type = Invalid_ParamType; - TrieGetCell(ParamTypesMap, name, type); - - if (orFail && type == Invalid_ParamType) { - abort(AMX_ERR_PARAMS, "Param type '%s' not found.", name); - } - - return type; -} - -static ParamType_Get(const T_ParamType:type, typeObject[S_ParamType]) { - ArrayGetArray(ParamTypes, _:type, typeObject); -} - -static ParamType_Update(const typeObject[S_ParamType]) { - new T_ParamType:type = ParamType_Find(typeObject[ParamType_Name], .orFail = true); - ArraySetArray(ParamTypes, _:type, typeObject); -} - -ParamType_iGetName(const T_ParamType:type) { - new typeObject[S_ParamType]; - ParamType_Get(type, typeObject); - - return typeObject[ParamType_Name]; -} - -ParamType_GetName(const T_ParamType:type, out[], const outLen) { - new typeObject[S_ParamType]; - ParamType_Get(type, typeObject); - - return copy(out, outLen, typeObject[ParamType_Name]); -} - -ParamType_SetReadCallback(const T_ParamType:type, const functionName[], const pluginIndex) { - new typeObject[S_ParamType]; - ParamType_Get(type, typeObject); - - // bool:(const JSON:valueJson, const Trie:p, const key[], const tag[]) - typeObject[ParamType_ReadCallback] = CreateOneForward(pluginIndex, functionName, FP_CELL, FP_CELL, FP_STRING, FP_STRING); - if (typeObject[ParamType_ReadCallback] == INVALID_HANDLE) { - abort(AMX_ERR_GENERAL, "Can't create callback by '%s' func of #%d plugin.", functionName, pluginIndex); - return; - } - - ParamType_Update(typeObject); -} - -enum _:S_CurrentParam { - Trie:CurrentParam_Params, - CurrentParam_Name[PARAM_KEY_MAX_LEN], -} -new Stack:g_stCurrentParams = Invalid_Stack; -new Trie:g_tCurrentParams = Invalid_Trie; -new g_sCurrentParamName[PARAM_KEY_MAX_LEN] = ""; - -static PushCurrentParam(const Trie:tParams, const sParamName[]) { - if (g_stCurrentParams == Invalid_Stack) { - g_stCurrentParams = CreateStack(S_CurrentParam); - } - - if (g_tCurrentParams != Invalid_Trie) { - new CurrentParam[S_CurrentParam]; - copy(CurrentParam[CurrentParam_Name], charsmax(CurrentParam[CurrentParam_Name]), g_sCurrentParamName); - CurrentParam[CurrentParam_Params] = g_tCurrentParams; - PushStackArray(g_stCurrentParams, CurrentParam); - } - - g_tCurrentParams = tParams; - copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), sParamName); -} - -static PopCurrentParam() { - if ( - g_stCurrentParams != Invalid_Stack - && !IsStackEmpty(g_stCurrentParams) - ) { - new CurrentParam[S_CurrentParam]; - PopStackArray(g_stCurrentParams, CurrentParam); - copy(g_sCurrentParamName, charsmax(g_sCurrentParamName), CurrentParam[CurrentParam_Name]); - g_tCurrentParams = CurrentParam[CurrentParam_Params]; - } else { - g_tCurrentParams = Invalid_Trie; - g_sCurrentParamName[0] = 0; - } -} - -bool:ParamType_Read( - const T_ParamType:type, - const JSON:valueJson, - const Trie:p, - const key[], - const tag[] = "" -) { - new typeObject[S_ParamType]; - ParamType_Get(type, typeObject); - - new JSON:linkedJson; - PCJson_HandleLinkedValue(valueJson, linkedJson); - - PushCurrentParam(p, key); - new bool:ret; - ExecuteForward(typeObject[ParamType_ReadCallback], ret, linkedJson, p, key, tag); - PopCurrentParam(); - - PCJson_FreeLinked(linkedJson, valueJson); - - return ret; -} - -ParamType_PrintListToConsole() { - // TODO: Сделать красиво табличкой как в vipm :) - - server_print("Registered param types:"); - for (new i = 0, ii = ArraySize(ParamTypes); i < ii; ++i) { - new typeObject[S_ParamType]; - ArrayGetArray(ParamTypes, i, typeObject); - - server_print(" - %s", typeObject[ParamType_Name]); - } -} - -ParamType_GetTypeAndTag(const str[], sParamType[], const iParamTypeLen, sParamTag[], const iParamTagLen) { - return strtok2(str, sParamType, iParamTypeLen, sParamTag, iParamTagLen, PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR); -} diff --git a/build/amxmodx/scripting/include/ParamsController.inc b/build/amxmodx/scripting/include/ParamsController.inc deleted file mode 100644 index 4ee0470..0000000 --- a/build/amxmodx/scripting/include/ParamsController.inc +++ /dev/null @@ -1,1671 +0,0 @@ -#if defined __params_controller_included - #endinput -#endif -#define __params_controller_included - -#include -#include -#include - -#define PARAM_TYPE_NAME_MAX_LEN 64 -#define PARAM_KEY_MAX_LEN 64 -#define PARAM_VALUE_MAX_LEN 512 -#define PARAM_TYPE_READ_CALLBACK_MAX_LEN 64 -#define PARAM_TYPE_TAG_MAX_LEN 64 -#define PARAM_SHORT_STRING_MAX_LEN 64 -#define PARAM_LONG_STRING_MAX_LEN 4096 -#define PARAM_CHAT_MESSAGE_MAX_LEN 190 - -stock const DEFAULT_PARAMS_BOOL_NAME[] = "Boolean"; -stock const DEFAULT_PARAMS_INT_NAME[] = "Integer"; -stock const DEFAULT_PARAMS_FLOAT_NAME[] = "Float"; -stock const DEFAULT_PARAMS_STR_NAME[] = "String"; -stock const DEFAULT_PARAMS_SHORT_STR_NAME[] = "ShortString"; -stock const DEFAULT_PARAMS_LONG_STR_NAME[] = "LongString"; -stock const DEFAULT_PARAMS_RGB_NAME[] = "RGB"; -stock const DEFAULT_PARAMS_MODEL_NAME[] = "Model"; -stock const DEFAULT_PARAMS_PLAYER_MODEL_NAME[] = "PlayerModel"; -stock const DEFAULT_PARAMS_SOUND_NAME[] = "Sound"; -stock const DEFAULT_PARAMS_RESOURCE_NAME[] = "Resource"; -stock const DEFAULT_PARAMS_FILE_NAME[] = "File"; -stock const DEFAULT_PARAMS_DIR_NAME[] = "Dir"; -stock const DEFAULT_PARAMS_CHAT_MESSAGE_NAME[] = "ChatMessage"; -stock const DEFAULT_PARAMS_TIME_INTERVAL_NAME[] = "TimeInterval"; -stock const DEFAULT_PARAMS_TIME_NAME[] = "Time"; -stock const DEFAULT_PARAMS_WEEK_DAY_NAME[] = "WeekDay"; -stock const DEFAULT_PARAMS_FLAGS_NAME[] = "Flags"; -stock const DEFAULT_PARAMS_REGEXP_NAME[] = "Regexp"; - -#define PARAMS_CONTROLLER_VERSION "1.4.2" -stock const PARAMS_CONTROLLER_LIBRARY[] = "PluginsController"; -stock const PARAMS_CONTROLLER_VERSION_CVAR_NAME[] = "params_controller_version"; - -stock const PARAMS_CONTROLLER_TYPE_TAG_DELIMITER[] = ":"; -#define PARAMS_CONTROLLER_TYPE_TAG_DELIMITER_CHAR ':' - -enum T_Param { Invalid_Param = INVALID_HANDLE } -enum T_ParamType { Invalid_ParamType = INVALID_HANDLE } - -enum _:E_NativeParam { - ParamArg_sKey, - ParamArg_sParamTypeName, - ParamArg_bRequired, -} - -enum E_ParamsReadErrorType { - ParamsReadError_None, - - /** - * Обязательный параметр отсутствует в переданном JSON-обьекте. - * - * Возникает только для обязательных параметров. - */ - ParamsReadError_RequiredParamNotPresented, - - /** - * Читаемое значение параметра некорректно. - * - * Значит что колбек чтения параметра вернул false. - * Может возникнуть даже для необязательных параметров. - */ - ParamsReadError_ParamValueIsInvalid, - - /** - * Тип параметра не был зарегистрирован на момент создания параметра. - * - * Возникает только для обязательных параметров с неизвестным типом. - * Для необязательных параметров с неизвестным типом значение тихо пропускается. - */ - ParamsReadError_UnknownParamType, -} - -/** - * Вызывается в тот момент, когда контроллер готов регистрировать новые типы параметров - * - * @noreturn - */ -forward ParamsController_OnRegisterTypes(); - -/** - * Вызывается после полной иницализации контроллера и регистрации всех типов - * - * @noreturn - */ -forward ParamsController_OnInited(); - -/** - * Принудительная инициализация контроллера параметров - * - * @note В ходе обработки этого натива будут вызваны форварды *_OnRegisterTypes и *_OnInited, - * а значит после его вызова можно создавать новые параметры (*_Param_Construct) - * - * @noreturn - */ -native ParamsController_Init(); - -/** - * Сохранение прочитанного значения параметра (число, float, bool, и т.д.) - * - * @note Можно вызывать только в рамках колбека чтения параметра - * - * @param iCell Сохраняемое значение - * - * @return true, если значение было установлено успешно, иначе false - */ -native bool:ParamsController_SetCell(const any:iCell); - -/** - * Сохранение прочитанного строкового значения параметра - * - * @note Можно вызывать только в рамках колбека чтения параметра - * @note Буфер для сохраняемой строки имеет размер 4096 ячеек (PARAM_LONG_STRING_MAX_LEN) - * Если требуется сохранить бОльшую строку, то сохраняйте напрямую - * - * @param sString Сохраняемая строка - * - * @return true, если значение было установлено успешно, иначе false - */ -native bool:ParamsController_SetString(const sString[]); - -/** - * Регистрация типа параметров - * - * @note Можно вызывать только после вызова форварда *_OnRegisterTypes - * - * @param sParamTypeName Название типа параметра (будет указываться при создании параметра) - * - * @return Хендлер зарегистрированного типа - */ -native T_ParamType:ParamsController_ParamType_Register(const sParamTypeName[]); - -/** - * Установка колбека для чтения параметра указанного типа - * - * @param iParamType Хендлер типа параметров, для которого будет установлен колбека - * @param sCallback Название функции-колбека - * - * @note Сигнатура колбека: bool:(const JSON:jValue, const Trie:tParams, const sParamKey[], const sParamTag[]) - * jValue: JSON-значение читаемого параметра - * tParams: гарантированно валидный хендлер Trie-структуры - * sParamKey: ключ читаемого параметра - * Колбек должен либо вернуть false, либо записать в tParams какое-либо значение и вернуть true - * В противном случае контроллер выведет в консоль соответствующую ошибку. - * - * @noreturn - */ -native ParamsController_ParamType_SetReadCallback(const T_ParamType:iParamType, const sCallback[]); - -/** - * Регистрация типа параметров с установкой колбека для чтения - * - * @param sParamTypeName Название типа параметра (будет указываться при создании параметра) - * @param sCallback Название функции-колбека - * - * @return Хендлер зарегистрированного типа - */ -stock T_ParamType:ParamsController_RegSimpleType(const sParamTypeName[], const sCallback[]) { - // Не то чтобы сейчас есть какие-то "сложные" типы, - // но пусть пока будет так) - new T_ParamType:iParamType = ParamsController_ParamType_Register(sParamTypeName); - ParamsController_ParamType_SetReadCallback(iParamType, sCallback); - - return iParamType; -} - -/** - * Создание экземпляра параметра. - * - * @param sKey Ключ параметра (по этому ключу будет читаться параметр из JSON-обьекта и по нему же значение будет заноситься в trie) - * @param sParamTypeName Название типа параметра - * @param bRequired Является ли параметр обязательным - * - * @note Если параметр был создан для разового использования, он может быть удалён нативом ParamsController_Param_Free(). - * @note Если тип sParamTypeName не зарегистрирован, параметр всё равно создаётся, но помечается как с неизвестным типом. - * При чтении такого параметра через ReadList: - * - обязательный → ошибка ParamsReadError_UnknownParamType (и log_error при вызове этого натива); - * - необязательный → тихо пропускается, даже если поле есть в JSON. - * - * @return Хендлер созданного параметра - */ -native T_Param:ParamsController_Param_Construct(const sKey[], const sParamTypeName[], const bool:bRequired = true); - -/** - * Удаление экземпляра параметра (очистка хендлера). - * - * @param param Хендлер экземпляра параметра, который нужно удалить (очистить). Всегда заменяется на Invalid_Param. - * - * @return Всегда Invalid_Param. - */ -native T_Param:ParamsController_Param_Free(&T_Param:param); - -stock Array:ParamsController_Param_FreeList(&Array:params) { - if (params == Invalid_Array) { - return params; - } - - for (new i = 0, ii = ArraySize(params); i < ii; ++i) { - ParamsController_Param_Free(ArrayGetCell(params, i)); - } - ArrayDestroy(params); - - return params; -} - -/** - * Чтение массива параметров из JSON-обьекта - * - * @param aParams Массив параметров - * @param jObj JSON-обьект - * @param tAppend trie-хендлер, в который будут записываться прочитанные параметры (по умолчанию создаётся новый) - * @param iErrType Переменная для записи типа ошибки - * @param sErrParamName Буфер для записи ключа параметра, вызвавшего ошибку - * @param iErrParamNameLen Длина буфера - * - * @note В случае возникновения ошибки функция вернёт trie-хендлер только с теми параметрами, - * которые были прочитаны до возникновения ошибки. Если в таком случае хендлер не нужен, - * то нужно его освободить при помощи TrieDestroy(). - * - * @return Валидный trie-хендлер с прочитанными параметрами. Если был передан параметр tAppend, то будет возвращён тот же хендлер - */ -native Trie:ParamsController_Param_ReadList(const Array:aParams, const JSON:jObj, &Trie:tAppend = Invalid_Trie, &E_ParamsReadErrorType:iErrType = ParamsReadError_None,sErrParamName[] = "", const iErrParamNameLen = 0); - -/** - * Чтение одного параметра из JSON-значения. - * - * @param param Хендлер параметра. - * @param valueJson JSON-значение. - * @param dest Хендлер Trie, куда будет записан прочитанный параметр. - * @param orFailKey Название поля, для которого выводить ошибку в случае её возникновения. Если пусто - ошибка не будет выброшена. - * - * @return true, если параметр прочитан успешно, false в случае ошибки. - */ -native bool:ParamsController_Param_Read(const T_Param:param, const JSON:valueJson, const Trie:dest, const orFailKey[] = ""); - -// Маркеры формата PC-аргументов натива. -// Первая ячейка всегда '$', вторая определяет тип содержимого. -#define PCARG_MARKER '$' -#define PCARG_TYPE_PARAM 'P' // одиночный T_Param хендл -#define PCARG_TYPE_ARRAY 'A' // Array: хендл со списком T_Param - -/** - * Конструктор параметра для передачи через аргументы натива. - * Компактная альтернатива тройке ("ключ", "тип", required). - * - * @note Возвращаемое значение передаётся как ОДИН аргумент натива вместо трёх. - * Совместимо с ParamsController_Param_ListFromNativeParams — оба стиля можно - * смешивать в одном вызове. - * - * @param key Ключ параметра - * @param typeName Название типа параметра - * @param required Является ли параметр обязательным - * - * @return Закодированный хендлер параметра (строка-маркер для передачи в натив) - */ -stock PCParam(const key[], const typeName[], const bool:required = false) { - // Формат: [PCARG_MARKER, PCARG_TYPE_PARAM, handle + 1, EOS] - // +1 чтобы handle=0 не читался как EOS при get_string на стороне сервера. - new buf[4]; - buf[0] = PCARG_MARKER; - buf[1] = PCARG_TYPE_PARAM; - buf[2] = _:ParamsController_Param_Construct(key, typeName, required) + 1; - buf[3] = EOS; - return buf; -} - -/** - * Упаковка набора PCParam()-значений в один аргумент натива. - * - * @note Возвращаемое значение передаётся как ОДИН аргумент натива. - * Совместимо с ParamsController_Param_ListFromNativeParams. - * Временный массив уничтожается внутри ListFromNativeParams. - * - * @param ... Результаты вызовов PCParam() - * - * @return Закодированный хендлер массива параметров (строка-маркер для передачи в натив) - */ -stock PCParams(any:...) { - new Array:arr = ArrayCreate(1, 1); - - for (new i = 0, count = numargs(); i < count; i++) { - if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { - ArrayPushCell(arr, T_Param:(getarg(i, 2) - 1)); - } - } - - // Формат: [PCARG_MARKER, PCARG_TYPE_ARRAY, array_handle + 1, EOS] - new buf[4]; - buf[0] = PCARG_MARKER; - buf[1] = PCARG_TYPE_ARRAY; - buf[2] = _:arr + 1; - buf[3] = EOS; - return buf; -} - -/** - * Создание Array: с хендлерами параметров из набора PCParam()-значений. - * - * @param ... Результаты вызовов PCParam() - * - * @return Хендлер динамического массива с параметрами - */ -stock Array:PCParamsArray(any:...) { - new Array:result = ArrayCreate(1, 1); - - for (new i = 0, count = numargs(); i < count; i++) { - if (getarg(i, 0) == PCARG_MARKER && getarg(i, 1) == PCARG_TYPE_PARAM) { - ArrayPushCell(result, T_Param:(getarg(i, 2) - 1)); - } - } - - return result; -} - -/** - * Создание параметра из параметров натива - * - * @note Должно вызываться только в рамках обработки вызова натива - * - * @param iParamOffset Номер параметра, с которого начинать читать - * - * @note Параметр создаётся по трём параметрам натива: - * - Ключ параметра - * - Название типа параметра - * - true/false является ли параметр обязательным - * - * @return Хендлер созданного параметра - */ -stock T_Param:ParamsController_Param_FromNativeParams(const iParamOffset) { - new sKey[PARAM_KEY_MAX_LEN]; - get_string(iParamOffset + ParamArg_sKey, sKey, charsmax(sKey)); - - new sParamTypeName[PARAM_TYPE_NAME_MAX_LEN]; - get_string(iParamOffset + ParamArg_sParamTypeName, sParamTypeName, charsmax(sParamTypeName)); - - new bool:bRequired = bool:get_param_byref(iParamOffset + ParamArg_bRequired); - - return ParamsController_Param_Construct(sKey, sParamTypeName, bRequired); -} - -/** - * Создание списка параметров из параметров натива. - * - * @note Должно вызываться только в рамках обработки вызова натива. - * - * @note Поддерживает несколько стилей передачи параметров (можно смешивать): - * - Классический: три аргумента натива на параметр ("ключ", "тип", required) - * - PCParam(): один аргумент натива — одиночный параметр - * - PCParams(): один аргумент натива — массив параметров - * - * @param iParamOffset Номер аргумента натива, с которого начинать читать - * @param iParamsCount Общее количество аргументов натива (первый параметр обработчика) - * @param aAppend Массив, в который дописывать параметры (по умолчанию создаётся новый) - * - * @return Хендлер динамического массива с прочитанными параметрами - */ -stock Array:ParamsController_Param_ListFromNativeParams(const iParamOffset, const iParamsCount, &Array:aAppend = Invalid_Array) { - if (aAppend == Invalid_Array) { - aAppend = ArrayCreate(1, 1); - } - - new i = iParamOffset; - new val[4]; - while (i <= iParamsCount) { - get_string(i, val, charsmax(val)); - if (val[0] == PCARG_MARKER) { - if (val[1] == PCARG_TYPE_PARAM) { - // PCParam(): одиночный параметр - ArrayPushCell(aAppend, T_Param:(val[2] - 1)); - } else if (val[1] == PCARG_TYPE_ARRAY) { - // PCParams(): временный массив параметров - new Array:arr = Array:(val[2] - 1); - for (new j = 0, jj = ArraySize(arr); j < jj; j++) { - ArrayPushCell(aAppend, ArrayGetCell(arr, j)); - } - ArrayDestroy(arr); - } - i += 1; - } else { - // Классический стиль: тройка ("ключ", "тип", required) - ArrayPushCell(aAppend, ParamsController_Param_FromNativeParams(i)); - i += E_NativeParam; - } - } - - return aAppend; -} - -// Single - -stock const PCSINGLE_PARAM_KEY[] = "__SINGLE"; - -/** - * Чтение одного параметра из JSON-значения. - * - * @param valueJson JSON-значение. - * @param type Название типа параметра. Может cодержать тэг. - * @param orFailKey Название поля, для которого выводить ошибку в случае её возникновения. Если пусто - ошибка не будет выброшена. - * - * @return Хендлер Trie с прочитанным параметром по ключу "__SINGLE". Даже в случае ошибки хендлер будет создан. - */ -stock Trie:PCSingle_Read(const JSON:valueJson, const type[], const orFailKey[] = "") { - new T_Param:param = ParamsController_Param_Construct(PCSINGLE_PARAM_KEY, type, true); - - new Trie:p = TrieCreate(); - ParamsController_Param_Read(param, valueJson, p, orFailKey); - - ParamsController_Param_Free(param); - return p; -} - -/** - * Чтение одного параметра из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param type Название типа параметра. Может cодержать тэг. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Хендлер Trie с прочитанным параметром по ключу "__SINGLE". Даже в случае ошибки хендлер будет создан. - */ -stock Trie:PCSingle_ReadFromObject(const JSON:objectJson, const key[], const type[], const bool:dotNot = false, const bool:orFail = false) { - if (!json_is_object(objectJson)) { - PCJson_ErrorForFile(objectJson, "Json value must be an object. (%d)", objectJson); - return Invalid_Trie; - } - - if (!json_object_has_value(objectJson, key, .dot_not = dotNot)) { - if (orFail) { - PCJson_ErrorForFile(objectJson, "Object must contain '%s' field.", key); - } - return Invalid_Trie; - } - - new JSON:valueJson = json_object_get_value(objectJson, key, dotNot); - new Trie:ret = PCSingle_Read(valueJson, type, orFail ? key : ""); - json_free(valueJson); - - return ret; -} - -/** - * Чтение числового типа параметра из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param type Название типа параметра. Может cодержать тэг. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock any:PCSingle_ObjCell(const JSON:objectJson, const key[], const type[], const any:def = 0, const bool:dotNot = false, const bool:orFail = false) { - new Trie:p = PCSingle_ReadFromObject(objectJson, key, type, dotNot, orFail); - - new any:value = def; - - if (p != Invalid_Trie) { - TrieGetCell(p, PCSINGLE_PARAM_KEY, value); - } - TrieDestroy(p); - - return value; -} - -/** - * Чтение числового типа параметра из JSON-значения. - * - * @param valueJson JSON-обьект. - * @param type Название типа параметра. Может cодержать тэг. - * @param def Значение по умолчанию, в случае ошибки. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock any:PCSingle_Cell(const JSON:valueJson, const type[], const any:def = 0, const orFailKey[] = "") { - new Trie:p = PCSingle_Read(valueJson, type, orFailKey); - - new any:value = def; - - if (p != Invalid_Trie) { - TrieGetCell(p, PCSINGLE_PARAM_KEY, value); - } - TrieDestroy(p); - - return value; -} - -/** - * Чтение параметра типа "Integer" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock PCSingle_ObjInt(const JSON:objectJson, const key[], const any:def = 0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_INT_NAME, def, dotNot, orFail); -} - -stock PCSingle_Int(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_INT_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "Float" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock Float:PCSingle_ObjFloat(const JSON:objectJson, const key[], const Float:def = 0.0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_FLOAT_NAME, def, dotNot, orFail); -} - -stock Float:PCSingle_Float(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_FLOAT_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "Bool" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock bool:PCSingle_ObjBool(const JSON:objectJson, const key[], const bool:def = false, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_BOOL_NAME, def, dotNot, orFail); -} - -stock bool:PCSingle_Bool(const JSON:valueJson, const key[], const any:def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, key, DEFAULT_PARAMS_BOOL_NAME, def, orFailKey); -} - -/** - * Чтение строкового типа параметра из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param type Название типа параметра. Может cодержать тэг. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjStr(const JSON:objectJson, const key[], const type[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new Trie:p = PCSingle_ReadFromObject(objectJson, key, type, dotNot, orFail); - - new ret; - if (p != Invalid_Trie && TrieKeyExists(p, PCSINGLE_PARAM_KEY)) { - ret = TrieGetString(p, PCSINGLE_PARAM_KEY, out, outLen); - } else { - ret = copy(out, outLen, def); - } - TrieDestroy(p); - - return ret; -} - -/** - * Чтение строкового типа параметра из JSON-обьекта. Inline вариант функуции PCSingle_ObjStr. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param type Название типа параметра. Может cодержать тэг. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock PCSingle_iObjStr(const JSON:objectJson, const key[], const type[] = DEFAULT_PARAMS_STR_NAME, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PARAM_VALUE_MAX_LEN]; - PCSingle_ObjStr(objectJson, key, type, out, charsmax(out), def, dotNot, orFail); - return out; -} - -/** - * Чтение строкового типа параметра из JSON-значения. - * - * @param valueJson JSON-обьект. - * @param type Название типа параметра. Может cодержать тэг. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_Str(const JSON:valueJson, const type[], out[], const outLen, const def[] = "", const orFailKey[] = "") { - new Trie:p = PCSingle_Read(valueJson, type, orFailKey); - - new ret; - if (p != Invalid_Trie && TrieKeyExists(p, PCSINGLE_PARAM_KEY)) { - ret = TrieGetString(p, PCSINGLE_PARAM_KEY, out, outLen); - } else { - ret = copy(out, outLen, def); - } - TrieDestroy(p); - - return ret; -} - -/** - * Чтение строкового типа параметра из JSON-значения. Inline вариант функуции PCSingle_Str. - * - * @param valueJson JSON-обьект. - * @param type Название типа параметра. Может cодержать тэг. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение. - */ -stock PCSingle_iStr(const JSON:valueJson, const type[] = DEFAULT_PARAMS_STR_NAME, const def[] = "", const orFailKey[] = "") { - new out[PARAM_VALUE_MAX_LEN]; - PCSingle_Str(valueJson, type, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "String" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PARAM_VALUE_MAX_LEN]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_STR_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_String(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_STR_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PARAM_VALUE_MAX_LEN]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_STR_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "ShortString" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjShortString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjShortString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PARAM_SHORT_STRING_MAX_LEN]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SHORT_STR_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_ShortString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_SHORT_STR_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iShortString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PARAM_SHORT_STRING_MAX_LEN]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_SHORT_STR_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "LongString" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjLongString(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjLongString(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PARAM_LONG_STRING_MAX_LEN]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_LONG_STR_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_LongString(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_LONG_STR_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iLongString(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PARAM_LONG_STRING_MAX_LEN]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_LONG_STR_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "Model" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjModel(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_Model(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_MODEL_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iModel(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_MODEL_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "PlayerModel" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjPlayerModel(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjPlayerModel(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_PlayerModel(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iPlayerModel(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_PLAYER_MODEL_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "Sound" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjSound(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjSound(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_SOUND_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_Sound(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_SOUND_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iSound(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_SOUND_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "Resource" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjResource(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjResource(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_RESOURCE_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_Resource(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_RESOURCE_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iResource(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_RESOURCE_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "File" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjFile(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjFile(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_FILE_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_File(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_FILE_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iFile(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_FILE_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "Dir" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjDir(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjDir(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PLATFORM_MAX_PATH]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_DIR_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_Dir(const JSON:valueJson, out[], const outLen, const def[] = "", const orFailKey[] = "") { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_DIR_NAME, out, outLen, def, orFailKey); -} - -stock PCSingle_iDir(const JSON:valueJson, const def[] = "", const orFailKey[] = "") { - new out[PLATFORM_MAX_PATH]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_DIR_NAME, out, charsmax(out), def, orFailKey); - return out; -} - -/** - * Чтение параметра типа "СhatMessage" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер для записи прочитанного значения. - * @param outLen Размер буфера. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Кол-во записанных в буфер ячеек. - */ -stock PCSingle_ObjChatMessage(const JSON:objectJson, const key[], out[], const outLen, const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, dotNot, orFail); -} - -stock PCSingle_iObjChatMessage(const JSON:objectJson, const key[], const def[] = "", const bool:dotNot = false, const bool:orFail = false) { - new out[PARAM_CHAT_MESSAGE_MAX_LEN]; - PCSingle_ObjStr(objectJson, key, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, charsmax(out), def, dotNot, orFail); - return out; -} - -stock PCSingle_ChatMessage(const JSON:valueJson, out[], const outLen, const def[] = "", const bool:orFail = false) { - return PCSingle_Str(valueJson, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, outLen, def, orFail); -} - -stock PCSingle_iChatMessage(const JSON:valueJson, const def[] = "", const bool:orFail = false) { - new out[PARAM_CHAT_MESSAGE_MAX_LEN]; - PCSingle_Str(valueJson, DEFAULT_PARAMS_CHAT_MESSAGE_NAME, out, charsmax(out), def, orFail); - return out; -} - -/** - * Чтение параметра типа "Regexp" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Хендлер скомпилированного выражения. - */ -stock Regex:PCSingle_ObjRegexp(const JSON:objectJson, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL, const bool:dotNot = false, const bool:orFail = false) { - return Regex:PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_REGEXP_NAME, def, dotNot, orFail); -} - -stock Regex:PCSingle_Regexp(const JSON:valueJson, const Regex:def = Regex:REGEX_PATTERN_FAIL, const orFailKey[] = "") { - return Regex:PCSingle_Cell(valueJson, DEFAULT_PARAMS_REGEXP_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "RGB" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param out Буфер [3] для записи компонент цвета (R, G, B). - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - */ -stock PCSingle_ObjRGB(const JSON:objectJson, const key[], out[3], const bool:dotNot = false, const bool:orFail = false) { - new Trie:p = PCSingle_ReadFromObject(objectJson, key, DEFAULT_PARAMS_RGB_NAME, dotNot, orFail); - if (p != Invalid_Trie) { - TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); - TrieDestroy(p); - } -} - -stock PCSingle_RGB(const JSON:valueJson, out[3], const orFailKey[] = "") { - new Trie:p = PCSingle_Read(valueJson, DEFAULT_PARAMS_RGB_NAME, orFailKey); - if (p != Invalid_Trie) { - TrieGetArray(p, PCSINGLE_PARAM_KEY, out, 3); - TrieDestroy(p); - } -} - -/** - * Чтение параметра типа "TimeInterval" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение в секундах. - */ -stock PCSingle_ObjTimeInterval(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, dotNot, orFail); -} - -stock PCSingle_TimeInterval(const JSON:valueJson, const def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_INTERVAL_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "Time" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение (секунды от начала суток). - */ -stock PCSingle_ObjTime(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_TIME_NAME, def, dotNot, orFail); -} - -stock PCSingle_Time(const JSON:valueJson, const def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, DEFAULT_PARAMS_TIME_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "WeekDay" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение (0=Вс, 1=Пн, ..., 6=Сб). - */ -stock PCSingle_ObjWeekDay(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_WEEK_DAY_NAME, def, dotNot, orFail); -} - -stock PCSingle_WeekDay(const JSON:valueJson, const def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, DEFAULT_PARAMS_WEEK_DAY_NAME, def, orFailKey); -} - -/** - * Чтение параметра типа "Flags" из JSON-обьекта. - * - * @param objectJson JSON-обьект. - * @param key Ключ, значение по которому нужно прочитать. - * @param def Значение по умолчанию, в случае ошибки или отсутствия поля. - * @param dotNot Использовать ли вложенные ключи через точку. - * @param orFail Выбрасывать ли ошибку в случае её возникновения. - * - * @return Прочитанное значение (битовая маска флагов). - */ -stock PCSingle_ObjFlags(const JSON:objectJson, const key[], const def = 0, const bool:dotNot = false, const bool:orFail = false) { - return PCSingle_ObjCell(objectJson, key, DEFAULT_PARAMS_FLAGS_NAME, def, dotNot, orFail); -} - -stock PCSingle_Flags(const JSON:valueJson, const def = 0, const orFailKey[] = "") { - return PCSingle_Cell(valueJson, DEFAULT_PARAMS_FLAGS_NAME, def, orFailKey); -} - -// Getters - -stock any:PCGet_Cell(const Trie:p, const key[], const any:def = 0) { - new any:cell = def; - if (p != Invalid_Trie) { - TrieGetCell(p, key, cell); - } - return cell; -} - -stock PCGet_Str(const Trie:p, const key[], out[], const outLen, const def[] = "") { - if (p != Invalid_Trie && TrieKeyExists(p, key)) { - return TrieGetString(p, key, out, outLen); - } else { - return copy(out, outLen, def); - } -} - -stock PCGet_iStr(const Trie:p, const key[], const def[] = "") { - new str[PARAM_VALUE_MAX_LEN]; - PCGet_Str(p, key, str, charsmax(str), def); - return str; -} - -stock Regex:PCGet_Regexp(const Trie:p, const key[], const Regex:def = Regex:REGEX_PATTERN_FAIL) { - return Regex:PCGet_Cell(p, key, def); -} - -stock bool:PCGet_RegexpMatch(const Trie:p, const key[], const string[]) { - new Regex:re = PCGet_Regexp(p, key); - if (re == Regex:REGEX_PATTERN_FAIL) { - return false; - } - - return regex_match_c(string, re) > 0; -} - -stock PCGet_iChatMessage(const Trie:p, const key[], const def[] = "") { - new str[PARAM_CHAT_MESSAGE_MAX_LEN]; - PCGet_Str(p, key, str, charsmax(str), def); - return str; -} - -stock bool:PCGet_ChatMessagePrint(const playerIndex, const Trie:p, const key[], const def[] = "", const sender = print_team_default) { - new str[PARAM_CHAT_MESSAGE_MAX_LEN]; - PCGet_Str(p, key, str, charsmax(str), def); - - if (!def[0] == EOS) { - return false; - } - - client_print_color(sender, playerIndex, "%s", str); - - return true; -} - -stock PCGet_Int(const Trie:p, const key[], const def = 0) { - return PCGet_Cell(p, key, def); -} - -stock Float:PCGet_Float(const Trie:p, const key[], const Float:def = 0.0) { - return PCGet_Cell(p, key, def); -} - -stock bool:PCGet_Bool(const Trie:p, const key[], const bool:def = false) { - return PCGet_Cell(p, key, def); -} - -stock PCGet_RGB(const Trie:p, const key[]) { - new color[3]; - TrieGetArray(p, key, color, 3); - - return color; -} - -stock PCGet_RGB_Red(const Trie:p, const key[]) { - return PCGet_RGB(p, key)[0]; -} - -stock PCGet_RGB_Green(const Trie:p, const key[]) { - return PCGet_RGB(p, key)[1]; -} - -stock PCGet_RGB_Blue(const Trie:p, const key[]) { - return PCGet_RGB(p, key)[2]; -} - -// Path Utils - -stock PCPath_IsPathSeparator(const ch) { - return ch == '/' || ch == '\'; -} - -stock PCPath_Normalize(out[], const outLen) { - replace_string(out, outLen, "\", "/"); - replace_string(out, outLen, "//", "/"); - replace_string(out, outLen, "./", ""); -} - -stock PCPath_MakePath(const path[], out[], const outLen, const absolutePrefix[] = "", const forceFileExtension[] = "") { - static __amxx_configsdir[PLATFORM_MAX_PATH]; - if (!__amxx_configsdir[0]) { - get_localinfo("amxx_configsdir", __amxx_configsdir, charsmax(__amxx_configsdir)); - } - - if (PCPath_IsPathSeparator(path[0])) { - formatex(out, outLen, "%s/%s", __amxx_configsdir, path) - } else { - if (absolutePrefix[0] != EOS) { - formatex(out, outLen, "%s/%s", absolutePrefix, path); - } else { - formatex(out, outLen, "%s/plugins/%s", __amxx_configsdir, path); - } - } - - PCPath_Normalize(out, outLen); - - if (forceFileExtension[0] != EOS) { - static ext[PLATFORM_MAX_PATH]; - formatex(ext, charsmax(ext), ".%s", forceFileExtension); - - if (strfind(out, ext, .ignorecase = true, .pos = strlen(out) - strlen(ext)) < 0) { - formatex(out, outLen, "%s%s", out, ext); - } - } -} - -stock PCPath_iMakePath(const path[], const absolutePrefix[] = "", const forceFileExtension[] = "") { - new out[PLATFORM_MAX_PATH]; - PCPath_MakePath(path, out, charsmax(out), absolutePrefix, forceFileExtension); - return out; -} - -stock bool:PCPath_GetFileName(const path[], out[], const outLen) { - new i = strlen(path) - 1; - - if (i < 0 || PCPath_IsPathSeparator(path[i])) { - return false; - } - - while (i > 0 && !PCPath_IsPathSeparator(path[i - 1])) { - --i; - } - - new j = 0, lastDot = -1; - while (j < outLen && path[i + j]) { - out[j] = path[i + j]; - if (out[j] == '.') { - lastDot = j; - } - - j++; - } - - if (lastDot >= 0 && lastDot != j - 1) { - out[lastDot] = EOS; - } - - return true; -} - -stock PCPath_iGetFileName(const path[]) { - new out[PLATFORM_MAX_PATH]; - PCPath_GetFileName(path, out, charsmax(out)); - return out; -} - -// Json Wrapper - -stock const PC_JSON_WRAPPER_FIELD_ROOT[] = "___PC_JSON_WRAPPER_FIELD_ROOT___dOnOtUsEtHiSkEyFoRrEaLdAtA___"; -stock const PC_JSON_WRAPPER_FIELD_HANDLER[] = "Handler"; -stock const PC_JSON_WRAPPER_FIELD_WORKDIR[] = "Workdir"; -stock const PC_JSON_WRAPPER_FIELD_FILE[] = "File"; -stock const PC_JSON_WRAPPER_FIELD_VALUE[] = "Value"; -stock const PC_JSON_WRAPPER_FIELD_LINKED[] = "Linked"; -#define PC_JSON_WRAPPER_MESSAGE_MAX_LEN 256 - -stock const PC_JSON_WRAPPER_LINK_PREFIX_FILE[] = "File:"; - -stock JSON:PCJson_ParseFile(const path[], const workdir[] = "") { - if (!file_exists(path)) { - abort(AMX_ERR_GENERAL, "[JSON][ERROR] File '%s' not found.", path); - return Invalid_JSON; - } - - new JSON:value = json_parse(path, .is_file = true, .with_comments = true); - if (value == Invalid_JSON) { - abort(AMX_ERR_GENERAL, "[JSON][ERROR] File '%s' contains invalid JSON value.", path); - return Invalid_JSON; - } - - new JSON:wrapper = json_init_object(); - json_object_set_bool(wrapper, PC_JSON_WRAPPER_FIELD_ROOT, true); - json_object_set_number(wrapper, PC_JSON_WRAPPER_FIELD_HANDLER, _:wrapper); - json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_FILE, path); - json_object_set_value(wrapper, PC_JSON_WRAPPER_FIELD_VALUE, value); - - if (workdir[0] != EOS) { - json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, workdir); - } else { - static autoWorkdir[PLATFORM_MAX_PATH]; - copy(autoWorkdir, charsmax(autoWorkdir), path); - for (new i = strlen(autoWorkdir) - 1; i >= 0; --i) { - if (PCPath_IsPathSeparator(autoWorkdir[i]) || i == 0) { - autoWorkdir[i] = EOS; - break; - } - } - json_object_set_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, autoWorkdir); - } - - return value; -} - -stock JSON:PCJson_Free(&JSON:value, const bool:rootOnly = false) { - if (value == Invalid_JSON) { - return Invalid_JSON; - } - - if (PCJson_IsWrapper(value)) { - json_free(value); - return Invalid_JSON; - } - - if (!PCJson_IsWrappedRoot(value)) { - if (rootOnly && !PCJson_IsRoot(value)) { - return value; - } else { - json_free(value); - return Invalid_JSON; - } - } - - new JSON:wrapper = PCJson_FindWrapper(value); - PCJson_FreeWrapper(wrapper); - - json_free(value); - return Invalid_JSON; -} - -stock JSON:PCJson_FreeWrapper(&JSON:value) { - if (!PCJson_IsWrapper(value)) { - abort(AMX_ERR_PARAMS, "Attempt to free not a wrapper via PCJson_FreeWrapper() function."); - return Invalid_JSON; - } - - new JSON:mainWrapperHandler = JSON:json_object_get_number(value, PC_JSON_WRAPPER_FIELD_HANDLER); - json_free(value); - json_free(mainWrapperHandler); - - return Invalid_JSON; -} - -stock bool:PCJson_IsWrapper(const JSON:value) { - if (value == Invalid_JSON) { - return false; - } - - return json_is_object(value) - && PCJson_IsRoot(value) - && json_object_has_value(value, PC_JSON_WRAPPER_FIELD_ROOT, JSONBoolean); -} - -stock bool:PCJson_IsRoot(const JSON:value) { - if (value == Invalid_JSON) { - return false; - } - - new JSON:parent = json_get_parent(value); - new bool:ret = parent == Invalid_JSON; - if (parent != Invalid_JSON) { - json_free(parent); - } - return ret; -} - -stock bool:PCJson_IsWrappedRoot(const JSON:value) { - if (value == Invalid_JSON) { - return false; - } - - new JSON:parent = json_get_parent(value); - new bool:ret = PCJson_IsWrapper(parent); - if (parent != Invalid_JSON) { - json_free(parent); - } - return ret; -} - -stock bool:PCJson_IsLink(const JSON:value) { - if (value == Invalid_JSON || !json_is_string(value)) { - return false; - } - - static str[sizeof PC_JSON_WRAPPER_LINK_PREFIX_FILE]; - json_get_string(value, str, charsmax(str)); - - return bool:equali(str, PC_JSON_WRAPPER_LINK_PREFIX_FILE, charsmax(str)); -} - -stock JSON:PCJson_FindRoot(const JSON:value, const Array:trace = Invalid_Array) { - if (value == Invalid_JSON) { - return Invalid_JSON; - } - - if (trace != Invalid_Array) { - ArrayPushCell(trace, json_get_type(value)); - } - - new JSON:parent; - new JSON:iter = json_get_parent(value); - if (iter == Invalid_JSON) { - return value; - } - - while (iter != Invalid_JSON) { - if (trace != Invalid_Array) { - ArrayPushCell(trace, json_get_type(iter)); - } - - parent = json_get_parent(iter); - - if (parent == Invalid_JSON) { - break; - } - json_free(iter); - iter = parent; - } - - return iter; -} - -stock JSON:PCJson_FindWrapper(const JSON:value, const Array:trace = Invalid_Array) { - new JSON:root = PCJson_FindRoot(value, .trace = trace); - - if (!PCJson_IsWrapper(root)) { - if (value != root) { - json_free(root); - } - return Invalid_JSON; - } - - return root; -} - -stock PCJson_GetWrapperWorkdir(const JSON:value, out[] = "", const outLen = 0, const def[] = "") { - new path[PLATFORM_MAX_PATH]; - - new JSON:wrapper = PCJson_FindWrapper(value); - if (wrapper != Invalid_JSON) { - json_object_get_string(wrapper, PC_JSON_WRAPPER_FIELD_WORKDIR, path, charsmax(path)); - } - - if (wrapper != value) { - json_free(wrapper); - } - - if (path[0] == EOS) { - copy(path, charsmax(path), def); - } - - if (outLen > 0) { - copy(out, outLen, path); - } - - return path; -} - -stock PCJson_GetWrapperFile(const JSON:value, out[] = "", const outLen = 0, const def[] = "", const Array:trace = Invalid_Array) { - new path[PLATFORM_MAX_PATH]; - - new JSON:wrapper = PCJson_FindWrapper(value, .trace = trace); - if (wrapper != Invalid_JSON) { - json_object_get_string(wrapper, PC_JSON_WRAPPER_FIELD_FILE, path, charsmax(path)); - } - - if (wrapper != value) { - json_free(wrapper); - } - - if (path[0] == EOS) { - copy(path, charsmax(path), def); - } - - if (outLen > 0) { - copy(out, outLen, path); - } - - return path; -} - -stock PCJson_ExtractLinkPath(const JSON:link, out[], const outLen) { - out[0] = EOS; - if (!PCJson_IsLink(link)) { - return 0; - } - - json_get_string(link, out, outLen); - return replace(out, outLen, PC_JSON_WRAPPER_LINK_PREFIX_FILE, ""); -} - -stock bool:PCJson_HandleLinkedValue(const JSON:value, &JSON:linkedOrSameValue) { - linkedOrSameValue = value; - if (!PCJson_IsLink(value)) { - return false; - } - - new JSON:wrapper = PCJson_FindWrapper(value); - if (wrapper == Invalid_JSON) { - return false; - } - - static linkPath[PLATFORM_MAX_PATH]; - PCJson_ExtractLinkPath(value, linkPath, charsmax(linkPath)); - - static workdir[PLATFORM_MAX_PATH]; - if (linkPath[0] == '.' && linkPath[1] == '/') { - workdir[0] = EOS; - } else { - PCJson_GetWrapperWorkdir(wrapper, workdir, charsmax(workdir)); - } - - json_free(wrapper); - - static path[PLATFORM_MAX_PATH]; - PCPath_MakePath( - linkPath, path, charsmax(path), - .absolutePrefix = workdir, - .forceFileExtension = "json" - ); - - new JSON:linkedValue = PCJson_ParseFile(path, workdir); - - new JSON:deepLinkedValue; - if (PCJson_HandleLinkedValue(linkedValue, deepLinkedValue)) { - PCJson_Free(linkedValue); - linkedOrSameValue = deepLinkedValue; - return true; - } else { - linkedOrSameValue = linkedValue; - return true; - } -} - -enum _:E_PCJson_CachedLinkedValueState { - PCJson_CachedLinkedValue_NotLinked = 0, - PCJson_CachedLinkedValue_Linked = 1, - PCJson_CachedLinkedValue_Cached = 2, -} - -stock PCJson_HandleCachedLinkedValue(const Trie:cache, const JSON:value, &JSON:linkedOrSameValue, &any:cachedCell, linkPath[], const linkPathLen) { - linkedOrSameValue = value; - - if (cache == Invalid_Trie) { - return _:PCJson_HandleLinkedValue(value, linkedOrSameValue); - } - - PCJson_ExtractLinkPath(value, linkPath, linkPathLen); - if (linkPath[0] == EOS) { - return PCJson_CachedLinkedValue_NotLinked; - } - - if (TrieGetCell(cache, linkPath, cachedCell)) { - return PCJson_CachedLinkedValue_Cached; - } - - return _:PCJson_HandleLinkedValue(value, linkedOrSameValue); -} - -#pragma deprecated Dont use example function, please... -stock someTag:USAGE_EXAMPLE__PCJson_HandleCachedLinkedValue(const JSON:value) { - // global - new Trie:cache; // linkPath[] => any:cachedCell - - // func - new JSON:linked, someTag:cachedCell = someTag:-1, linkPath[PLATFORM_MAX_PATH]; - if (PCJson_HandleCachedLinkedValue(cache, value, linked, cachedCell, linkPath, charsmax(linkPath)) == PCJson_CachedLinkedValue_Cached) { - return cachedCell; - } - - // cachedCell = YourJsonReadFunc(linked); - - if (linkPath[0] != EOS && cachedCell >= 0) { - TrieSetCell(cache, linkPath, cachedCell); - } - - PCJson_FreeLinked(linked, value); - return cachedCell; -} - -stock JSON:PCJson_FreeLinked(&JSON:value, const JSON:sourceJson = Invalid_JSON) { - if (value == sourceJson) { - return value; - } - - return PCJson_Free(value, .rootOnly = true); -} - -stock bool:PCJson_LogForFile(const JSON:value, const prefix[], const messageFormat[], any:...) { - static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; - vformat(msg, charsmax(msg), messageFormat, 4); - - new Array:trace = ArrayCreate(1, 1); - - static path[PLATFORM_MAX_PATH]; - PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*", .trace = trace); - - log_amx("[JSON][%s] File: %s", prefix, path); - // log_amx("[JSON][%s] Trace: %s", prefix, PCJSon_TraceToString(trace, .reverse = true)); // Экспериментальное - log_amx("[JSON][%s] Log: %s", prefix, msg); - - ArrayDestroy(trace); - - return false; -} - -stock bool:PCJson_ErrorForFile(const JSON:value, const messageFormat[], any:...) { - static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; - vformat(msg, charsmax(msg), messageFormat, 3); - - static path[PLATFORM_MAX_PATH]; - PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*"); - - abort(AMX_ERR_GENERAL, "[JSON][ERROR] %s (File '%s')", msg, path); - - return false; -} -#define PCJson_AbortForFile PCJson_ErrorForFile - -stock bool:PCJson_FailForFile(const JSON:value, const messageFormat[], any:...) { - static msg[PC_JSON_WRAPPER_MESSAGE_MAX_LEN]; - vformat(msg, charsmax(msg), messageFormat, 3); - - static path[PLATFORM_MAX_PATH]; - PCJson_GetWrapperFile(value, path, charsmax(path), .def = "*unknown file*"); - - set_fail_state("[JSON][FATAL ERROR] %s (File '%s')", msg, path); - - return false; -} - -stock PCJSon_TraceToString(const Array:trace, const bool:reverse = false) { - new str[256]; - for (new i = 0, ii = ArraySize(trace); i < ii; ++i) { - if (i > 0) { - add(str, charsmax(str), "->"); - } - - switch (ArrayGetCell(trace, reverse ? ii - i - 1 : i)) { - case JSONError: - // Хз зачем, но пусть будет) - add(str, charsmax(str), "err"); - case JSONNull: - add(str, charsmax(str), "null"); - case JSONString: - add(str, charsmax(str), "str"); - case JSONNumber: - add(str, charsmax(str), "num"); - case JSONObject: - add(str, charsmax(str), "obj"); - case JSONArray: - add(str, charsmax(str), "arr"); - case JSONBoolean: - add(str, charsmax(str), "bool"); - } - } - - return str; -} - -// Cvars - -stock PCCvar_Const(const name[], const value[]) { - new p = create_cvar(name, value); - set_pcvar_string(p, value); - - return p; -}