Постараюсь объяснить, как можно сделать лучше и что вообще нужно понимать под "модульностью", а также, как всё это подружить с модом. Имею ввиду, что "модуль" - это полностью автономная или частично автономная система, которая имеет свои API функции для взаимодействия, помещённая в отдельный/отдельные инклуды.
Главный принцип - всё взаимодействие (обработка) происходит в моде, а рассчёты выполняются в инклудах.
Для начала, необходимо определить, какие системы будут использоваться. Далее, необходимо разделить эти системы на два типа: самостоятельная и зависимая. Самостоятельная - используется отдельно от других систем. Зависимая - используется совместно с другими системами. Подключать инклуды в моде нужно в порядке - сначала самостоятельные системы, а только потом зависимые.
Как понять, какая система к какому типу относится?
Система аккаунтов - самая важная на сервере, без которой невозможно представить игру на нём. Данная система взаимосвязана практически со всеми остальными системами. Важно понять, что мы используем функции аккаунта в других системах, а не внутри аккаунта используем функции других систем. Соответственно, это самостоятельная система (модуль). Что она в себя включает? Например:
CheckPlayerAccount(playerid) - проверить наличие аккаунта (зарегистрирован или нет)
GetPlayerAccountID(playerid) - получить ID аккаунта
GetPlayerAccountName(playerid) - получить имя аккаунта (игрока)
Разберём подробнее функцию CheckPlayerAccount. Где её применять и что она делает? Применяем мы её в OnPlayerConnect. В ней мы отправляем SELECT запрос в базу данных, чтобы найти информацию игрока по его имени. Сразу вопрос - где брать и хранить имя? Можно брать имя в OnPlayerConnect, но что такое имя игрока? Разве это не относится к информации аккаунта? Относится. Соответственно, зачем нам использовать функцию GetPlayerName в OnPlayerConnect, когда логичнее её переместить в CheckPlayerAccount.
[gamemode.pwn]
public OnPlayerConnect(playerid)
{
CheckPlayerAccount(playerid);
return 1;
}
[account.inc]
new account_name[MAX_PLAYERS][MAX_PLAYER_NAME];
#define GetPlayerAccountName(%0) account_name[%0]
stock CheckPlayerAccount(playerid)
{
GetPlayerName(playerid, account_name[playerid], MAX_PLAYER_NAME);
static const string[] = "SELECT * FROM `accounts` WHERE `name` = '%e' LIMIT 1;";
new query[szeof string + MAX_PLAYER_NAME];
mysql_format(db, query, sizeof query, string, account_name[playerid]);
mysql_tquery(db, query, "OnCheckPlayerAccount", "i", playerid);
return 1;
}
Пару слов про массив account_name и макрос GetPlayerAccountName. Так как мы пишем отдельную независимую систему, было бы хорошо, чтобы у неё были свои API функции и переменные/массивы для хранения информации. Делать отдельную функцию, которая будет возвращать строку - не лучшая практика. Поэтому мы объявляем массив account_name через оператор new, чтобы его можно было вызывать из других инклудов. Далее мы создаём макрос, который называется "как функция", а именно GetPlayerAccountName. Зачем создавать макросы/функции, когда можно вызывать переменную/массив напрямую? В первую очередь, чтобы не дать совершить самим себе ошибки в будущем, а также увеличиваем читаемость кода и не позволяем "как-то не так" использовать переменные/массивы. Ещё одно удобство заключается в том, что все API функции будут иметь свой "корень" в названии функции, например Account, давая нам понять к какой системе относится данная функция. Важно отметить, что создавать отдельные функции/макросы для использования той или иной переменной - не правило, а лишь рекомендация. Бывает, что рациональнее использовать что-то напрямую.
Отправив запрос, мы ожидаем результат в функции OnCheckPlayerAccount. Теперь нужно определить, где эта функции должна создаваться - в моде или в инклуде? В данном случае, мы создаём её в инклуде.
Добавим после массива account_name:
[account.inc]
static enum E_ACCOUNT_INFO
{
account_id,
}
static account_info[MAX_PLAYERS][E_ACCOUNT_INFO];
Добавим после макроса GetPlayerAccountName:
[account.inc]
stock GetPlayerAccountID(playerid)
{
return account_info[playerid][account_id];
}
Теперь создаём функцию OnCheckPlayerAccount:
[account.inc]
forward OnCheckPlayerAccount(playerid);
public OnCheckPlayerAccount(playerid)
{
if(cache_num_rows())
{
cache_get_value_name_int(0, "pID", account_info[playerid][account_id]);
// загружаем всю остальную информацию
return OnPlayerConnected(playerid, true);
}
else
return OnPlayerConnected(playerid, false);
}
Что это за функция OnPlayerConnected(playerid, bool:status), где её использовать и что за параметр мы передаём? Данная функция указывает нам, что мы обратились в базу данных и получили ответ для данного игрока. Параметр status говорит нам о том, зарегистрирован ли игрок или нет. Если какая-либо информация найдена - загружаем и вызываем OnPlayerConnected с параметром true, иначе с параметром false. Использование данной функции должно быть в gamemode.pwn, но объявление её через forward должно быть в инклуде.
[account.inc] forward OnPlayerConnected(playerid, bool:status);
[gamemode.pwn]
public OnPlayerConnect(playerid)
{
CheckPlayerAccount(playerid);
return 1;
}
public OnPlayerConnected(playerid, bool:status)
{
if(status)
// авторизация
else
// регистрация
return 1;
}
Таким образом, мы сделали вызов функции из системы аккаунтов, провели всю обработку внутри инклуда и выдали результат в мод. Подобным образом должно происходить вся взаимосвязь мод - инклуд / инклуд - мод.

Касаемо зависимых систем, например фракции. Алгоритм действий похож, только мы допускаем, что в этой системе мы будем использовать функции из других систем, например система аккаунта, получая ID или имя игрока для дальнейших действий.

Всё сводится к тому, чтобы вынести все действия в инклуд этой системы, а получаемый результат обработать в моде. В случае, когда вы затрудняетесь правильно распределить действия, а именно возникают проблемы с областью видимости, значит, этот участок кода необходимо перенести в мод.
Также стоит поговорить о более сложных системах, где используется более одного инклуда. В таком случае, создаём отдельную папку и уже в ней располагаем необходимые инклуды. Допустим, у нас есть система инвентаря. В ней необходимо хранить основные данные игрока, данные для визуальной части, а так же сами функции взаимодействия. Создаём папку inventory, в ней создаём инклуды core и ui. В ui инклуде будет находиться всё, что необходимо для отрисовки визуальной части, а в core инклуде основные данные и функции взаимодействия с игроком.
При подключении в моде этих двух инклудов так:
#include "../modules/inventory/core.inc" #include "../modules/inventory/ui.inc"
У нас возникнет проблема, что в инклуде core мы не можем обратиться к функциям из ui инклуда.
При подключении так:
#include "../modules/inventory/ui.inc" #include "../modules/inventory/core.inc"
У нас возникнет проблема, что теперь в ui инклуде невозможно получить данные из core инклуда.
Решение? Можно объединить всё в один инклуд, чтобы все необходимые константы и функции были видны друг другу, но мы поступим иначе. Можно подключить ui инклуд внутри core инклуда.
[core.inc] // создание всех необходимых макросов, констант, массивов и переменных #include "../modules/inventory/ui.inc" // созданием функций для взаимодействия с игроком
В моде оставляем подключение только core инклуда. Таким образом мы "скрыли" подключение одного из инклудов системы внутри другого (основного) и нет проблемы с областью видимости.
Дополню ещё, что все переменные/функции, которые используются только внутри инклуда, нужно создавать с помощью static, чтобы лишний раз их нельзя было вызывать где-то ещё, помимо этого инклуда. Также можно делать приставку к названию в виде нижнего подчёркивания "_" вначале, чтобы явно указывать, что функция не распространяется для других инклудов, так вместо OnCheckPlayerAccount можно использовать _OnCheckPlayerAccount.
В случае, если у нас многосоставная система, на подобии инвентаря, и нам необходимо создать функцию, которая должна быть видна во всех подключаемых инклудах, но не видна в моде, мы не можем создать функцию с помощью static, так как она не будет видна в подключаемых инклудах. Есть обходной путь:
[core.inc]
stock TestFunction()
{
return 1;
}
// в конце core.inc
#define TestFunction _TestFunction
Саму функцию мы делаем глобальной, но потом "скрываем" её. Так как макрос создаётся в самом конце инклуда, он начинает действовать для мода, а не для самого инклуда. При этом макрос имеет приоритет в вызове, по сравнению с функцией. Поэтому с помощью данного макроса мы перенаправляем вызов функции на несуществующую функцию _TestFunction. Использовав функцию TestFunction в моде, мы получим ошибку, что такая функция не существует.
Ещё дополню, что есть способ использовать одинаковое название для функций в разных инклудах, которые невозможно вызывать в моде. Маловероятно, что это кому-то пригодится, но вот пример:
[core_1.inc]
stock TestFunction()
{
return 1;
}
// в конце core_1.inc
static stock PREFIX1_Test(){}
#if defined _ALS_TestFunction
#undef TestFunction
#else
#define _ALS_TestFunction
#endif
#define TestFunction PREFIX1_TestFunction
[core_2.inc]
stock TestFunction()
{
return -1;
}
// в конце core_2.inc
static stock PREFIX2_Test(){}
#if defined _ALS_TestFunction
#undef TestFunction
#else
#define _ALS_TestFunction
#endif
#define TestFunction PREFIX2_TestFunction
Лично от себя рекомендую писать комментарии к собственным системам, чтобы в дальнейшем чётко понимать, что можно использовать в моде от этой системы. Я делаю так, перед подключением, вставляю подсказку:
/* Name_... -> function NAME_... -> define / const name_... -> var # -> define * -> const @ -> callback i -> iterator e -> enum p -> pvar s -> svar - -> new > -> function сmd -> command */
Ниже подключаю инклуды подобным образом:
#include "../modules/account.inc"
/*
# INVALID_ACCOUNT_ID (-1)
# GetPlayerAccountName(%0) (account_name[%0])
# GetPlayerAccountPassword(%0) (account_password[%0])
* LEN_ACCOUNT_NAME (MAX_PLAYER_NAME)
* SIZE_ACCOUNT_NAME (LEN_ACCOUNT_NAME+1)
* LEN_ACCOUNT_PASSWORD (64)
* SIZE_ACCOUNT_PASSWORD (LEN_ACCOUNT_PASSWORD+1)
@ OnPlayerConnected(playerid, bool:status)
i Account
- account_name[MAX_PLAYERS][SIZE_ACCOUNT_NAME]
- account_password[MAX_PLAYERS][SIZE_ACCOUNT_PASSWORD]
> CheckPlayerAccount(playerid)
> GetPlayerAccountID(playerid)
*/
Сообщение отредактировал M I S T E V: 24 ноября 2025 - 10:02
Вход
Регистрация
Помощь














