From 47cc3856d2f9a20393a6a35cd4fd94fc7e33503e Mon Sep 17 00:00:00 2001 From: ArKaNeMaN Date: Sat, 28 Mar 2026 00:58:02 +0300 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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