Архитектура - NikitaFoxze/Offensive-Core GitHub Wiki
Home ▸ Архитектура
[!TIP] Этот раздел описывает ключевые архитектурные решения проекта, включая
модульность
,структуру файлов
иорганизацию кода
.
Модульность
Всё, о чём здесь говорится - итог кропотливого и напряжённого труда. Архитектуру проекта уже будет сложно полностью переделать и очень долго по времени. Результат этой работы представляется весьма удачным и легко адаптируемым.
Эволюция архитектуры
Изначально весь код проекта разрабатывался в единственном .pwn
файле. Однако по мере роста (добавления различных режимов, сложных систем и дополнительных возможностей) возникла необходимость в модульной архитектуре. В какой-то момент стало очень тяжело продолжать писать код в одном файле.
Преимущества модульного подхода
- Упрощение отладки: Проблемы локализуются в конкретных модулях, а не в едином файле
- Гибкость: Возможность подключения модулей (например,
weapon-config
,nex-ac
) через механизм"хуков"
- Масштабируемость: Упрощенное добавление нового функционала
В процессе разработки появлялось ощущение, что Pawn вообще не создан для модульности, поэтому в коде сервера есть специфичные моменты, но плюсы перевешивают минусы.
Структура файлов
Эта структура кажется наиболее практичной и гибкой. Папки легко перестраивать, а имена файлов позволяют создавать новые модули, если потребуется.
Папки
Название папки | Назначение |
---|---|
sources |
Корневая директория проекта |
core |
Основная папка с базовыми модулями (админка, античит, БД, ...) |
modes |
Содержит поддиректории с игровыми режимами (DM, TDM, ...) |
modules |
Дополнительные модули (дина, промокоды, торговая площадка) |
На данный момент это все основные папки с системами.
Файлы
Название файла | Назначение |
---|---|
name_head.inc |
Файл содержащий основные макросы, функции, ... |
name_td.inc |
Файл содержащий все TextDraw's системы |
name_main.pwn |
Файл содержащий весь функционал системы |
Сейчас это ключевые файлы в любой системе. Если вы хотите изменить архитектуру, просто создайте новые файлы. Например, можно создать файл name_dialogs.inc
, который будет содержать все диалоги. Не забудьте подключить его.
Пояснение
[!IMPORTANT] Почти все файлы подключаются в
offensive-core.pwn
в папкеgamemodes
. Исключением являются файлы в папкеmodes
, где находятся все режимы и их функционал. Они подключаются в файлахincludes_head.inc
- для заголовочных иincludes_main.inc
- для основных файлов. Эти файлы также находятся в папкеmodes
.
Все файлы в папке sources
связаны между собой так или иначе, они имеют прямое влияние на геймплей игрока и не только, также "хукают"
некоторые колбэки, но не все, некоторые объявляются как функция (например, stock Modes_OnGameModeInit()
) и вызываются уже в файле offensive-core.pwn
, чтобы контролировать очередность вызовов этих колбэков.
Например, нам нужно, чтобы колбэк public OnGameModeInit()
в файле database_main.pwn
вызывался первым, а public OnGameModeExit()
- последним. Для этого мы создаем функции stock MySQL_OnGameModeInit()
и stock MySQL_OnGameModeExit()
в самом файле database_main.pwn
и вызываем их в offensive-core.pwn
как задумали.
Структура кода
В каждом файле имеются заголовки
и подзаголовки
блоков кода. Эти блоки кода могут быть, а могут и не быть, но у них есть очерёдность, которая соблюдается в каждом файле. В заголовочных (.inc) и в главных (.pwn) файлах они немного разнятся, но суть одна. Блоки помогают ориентироваться в коде, которого может быть 6000 строк и более.
Блоки кода
Например, сокращённая структура в файле player_main.pwn
.
/*
* |>======================<|
* | About: Player main |
* | Author: Foxze |
* |>======================<|
*/
/*
* |>--------------------------------------------<|
* | Functions |
* |>--------------------------------------------<|
* Public:
- OnPlayerConnect(playerid)
- OnPlayerDisconnect(playerid, reason)
* Stock:
- GivePlayerRank(playerid, rank)
* |>--------------------------------------------<|
* | Enums |
* |>--------------------------------------------<|
- E_PLAYER_INFO
* |>--------------------------------------------<|
* | Commands |
* |>--------------------------------------------<|
- menu(playerid)
* |>--------------------------------------------<|
* | Dialogs |
* |>--------------------------------------------<|
- Login
* |>--------------------------------------------<|
* | Interfaces |
* |>--------------------------------------------<|
- (None)
*/
#if defined _INC_PLAYER_MAIN
#endinput
#endif
#define _INC_PLAYER_MAIN
/*
* |>-------------<|
* | Enums |
* |>-------------<|
*/
enum E_PLAYER_INFO {
ORM:e_ORM_ID,
e_ID,
e_Name[MAX_PLAYER_NAME],
e_Password[MAX_LENGTH_PASSWORD]
}
/*
* |>------------<|
* | Vars |
* |>------------<|
*/
static
pInfo[MAX_PLAYERS][E_PLAYER_INFO];
/*
* |>-----------------<|
* | Functions |
* |>-----------------<|
*/
stock GivePlayerRank(playerid, rank)
{
// Код...
return 1;
}
/*
* |>-------------<|
* | Reset |
* |>-------------<|
*/
static ResetPlayerGlobalData(playerid)
{
pInfo[playerid][e_ORM_ID] = ORM:0;
pInfo[playerid][e_ID] = -1;
pInfo[playerid][e_Name][0] =
pInfo[playerid][e_Password][0] = EOS;
return 1;
}
/*
* |>----------------<|
* | Commands |
* |>----------------<|
*/
CMD:menu(playerid)
{
// Код...
}
/*
* |>---------------<|
* | Dialogs |
* |>---------------<|
*/
DialogCreate:Login(playerid)
{
// Код...
return 1;
}
DialogResponse:Login(playerid, response, listitem, inputtext[])
{
// Код...
return 1;
}
/*
* |>-------------<|
* | MySQL |
* |>-------------<|
*/
// Различные MySQL запросы, например, авторизация и т.д.
/*
* |>-----------------<|
* | Callbacks |
* |>-----------------<|
*/
/*
* |>-------------------<|
* | OnPlayerConnect |
* |>-------------------<|
*/
stock Player_OnPlayerConnect(playerid)
{
new playerLastIP[MAX_LENGTH_IP], playerName[MAX_PLAYER_NAME];
GetPlayerIp(playerid, playerLastIP, MAX_LENGTH_IP);
GetPlayerName(playerid, playerName, MAX_PLAYER_NAME);
return 0;
}
/*
* |>----------------------<|
* | OnPlayerDisconnect |
* |>----------------------<|
*/
stock Player_OnPlayerDisconnect(playerid, reason)
{
// Код...
return 0;
}
/*
* |>-------<|
* | ALS |
* |>-------<|
*/
// Например:
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect Player_OnPlayerConnect
#if defined Player_OnPlayerConnect
forward Player_OnPlayerConnect(playerid);
#endif
Пояснение
Обратите внимание на стиль именования переменных и функций. Глобальные и локальные переменные и функции следуют PascalCase
, за исключением локальных переменных в самих функциях, которые начинаются со строчной буквы (например, playerName[MAX_PLAYER_NAME]
), а также аргументов функций. Переменные с суффиксом "id"
в конце, такие как playerid
или modeid
, также являются исключением.
Приставки имеют функции только в папке modes
, например, создание сессии в TDM режиме - TDM_CreateSession(sessionid)
, это выглядит более читабельным и удобным.
Вы, наверное, заметили, что функция Player_OnPlayerConnect(playerid)
хоть и является обычной функцией, но числиться колбэком. Это сделано для удобства, чтобы понимать, что эта функция вызывается в колбэке где-то в offensive-core.pwn
. Последний "хук" написан лишь для примера блока.
Элемент | Стиль | Пример |
---|---|---|
Глобальные/Локальные переменные | PascalCase | PlayerTimerSpawn[MAX_PLAYERS] |
Локальные переменные в функциях | camelCase | playerName[MAX_PLAYER_NAME] |
Аргументы в функциях | camelCase | const stringDatetime[] |
Глобальные/Локальные функции | PascalCase | GivePlayerRank |
Функции в папке modes | Префикс_Функция | TDM_CreateSession |
Итоги
Мы обсудили ключевые аспекты: структуру файлов и папок, код и стиль его написания. Важно помнить, что все эти элементы применяются по всему проекту. Их нужно соблюдать. Да, есть альтернативные подходы, но в этом проекте используются именно эти.