Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.zip
.build
plugins-*.ini
.claude
45 changes: 45 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
369 changes: 369 additions & 0 deletions PLACEHOLDERS.md

Large diffs are not rendered by default.

44 changes: 41 additions & 3 deletions amxmodx/scripting/ParamsController.sma
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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();
}
Expand All @@ -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();
}
4 changes: 2 additions & 2 deletions amxmodx/scripting/ParamsController/API/General.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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);
}
55 changes: 27 additions & 28 deletions amxmodx/scripting/ParamsController/API/Param.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion amxmodx/scripting/ParamsController/API/Utils.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <amxmodx>
#include <json>
#include <ParamsController>

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)
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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[]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Loading
Loading