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/.gitignore b/.gitignore index 29c91b0..40b0a40 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.zip .build plugins-*.ini +.claude 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. diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md new file mode 100644 index 0000000..387ad6b --- /dev/null +++ b/PLACEHOLDERS.md @@ -0,0 +1,369 @@ +# Placeholder System + +Плейсхолдеры — это механизм подстановки динамических значений в строки вида `{key}` или `{prefix:key}`. Используется для форматирования строк с данными, которые меняются в рантайме (имя карты, authid игрока и т.п.). + +## Концепции + +### Группа (`T_PHGroup`) + +Группа объединяет логически связанные ключи под одним **префиксом**. В строках плейсхолдеры этой группы выглядят как `{prefix:key}`. + +Специальная **глобальная группа** (пустой префикс) — для плейсхолдеров без префикса: `{key}`. Доступна через `PCPH_GetGlobalGroup()`. + +### Ключ + +Конкретный плейсхолдер внутри группы, например `authid` в группе `p`. Ключи регистрируются заранее; незарегистрированные плейсхолдеры сохраняются в строке как есть. + +### Контекст + +Когда одна группа описывает несколько сущностей (например, разные игроки), каждый плейсхолдер может иметь разное значение в зависимости от **контекстного ключа** — произвольной строки-идентификатора (обычно индекс игрока). + +Перед вызовом `PCPH_Format` нужный контекст кладётся на стек группы через `PCPH_PushContext` / `PCPH_PushIntContext`, а после — снимается через `PCPH_PopContext`. + +### Прокси-группа + +Прокси-группа — это группа с собственным префиксом и **независимым стеком контекстов**, которая разделяет ключи, колбеки и хранилище значений с группой-источником. + +Позволяет держать два независимых контекста одной логической группы одновременно. Типичный кейс — пункты меню, где каждый пункт соответствует игроку: `{p:name}` — тот, кто открыл меню, `{target:name}` — игрок в пункте меню, оба читают данные из группы `p`. + +--- + +### Режимы хранения значений + +| Режим | Когда использовать | Как обновлять | +|---|---|---| +| **Проактивный** | Значение известно заранее и меняется по событиям (например, 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 ← пользовательские группы и ключи + ├─ PCPH_OnRegisterProxyGroups ← прокси-группы (все обычные группы уже доступны) + └─ ParamsController_OnInited ← можно использовать API +``` + +Регистрировать группы и ключи нужно в `PCPH_OnRegisterGroups`. После `OnInited` регистрация тоже работает, но семантически правильнее делать это в форварде. + +--- + +## API + +### Форварды + +```pawn +forward PCPH_OnRegisterGroups(); +``` + +Вызывается до `OnInited`. Здесь нужно регистрировать свои группы и ключи. + +```pawn +forward PCPH_OnRegisterProxyGroups(); +``` + +Вызывается после `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_RegisterProxyGroup(const sPrefix[], const T_PHGroup:hSourceGroup); +``` + +Регистрирует прокси-группу с уникальным префиксом. Прокси разделяет ключи, колбеки и хранилище значений с `hSourceGroup`, но имеет собственный стек контекстов. Регистрировать в `PCPH_OnRegisterProxyGroups`. Регистрировать ключи и колбеки — только на группе-источнике. + +```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). + +--- + +### Скомпилированные шаблоны + +Для строк, которые форматируются часто (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 +#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 +``` + +**Встроенные плейсхолдеры:** + +| Плейсхолдер | Константы | Описание | +|---|---|---| +| `{real-map}` | `DEFAULT_PH_GLOBAL_REAL_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 bf960f1..35880a4 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,17 +28,48 @@ PluginInit() { Forwards_Init(); Param_Init(); - DefaultObjects_ParamType_Register(); + PHGroup_Init(); + DefaultObjects_Register(); - // Тут регать типы + // Тут регать типы параметров Forwards_RegAndCall("ParamsController_OnRegisterTypes", ET_IGNORE); + // Тут регать группы и ключи плейсхолдеров + Forwards_RegAndCall("PCPH_OnRegisterGroups", ET_IGNORE); + + // Тут регать прокси-группы (все обычные группы уже зарегистрированы) + Forwards_RegAndCall("PCPH_OnRegisterProxyGroups", ET_IGNORE); + register_srvcmd("params_controller_types", "@SrvCmd_Types"); - // После этого можно юзать типы + // После этого можно юзать типы и плейсхолдеры Forwards_RegAndCall("ParamsController_OnInited", ET_IGNORE); } +public client_authorized(playerIndex, const authId[]) { + if (!IsPluginInited()) { + return; + } + + 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() { ParamType_PrintListToConsole(); } @@ -49,9 +81,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/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/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 new file mode 100644 index 0000000..f16385a --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/ParamType/PHTemplate.inc @@ -0,0 +1,15 @@ +#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) { + 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/Global.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc new file mode 100644 index 0000000..592ab1d --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Global.inc @@ -0,0 +1,69 @@ +#include +#include +#include + +DefaultObjects_PH_Global_Register() { + 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_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); + + 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 new file mode 100644 index 0000000..a8f829f --- /dev/null +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Placeholder/Player.inc @@ -0,0 +1,39 @@ +#include +#include +#include + +static T_PHGroup:PlayerGroup = Invalid_PHGroup; + +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_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); +} diff --git a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc index ddf5dea..641b569 100644 --- a/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc +++ b/amxmodx/scripting/ParamsController/DefaultObjects/Registrar.inc @@ -18,8 +18,12 @@ #include "ParamsController/DefaultObjects/ParamType/WeekDay" #include "ParamsController/DefaultObjects/ParamType/Flags" #include "ParamsController/DefaultObjects/ParamType/Regexp" +#include "ParamsController/DefaultObjects/ParamType/PHTemplate" -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 +42,22 @@ DefaultObjects_ParamType_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(); +} + +DefaultObjects_OnClientAuth(const playerIndex, const authId[]) { + DefaultObjects_PH_Player_OnClientAuth(playerIndex, 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/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 new file mode 100644 index 0000000..375580f --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/General.inc @@ -0,0 +1,100 @@ +#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"); +} + +@API_PH_Format() { + enum {Arg_Out = 1, Arg_OutLen, Arg_Format} + + API_CheckPluginInited(); + + static str[PH_FORMAT_MAX_LEN]; + get_string(Arg_Format, str, charsmax(str)); + + static out[PH_FORMAT_MAX_LEN]; + PHGroup_FormatString(str, out, charsmax(out)); + + 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} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static value[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, value, charsmax(value)); + + PHGroup_SetCurrentValue(value); +} + +@API_PH_Cb_SetInt() { + enum {Arg_Value = 1} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static value[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), value, charsmax(value)); + + PHGroup_SetCurrentValue(value); +} + +@API_PH_Cb_SetFloat() { + enum {Arg_Value = 1} + + API_CheckPluginInited(); + API_CheckCurrentPH(); + + static value[PH_VALUE_MAX_LEN]; + format(value, charsmax(value), "%.2f", get_param_f(Arg_Value)); + + PHGroup_SetCurrentValue(value); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc new file mode 100644 index 0000000..49769d2 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Group.inc @@ -0,0 +1,100 @@ +#include +#include "ParamsController/API/Utils" +#include "ParamsController/Placeholders/Objects/PHGroup" + +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"); + register_native("PCPH_Group_PopContext", "@API_PH_PopContext"); +} + +T_PHGroup:@API_PH_RegisterGroup() { + enum {Arg_Prefix = 1} + + API_CheckPluginInited(); + + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, prefix, charsmax(prefix)); + + if (prefix[0] == 0) { + abort(AMX_ERR_PARAMS, "PH 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; + } + + 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} + + API_CheckPluginInited(); + + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + get_string(Arg_Prefix, prefix, charsmax(prefix)); + + return PHGroup_Find(prefix); +} + +T_PHGroup:@API_PH_GetGlobalGroup() { + API_CheckPluginInited(); + + return PHGroup_GetGlobal(); +} + +@API_PH_PushContext() { + enum {Arg_Group = 1, Arg_ContextKey} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); + + PHGroup_PushContext(group, contextKey); +} + +@API_PH_PopContext() { + enum {Arg_Group = 1} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + PHGroup_PopContext(group); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc new file mode 100644 index 0000000..e07cbc8 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/API/Key.inc @@ -0,0 +1,97 @@ +#include +#include "ParamsController/API/Utils" +#include "ParamsController/Placeholders/Objects/PHGroup" + +API_PH_Key_Register() { + 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() { + enum {Arg_Group = 1, Arg_Key} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); + + PHGroup_RegisterKey(group, key); +} + +@API_PH_SetGroupKeyCallback(const pluginIndex) { + enum {Arg_Group = 1, Arg_Key, Arg_Callback} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); + + static callback[PH_CALLBACK_MAX_LEN]; + get_string(Arg_Callback, callback, charsmax(callback)); + + PHGroup_SetKeyCallback(group, key, callback, pluginIndex); +} + +@API_PH_SetForStr() { + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); + + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); + + static value[PH_VALUE_MAX_LEN]; + get_string(Arg_Value, value, charsmax(value)); + + PHGroup_SetValue(group, key, contextKey, value); +} + +@API_PH_SetIntForStr() { + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); + + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); + + static value[PH_VALUE_MAX_LEN]; + num_to_str(get_param(Arg_Value), value, charsmax(value)); + + PHGroup_SetValue(group, key, contextKey, value); +} + +@API_PH_SetFloatForStr() { + enum {Arg_Group = 1, Arg_Key, Arg_ContextKey, Arg_Value} + + API_CheckPluginInited(); + + new T_PHGroup:group = T_PHGroup:get_param(Arg_Group); + + static key[PH_KEY_MAX_LEN]; + get_string(Arg_Key, key, charsmax(key)); + + static contextKey[PH_CONTEXT_KEY_MAX_LEN]; + get_string(Arg_ContextKey, contextKey, charsmax(contextKey)); + + static value[PH_VALUE_MAX_LEN]; + format(value, charsmax(value), "%.2f", get_param_f(Arg_Value)); + + PHGroup_SetValue(group, key, contextKey, value); +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc new file mode 100644 index 0000000..54e96a3 --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHGroup.inc @@ -0,0 +1,384 @@ +#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], // текущий активный контекст (вершина стека) + T_PHGroup:PHGroup_Source, // источник данных (Invalid_PHGroup для обычных групп) +} + +static Array:PHGroups = Invalid_Array; +static Trie:PHGroupsMap = Invalid_Trie; +static T_PHGroup:PHGlobalGroup = Invalid_PHGroup; + +static T_PHGroup:CurrentPHGroup = Invalid_PHGroup; +static CurrentPHKey[PH_KEY_MAX_LEN]; +static CurrentPHContext[PH_CONTEXT_KEY_MAX_LEN]; + +PHGroup_Init() { + if (PHGroups == Invalid_Array) { + PHGroups = ArrayCreate(S_PHGroup, 1); + } + + if (PHGroupsMap == Invalid_Trie) { + PHGroupsMap = TrieCreate(); + } + + PHGlobalGroup = PHGroup_Construct(""); +} + +T_PHGroup:PHGroup_Construct(const prefix[]) { + new groupData[S_PHGroup]; + + 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] = EOS; + groupData[PHGroup_Source] = Invalid_PHGroup; + + new T_PHGroup:group = T_PHGroup:ArrayPushArray(PHGroups, groupData); + + 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; +} + +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); + + 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; +} + +T_PHGroup:PHGroup_Find(const prefix[]) { + new T_PHGroup:group = Invalid_PHGroup; + TrieGetCell(PHGroupsMap, prefix, group); + return group; +} + +T_PHGroup:PHGroup_GetGlobal() { + return PHGlobalGroup; +} + +static PHGroup_Get(const T_PHGroup:group, groupData[S_PHGroup]) { + ArrayGetArray(PHGroups, _:group, groupData); +} + +static PHGroup_Update(const T_PHGroup:group, const groupData[S_PHGroup]) { + ArraySetArray(PHGroups, _:group, groupData); +} + +// Составной ключ для Values Trie: "key:contextKey" или просто "key" если контекст пустой +static PHGroup_BuildValueKey(const key[], const contextKey[], out[], const outLen) { + if (contextKey[0] == 0) { + copy(out, outLen, key); + } else { + format(out, outLen, "%s:%s", key, contextKey); + } +} + +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); + } +} + +PHGroup_SetKeyCallback(const T_PHGroup:group, const key[], const callback[], const pluginIndex) { + 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; + } + + new oldFwd; + if (TrieGetCell(groupData[PHGroup_Callbacks], key, oldFwd) && oldFwd != INVALID_HANDLE) { + DestroyForward(oldFwd); + } + + // (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], key, fwd); +} + +PHGroup_PushContext(const T_PHGroup:group, const contextKey[]) { + new groupData[S_PHGroup]; + PHGroup_Get(group, 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]), contextKey); + + PHGroup_Update(group, groupData); +} + +PHGroup_PopContext(const T_PHGroup:group) { + new groupData[S_PHGroup]; + PHGroup_Get(group, 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] = EOS; + } + + PHGroup_Update(group, groupData); +} + +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(dataGroup, groupData); + + static valueKey[PH_KEY_MAX_LEN + PH_CONTEXT_KEY_MAX_LEN + 2]; + PHGroup_BuildValueKey(key, contextKey, valueKey, charsmax(valueKey)); + + TrieSetString(groupData[PHGroup_Values], valueKey, value); +} + +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(dataGroup, groupData); + + 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], valueKey, out, outLen); +} + +bool:PHGroup_IsKeyRegistered(const T_PHGroup:group, const key[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); + new groupData[S_PHGroup]; + 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(dataGroup, groupData); + + new fwd; + if (!TrieGetCell(groupData[PHGroup_Callbacks], key, fwd) || fwd == INVALID_HANDLE) { + return; + } + + CurrentPHGroup = group; + copy(CurrentPHKey, charsmax(CurrentPHKey), key); + copy(CurrentPHContext, charsmax(CurrentPHContext), contextKey); + + new ret; + ExecuteForward(fwd, ret, key, contextKey); + + CurrentPHGroup = Invalid_PHGroup; + CurrentPHKey[0] = EOS; + CurrentPHContext[0] = EOS; +} + +PHGroup_FormatString(const src[], out[], const outLen) { + if (outLen < 1) { + return; + } + + new outPos = 0; + new inPos = 0; + new srcLen = strlen(src); + + while (inPos < srcLen && outPos < outLen - 1) { + if (src[inPos] != '{') { + out[outPos++] = src[inPos++]; + continue; + } + + new closing = inPos + 1; + while (closing < srcLen && src[closing] != '}') { + ++closing; + } + + if (closing >= srcLen) { + // Незакрытая скобка — выводим как есть + out[outPos++] = src[inPos++]; + continue; + } + + static placeholder[PH_GROUP_PREFIX_MAX_LEN + PH_KEY_MAX_LEN + 2]; + new placeholderLen = min(closing - inPos - 1, charsmax(placeholder)); + + for (new k = 0; k < placeholderLen; ++k) { + placeholder[k] = src[inPos + 1 + k]; + } + placeholder[placeholderLen] = 0; + + static prefix[PH_GROUP_PREFIX_MAX_LEN]; + static key[PH_KEY_MAX_LEN]; + + new colon = -1; + for (new k = 0; k < placeholderLen; ++k) { + if (placeholder[k] == ':') { + colon = k; + break; + } + } + + if (colon < 0) { + prefix[0] = 0; + copy(key, charsmax(key), placeholder); + } else { + new prefixLen = min(colon, charsmax(prefix)); + for (new k = 0; k < prefixLen; ++k) { + prefix[k] = placeholder[k]; + } + prefix[prefixLen] = 0; + copy(key, charsmax(key), placeholder[colon + 1]); + } + + new T_PHGroup:group = (prefix[0] == 0) + ? PHGlobalGroup + : PHGroup_Find(prefix); + + if (group != Invalid_PHGroup && PHGroup_IsKeyRegistered(group, key)) { + // Захватываем контекст до вызова колбека + static currentContext[PH_CONTEXT_KEY_MAX_LEN]; + new groupData[S_PHGroup]; + PHGroup_Get(group, groupData); + copy(currentContext, charsmax(currentContext), groupData[PHGroup_CurrentContext]); + + PHGroup_CallCallback(group, key, currentContext); + + static value[PH_VALUE_MAX_LEN]; + 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]; + } + } + } else { + // Неизвестная группа или незарегистрированный ключ — сохраняем как есть + out[outPos++] = '{'; + for (new k = 0; k < placeholderLen && outPos < outLen - 1; ++k) { + out[outPos++] = placeholder[k]; + } + if (outPos < outLen - 1) { + out[outPos++] = '}'; + } + } + + inPos = closing + 1; + } + + out[outPos] = 0; +} + +PHGroup_GetKeyForward(const T_PHGroup:group, const key[]) { + new T_PHGroup:dataGroup = PHGroup_DataGroup(group); + new groupData[S_PHGroup]; + PHGroup_Get(dataGroup, groupData); + + new fwd = INVALID_HANDLE; + TrieGetCell(groupData[PHGroup_Callbacks], key, fwd); + 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; +} + +PHGroup_SetCurrentValue(const value[]) { + PHGroup_SetValue(CurrentPHGroup, CurrentPHKey, CurrentPHContext, value); +} + +API_CheckCurrentPH() { + if (!PHGroup_IsInCallback()) { + abort(AMX_ERR_PARAMS, "Attempt to set PH value outside the key callback."); + } +} diff --git a/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc new file mode 100644 index 0000000..1aa5cca --- /dev/null +++ b/amxmodx/scripting/ParamsController/Placeholders/Objects/PHTemplate.inc @@ -0,0 +1,177 @@ +#if defined __pc_src_phtemplate_included + #endinput +#endif +#define __pc_src_phtemplate_included + +#include +#include +#include "ParamsController/Placeholders/Objects/PHGroup" + +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); + static partData[S_PHTemplatePart]; + static value[PH_VALUE_MAX_LEN]; + + 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 { + 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]; + } + } + } + } + + out[outPos] = EOS; +} + +PHTemplate_Destroy(const T_PHTemplate:tmpl) { + if (tmpl != Invalid_PHTemplate) { + new Array:arr = Array:tmpl; + ArrayDestroy(arr); + } +} diff --git a/amxmodx/scripting/include/ParamsController.inc b/amxmodx/scripting/include/ParamsController.inc index ba85d78..caf3887 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"; @@ -45,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, @@ -944,6 +969,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) { @@ -1514,6 +1558,370 @@ 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 + +// --- Default placeholders --- + +/** + * Вызывается когда контроллер готов к регистрации групп и ключей плейсхолдеров. + * Вызывается до форварда *_OnInited. + * + * @noreturn + */ +forward PCPH_OnRegisterGroups(); + +/** + * Вызывается после PCPH_OnRegisterGroups, когда все обычные группы уже зарегистрированы. + * Здесь следует регистрировать прокси-группы. + * + * @noreturn + */ +forward PCPH_OnRegisterProxyGroups(); + +/** + * Регистрация группы плейсхолдеров с указанным префиксом. + * Плейсхолдеры этой группы в строках имеют вид {prefix:key}. + * + * @param prefix Уникальный префикс группы (например, "p" для группы игрока) + * + * @return Хендлер зарегистрированной группы + */ +native T_PHGroup:PCPH_RegisterGroup(const prefix[]); + +/** + * Поиск группы плейсхолдеров по префиксу. + * + * @param prefix Префикс группы + * + * @return Хендлер группы или Invalid_PHGroup если не найдена + */ +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} (без префикса). + * + * @return Хендлер глобальной группы + */ +native T_PHGroup:PCPH_GetGlobalGroup(); + +/** + * Регистрация ключа в группе без колбека (проактивный режим). + * Значение устанавливается вручную через PCPH_Set / PCPH_SetForStr / PCPH_SetForInt. + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * + * @noreturn + */ +native PCPH_Group_RegisterKey(const T_PHGroup:group, const key[]); + +/** + * Установка колбека для уже зарегистрированного ключа в группе. + * + * @note Сигнатура колбека: (const key[], const contextKey[]) + * key: ключ запрашиваемого плейсхолдера + * contextKey: текущий контекст группы (например, индекс игрока) + * Колбек должен вызвать PCPH_Cb_Set / PCPH_Cb_SetInt / PCPH_Cb_SetFloat + * для установки актуального значения. + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера (должен быть предварительно зарегистрирован) + * @param callback Название функции-колбека + * + * @noreturn + */ +native PCPH_Group_SetKeyCallback(const T_PHGroup:group, const key[], const callback[]); + +/** + * Помещение нового контекста на стек группы. + * Контекст — произвольная строка-идентификатор (например, номер entity игрока). + * + * @param group Хендлер группы + * @param contextKey Идентификатор нового контекста + * + * @noreturn + */ +native PCPH_Group_PushContext(const T_PHGroup:group, const contextKey[]); + +/** + * Снятие верхнего контекста со стека группы. + * + * @param group Хендлер группы + * + * @noreturn + */ +native PCPH_Group_PopContext(const T_PHGroup:group); + +/** + * Установка строкового значения текущего плейсхолдера. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param value Устанавливаемое значение + * + * @noreturn + */ +native PCPH_Cb_Set(const value[]); + +/** + * Установка числового (int) значения текущего плейсхолдера. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param value Устанавливаемое значение + * + * @noreturn + */ +native PCPH_Cb_SetInt(const any:value); + +/** + * Установка числового (float) значения текущего плейсхолдера. + * Форматируется с точностью до 2 знаков после запятой. + * + * @note Можно вызывать только в рамках колбека ключа плейсхолдера + * + * @param value Устанавливаемое значение + * + * @noreturn + */ +native PCPH_Cb_SetFloat(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[]); + +/** + * Проактивная установка числового (int) значения с явным строковым контекстным ключом (вне колбека). + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param contextKey Контекстный ключ + * @param value Устанавливаемое значение + * + * @noreturn + */ +native PCPH_SetIntForStr(const T_PHGroup:group, const key[], const contextKey[], const any:value); + +/** + * Проактивная установка числового (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); + +/** + * Проактивная установка строкового значения для пустого контекста (вне колбека). + * + * @param group Хендлер группы + * @param key Ключ плейсхолдера + * @param value Устанавливаемое значение + * + * @noreturn + */ +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); +} + +/** + * Замена плейсхолдеров в строке format, результат записывается в out. + * Плейсхолдеры вида {key} и {prefix:key} заменяются на актуальные значения. + * Неизвестные группы и незарегистрированные ключи сохраняются как есть. + * + * @param out Буфер для результирующей строки + * @param outLen Размер буфера + * @param format Исходная строка с плейсхолдерами + * + * @noreturn + */ +native PCPH_Format(out[], const outLen, const format[]); + +/** + * Компилирует строку с плейсхолдерами в предварительно разобранный шаблон. + * Кеширует хендлеры групп и форвардов — при каждом вызове 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 для случаев, когда контекстный ключ — число (например, индекс игрока). + * + * @param group Хендлер группы + * @param contextKey Числовой идентификатор контекста + * + * @noreturn + */ +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 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 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 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 PCPH_Gl_RegisterKey(const key[]) { + PCPH_Group_RegisterKey(PCPH_GetGlobalGroup(), key); +} + +/** + * Установка колбека для ключа в глобальной группе. + */ +stock PCPH_Gl_SetKeyCallback(const key[], const callback[]) { + PCPH_Group_SetKeyCallback(PCPH_GetGlobalGroup(), key, callback); +} + +/** + * Регистрация ключа в группе с одновременной установкой колбека. + */ +stock PCPH_Group_RegKey(const T_PHGroup:group, const key[], const callback[]) { + PCPH_Group_RegisterKey(group, key); + PCPH_Group_SetKeyCallback(group, key, callback); +} + +/** + * Регистрация ключа в глобальной группе с одновременной установкой колбека. + */ +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[]) {