[SourcePawn] Урок 7 - Панели и Меню

R1KO

fuck society
Сообщения
9,460
Реакции
7,798
  • Команда форума
  • #1
[SourcePawn] Урок 7 - Панели и Меню

<- К содержанию
SourceMod имеет 2 стиля меню:
  • Valve Style - так же называемое "ESC" меню; 8 пунктов на странице, нельзя добавить текст без занятия слота или спрятать (отключить) текст. Это меню, которое отображается в меню игры при нажатии на ESC.
  • Radio Style - так же называемый "AMX" меню; 10 пунктов на странице (в CS:GO 9), можно добавить любой текст. Это обычное привычное всем меню админки и отправки радиокоманд.

В SourceMod существует 2 вида меню: Панели и Меню.
В чем же разница?
  • Панель - более простой инструмент для создания меню. Может иметь как пункты так и простой текст. Панель считается временным объектом, хотя хранится может столько, сколько понадобится. Образный вызов для панели вызывается только при нажатии на пункт и при закрытии. Нумерация пунктов от 1 до 10.
  • Меню - является более сложным инструментом. Содержит пункты и их описание. Позволяет изменять заглавие, текст отображаемый в пунктах и их стиль. Обратный вызов может быть вызван при уничтожении меню, при закрытии, отображении меню и пунктов. Нумерация пунктов от 0 до N.

Panel - производный тип от Handle.
Методы типа Panel:
  • Panel - Создает объект типа Panel
    PHP:
    Panel hPanel = new Panel();
  • SetTitle - Добавляет заглавие к панели
    PHP:
    hPanel.SetTitle("Заглавие");
  • DrawItem - Добавляет пункт в панель и возвращает его позицию
    PHP:
    hPanel.DrawItem("Пункт 1");
    hPanel.DrawItem("Пункт 2");
    hPanel.DrawItem("Пункт 3");
  • DrawText - Добавляет строку текста в панель. Она не имеет номера.
    PHP:
    hPanel.DrawText("Текст 1");
    hPanel.DrawText("Текст 2");
  • CanDrawFlags - Возвращает поддерживает ли эта панель указанный стиль пунктов
    PHP:
    if(hPanel.CanDrawFlags(ITEMDRAW_DEFAULT))
    {
    // ...
    }
  • Send - Отправляется панель игроку
    PHP:
    hPanel.Send(iClient, MenuHandler_MyPanel, 20);

Свойства типа Panel:
  • TextRemaining - Возвращает сколько текста еще может содержать панель.
  • CurrentKey - Возвращает/Устанавливает текущую позицию для добавления пункта
  • Style - Возвращает текущий стиль панели.

Menu - производный тип от Handle.
Методы типа Menu:
  • Menu - Создает объект типа Menu
    PHP:
    Menu hMenu = new Menu(MenuHandler_MyMenu);
  • SetTitle - Добавляет заглавие к меню. Поддерживает форматирование текста.
    PHP:
    hMenu.SetTitle("Заглавие");
  • GetTitle - Получает заглавие меню.
    PHP:
    char szTitle[128];
    hMenu.GetTitle(szTitle, sizeof(szTitle));
  • AddItem - Добавляет пункт в меню.
    PHP:
    hMenu.AddItem("описание", "название", стиль);
    hMenu.AddItem("item1", "Пункт 1");
    hMenu.AddItem("item2", "Пункт 2", ITEMDRAW_DISABLED);
  • InsertItem - Вставляет пункт на указанной позиции в меню.
    PHP:
    hMenu.InsertItem(позиция, "описание", "название", стиль);
    hMenu.InsertItem(3, "item4", "Пункт 4");
    hMenu.InsertItem(6, "item7", "Пункт 7", ITEMDRAW_DISABLED);
  • GetItem - Получает информацию о пункте (описание, название, стиль)
    PHP:
    hMenu.GetItem(int position, char[] infoBuf, int infoBufLen, int &style, char[] dispBuf, int dispBufLen)
    // position - позиция пункта
    // infoBuf - буфер для помещения описания пункта
    // infoBufLen - размер буфера для описания пункта
    // style - буфер для помещения стиля пункта
    // dispBuf - буфер для помещения названия пункта
    // dispBufLen - размер буфера для названия пункта
    
    // Получаем всё
    char szInfo[128], szTitle[128];
    int iStyle;
    hMenu.GetItem(iItem, szInfo, sizeof(szInfo), iStyle, szTitle, sizeof(szTitle));
    
    // Получаем только описание
    char szInfo[128];
    hMenu.GetItem(iItem, szInfo, sizeof(szInfo));
    
    // Получаем только название
    char szTitle[128];
    hMenu.GetItem(iItem, "", 0_, szTitle, sizeof(szTitle));
    
    // Получаем только стиль
    int iStyle;
    hMenu.GetItem(iItem, _, _, iStyle);
    
    // Получаем только название и описание
    char szInfo[128], szTitle[128];
    hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szTitle, sizeof(szTitle));
  • RemoveItem - Удаляет пункт из меню. При этом все пункты сдвигаются.
    PHP:
    hMenu.RemoveItem(номер_пункта);
    hMenu.RemoveItem(4);
  • RemoveAllItems - Удаляет все пункты из меню.
    PHP:
    hMenu.RemoveAllItems();
  • ToPanel - Создает объект типа Panel на основе меню.
    PHP:
    Panel hPanel = hMenu.ToPanel();
  • Display - Отправляет меню игроку
    PHP:
    hMenu.Display(iClient, 20); // 20 это количество секунд, по истечению которых меню будет закрыто. Чтобы убрать лимит по времени необходимо установить 0 или MENU_TIME_FOREVER
  • DisplayAt - Отправляет меню игроку, начиная с указанного пункта
    PHP:
    hMenu.DisplayAt(iClient, 4, 20); // 4 это номер пункта, который будет первым.
  • Cancel - Отменяет меню у всех игроков
    PHP:
    hMenu.Cancel();

Свойства типа Menu:
  • Pagination - Возвращает/Устанавливает количество пунктов на странице. Чтобы убрать разбиение на страницы установить MENU_NO_PAGINATION.
  • OptionFlags - Возвращает/Устанавливает флаги опций меню
  • ExitButton - Возвращает/Устанавливает наличие кнопки "Выход".
  • ExitBackButton - Возвращает/Устанавливает наличие кнопки "Назад".
  • ItemCount - Возвращает количество пунктов в меню.
  • Style - Возвращает стиль меню.
  • Selection - Возвращает номер первого пункта на странице с выбранным пунктом. Нужен для отправки меню с этого же места, где был выбран пункт.

Стили пунктов:
  • ITEMDRAW_DEFAULT - Нормальный пункт.
  • ITEMDRAW_DISABLED - Пункт, который нельзя выбрать (белого цвета).
  • ITEMDRAW_RAWLINE - Пункт, который не имеет слота и имеет только текст
  • ITEMDRAW_NOTEXT - Пункт, без текста
  • ITEMDRAW_SPACER - Пункт, для отступа
  • ITEMDRAW_IGNORE - Пункт, будет игнорироваться (rawline + notext)
  • ITEMDRAW_CONTROL - Пункт, который имеет функцию управления (назад / далее / выход)

Обратный вызов.
И меню и панели имею одинаковый обратный вызов:
PHP:
typedef MenuHandler = function int(Menu menu, MenuAction action, int param1, int param2)
  • Menu menu - Объект меню
  • MenuAction action - Тип события обратного вызова
  • int param1 - Параметр 1
  • int param2 - Параметр 2

События обратного вызова
  • MenuAction_Start- Меню было начато. Это гарантирует что будет вызвано событие завершения меню.
    • param1 - Всегда 0
    • param2 - Всегда 0
  • MenuAction_Display- Меню было отображено игроку.
    • param1 - Индекс игрока
    • param2 - MenuPanel Handle
  • MenuAction_Select- Игрок нажал на пункт.
    • param1 - Индекс игрока
    • param2 - Номер пункта
  • MenuAction_DisplayItem- Отображение содержимого пункта игроку.
    • param1 - Индекс игрока
    • param2 - Номер пункта
  • Чтобы изменить отображение пункта вернуть RedrawMenuItem().
  • MenuAction_DrawItem- Отображение стиля пункта игроку.
    • param1 - Индекс игрока
    • param2 - Номер пункта
  • Чтобы изменить - вернуть новый стиль.
  • MenuAction_Cancel- Меню было отменено.
    • param1 - Индекс игрока
    • param2- Причина:
      • MenuCancel_Disconnected - Игрок был отключен от сервера
      • MenuCancel_Interrupted - Другое меню перебило наше
      • MenuCancel_Exit - Игрок нажал на кнопку "Выход"
      • MenuCancel_NoDisplay - Меню не может быть отображено игроку
      • MenuCancel_Timeout - Время отображения вышло
      • MenuCancel_ExitBack - Игрок нажал на кнопку "Назад"
  • MenuAction_End- Меню было полностью завершено. Именно в этом событии следует удалять его.
    • param1- Причина:
      • MenuEnd_Selected - Игрок нажал на пункт
      • MenuEnd_Exit - Игрок нажал на кнопку "Выход"
      • MenuEnd_ExitBack - Игрок нажал на кнопку "Назад"
      • MenuEnd_Cancelled - Меню было отменено (Причин param2)
      • MenuEnd_VotingDone - Голосование завершено
      • MenuEnd_VotingCancelled - Голосование отменено
    • param2 - Либо 0, либо причина.
Для панели действительны только 2 события: MenuAction_Cancel и MenuAction_Select.
Для меню действительны все события. Но чтобы они вызывались их необходимо указать при создании меню:
PHP:
Menu hMenu = new Menu(MenuHandler_MyMenu, События);

// Если события не указаны используется MENU_ACTIONS_DEFAULT, который равен MenuAction_Select|MenuAction_Cancel|MenuAction_End

// Запросим события отображения содержимого пунктов, отображения меню, выбора пункта и отмены:
Menu hMenu = new Menu(MenuHandler_MyMenu, MenuAction_Cancel|MenuAction_Select|MenuAction_Display|MenuAction_DisplayItem);

Обратный вызов со всеми доступными событиями (кроме Start) и примером их использования
PHP:
// Для удобства переименовал параметры
public int MenuHandler_MyMenu(Menu hMenu, MenuAction action, int iClient, int iItem)
{
    switch(action)
    {
        case MenuAction_End:    // Меню завершилось
        {
            // Оно нам больше не нужно. Удалим его
            delete hMenu;
        }
        case MenuAction_Cancel:    // Меню было отменено
        {
            if(iItem == MenuCancel_ExitBack)    // Если игрок нажал кнопку "Назад"
            {
                // Отправим ему сообщение что нет возможности вернуться назад.
                // Бывает же что конопку "Назад" назад добавили там, где это не нужно
                PrintToChat(iClient, "Извините, но назад вернуться нельзя!");
            }
        }
        case MenuAction_Display:    // Меню отображено игроку
        {
            // Изменим заглавие
            char szTitle[128];
            FormatEx(szTitle, sizeof(szTitle), "%T", "фраза_из_перевода", iClient);
            (view_as<Panel>(iItem)).SetTitle(szTitle); // iItem имеет тип int, его нужно привести к типу Panel и использовать метод SetTitle для установки заглавия.
        }
        case MenuAction_Select:    // Игрок выбрал пункт
        {
            char szInfo[64], szTitle[128];
            hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szTitle, sizeof(szTitle));
            PrintToChat(iClient, "Вы выбрали пункт: %i (%s, инфо: %s)", iItem, szTitle, szInfo);
        }
        case MenuAction_DisplayItem:    // Пункт будет отображен игроку (его содержимое)
        {
            // Изменим его
            char szItem[128], szTitle[128];
            hMenu.GetItem(iItem, "", 0, _, szTitle, sizeof(szTitle));    // Получим название пункта и используем его имя фразы в переводе
            FormatEx(szItem, sizeof(szItem), "%T", szTitle, iClient);
            return RedrawMenuItem(szItem); // Изменяем отображаемый текст пункта
        }
        case MenuAction_DrawItem:    // Пункт будет отображен игроку (его стиль)
        {
            // Давайте скроем пункты с описанием "hide" и сделаем недоступными пункты с описанием "block"
            char szInfo[64];
            hMenu.GetItem(iItem, szInfo, sizeof(szInfo));
            if(strcmp(szInfo, "hide") == 0)
            {
                return ITEMDRAW_RAWLINE;
            }

            if(strcmp(szInfo, "block") == 0)
            {
                return ITEMDRAW_DISABLED;
            }
        }
    }

    return 0;
}

Примеры создания и работы с панелями и меню, а так же голосованиями добавлю немного позже.
 
Последнее редактирование:

Grey83

не пишу плагины с весны 2022
Сообщения
8,805
Реакции
5,254
Radio Style - так же называемый "AMX" меню; 10 пунктов на странице
Вообще-то зависит от игры. Может быть и 9 (CS:GO, например).
InsertItem - Вставляет пункт на указанной позиции в меню.
PHP:
hMenu.AddItem(позиция, "описание", "название", стиль);
hMenu.AddItem(3, "item4", "Пункт 4");
hMenu.AddItem(6, "item7", "Пункт 7", ITEMDRAW_DISABLED);
Хм откуда
AddItem взялось?:nea:

Надо бы указать про стили и разбиение на страницы:
Anything other than DEFAULT or DISABLED will be completely ignored when paginating.
И что вызов всех действий меню можно делать через MENU_ACTIONS_ALL
 
  • Мне нравится
Реакции: R1KO

Rabb1t

Амбассадор
Сообщения
2,968
Реакции
1,430
  • Команда форума
  • #3
@R1KO, а разве в новом синтаксисе есть new?
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,805
Реакции
5,254
@Rabb1t, есть
Обычно с различными хэндлами используется, как в примере с созданием хэндлов панели и меню.
 

DenisPukin

Капитан Костыль
Сообщения
185
Реакции
34
А как поймать номер выбранной опции в панели?
 

Rabb1t

Амбассадор
Сообщения
2,968
Реакции
1,430
  • Команда форума
  • #7
@DenisPukin,
PHP:
public int Handler(Menu menu, MenuAction action, int param1, int param2) 
{
    if (action == MenuAction_Select && param2 == %n) // Вместо %n - вставляй номер нужного пункта.
    { 
        /* Делай что нужно. */
    }
}
 

DenisPukin

Капитан Костыль
Сообщения
185
Реакции
34
@Rabb1t, Спасибо
--- Добавлено позже ---
Хм. Тут ошибочка!
Вот строка:
PHP:
SendPanelToClient(panel, client, Kit_Panel, 0);
Вот ошибка:
error 100 function prototypes do not match
--- Добавлено позже ---
Ты наверное так не поймешь нужно код скидывать.
--- Добавлено позже ---
PHP:
new Handle:panel = CreatePanel(); 
      SetPanelTitle(panel, "Core\n \n"); 
      DrawPanelItem(panel, "1"); 
      DrawPanelItem(panel, "2"); 
      DrawPanelItem(panel, "3"); 
      DrawPanelItem(panel, "Выход", ITEMDRAW_CONTROL); 
      SendPanelToClient(panel, client, Panel, 0); 
      CloseHandle(panel); 

public Panel(Handle:panel, MenuAction:action, client, param1, param2) 
{ 
    new Handle:kv = CreateKeyValues("WeaponGroup");
    FileToKeyValues(kv,    "addons/sourcemod/configs/Core.txt");
        if (action == MenuAction_Select)
        { 
         if (param2 == 1)
         {
         if(KvJumpToKey(kv, "Terror"))
            {
                decl String:sWeapon[64];
                KvGetString(kv, "w1",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w2",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w3",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvRewind(kv);
            CloseHandle(kv);
            }
          }
         
        else if (param2 == 2)
         {
         if(KvJumpToKey(kv, "SWAT"))
            {
                decl String:sWeapon[64];
                KvGetString(kv, "w1",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w2",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w3",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvRewind(kv);
            }
            CloseHandle(kv);
         }
         
         else if (param2 == 3)
         {
         if(KvJumpToKey(kv, "Sniper"))
            {
                decl String:sWeapon[64];
                KvGetString(kv, "w1",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w2",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvGetString(kv,    "w3",    sWeapon,    sizeof(sWeapon));
                GivePlayerItem(client, sWeapon);
                KvRewind(kv);
            }
            CloseHandle(kv);
          }
        }
--- Добавлено позже ---
Всё я разобрался.
 
Последнее редактирование:

_wS_

Участник
Сообщения
401
Реакции
785
Этот MenuAction_End может ввести в заблуждение, т.к. есть еще такая штука как:
native bool:VoteMenu(Handle:menu, clients[], numClients, time, flags=0);

Я вот привык что раз меню закрылось для игрока, то всё, ему капец (MenuAction_End), но вот если это голосование (VoteMenu), то MenuAction_End вызывается лишь после того, как все игроки закроют меню или время голосования истечёт. В заблуждение еще ввело то, что когда тестируешь с ботами, MenuAction_End срабатывает сразу же, но потестил с парнем, и оказывается оно вызывается в самом конце голосования. Один из заказов вынудил вникнуть в эту стандартную систему SM голосований, никогда раньше их не трогал, оказалось хорошая штука =)
 

DeathScore13

пирожок. пирожочек.
Сообщения
746
Реакции
428
Заметил что тут нет описания CloseHandle(hPanel);, который я встречал в некоторых плагинах. Для чего он нужен?
 

Rabb1t

Амбассадор
Сообщения
2,968
Реакции
1,430
  • Команда форума
  • #13
Заметил что тут нет описания CloseHandle(hPanel);
Просто эта ф-я не относится к меню, однако ты можешь в API SM заметить подобное:
1579163393447.png


Также можно делать проще (на мой взгляд): hPanel.Close();
Данный вариант мне когда посоветовал @Коробка из под бананов, удобный в общем вариант. Но что использовать решай сам.
 

R1KO

fuck society
Сообщения
9,460
Реакции
7,798
  • Команда форума
  • #14
Просто эта ф-я не относится к меню, однако ты можешь в API SM заметить подобное:
Посмотреть вложение 51092

Также можно делать проще (на мой взгляд): hPanel.Close();
Данный вариант мне когда посоветовал @Коробка из под бананов, удобный в общем вариант. Но что использовать решай сам.
Это аналогичная ф-я. просто на новом синтаксисе.
Заметил что тут нет описания CloseHandle(hPanel);, который я встречал в некоторых плагинах. Для чего он нужен?
Уничтожает экземляр хендла и освобождает память, занимаемую им.
 

MaZa

Участник
Сообщения
1,733
Реакции
982
Опечатка
C-подобный:
// Получаем только название
char szTitle[128];
hMenu.GetItem(iItem, "", 0_, szTitle, sizeof(szTitle));

>>

C-подобный:
// Получаем только название
char szTitle[128];
hMenu.GetItem(iItem, "", 0, _, szTitle, sizeof(szTitle));
 

DeathScore13

пирожок. пирожочек.
Сообщения
746
Реакции
428
есть ли смысл уничтожать хендл меню после MenuAction_Cancel?
 

Tomiks

Участник
Сообщения
419
Реакции
288
есть ли смысл уничтожать хендл меню после MenuAction_Cancel?
Когда используется MenuAction_Cancel, то после него вызывается MenuAction_End и там просто пишешь delete menuHandle;

p.s. надеюсь не ошибся, сам так делаю, вроде как проблем не возникало

C++:
// это каллбек после создания меню
public int menuCallback(Menu menu, MenuAction action, int iClient, int item)
{
    switch(action)
    {
        case MenuAction_Select:
        {
            // code...
        }
        case MenuAction_End: delete menu;
        case MenuAction_Cancel:
        {
            // code..
        }
    }
}

Так что нету, наверное
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,805
Реакции
5,254
DeathScore13, событие MenuAction_Cancel вызывается пол нескольким причинам:
C-подобный:
/**
 * Reasons a menu can be cancelled (MenuAction_Cancel).
 */
enum
{
    MenuCancel_Disconnected = -1,   /**< Client dropped from the server */
    MenuCancel_Interrupted = -2,    /**< Client was interrupted with another menu */
    MenuCancel_Exit = -3,           /**< Client exited via "exit" */
    MenuCancel_NoDisplay = -4,      /**< Menu could not be displayed to the client */
    MenuCancel_Timeout = -5,        /**< Menu timed out */
    MenuCancel_ExitBack = -6        /**< Client selected "exit back" on a paginated menu */
};
 

DeathScore13

пирожок. пирожочек.
Сообщения
746
Реакции
428
DeathScore13, событие MenuAction_Cancel вызывается пол нескольким причинам:
C-подобный:
/**
* Reasons a menu can be cancelled (MenuAction_Cancel).
*/
enum
{
    MenuCancel_Disconnected = -1,   /**< Client dropped from the server */
    MenuCancel_Interrupted = -2,    /**< Client was interrupted with another menu */
    MenuCancel_Exit = -3,           /**< Client exited via "exit" */
    MenuCancel_NoDisplay = -4,      /**< Menu could not be displayed to the client */
    MenuCancel_Timeout = -5,        /**< Menu timed out */
    MenuCancel_ExitBack = -6        /**< Client selected "exit back" on a paginated menu */
};
думаю, это как раз то, что мне нужно. получается что при отключении игрока MenuCancel_Exit не вызывается и хендл так и остается не схлопнутым, верно?
 

DENFER

Пишу плагины за два биг тести и картошку фри..
Сообщения
260
Реакции
289
думаю, это как раз то, что мне нужно. получается что при отключении игрока MenuCancel_Exit не вызывается и хендл так и остается не схлопнутым, верно?
Нет, MenuAction_End отработает в любом случае, даже если ты этого не хочешь ;) Более правильно удалять менюшку там.
Screenshot_1.png
 
Последнее редактирование:
Сверху Снизу