Difference between revisions of "Menu API (SourceMod)=ru"

From AlliedModders Wiki
Jump to: navigation, search
m
(added ru translation)
Line 1: Line 1:
SourceMod has an extensive API for building and displaying menus to clients. Unlike AMX Mod X, this API is highly state driven. Menus are based on callbacks which are guaranteed to be fired.
+
SourceMod обладает большим API для создания и отображений меню для клиентов. В отличии от AMX Mod X, API SourceMod'a обладает широким функционалом. Меню базируются на обратных вызовах (callbacks), которые гарантированно будут вызваны.
  
For C++, the Menu API can be found in <tt>public/IMenuManager.h</tt>. For SourcePawn, it is in <tt>plugins/include/menus.inc</tt>.
+
Для C++, API меню может быть найдено в <tt>public/IMenuManager.h</tt>. Для SourcePawn - <tt>scripting/include/menus.inc</tt>.
  
=Objects=
+
=Объекты=
The SourceMod Menu System is based on an object oriented hierarchy. Understanding this hierarchy, even for scripting, is critical to using menus effectively.
+
Система меню SourceMod основана на объектно ориентированной иерархии. Понимание этой иерархии, даже для скриптинга, является ключевым моментом для эффективного использования меню.
  
==Styles==
+
==Стили==
The top level object is a ''MenuStyle'' (<tt>IMenuStyle</tt> in C++). Styles describe a unique menu system. There are two such styles built into SourceMod:
+
Высший уровень объектов называется ''MenuStyle'' (<tt>IMenuStyle</tt> в C++). Стили описывают уникальную систему меню. Всего есть два стиля, встроенных в SourceMod:
*Valve Style, also called "ESC" menus; 8 items per page, no raw/disabled text can be rendered
+
*Valve Styly, так же называемым "ESC" меню; 8 пунктов на странице, нельзя добавить текст без занятия слота или спрятать (отключить) текст.
*Radio Style, also called "AMX" menus; 10 items per page, raw/disabled text can be rendered
+
*Radio Style, так же называемый "AMX" меню; 10 пунктов на странице, можно добавить любой текст.
  
Each MenuStyle has its own rules and properties. You can think of them as existing on separate "channels."  For example, two different menus can exist on a player's screen as both a Valve menu and a Radio menu at the same time, and SourceMod will be able to manage both without any problems. This is because each style keeps track of its own menus separately.
+
Каждый MenuStyle обладает своими правилами и свойствами. Вы можете считать, что каждый из них существует на раздельных "каналах". Например, два разных меню могут быть на экране игрока, будучи Valve и Radio меню одновременно, и SourceMod сможет обработать их обоих без всяких проблем. Это всё возможно благодаря тому, что каждый стиль отслеживает свои меню отдельно.
  
==Panels==
+
==Панели==
Menu displays are drawn with a lower level interface called ''Panels'' (<tt>IMenuPanel</tt> in C++). Panels describe exactly one chunk of display text. Both selectable items and raw text can be added to a panel as long as its parent style supports the contents you're trying to draw. For example, the Valve style does not support drawing raw text or disabled items. But with a Radio-style Panel, you can display a large amount of on-screen data in your own format.
+
Меню низшего уровня называется ''Панелью'' (<tt>IMenuPanel</tt> в C++). Панель - один ''''кусок'''' текста. Пункты и текст могут добавляться к панели столько раз, сколько поддерживает родительский стиль. Например, Valve стиль не поддерживает отображение текста без занятия слота или отключенных пунктов. Однако с Radio стилем панели это становится возможным, и вы можете отобразить большое количество текста так, как вам это захочется.  
  
Panels are considered temporary objects. They are created, rendered, displayed, and destroyed. Although they can be saved indefinitely, it is not necessary to do so.
+
Панель считается временным объектом. Она создается, рендерится, отображается, и затем удаляется. Хотя она может быть сохранена на неопределенное время, если в этом есть необходимость.
  
Valve Style drawing rules/limitations:
+
Правила и ограничения Vavle стиля:
*Max items per page is 8.
+
*Максимум пунктов на странице: 8
*Disabled items cannot be drawn.
+
*Отключенный текст не отображается.
*Raw text cannot be drawn.
+
*Нельзя написать текст, не заняв слота.
*Spacers do not add a space/newline, giving a "cramped" feel.
+
*Пробелы не добавляются к пробелам\новым линиям, добавляя чувство "ограниченности".  
*Users must press "ESC" or be at their console to view the menu.
+
*Пользователь должен нажать "ESC" или быть в своей консоли, чтобы увидеть меню.
  
Radio Style drawing rules/limitations:
+
Правила и ограничения Radio стиля:
*Max items per page is 10.
+
*10 пунктов на странице
*Titles appear white; items appear yellow, unless disabled, in which case they are white.
+
*Заглавия белые; пункты желтые, если не отключены. Если отключены, то белого цвета.
*The 0th item is always white. For consistency, this means navigational controls explained in the next section are always white, and simply not drawn if disabled.
+
*Нулевой элемент всегда белый. Для согласованности это значит, что навигационное управление всегда белое и находится в следующей секции, и просто не отрисовывается, если отсутствует.
  
==Menus==
+
==Меню==
Lastly, there are plain ''Menus'' (<tt>IBaseMenu</tt> in C++). These are helper objects designed for storing a menu based on selectable items. Unlike low-level panels, menus are containers for '''items''', and can only contain items which are selectable (i.e., do not contain raw text). They fall into two categories:
+
Наконец, есть просто ''Меню'' (<tt>IBaseMenu</tt> in C++). Это вспомогательный объект, созданный для хранения базированных на меню выбираемых пунктов. В отличии от низкоуровневых панелей, меню содержит '''пункты''', и может содержать только те пункты, которые можно выбрать (т.е. занимающие слоты). Они делятся на две категории:
*Non-paginated: The menu can only have a certain number of items on it, and no control/navigation options will be added, except for an "Exit" button which will always be in the last position supported by the style.
+
*Не нумерованные: эти меню могут иметь определенное число пунктов, и не имеют навигационных опций, кроме кнопки "Выход", которая всегда добавляется на последнюю позицию, поддерживаемую стилем.
**Valve Style maximum items: 8
+
**Valve Стиль: максимум пунктов: 8
**Radio Style maximum items: 10
+
**Radio Стиль: максимум пунктов: 10
*Paginated: The menu can have any number of items. When displayed, only a certain number of items will be drawn at a time.  Automatic navigation controls are added so players can easily move back and forth to different "pages" of items in the menu.
+
*Нумерованные: такие меню могут содержать сколь угодно пунктов. Во время отображения будет показано только определенное число пунктов. Автоматически будут добавлены навигационные пункты, таким образом, игроки легко могут возвращаться назад и вперед к определенным "страницам" пунктов меню.
**"Previous" is always drawn as the first navigation item, third from the last supported position. This will not be drawn if the menu only contains one page. If there are no previous pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.
+
**"Назад" всегда отображается как первый навигационный пункт, третий с конца поддерживаемых позиций. Не будет добавлен, если меню содержит только одну страницу. Если нет никаких предыдущих страниц, текст никогда не будет отображен, ни в каком стиле. Если возможно, то меню будет дополнено необходимыми пробелами.
***Valve Style position: 6
+
***Valve Стиль, позиция: 6
***Radio Style position: 8
+
***Radio Стиль, позиция: 8
**"Next" is always drawn as the second navigation item, second from the last supported position. This will not be drawn if the menu only contains one page. If there are no further pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.
+
**"Вперед" всегда отображается как второй навигационный пункт, второй с конца из доступных позиций. Не отображается, если меню состоит из одной страницы. Если нет следующих страниц, то текс не будет отображен при любом стиле. Если возможно, то меню будет дополнено необходимыми пробелами.
***Valve Style position: 7
+
***Valve Стиль, позиция: 7
***Radio Style position: 9
+
***Radio Стиль, позиция: 9
**"Exit" is drawn if the menu has the exit button property set.  It is always the last supported item position.
+
**"Выход" отображается, если у меню включена кнопка "выход". Всегда занимает последнюю из возможных позиций
***Valve Style position: 8
+
***Valve Стиль, позиция: 8
***Radio Style position: 10
+
***Radio Стиль, позиция: 10
  
The purpose of Menus is to simplify the procedure of storing, drawing, and calculating the selection of items. Thus, menus do not allow for adding raw text, as that would considerably complicate the drawing algorithm''Note: The C++ API supports hooking <tt>IBaseMenu</tt> drawing procedures and adding raw text; this will be added to the scripting API soon.''
+
Цель меню - упростить процедуру хранения, отображения, и вычисления выбранного пункта. Таким образом, меню не позволяет добавлять пустой текст, так как это значительно усложняет алгоритм отображения.   
  
Internally, Menus are drawn via a ''RenderMenu'' algorithm. This algorithm creates a temporary panel and fills it with items from menus. This panel is then displayed to a client. The algorithm attempts to create a consistent feel across all menus, and across all styles. Thus any menu displayed via the <tt>IBaseMenu</tt> class, or <tt>Menu</tt> Handles, will look and act the same, and the Menu API is based off the Panel API.
+
Внутренне, меню отображается через ''RenderMenu'' алогритм. Этот алгоритм создает временную панель и заполняет её пунктами из меню. Эта панель отображается клиенту. Алгоритм пытается создать свободное перемещение по всем меню, и по всем стилям. Таким образом, все меню отображаются через <tt>IBaseMenu</tt> класс, или <tt>Menu</tt> дескрипторы (Handles), и будет выглядеть и действовать так же, как и меню API, основанное на панельном API.
  
 +
=Обратные вызовы (callbacks)=
 +
==Краткий обзор==
 +
Меню базируются на системе обратных вызовов. Каждый обратный вызов предоставляет собой действие, которые происходят во время ''цикла отображения меню''. Цикл состоит из множества уведомлений:
 +
*Начало уведомления.
 +
**Уведомление об отображении, если меню может быть отображено клиенту.
 +
**Уведомление о выборе пункта или кнопки отмены.
 +
*Конец уведомления.
  
=Callbacks=
+
Так как ''End'' обозначает конец полного цикла отображения, то обычно его используют для уничтожения временных меню.
==Overview==
 
Menus are a callback based system.  Each callback represents an action that occurs during a ''menu display cycle''.  A cycle consists of a number of notifications:
 
*Start notification.
 
**Display notification if the menu can be displayed to the client.
 
**Either an item select or menu cancel notification.
 
*End notification.
 
  
Since ''End'' signifies the end of a full display cycle, it is usually used to destroy temporary menus.
+
==Спецификация==
 +
Детальное объяснение этих событий ниже. Для C++, <tt>IBaseMenu</tt> указатель всегда доступен. Для SourcePawn, <tt>Menu</tt> хандл и <tt>MenuAction</tt> всегда установлены в <tt>MenuHandler</tt> callback. В отличии от C++, SourcePawn API позволяет вызвать лишь некоторые действия, если они были запрошены во время создания меню. Это сделано в целях оптимизации. Однако, эти действия не могут быть не вызваны.
  
==Specification==
+
*'''Start'''. Меню было признано. Это не означает, что меню будет отображено; однако, это гарантирует, что событие "OnMenuEnd" будет вызвано.
A detailed explanation of these events is belowFor C++, an <tt>IBaseMenu</tt> pointer is always available. For SourcePawn, a <tt>Menu</tt> Handle and a <tt>MenuAction</tt> are always set in the <tt>MenuHandler</tt> callbackUnlike C++, the SourcePawn API allows certain actions to only be called if they are requested at menu creation time. This is an optimization. However, certain actions cannot be prevented from being called.
+
**<tt>OnMenuStart()</tt> в C++.
 +
**<tt>MenuAction_Start</tt> в SourcePawn. Не будет вызвано, если не было запрошено.
 +
***<tt>param1</tt>: Игнорируется (всегда 0).
 +
***<tt>param2</tt>: Игнорируется (всегда 0).
 +
*'''Display'''Меню отобразилось клиенту.
 +
**<tt>OnMenuDisplay()</tt> в C++. <tt>IMenuPanel</tt> доступны указатель и индекс клиента.
 +
**<tt>MenuAction_Display</tt> в SourcePawn. Не будет вызвано, если не было запрошено.
 +
***<tt>param1</tt>: Индекс клиента.
 +
***<tt>param2</tt>: Хандл меню.
 +
*'''Select'''. Пункт был выбран. Будет дана позиция пункта в меню, а не нажатая кнопка (если меню не является панелью)
 +
**<tt>OnMenuSelect()</tt> в C++. Передаются индекс клиента и позиция пункта.
 +
**<tt>MenuAction_Select</tt> в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
 +
***<tt>param1</tt>: Индекс клиента.
 +
***<tt>param2</tt>: Позиция пункта.
 +
*'''Cancel'''Отображение меню у одного из клиентов было закрыто.
 +
**<tt>OnMenuCancel()</tt> в C++. Доступна причина закрытия.
 +
**<tt>MenuAction_Cancel</tt> в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
 +
***<tt>param1</tt>: Индекс клиента.
 +
***<tt>param2</tt>: Код причины закрытия меню.
 +
*'''End'''. Цикл отображения меню закончился; это означает, что "Start" действие было, и одно из действий "Select" или "Cancel" так же произошло. Обычно в этом месте отчищаются ресурсы меню или само меню удаляется.
 +
**<tt>OnMenuEnd()</tt> в C++.
 +
**<tt>MenuAction_End</tt> в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
 +
***<tt>param1</tt>: Причина завершения меню.
 +
***<tt>param2</tt>: если param1 == MenuEnd_Cancelled, то здесь содержится код причины закрытия меню.
  
*'''Start'''.  The menu has been acknowledged.  This does not mean it will be displayed; however, it guarantees that "OnMenuEnd" will be called.
+
==Панели==
**<tt>OnMenuStart()</tt> in C++.
+
Для панелей цикл жизни другой. Панели могут вызывать лишь два callback'а из написанных выше, и гарантируется, что один из них точно будет вызван для данного цикла отображения. Для C++, <tt>IBaseMenu</tt> указатель будет всегда <tt>NULL</tt>. Для SourcePawn, меню Handle будет всегда <tt>INVALID_HANDLE</tt>.
**<tt>MenuAction_Start</tt> in SourcePawn.  This action is not triggered unless requested.
 
***<tt>param1</tt>: Ignored (always 0).
 
***<tt>param2</tt>: Ignored (always 0).
 
*'''Display'''.  The menu is being displayed to a client.
 
**<tt>OnMenuDisplay()</tt> in C++.  An <tt>IMenuPanel</tt> pointer and client index are available.
 
**<tt>MenuAction_Display</tt> in SourcePawn.  This action is not triggered unless requested.
 
***<tt>param1</tt>: A client index.
 
***<tt>param2</tt>: A Handle to a menu panel.
 
*'''Select'''.  An item on the menu has been selected.  The item position given will be the position in the menu, rather than the key pressed (unless the menu is a raw panel). 
 
**<tt>OnMenuSelect()</tt> in C++.  A client index and item position are passed.
 
**<tt>MenuAction_Select</tt> in SourcePawn.  This action is always triggerable, whether requested or not.
 
***<tt>param1</tt>: A client index.
 
***<tt>param2</tt>: An item position.
 
*'''Cancel'''.  The menu's display to one client has been cancelled.
 
**<tt>OnMenuCancel()</tt> in C++.  A reason for cancellation is provided.
 
**<tt>MenuAction_Cancel</tt> in SourcePawn.  This action is always triggerable, whether requested or not.
 
***<tt>param1</tt>: A client index.
 
***<tt>param2</tt>: A menu cancellation reason code.
 
*'''End'''.  The menu's display cycle has finished; this means that the "Start" action has occurred, and either "Select" or "Cancel" has occurred thereafter. This is typically where menu resources are removed/deleted.
 
**<tt>OnMenuEnd()</tt> in C++.
 
**<tt>MenuAction_End</tt> in SourcePawn.  This action is always triggered, whether requested or not.
 
***<tt>param1</tt>: A menu end reason code.
 
***<tt>param2</tt>: If param1 was MenuEnd_Cancelled, this contains a menu cancellation reason code.
 
  
==Panels==
+
*'''Select'''Кнопка была нажата. Этой кнопкой может быть любое число. Например, если в панели у вас 2 пункта, то клиент может вызвать это событие, нажав "43".
For panels, the callback rules changePanels only receive two of the above callbacks, and it is guaranteed that only one of them will be called for a given display cycle. For C++, the <tt>IBaseMenu</tt> pointer will always be <tt>NULL</tt>. For SourcePawn, the menu Handle will always be <tt>INVALID_HANDLE</tt>.
+
**<tt>OnMenuSelect()</tt> в C++. Передаются индекс клиента и нажатая кнопка.
 +
**<tt>MenuAction_Select</tt> в SourcePawn.
 +
***<tt>param1</tt>: Индекс клиента.
 +
***<tt>param2</tt>: Номер нажатой клавиши.
 +
*'''Cancel'''. Отображение меню у одного из клиентов было закрыто.
 +
**<tt>OnMenuCancel()</tt> в C++. Доступна причина закрытия.
 +
**<tt>MenuAction_Cancel</tt> в SourcePawn.
 +
***<tt>param1</tt>: Индекс клиента.
 +
***<tt>param2</tt>: Код причины закрытия меню.
  
*'''Select'''.  A key has been pressed.  This can be any number and should not be considered as reliably in bounds.  For example, even if you only had 2 items in your panel, a client could trigger a key press of "43."
 
**<tt>OnMenuSelect()</tt> in C++.  A client index and key number pressed are passed.
 
**<tt>MenuAction_Select</tt> in SourcePawn.
 
***<tt>param1</tt>: A client index.
 
***<tt>param2</tt>: Number of the key pressed.
 
*'''Cancel'''.  The menu's display to one client has been cancelled.
 
**<tt>OnMenuCancel()</tt> in C++.  A reason for cancellation is provided.
 
**<tt>MenuAction_Cancel</tt> in SourcePawn.
 
***<tt>param1</tt>: A client index.
 
***<tt>param2</tt>: A menu cancellation reason code.
 
  
 +
=Примеры=
 +
Для начала попробуем сделать очень простое меню:
  
=Examples=
+
<pre>Вы любите яблоки?
First, let's start off with a very basic menu. We want the menu to look like this:
+
1. Да
 +
2. Нет</pre>
  
<pre>Do you like apples?
+
Сделаем это как меню и как панель, чтобы увидеть различия в их API.
1. Yes
 
2. No</pre>
 
  
We'll draw this menu with both a basic Menu and a Panel to show the API differences.
+
==Простое меню==
 
+
Сперва напишем наш пример через API Меню. Для пошагового руководства смотрите [[Menus Step By Step (SourceMod Scripting)]].
==Basic Menu==
 
First, let's write our example using the Menu building API.
 
  
 
<pawn>public OnPluginStart()
 
<pawn>public OnPluginStart()
 
{
 
{
RegConsoleCmd("menu_test1", Menu_Test1)
+
// зарегистрировали консольную команду menu_test1 для открытия меню.
 +
RegConsoleCmd("menu_test1", Menu_Test1);
 
}
 
}
  
 +
// хандл меню
 
public MenuHandler1(Handle:menu, MenuAction:action, param1, param2)
 
public MenuHandler1(Handle:menu, MenuAction:action, param1, param2)
 
{
 
{
/* If an option was selected, tell the client about the item. */
+
/* Если был выбран какой-либо пункт, то сообщим клиенту о его выборе. */
 
if (action == MenuAction_Select)
 
if (action == MenuAction_Select)
 
{
 
{
new String:info[32]
+
new String:info[32]; // переменная для хранения выбора
new bool:found = GetMenuItem(menu, param2, info, sizeof(info))
+
new bool:found = GetMenuItem(menu, param2, info, sizeof(info)); // получаем информацию о выбранном в меню пункте
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info)
+
PrintToConsole(param1, "Вы нажали: %d (найдено? %d информация: %s)", param2, found, info); // пишем клиенту в консоль
 
}
 
}
/* If the menu was cancelled, print a message to the server about it. */
+
/* Если меню было отменено, то сообщим об этом серверу. */
 
else if (action == MenuAction_Cancel)
 
else if (action == MenuAction_Cancel)
 
{
 
{
PrintToServer("Client %d's menu was cancelledReason: %d", param1, param2)
+
PrintToServer("Клиент %d' закрыл менюПричина: %d", param1, param2);
 
}
 
}
/* If the menu has ended, destroy it */
+
/* Если меню "закончилось", то удалим его из памяти */
 
else if (action == MenuAction_End)
 
else if (action == MenuAction_End)
 
{
 
{
CloseHandle(menu)
+
CloseHandle(menu);
 
}
 
}
 
}
 
}
Line 145: Line 146:
 
public Action:Menu_Test1(client, args)
 
public Action:Menu_Test1(client, args)
 
{
 
{
new Handle:menu = CreateMenu(MenuHandler1)
+
new Handle:menu = CreateMenu(MenuHandler1);
SetMenuTitle(menu, "Do you like apples?")
+
SetMenuTitle(menu, "Вы любите яблоки?");
AddMenuItem(menu, "yes", "Yes")
+
AddMenuItem(menu, "да", "да");
AddMenuItem(menu, "no", "No")
+
AddMenuItem(menu, "нет", "нет");
SetMenuExitButton(menu, false)
+
SetMenuExitButton(menu, false);
DisplayMenu(menu, client, 20)
+
DisplayMenu(menu, client, 20);
 
 
return Plugin_Handled
+
return Plugin_Handled;
 
}</pawn>
 
}</pawn>
  
Note a few very important points from this example:
+
Несколько очень важных замечаний о этом примере:
*One of either <tt>Select</tt> or <tt>Cancel</tt> will always be sent to the action handler.
+
*Одно из <tt>Select</tt> или <tt>Cancel</tt> событий всегда будет отослано обработчику действий (action handler).
*<tt>End</tt> will always be sent to the action handler.
+
*<tt>End</tt> всегда будет отослано обработчику действий (action handler).
*We destroy our Menu in the <tt>End</tt> action, because our Handle is no longer needed. If we had destroyed the Menu after <tt>DisplayMenu</tt>, it would have canceled the menu's display to the client.
+
*Мы удаляем наше меню в <tt>End</tt> действии, потому что наш дескриптор (Handle) нам больше не нужен. Если бы мы удалили его после <tt>DisplayMenu</tt>, это бы отменило отображение меню клиенту.
*Menus, by default, have an exit button. We disabled this in our example.
+
*Меню по умолчанию имеет кнопку выхода. В нашем примере мы его отключили.
*Our menu is set to display for 20 seconds. That means that if the client does not select an item within 20 seconds, the menu will be canceled. This is usually desired for menus that are for voting. Note that unlike AMX Mod X, you do not need to set a timer to make sure the menu will be ended.
+
*Наше меню будет отображаться 20 секунд. Это означает, что если клиент ничего не выбрал в течении 20 секунд, то меню будет закрыто. Это обычно необходимо для меню с голосованиями. В отличии от AMX Mod X, вам не нужно создавать таймер, чтобы быть увереным, что меню "закончилось".
*Although we created and destroyed a new Menu Handle, we didn't need to. It is perfectly acceptable to create the Handle once for the lifetime of the plugin.
+
*Мы создали и уничтожили новый дескриптор меню (Menu Handle), однако мы можем этого и не делать. Вполне допустимо создать дескриптор (Handle) один раз для всего времени жизни плагина.
 +
*Чтобы нормально отображались русские буквы, кодировка файла должна быть UTF-8 (без BOOM), иначе будут квадратики вместо русских букв.
  
Our finished menu and attached console output looks like this (I selected "Yes"):
+
Наше законченное меню и приложеный вывод в консоли выглядит как на картинке ниже (был выбран ответ "Да", и картинка взята с англ вики, прим. переводчика).
  
 
[[Image:Basic_menu_1.PNG]]
 
[[Image:Basic_menu_1.PNG]]
  
==Basic Panel==
+
==Базовая панель==
Now, let's rewrite our example to use Panels instead.
+
Теперь давайте перепишем наш пример с использованием Панели вместо Меню.
  
 
<pawn>public OnPluginStart()
 
<pawn>public OnPluginStart()
 
{
 
{
RegConsoleCmd("panel_test1", Panel_Test1)
+
// зарегистрировали консольную команду panel_test1 для открытия меню.
 +
RegConsoleCmd("panel_test1", Panel_Test1);
 
}
 
}
  
Line 179: Line 182:
 
if (action == MenuAction_Select)
 
if (action == MenuAction_Select)
 
{
 
{
PrintToConsole(param1, "You selected item: %d", param2)
+
PrintToConsole(param1, "Вы выбрали: %d", param2);
 
} else if (action == MenuAction_Cancel) {
 
} else if (action == MenuAction_Cancel) {
PrintToServer("Client %d's menu was cancelled. Reason: %d", param1, param2)
+
PrintToServer("Клиент %d' закрыл панель. Причина: %d", param1, param2);
 
}
 
}
 
}
 
}
Line 188: Line 191:
 
{
 
{
 
new Handle:panel = CreatePanel();
 
new Handle:panel = CreatePanel();
SetPanelTitle(panel, "Do you like apples?")
+
SetPanelTitle(panel, "Вы любите яблоки?");
DrawPanelItem(panel, "Yes")
+
DrawPanelItem(panel, "Да");
DrawPanelItem(panel, "No")
+
DrawPanelItem(panel, "Нет");
 
 
SendPanelToClient(panel, client, PanelHandler1, 20)
+
SendPanelToClient(panel, client, PanelHandler1, 20);
  
CloseHandle(panel)
+
CloseHandle(panel);
 
 
return Plugin_Handled
+
return Plugin_Handled;
 
}</pawn>
 
}</pawn>
  
As you can see, Panels are significantly different.
+
Как вы можете заметить, Панели сильно отличаются от Меню:
*We can destroy the Panel as soon as we're done displaying it. We can create the Panel once and keep re-using it, but we can destroy it at any time without interrupting client menus.
+
*Мы можем удалить Панель как только закончим его отображать. Мы можем создать Панель один раз, и продолжать использовать его много раз, однако мы можем удалить его в любой момент, без нарушения цикла отображения клиентского меню.
*The Handler function gets much less data. Since panels are designed as a raw display, no "item" information is saved internally. Thus, the handler function only knows whether the display was canceled or whether (and what) numerical key was pressed.
+
*Обработчик Панели (Handler) получает гораздо меньше информации. Так как Панели разрабатывались как грубый вывод текста, была сохранена возможность добавления информации без занятия слота. Поэтому обработчик может знать лишь об отмене или о нажатии любой клавиши с номером.
*There is no automation. You cannot add more than a certain amount of selectable items to a Panel and get pagination. Automated control functionality requires using the heftier Menu object API.
+
*Отсутствует автоматизация. Вы не можете добавить больше определенного числа выбираемых пунктов и добавить нумерацию. Автоматический контроль требует использования тяжелого объектного API Меню.
  
Our finished display and console output looks like this (I selected "Yes"):
+
Наше законченная панель и приложеный вывод в консоли выглядит как на картинке ниже (был выбран ответ "Да", и картинка взята с англ вики, прим. переводчика):
  
 
[[Image:Basic_panel_1.PNG]]
 
[[Image:Basic_panel_1.PNG]]
  
==Basic Paginated Menu==
+
==Базовое меню с нумерацией==
Now, let's take a more advanced example -- pagination. Let's say we want to build a menu for changing the map. An easy way to do this is to read the <tt>maplist.txt</tt> file at the start of a plugin and build a menu out of it.
+
Теперь сделаем пример с большим функционалом -- нумерацией. Попробуем сделать меню для смены карты. Самый простой путь - прочесть <tt>maplist.txt</tt> файл в начале плагина и создать меню по этому файла.
  
Since reading and parsing a file is an expensive operation, we only want to do this once per map. Thus we'll build the menu in <tt>OnMapStart</tt>, and we won't call <tt>CloseHandle</tt> until <tt>OnMapEnd</tt>.
+
Так как чтение и парсинг файла довольно долгая операция, мы будем делать это только раз за карту. Поэтому мы построим меню в <tt>OnMapStart</tt>, и мы не будем вызывать <tt>CloseHandle</tt> до события <tt>OnMapEnd</tt>.
  
Source code:
+
Исходный код:
 
<pawn>new Handle:g_MapMenu = INVALID_HANDLE
 
<pawn>new Handle:g_MapMenu = INVALID_HANDLE
  
 
public OnPluginStart()
 
public OnPluginStart()
 
{
 
{
RegConsoleCmd("menu_changemap", Command_ChangeMap)
+
RegConsoleCmd("menu_changemap", Command_ChangeMap);
 
}
 
}
  
 
public OnMapStart()
 
public OnMapStart()
 
{
 
{
g_MapMenu = BuildMapMenu()
+
g_MapMenu = BuildMapMenu();
 
}
 
}
  
Line 230: Line 233:
 
if (g_MapMenu != INVALID_HANDLE)
 
if (g_MapMenu != INVALID_HANDLE)
 
{
 
{
CloseHandle(g_MapMenu)
+
CloseHandle(g_MapMenu);
g_MapMenu = INVALID_HANDLE
+
g_MapMenu = INVALID_HANDLE;
 
}
 
}
 
}
 
}
Line 237: Line 240:
 
Handle:BuildMapMenu()
 
Handle:BuildMapMenu()
 
{
 
{
/* Open the file */
+
/* открываем файл */
new Handle:file = OpenFile("maplist.txt", "rt")
+
new Handle:file = OpenFile("maplist.txt", "rt");
 
if (file == INVALID_HANDLE)
 
if (file == INVALID_HANDLE)
 
{
 
{
return INVALID_HANDLE
+
return INVALID_HANDLE;
 
}
 
}
 
 
/* Create the menu Handle */
+
/* создаем хандл меню */
 
new Handle:menu = CreateMenu(Menu_ChangeMap);
 
new Handle:menu = CreateMenu(Menu_ChangeMap);
new String:mapname[255]
+
new String:mapname[255];
 
while (!IsEndOfFile(file) && ReadFileLine(file, mapname, sizeof(mapname)))
 
while (!IsEndOfFile(file) && ReadFileLine(file, mapname, sizeof(mapname)))
 
{
 
{
 
if (mapname[0] == ';' || !IsCharAlpha(mapname[0]))
 
if (mapname[0] == ';' || !IsCharAlpha(mapname[0]))
 
{
 
{
continue
+
continue;
 
}
 
}
/* Cut off the name at any whitespace */
+
/* выбрасываем все пробелы из названия */
new len = strlen(mapname)
+
new len = strlen(mapname);
 
for (new i=0; i<len; i++)
 
for (new i=0; i<len; i++)
 
{
 
{
 
if (IsCharSpace(mapname[i]))
 
if (IsCharSpace(mapname[i]))
 
{
 
{
mapname[i] = '\0'
+
mapname[i] = '\0';
break
+
break;
 
}
 
}
 
}
 
}
/* Check if the map is valid */
+
/* проверяем карту на валидность */
 
if (!IsMapValid(mapname))
 
if (!IsMapValid(mapname))
 
{
 
{
continue
+
continue;
 
}
 
}
/* Add it to the menu */
+
/* добавляем карту в меню */
AddMenuItem(menu, mapname, mapname)
+
AddMenuItem(menu, mapname, mapname);
 
}
 
}
/* Make sure we close the file! */
+
/* не забываем закрыть файл! */
CloseHandle(file)
+
CloseHandle(file);
 
 
/* Finally, set the title */
+
/* наконец, устанавливаем заглавие меню */
SetMenuTitle(menu, "Please select a map:")
+
SetMenuTitle(menu, "Выберите карту:");
 
 
return menu
+
return menu;
 
}
 
}
  
Line 284: Line 287:
 
if (action == MenuAction_Select)
 
if (action == MenuAction_Select)
 
{
 
{
new String:info[32]
+
new String:info[32];
  
/* Get item info */
+
/* получаем информацию о пункте */
new bool:found = GetMenuItem(menu, param2, info, sizeof(info))
+
new bool:found = GetMenuItem(menu, param2, info, sizeof(info));
  
/* Tell the client */
+
/* говорим об этом клиенту */
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info)
+
PrintToConsole(param1, "Вы выбрали: %d (найдено? %d информация: %s)", param2, found, info);
  
/* Change the map */
+
/* меняем карту */
ServerCommand("changelevel %s", info)
+
ServerCommand("changelevel %s", info);
 
}
 
}
 
}
 
}
Line 301: Line 304:
 
if (g_MapMenu == INVALID_HANDLE)
 
if (g_MapMenu == INVALID_HANDLE)
 
{
 
{
PrintToConsole(client, "The maplist.txt file was not found!")
+
PrintToConsole(client, "Файл maplist.txt не был найден!");
return Plugin_Handled
+
return Plugin_Handled;
 
}
 
}
 
 
DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER)
+
DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER);
 
 
return Plugin_Handled
+
return Plugin_Handled;
 
}</pawn>
 
}</pawn>
  
This menu results in many selections (my <tt>maplist.txt</tt> file had around 18 maps). So, our final menu has 3 pages, which side by side, look like:
+
Результатом такого меню будет множество пунктов (<tt>maplist.txt</tt> файл содержал примерно 18 карт). Таким образом, конечное меню содержит три страницы, которые идут друг за другом, и выглядят вот так:
  
 
[[Image:Basic_menu_2_page1.PNG]]
 
[[Image:Basic_menu_2_page1.PNG]]
Line 316: Line 319:
 
[[Image:Basic_menu_2_page3.PNG]]
 
[[Image:Basic_menu_2_page3.PNG]]
  
Finally, the console output printed this before the map changed to my selection, <tt>cs_office</tt>:
+
Сообщение в консоль будет выведено до смены карты, например, если выбрали <tt>cs_office</tt>:
<pre>You selected item: 8 (found? 1 info: cs_office)</pre>
+
<pre>Вы выбрали: 8 (найдено? 1 информация: cs_office)</pre>
 
 
Displaying and designing this Menu with a raw <tt>ShowMenu</tt> message or <tt>Panel</tt> API would be very time consuming and difficult.  We would have to keep track of all the items in an array of hardcoded size, pages which the user is viewing, and write a function which calculated item selection based on current page and key press.  The Menu system, thankfully, handles all of this for you.
 
  
'''Notes:'''
+
Отображение и разработка этого меню через <tt>ShowMenu</tt> сообщение или <tt>Панельный</tt> API занимает много времени и является довольно сложной задачей. Придется отслеживать все пункты в массиве большого размера, страницы, которые пользователь просматривает, и написать функцию, которая будет вычислять, на основе текущей странице, пункт, который был выбран и какую кнопку нажали. Меню системы это всё делает за вас.
*Control options which are not available are not drawn.  For example, in the first page, you cannot go "back," and in the last page, you cannot go "next."  Despite this, the menu API tries to keep each the interface as consistent as possible.  Thus, visually, each navigational control is always in the same position. 
 
*Although we specified no time out for our menu, if we had placed a timeout, flipping through pages does not affect the overall time.  For example, if we had a timeout of 20, each successive page flip would continue to detract from the overall display time, rather than restart the allowed hold time back to 20.
 
*If we had disabled the Exit button, options 8 and 9 would still be "Back" and "Next," respectively.
 
*Again, we did not free the Menu Handle in <tt>MenuAction_End</tt>. This is because our menu is global/static, and we don't want to rebuild it every time.
 
*These images show "Back."  In SourceMod revisions 1011 and higher, "Back" is changed to "Previous," and "Back" is reserved for the special "ExitBack" functionality.
 
  
 +
'''Заметки:'''
 +
*Пункты управления, которые не доступны, не рисуются. Например, на первой странице вы не можете нажать "назад", а на последней странице вы не можете нажать "вперед". Несмотря на это, меню пытается удерживать каждый интерфейс последовательно. Таким образом, визуально, каждая навигационная клавиша всегда на одной и той же позиции.
 +
*Если указать таймаут меню, то переключение между страницами не воздействует на общее время открытия меню. Например, если мы установим таймаут на 20 секунд, то каждое листание страницы будет устанавливать таймайт равный (20 - время, уже затраченное на просмотр). Только переоткрытие меню позволит вернуть таймаут времени на 20 секунд.
 +
*Если отключить кнопку выхода, то не смотря на это, пункты 8 и 9 будут всё так же "назад" и вперед, соответственно.
 +
*Мы не освобождаем дескриптор (Handle) меню в <tt>MenuAction_End</tt>, потому что наше меню глобальное/статическое, и нам не нужно пересоздавать его каждый раз.
 +
*На изображении отображена кнопка "Назад". В SourceMod версии 1011 и выше, кнопка "Назад" была переименована в "Предыдующая", и "Назад" зарезервированно для "ExitBack" функциональности. (актуально для ENG версии, на ру вроде бы оба пункта "назад". прим. переводчик)
  
=Voting=
+
=Голосование=
SourceMod also has API for displaying menus as votable choices to more than one client. SourceMod automatically handles selecting an item and randomly picking a tie-breaker. The voting API adds two new <tt>MenuAction</tt> values, which for vote displays, are '''always''' passed:
+
SourceMod также имеет API для отображения меню как голосования для больше чем одного клиента. SourceMod автоматически обрабатываем выбранные пункты and randomly picking a tie-breaker (не знаю как перевести. прим. переводчика). Так же API голосования добавляет два новых значения для <tt>MenuAction</tt>, которые во время отображения меню всегда вызываются.
  
*<tt>MenuAction_VoteStart</tt>: Fired after <tt>MenuAction_Start</tt> when the voting has officially started.
+
*<tt>MenuAction_VoteStart</tt>: Вызывается после <tt>MenuAction_Start</tt>, когда голосование оффициально началось.
*<tt>MenuAction_VoteEnd</tt>: Fired when all clients have either voted or cancelled their vote menu. The chosen item is passed through <tt>param1</tt>. This is fired '''before''' <tt>MenuAction_End</tt>. It is important to note that it does not supercede <tt>MenuAction_End</tt>, nor is it the same thing. Menus should never be destroyed in <tt>MenuAction_VoteEnd</tt>. '''Note:''' This is not called if <tt>SetVoteResultCallback</tt>() is used.
+
*<tt>MenuAction_VoteEnd</tt>: Вызывается после того, как все клиенты проголосовали или отменили свои меню голосований. Выбранный предмет может быть получен через <tt>param1</tt>. Вызывается '''перед''' <tt>MenuAction_End</tt>. Важно заметить, что это не замена <tt>MenuAction_End</tt>, это разные вещи. Нельзя освобождать меню в <tt>MenuAction_VoteEnd</tt>. '''Заметка:''' не вызывается, если используется <tt>SetVoteResultCallback</tt>().
*<tt>MenuAction_VoteCancel</tt>: Fired if the menu is cancelled while the vote is in progress. If this is called, <tt>MenuAction_VoteEnd</tt> or the result callback will not be called, but <tt>MenuAction_End</tt> will be afterwardsA vote cancellation reason is passed in <tt>param1</tt>.  
+
*<tt>MenuAction_VoteCancel</tt>: Вызывается, если меню было отмеено в процессе голосования. Если вызвалось, то <tt>MenuAction_VoteEnd</tt> или обратный вызов (callback) для результата голосований не будет вызваны, но <tt>MenuAction_End</tt> будет после этогоПричина отмены голосования находится в <tt>param1</tt>.  
  
The voting system extends overall menus with two additional properties:
+
Система меню - это то же самое меню, только с двумя дополнительными свойствами:
*Only one vote can be active at a time. You must call <tt>IsVoteInProgress</tt>() or else <tt>VoteMenu</tt>() will fail.
+
*Только одно голосование может быть активно. Вы должны проверять, не запущено ли голосование (<tt>IsVoteInProgress</tt>()), иначе <tt>VoteMenu</tt>() не сработает.
*If a client votes and then disconnects while the vote is still active, the client's vote will be invalidated.
+
*Если клиент во время голосования отключится, то его голос будет недействительным.  
  
The example below shows has to create a function called <tt>DoVoteMenu()</tt> which will ask all clients whether or not they would like to change to the given map.
+
Пример внизу показывает, как создать функцию <tt>DoVoteMenu()</tt>, которая будет спрашивать, хотят ли они сменить карту на предложенную.
  
==Simple Vote==
+
==Простое Голосование==
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
 
{
 
{
 
if (action == MenuAction_End)
 
if (action == MenuAction_End)
 
{
 
{
/* This is called after VoteEnd */
+
/* Вызывается после VoteEnd */
 
CloseHandle(menu);
 
CloseHandle(menu);
 
} else if (action == MenuAction_VoteEnd) {
 
} else if (action == MenuAction_VoteEnd) {
/* 0=yes, 1=no */
+
/* 0=да, 1=нет */
 
if (param1 == 0)
 
if (param1 == 0)
 
{
 
{
new String:map[64]
+
new String:map[64];
GetMenuItem(menu, param1, map, sizeof(map))
+
GetMenuItem(menu, param1, map, sizeof(map));
 
ServerCommand("changelevel %s", map);
 
ServerCommand("changelevel %s", map);
 
}
 
}
Line 367: Line 369:
 
}
 
}
  
new Handle:menu = CreateMenu(Handle_VoteMenu)
+
new Handle:menu = CreateMenu(Handle_VoteMenu);
SetMenuTitle(menu, "Change map to: %s?", map)
+
SetMenuTitle(menu, "Сменить карту на: %s?", map);
AddMenuItem(menu, map, "Yes")
+
AddMenuItem(menu, map, "да");
AddMenuItem(menu, "no", "No")
+
AddMenuItem(menu, "no", "нет");
SetMenuExitButton(menu, false)
+
SetMenuExitButton(menu, false);
 
VoteMenuToAll(menu, 20);
 
VoteMenuToAll(menu, 20);
 
}</pawn>
 
}</pawn>
  
==Advanced Voting==
+
==Продвинутое Голосование==
If you need more information about voting results than <tt>MenuAction_VoteEnd</tt> gives you, you can choose to have a different callback invoked. The new callback will provide much more information, but at a price: <tt>MenuAction_VoteEnd</tt> will not be called, and you will have to decide how to interpret the results. This is done via <tt>SetVoteResultCallback</tt>().
+
Если вам нужно больше информации о результатах голосовании, чем вам дает <tt>MenuAction_VoteEnd</tt>, вы можете указать отдельный обратный вызов (callback). Новый обратный вызов будет давать больше информации, но за опреденную цену: <tt>MenuAction_VoteEnd</tt> не будет вызвано, и вам придется самим решать, как обработать результат. Это делается с помощью <tt>SetVoteResultCallback</tt>().
  
Example:
+
Пример:
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
 
{
 
{
 
if (action == MenuAction_End)
 
if (action == MenuAction_End)
 
{
 
{
/* This is called after VoteEnd */
+
/* Вызывается после VoteEnd */
 
CloseHandle(menu);
 
CloseHandle(menu);
 
}
 
}
Line 395: Line 397:
 
const item_info[][2])
 
const item_info[][2])
 
{
 
{
/* See if there were multiple winners */
+
/* Проверяем, нет ли сразу двух победителей */
 
new winner = 0;
 
new winner = 0;
 
if (num_items > 1
 
if (num_items > 1
Line 403: Line 405:
 
}
 
}
 
 
new String:map[64]
+
new String:map[64];
GetMenuItem(menu, item_info[winner][VOTEINFO_ITEM_INDEX], map, sizeof(map))
+
GetMenuItem(menu, item_info[winner][VOTEINFO_ITEM_INDEX], map, sizeof(map));
ServerCommand("changelevel %s", map)
+
ServerCommand("changelevel %s", map);
 
}
 
}
  
Line 415: Line 417:
 
}
 
}
  
new Handle:menu = CreateMenu(Handle_VoteMenu)
+
new Handle:menu = CreateMenu(Handle_VoteMenu);
SetVoteResultCallback(menu, Handle_VoteResults)
+
SetVoteResultCallback(menu, Handle_VoteResults);
SetMenuTitle(menu, "Change map to: %s?", map)
+
SetMenuTitle(menu, "Сменить карту на: %s?", map);
AddMenuItem(menu, map, "Yes")
+
AddMenuItem(menu, map, "Yes");
AddMenuItem(menu, "no", "No")
+
AddMenuItem(menu, "no", "No");
SetMenuExitButton(menu, false)
+
SetMenuExitButton(menu, false);
 
VoteMenuToAll(menu, 20);
 
VoteMenuToAll(menu, 20);
 
}</pawn>
 
}</pawn>
  
 
=ExitBack=
 
=ExitBack=
ExitBack is a special term to refer to the "ExitBack Button."  This button is disabled by defaultNormally, paginated menus have no "Previous" item for the first page. If the "ExitBack" button is enabled, the "Previous" item will show up as "Back."  
+
ExitBack - специальный термин для обозначения "ExitBack Кнопки". По умолчанию эта кнопка отключенаОбычно нумерованные меню не имеют пункта "Назад" для первой страници. Если "ExitBack" кнопка включена, появится кнопка "Назад".   
  
Selecting the "ExitBack" option will exit the menu with <tt>MenuCancel_ExitBack</tt> and <tt>MenuEnd_ExitBack</tt>. The functionality of this is the same as a normal menu exit internally; extra functionality must be defined through the callbacks.
+
Выбор опции "ExitBack" для меню в обратном вызове будет в действии <tt>MenuCancel_ExitBack</tt> и <tt>MenuEnd_ExitBack</tt>. Функциональность этого такая же, как и у простого выхода из меню; дополнительная функциональность должна быть определена через обратный вызов.
  
=Closing Menu Handles=
+
=Освобождение Дескрипторов (Handle) меню=
It is only necessary to close a menu handle on <tt>MenuAction_End</tt>. The <tt>MenuAction_End</tt> is done every time a menu is closed and no longer needed.
+
Важно закрывать дескриптор меню в <tt>MenuAction_End</tt>. <tt>MenuAction_End</tt> вызывается когда меню закрылось и больше не нужно.
  
=Translations=
+
=Переводы=
It is possible to dynamically translate menus to each player through the <tt>MenuAction_DisplayItem</tt> callback. A special native, <tt>RedrawMenuItem</tt>, is used to transform the text while inside the callback. Let's redo the vote example from earlier to be translated:
+
Есть возможность динамически переводить меню для каждого игрока через <tt>MenuAction_DisplayItem</tt> callback. Специальный натив, <tt>RedrawMenuItem</tt>, используется чтобы преобразовать текст внутри вызова. Давайте переделаем наш пример голосования с переводами.
  
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
 
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
Line 439: Line 441:
 
if (action == MenuAction_End)
 
if (action == MenuAction_End)
 
{
 
{
/* This is called after VoteEnd */
+
/* Вызывается после VoteEnd */
 
CloseHandle(menu);
 
CloseHandle(menu);
 
} else if (action == MenuAction_VoteEnd) {
 
} else if (action == MenuAction_VoteEnd) {
/* 0=yes, 1=no */
+
/* 0=да, 1=нет */
 
if (param1 == 0)
 
if (param1 == 0)
 
{
 
{
new String:map[64]
+
new String:map[64];
GetMenuItem(menu, param1, map, sizeof(map))
+
GetMenuItem(menu, param1, map, sizeof(map));
 
ServerCommand("changelevel %s", map);
 
ServerCommand("changelevel %s", map);
 
}
 
}
 
} else if (action == MenuAction_DisplayItem) {
 
} else if (action == MenuAction_DisplayItem) {
/* Get the display string, we'll use it as a translation phrase */
+
/* Получаем отображаемую строку, будем использовать как фразу перевода */
 
decl String:display[64];
 
decl String:display[64];
 
GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));
 
GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));
  
/* Translate the string to the client's language */
+
/* Переводим строку на язык клиента */
 
decl String:buffer[255];
 
decl String:buffer[255];
 
Format(buffer, sizeof(buffer), "%T", display, param1);
 
Format(buffer, sizeof(buffer), "%T", display, param1);
  
/* Override the text */
+
/* Перерисовываем текст */
 
return RedrawMenuItem(buffer);
 
return RedrawMenuItem(buffer);
 
} else if (action == MenuAction_Display) {
 
} else if (action == MenuAction_Display) {
/* Panel Handle is the second parameter */
+
/* Дескриптор панели будет вторым параметром */
 
new Handle:panel = Handle:param2;
 
new Handle:panel = Handle:param2;
 
 
 
/* Get the map name we're changing to from the first item */
 
/* Get the map name we're changing to from the first item */
 +
/* Получаем название карты, на которую меняем, из первого пункта */
 
decl String:map[64];
 
decl String:map[64];
 
GetMenuItem(menu, 0, map, sizeof(map));
 
GetMenuItem(menu, 0, map, sizeof(map));
 
 
/* Translate to our phrase */
+
/* Переводим нашу фразу */
 
decl String:buffer[255];
 
decl String:buffer[255];
 
Format(buffer, sizeof(buffer), "%T", "Change map to?", client, map);
 
Format(buffer, sizeof(buffer), "%T", "Change map to?", client, map);
Line 483: Line 486:
 
}
 
}
  
new Handle:menu = CreateMenu(Handle_VoteMenu, MenuAction_DisplayItem|MenuAction_Display)
+
new Handle:menu = CreateMenu(Handle_VoteMenu, MenuAction_DisplayItem|MenuAction_Display);
SetMenuTitle(menu, "Change map to: %s?", map)
+
SetMenuTitle(menu, "Сменить карту на: %s?", map);
AddMenuItem(menu, map, "Yes")
+
AddMenuItem(menu, map, "Yes");
AddMenuItem(menu, "no", "No")
+
AddMenuItem(menu, "no", "No");
SetMenuExitButton(menu, false)
+
SetMenuExitButton(menu, false);
 
VoteMenuToAll(menu, 20);
 
VoteMenuToAll(menu, 20);
 
}</pawn>
 
}</pawn>
  
[[Category:Ru:SourceMod Scripting]]
+
[[Category:SourceMod Development]]
 +
[[Category:SourceMod Scripting]]
 +
 
 +
{{LanguageSwitch}}

Revision as of 10:15, 20 November 2013

SourceMod обладает большим API для создания и отображений меню для клиентов. В отличии от AMX Mod X, API SourceMod'a обладает широким функционалом. Меню базируются на обратных вызовах (callbacks), которые гарантированно будут вызваны.

Для C++, API меню может быть найдено в public/IMenuManager.h. Для SourcePawn - scripting/include/menus.inc.

Объекты

Система меню SourceMod основана на объектно ориентированной иерархии. Понимание этой иерархии, даже для скриптинга, является ключевым моментом для эффективного использования меню.

Стили

Высший уровень объектов называется MenuStyle (IMenuStyle в C++). Стили описывают уникальную систему меню. Всего есть два стиля, встроенных в SourceMod:

  • Valve Styly, так же называемым "ESC" меню; 8 пунктов на странице, нельзя добавить текст без занятия слота или спрятать (отключить) текст.
  • Radio Style, так же называемый "AMX" меню; 10 пунктов на странице, можно добавить любой текст.

Каждый MenuStyle обладает своими правилами и свойствами. Вы можете считать, что каждый из них существует на раздельных "каналах". Например, два разных меню могут быть на экране игрока, будучи Valve и Radio меню одновременно, и SourceMod сможет обработать их обоих без всяких проблем. Это всё возможно благодаря тому, что каждый стиль отслеживает свои меню отдельно.

Панели

Меню низшего уровня называется Панелью (IMenuPanel в C++). Панель - один 'кусок' текста. Пункты и текст могут добавляться к панели столько раз, сколько поддерживает родительский стиль. Например, Valve стиль не поддерживает отображение текста без занятия слота или отключенных пунктов. Однако с Radio стилем панели это становится возможным, и вы можете отобразить большое количество текста так, как вам это захочется.

Панель считается временным объектом. Она создается, рендерится, отображается, и затем удаляется. Хотя она может быть сохранена на неопределенное время, если в этом есть необходимость.

Правила и ограничения Vavle стиля:

  • Максимум пунктов на странице: 8
  • Отключенный текст не отображается.
  • Нельзя написать текст, не заняв слота.
  • Пробелы не добавляются к пробелам\новым линиям, добавляя чувство "ограниченности".
  • Пользователь должен нажать "ESC" или быть в своей консоли, чтобы увидеть меню.

Правила и ограничения Radio стиля:

  • 10 пунктов на странице
  • Заглавия белые; пункты желтые, если не отключены. Если отключены, то белого цвета.
  • Нулевой элемент всегда белый. Для согласованности это значит, что навигационное управление всегда белое и находится в следующей секции, и просто не отрисовывается, если отсутствует.

Меню

Наконец, есть просто Меню (IBaseMenu in C++). Это вспомогательный объект, созданный для хранения базированных на меню выбираемых пунктов. В отличии от низкоуровневых панелей, меню содержит пункты, и может содержать только те пункты, которые можно выбрать (т.е. занимающие слоты). Они делятся на две категории:

  • Не нумерованные: эти меню могут иметь определенное число пунктов, и не имеют навигационных опций, кроме кнопки "Выход", которая всегда добавляется на последнюю позицию, поддерживаемую стилем.
    • Valve Стиль: максимум пунктов: 8
    • Radio Стиль: максимум пунктов: 10
  • Нумерованные: такие меню могут содержать сколь угодно пунктов. Во время отображения будет показано только определенное число пунктов. Автоматически будут добавлены навигационные пункты, таким образом, игроки легко могут возвращаться назад и вперед к определенным "страницам" пунктов меню.
    • "Назад" всегда отображается как первый навигационный пункт, третий с конца поддерживаемых позиций. Не будет добавлен, если меню содержит только одну страницу. Если нет никаких предыдущих страниц, текст никогда не будет отображен, ни в каком стиле. Если возможно, то меню будет дополнено необходимыми пробелами.
      • Valve Стиль, позиция: 6
      • Radio Стиль, позиция: 8
    • "Вперед" всегда отображается как второй навигационный пункт, второй с конца из доступных позиций. Не отображается, если меню состоит из одной страницы. Если нет следующих страниц, то текс не будет отображен при любом стиле. Если возможно, то меню будет дополнено необходимыми пробелами.
      • Valve Стиль, позиция: 7
      • Radio Стиль, позиция: 9
    • "Выход" отображается, если у меню включена кнопка "выход". Всегда занимает последнюю из возможных позиций
      • Valve Стиль, позиция: 8
      • Radio Стиль, позиция: 10

Цель меню - упростить процедуру хранения, отображения, и вычисления выбранного пункта. Таким образом, меню не позволяет добавлять пустой текст, так как это значительно усложняет алгоритм отображения.

Внутренне, меню отображается через RenderMenu алогритм. Этот алгоритм создает временную панель и заполняет её пунктами из меню. Эта панель отображается клиенту. Алгоритм пытается создать свободное перемещение по всем меню, и по всем стилям. Таким образом, все меню отображаются через IBaseMenu класс, или Menu дескрипторы (Handles), и будет выглядеть и действовать так же, как и меню API, основанное на панельном API.

Обратные вызовы (callbacks)

Краткий обзор

Меню базируются на системе обратных вызовов. Каждый обратный вызов предоставляет собой действие, которые происходят во время цикла отображения меню. Цикл состоит из множества уведомлений:

  • Начало уведомления.
    • Уведомление об отображении, если меню может быть отображено клиенту.
    • Уведомление о выборе пункта или кнопки отмены.
  • Конец уведомления.

Так как End обозначает конец полного цикла отображения, то обычно его используют для уничтожения временных меню.

Спецификация

Детальное объяснение этих событий ниже. Для C++, IBaseMenu указатель всегда доступен. Для SourcePawn, Menu хандл и MenuAction всегда установлены в MenuHandler callback. В отличии от C++, SourcePawn API позволяет вызвать лишь некоторые действия, если они были запрошены во время создания меню. Это сделано в целях оптимизации. Однако, эти действия не могут быть не вызваны.

  • Start. Меню было признано. Это не означает, что меню будет отображено; однако, это гарантирует, что событие "OnMenuEnd" будет вызвано.
    • OnMenuStart() в C++.
    • MenuAction_Start в SourcePawn. Не будет вызвано, если не было запрошено.
      • param1: Игнорируется (всегда 0).
      • param2: Игнорируется (всегда 0).
  • Display. Меню отобразилось клиенту.
    • OnMenuDisplay() в C++. IMenuPanel доступны указатель и индекс клиента.
    • MenuAction_Display в SourcePawn. Не будет вызвано, если не было запрошено.
      • param1: Индекс клиента.
      • param2: Хандл меню.
  • Select. Пункт был выбран. Будет дана позиция пункта в меню, а не нажатая кнопка (если меню не является панелью)
    • OnMenuSelect() в C++. Передаются индекс клиента и позиция пункта.
    • MenuAction_Select в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
      • param1: Индекс клиента.
      • param2: Позиция пункта.
  • Cancel. Отображение меню у одного из клиентов было закрыто.
    • OnMenuCancel() в C++. Доступна причина закрытия.
    • MenuAction_Cancel в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
      • param1: Индекс клиента.
      • param2: Код причины закрытия меню.
  • End. Цикл отображения меню закончился; это означает, что "Start" действие было, и одно из действий "Select" или "Cancel" так же произошло. Обычно в этом месте отчищаются ресурсы меню или само меню удаляется.
    • OnMenuEnd() в C++.
    • MenuAction_End в SourcePawn. Это действие всегда вызывается, вне зависимости от запроса.
      • param1: Причина завершения меню.
      • param2: если param1 == MenuEnd_Cancelled, то здесь содержится код причины закрытия меню.

Панели

Для панелей цикл жизни другой. Панели могут вызывать лишь два callback'а из написанных выше, и гарантируется, что один из них точно будет вызван для данного цикла отображения. Для C++, IBaseMenu указатель будет всегда NULL. Для SourcePawn, меню Handle будет всегда INVALID_HANDLE.

  • Select. Кнопка была нажата. Этой кнопкой может быть любое число. Например, если в панели у вас 2 пункта, то клиент может вызвать это событие, нажав "43".
    • OnMenuSelect() в C++. Передаются индекс клиента и нажатая кнопка.
    • MenuAction_Select в SourcePawn.
      • param1: Индекс клиента.
      • param2: Номер нажатой клавиши.
  • Cancel. Отображение меню у одного из клиентов было закрыто.
    • OnMenuCancel() в C++. Доступна причина закрытия.
    • MenuAction_Cancel в SourcePawn.
      • param1: Индекс клиента.
      • param2: Код причины закрытия меню.


Примеры

Для начала попробуем сделать очень простое меню:

Вы любите яблоки?
1. Да
2. Нет

Сделаем это как меню и как панель, чтобы увидеть различия в их API.

Простое меню

Сперва напишем наш пример через API Меню. Для пошагового руководства смотрите Menus Step By Step (SourceMod Scripting).

public OnPluginStart()
{
	// зарегистрировали консольную команду menu_test1 для открытия меню.
	RegConsoleCmd("menu_test1", Menu_Test1);
}
 
// хандл меню
public MenuHandler1(Handle:menu, MenuAction:action, param1, param2)
{
	/* Если был выбран какой-либо пункт, то сообщим клиенту о его выборе. */
	if (action == MenuAction_Select)
	{
		new String:info[32]; // переменная для хранения выбора
		new bool:found = GetMenuItem(menu, param2, info, sizeof(info)); // получаем информацию о выбранном в меню пункте
		PrintToConsole(param1, "Вы нажали: %d (найдено? %d информация: %s)", param2, found, info); // пишем клиенту в консоль
	}
	/* Если меню было отменено, то сообщим об этом серверу. */
	else if (action == MenuAction_Cancel)
	{
		PrintToServer("Клиент %d' закрыл меню.  Причина: %d", param1, param2);
	}
	/* Если меню "закончилось", то удалим его из памяти */
	else if (action == MenuAction_End)
	{
		CloseHandle(menu);
	}
}
 
public Action:Menu_Test1(client, args)
{
	new Handle:menu = CreateMenu(MenuHandler1);
	SetMenuTitle(menu, "Вы любите яблоки?");
	AddMenuItem(menu, "да", "да");
	AddMenuItem(menu, "нет", "нет");
	SetMenuExitButton(menu, false);
	DisplayMenu(menu, client, 20);
 
	return Plugin_Handled;
}

Несколько очень важных замечаний о этом примере:

  • Одно из Select или Cancel событий всегда будет отослано обработчику действий (action handler).
  • End всегда будет отослано обработчику действий (action handler).
  • Мы удаляем наше меню в End действии, потому что наш дескриптор (Handle) нам больше не нужен. Если бы мы удалили его после DisplayMenu, это бы отменило отображение меню клиенту.
  • Меню по умолчанию имеет кнопку выхода. В нашем примере мы его отключили.
  • Наше меню будет отображаться 20 секунд. Это означает, что если клиент ничего не выбрал в течении 20 секунд, то меню будет закрыто. Это обычно необходимо для меню с голосованиями. В отличии от AMX Mod X, вам не нужно создавать таймер, чтобы быть увереным, что меню "закончилось".
  • Мы создали и уничтожили новый дескриптор меню (Menu Handle), однако мы можем этого и не делать. Вполне допустимо создать дескриптор (Handle) один раз для всего времени жизни плагина.
  • Чтобы нормально отображались русские буквы, кодировка файла должна быть UTF-8 (без BOOM), иначе будут квадратики вместо русских букв.

Наше законченное меню и приложеный вывод в консоли выглядит как на картинке ниже (был выбран ответ "Да", и картинка взята с англ вики, прим. переводчика).

Basic menu 1.PNG

Базовая панель

Теперь давайте перепишем наш пример с использованием Панели вместо Меню.

public OnPluginStart()
{
	// зарегистрировали консольную команду panel_test1 для открытия меню.
	RegConsoleCmd("panel_test1", Panel_Test1);
}
 
public PanelHandler1(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		PrintToConsole(param1, "Вы выбрали: %d", param2);
	} else if (action == MenuAction_Cancel) {
		PrintToServer("Клиент %d' закрыл панель. Причина: %d", param1, param2);
	}
}
 
public Action:Panel_Test1(client, args)
{
	new Handle:panel = CreatePanel();
	SetPanelTitle(panel, "Вы любите яблоки?");
	DrawPanelItem(panel, "Да");
	DrawPanelItem(panel, "Нет");
 
	SendPanelToClient(panel, client, PanelHandler1, 20);
 
	CloseHandle(panel);
 
	return Plugin_Handled;
}

Как вы можете заметить, Панели сильно отличаются от Меню:

  • Мы можем удалить Панель как только закончим его отображать. Мы можем создать Панель один раз, и продолжать использовать его много раз, однако мы можем удалить его в любой момент, без нарушения цикла отображения клиентского меню.
  • Обработчик Панели (Handler) получает гораздо меньше информации. Так как Панели разрабатывались как грубый вывод текста, была сохранена возможность добавления информации без занятия слота. Поэтому обработчик может знать лишь об отмене или о нажатии любой клавиши с номером.
  • Отсутствует автоматизация. Вы не можете добавить больше определенного числа выбираемых пунктов и добавить нумерацию. Автоматический контроль требует использования тяжелого объектного API Меню.

Наше законченная панель и приложеный вывод в консоли выглядит как на картинке ниже (был выбран ответ "Да", и картинка взята с англ вики, прим. переводчика):

Basic panel 1.PNG

Базовое меню с нумерацией

Теперь сделаем пример с большим функционалом -- нумерацией. Попробуем сделать меню для смены карты. Самый простой путь - прочесть maplist.txt файл в начале плагина и создать меню по этому файла.

Так как чтение и парсинг файла довольно долгая операция, мы будем делать это только раз за карту. Поэтому мы построим меню в OnMapStart, и мы не будем вызывать CloseHandle до события OnMapEnd.

Исходный код:

new Handle:g_MapMenu = INVALID_HANDLE
 
public OnPluginStart()
{
	RegConsoleCmd("menu_changemap", Command_ChangeMap);
}
 
public OnMapStart()
{
	g_MapMenu = BuildMapMenu();
}
 
public OnMapEnd()
{
	if (g_MapMenu != INVALID_HANDLE)
	{
		CloseHandle(g_MapMenu);
		g_MapMenu = INVALID_HANDLE;
	}
}
 
Handle:BuildMapMenu()
{
	/* открываем файл */
	new Handle:file = OpenFile("maplist.txt", "rt");
	if (file == INVALID_HANDLE)
	{
		return INVALID_HANDLE;
	}
 
	/* создаем хандл меню */
	new Handle:menu = CreateMenu(Menu_ChangeMap);
	new String:mapname[255];
	while (!IsEndOfFile(file) && ReadFileLine(file, mapname, sizeof(mapname)))
	{
		if (mapname[0] == ';' || !IsCharAlpha(mapname[0]))
		{
			continue;
		}
		/* выбрасываем все пробелы из названия */
		new len = strlen(mapname);
		for (new i=0; i<len; i++)
		{
			if (IsCharSpace(mapname[i]))
			{
				mapname[i] = '\0';
				break;
			}
		}
		/* проверяем карту на валидность */
		if (!IsMapValid(mapname))
		{
			continue;
		}
		/* добавляем карту в меню */
		AddMenuItem(menu, mapname, mapname);
	}
	/* не забываем закрыть файл! */
	CloseHandle(file);
 
	/* наконец, устанавливаем заглавие меню */
	SetMenuTitle(menu, "Выберите карту:");
 
	return menu;
}
 
public Menu_ChangeMap(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		new String:info[32];
 
		/* получаем информацию о пункте */
		new bool:found = GetMenuItem(menu, param2, info, sizeof(info));
 
		/* говорим об этом клиенту */
		PrintToConsole(param1, "Вы выбрали: %d (найдено? %d информация: %s)", param2, found, info);
 
		/* меняем карту */
		ServerCommand("changelevel %s", info);
	}
}
 
public Action:Command_ChangeMap(client, args)
{
	if (g_MapMenu == INVALID_HANDLE)
	{
		PrintToConsole(client, "Файл maplist.txt не был найден!");
		return Plugin_Handled;
	}	
 
	DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER);
 
	return Plugin_Handled;
}

Результатом такого меню будет множество пунктов (maplist.txt файл содержал примерно 18 карт). Таким образом, конечное меню содержит три страницы, которые идут друг за другом, и выглядят вот так:

Basic menu 2 page1.PNG Basic menu 2 page2.PNG Basic menu 2 page3.PNG

Сообщение в консоль будет выведено до смены карты, например, если выбрали cs_office:

Вы выбрали: 8 (найдено? 1 информация: cs_office)

Отображение и разработка этого меню через ShowMenu сообщение или Панельный API занимает много времени и является довольно сложной задачей. Придется отслеживать все пункты в массиве большого размера, страницы, которые пользователь просматривает, и написать функцию, которая будет вычислять, на основе текущей странице, пункт, который был выбран и какую кнопку нажали. Меню системы это всё делает за вас.

Заметки:

  • Пункты управления, которые не доступны, не рисуются. Например, на первой странице вы не можете нажать "назад", а на последней странице вы не можете нажать "вперед". Несмотря на это, меню пытается удерживать каждый интерфейс последовательно. Таким образом, визуально, каждая навигационная клавиша всегда на одной и той же позиции.
  • Если указать таймаут меню, то переключение между страницами не воздействует на общее время открытия меню. Например, если мы установим таймаут на 20 секунд, то каждое листание страницы будет устанавливать таймайт равный (20 - время, уже затраченное на просмотр). Только переоткрытие меню позволит вернуть таймаут времени на 20 секунд.
  • Если отключить кнопку выхода, то не смотря на это, пункты 8 и 9 будут всё так же "назад" и вперед, соответственно.
  • Мы не освобождаем дескриптор (Handle) меню в MenuAction_End, потому что наше меню глобальное/статическое, и нам не нужно пересоздавать его каждый раз.
  • На изображении отображена кнопка "Назад". В SourceMod версии 1011 и выше, кнопка "Назад" была переименована в "Предыдующая", и "Назад" зарезервированно для "ExitBack" функциональности. (актуально для ENG версии, на ру вроде бы оба пункта "назад". прим. переводчик)

Голосование

SourceMod также имеет API для отображения меню как голосования для больше чем одного клиента. SourceMod автоматически обрабатываем выбранные пункты and randomly picking a tie-breaker (не знаю как перевести. прим. переводчика). Так же API голосования добавляет два новых значения для MenuAction, которые во время отображения меню всегда вызываются.

  • MenuAction_VoteStart: Вызывается после MenuAction_Start, когда голосование оффициально началось.
  • MenuAction_VoteEnd: Вызывается после того, как все клиенты проголосовали или отменили свои меню голосований. Выбранный предмет может быть получен через param1. Вызывается перед MenuAction_End. Важно заметить, что это не замена MenuAction_End, это разные вещи. Нельзя освобождать меню в MenuAction_VoteEnd. Заметка: не вызывается, если используется SetVoteResultCallback().
  • MenuAction_VoteCancel: Вызывается, если меню было отмеено в процессе голосования. Если вызвалось, то MenuAction_VoteEnd или обратный вызов (callback) для результата голосований не будет вызваны, но MenuAction_End будет после этого. Причина отмены голосования находится в param1.

Система меню - это то же самое меню, только с двумя дополнительными свойствами:

  • Только одно голосование может быть активно. Вы должны проверять, не запущено ли голосование (IsVoteInProgress()), иначе VoteMenu() не сработает.
  • Если клиент во время голосования отключится, то его голос будет недействительным.

Пример внизу показывает, как создать функцию DoVoteMenu(), которая будет спрашивать, хотят ли они сменить карту на предложенную.

Простое Голосование

public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Вызывается после VoteEnd */
		CloseHandle(menu);
	} else if (action == MenuAction_VoteEnd) {
		/* 0=да, 1=нет */
		if (param1 == 0)
		{
			new String:map[64];
			GetMenuItem(menu, param1, map, sizeof(map));
			ServerCommand("changelevel %s", map);
		}
	}
}
 
DoVoteMenu(const String:map[])
{
	if (IsVoteInProgress())
	{
		return;
	}
 
	new Handle:menu = CreateMenu(Handle_VoteMenu);
	SetMenuTitle(menu, "Сменить карту на: %s?", map);
	AddMenuItem(menu, map, "да");
	AddMenuItem(menu, "no", "нет");
	SetMenuExitButton(menu, false);
	VoteMenuToAll(menu, 20);
}

Продвинутое Голосование

Если вам нужно больше информации о результатах голосовании, чем вам дает MenuAction_VoteEnd, вы можете указать отдельный обратный вызов (callback). Новый обратный вызов будет давать больше информации, но за опреденную цену: MenuAction_VoteEnd не будет вызвано, и вам придется самим решать, как обработать результат. Это делается с помощью SetVoteResultCallback().

Пример:

public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Вызывается после VoteEnd */
		CloseHandle(menu);
	}
}
 
public Handle_VoteResults(Handle:menu, 
			num_votes, 
			num_clients, 
			const client_info[][2], 
			num_items, 
			const item_info[][2])
{
	/* Проверяем, нет ли сразу двух победителей */
	new winner = 0;
	if (num_items > 1
	    && (item_info[0][VOTEINFO_ITEM_VOTES] == item_info[1][VOTEINFO_ITEM_VOTES]))
	{
		winner = GetRandomInt(0, 1);
	}
 
	new String:map[64];
	GetMenuItem(menu, item_info[winner][VOTEINFO_ITEM_INDEX], map, sizeof(map));
	ServerCommand("changelevel %s", map);
}
 
DoVoteMenu(const String:map[])
{
	if (IsVoteInProgress())
	{
		return;
	}
 
	new Handle:menu = CreateMenu(Handle_VoteMenu);
	SetVoteResultCallback(menu, Handle_VoteResults);
	SetMenuTitle(menu, "Сменить карту на: %s?", map);
	AddMenuItem(menu, map, "Yes");
	AddMenuItem(menu, "no", "No");
	SetMenuExitButton(menu, false);
	VoteMenuToAll(menu, 20);
}

ExitBack

ExitBack - специальный термин для обозначения "ExitBack Кнопки". По умолчанию эта кнопка отключена. Обычно нумерованные меню не имеют пункта "Назад" для первой страници. Если "ExitBack" кнопка включена, появится кнопка "Назад".

Выбор опции "ExitBack" для меню в обратном вызове будет в действии MenuCancel_ExitBack и MenuEnd_ExitBack. Функциональность этого такая же, как и у простого выхода из меню; дополнительная функциональность должна быть определена через обратный вызов.

Освобождение Дескрипторов (Handle) меню

Важно закрывать дескриптор меню в MenuAction_End. MenuAction_End вызывается когда меню закрылось и больше не нужно.

Переводы

Есть возможность динамически переводить меню для каждого игрока через MenuAction_DisplayItem callback. Специальный натив, RedrawMenuItem, используется чтобы преобразовать текст внутри вызова. Давайте переделаем наш пример голосования с переводами.

public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Вызывается после VoteEnd */
		CloseHandle(menu);
	} else if (action == MenuAction_VoteEnd) {
		/* 0=да, 1=нет */
		if (param1 == 0)
		{
			new String:map[64];
			GetMenuItem(menu, param1, map, sizeof(map));
			ServerCommand("changelevel %s", map);
		}
	} else if (action == MenuAction_DisplayItem) {
		/* Получаем отображаемую строку, будем использовать как фразу перевода */
		decl String:display[64];
		GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));
 
		/* Переводим строку на язык клиента */
		decl String:buffer[255];
		Format(buffer, sizeof(buffer), "%T", display, param1);
 
		/* Перерисовываем текст */
		return RedrawMenuItem(buffer);
	} else if (action == MenuAction_Display) {
		/* Дескриптор панели будет вторым параметром */
		new Handle:panel = Handle:param2;
 
		/* Get the map name we're changing to from the first item */
		/* Получаем название карты, на которую меняем, из первого пункта */
		decl String:map[64];
		GetMenuItem(menu, 0, map, sizeof(map));
 
		/* Переводим нашу фразу */
		decl String:buffer[255];
		Format(buffer, sizeof(buffer), "%T", "Change map to?", client, map);
 
		SetPanelTitle(panel, buffer);
	}
}
 
DoVoteMenu(const String:map[])
{
	if (IsVoteInProgress())
	{
		return;
	}
 
	new Handle:menu = CreateMenu(Handle_VoteMenu, MenuAction_DisplayItem|MenuAction_Display);
	SetMenuTitle(menu, "Сменить карту на: %s?", map);
	AddMenuItem(menu, map, "Yes");
	AddMenuItem(menu, "no", "No");
	SetMenuExitButton(menu, false);
	VoteMenuToAll(menu, 20);
}
Warning: This template (and by extension, language format) should not be used, any pages using it should be switched to Template:Languages

View this page in:  English  Russian  简体中文(Simplified Chinese)