Создание меню в SourcePawn

The End Is Near...

Russian Roulette
Сообщения
874
Реакции
691
Создание меню в SourcePawn

SourceMod имеет обширный API для создания и отображения меню клиентам.

Стили
Объект верхнего уровня MenuStyle (IMenuStyle в C++). Есть два стиля отображения меню
  • Стиль Valve - так называемое "ESC" меню; 8 элементов на странице
  • Стиль Radio - так называемое "AMX" меню; 10 элементов на странице
Каждый MenuStyle имеет свои собственные правила и свойства. Два разных меню могут существовать на экране игрока как Valve меню и Radio меню. В то же время, SourceMod будет в состоянии управлять этими меню. Это связано с тем, что каждый стиль отслеживает свое меню отдельно.

Панели
Объект с более низким уровня интерфейсом Panels (IMenuPanel в C++). Панели позволяют сделать отображение, используя лишь только текст т.е. не используя выделительных пунктов (Select).
Панели отображаются временно. Хотя они могут быть сохранены на неопределенный срок, не рекомендуется это делать.
Стиль Valve
  • Максимальное количество элементов на странице 8
  • Заблокированные элементы не могут быть осуществлены
  • Raw текст не может быть осуществлен
  • Осуществление между текстом пространства не добавят возможности добавить в панель больше символов
  • Клиент должен будет нажать кнопку "ESC" или, по крайней открыть консоль для просмотра меню
Стиль Radio
  • Максимальное количество элементов на странице 10
  • Оглавление белое, элементы желтые, но если их отключить, то они будут белыми
  • Элемент '0' всегда белый. Это означает, что навигационные кнопки управления всегда белого цвета

Меню
Есть простые Menu (IBaseMenu в C++). Эти вспомогательные объекты, предназначенные для хранения меню на основе выбора элементов. В отличие от низкого уровня панелей, меню могут содержать множество элементов, а также могут содержать только те пункты, которые выбираются (т. е. не содержат "белого" текста).
Они делятся на две категории:
Не пронумерованные - меню содержит в себе только лишь определенное количество элементов.
Нет пунктов навигации (Вперед; Назад). Исключение: Выход.
  • [*]Назад (Previous) - Используется для возвращения просмотренных элементов; Перелистываемые страницы назад
    • Стиль Valve: Позиция в меню: 6
    • Стиль Radio: Позиция в меню: 8
    [*]Далее (Next) - Используется для просматривания следующих страниц; Перелистывание страницы вперед
    • Стиль Valve: Позиция в меню: 7
    • Стиль Radio: Позиция в меню: 9
    [*]Выход (Exit) - Используется для выхода из меню
    • Стиль Valve: Позиция в меню: 8
    • Стиль Radio: Позиция в меню: 10


Цель меню - Упростить процедуру хранения и отображения информации. Таким образом, меню не позволяют добавить raw текст, так как это значительно усложняет отображение алгоритма.
Примечание. C++ API поддерживает подключение IBaseMenu процедуры отображения и добавления raw текста, это будет добавлено в сценарии API в ближайшее время.
Внутри меню отображение используется с помощью RenderMenu алгоритма. Этот алгоритм создает временную панель и наполняет ее элементы из меню. Эта панель будет отображаться на клиенте. Алгоритм пытается создать свободное перемещение между всеми меню, и во всех стилях. Таким образом, любое меню отображается с помощью IBaseMenu класса, или меню Handle будет отображаться и использоваться так же как и меню Panel API.

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

Спецификация
Для C++, IBaseMenu всегда доступен. Для SourcePawn, Menu Handle и MenuAction всегда устанавливаются в MenuHandler обратного вызова. В отличие от C++, API позволяет SourcePawn выполнять определенные действия, если только меню было запрошено во время создания. Это оптимизация. Тем не менее, некоторые действия не могут быть предотвращены создания.
  • Start. Создание меню, причем OnMenuEnd гарантированно будет вызываться.
    • MenuAction_Start() в SourcePawn. Это действие не срабатывает при вызове.
      • param1: Игнорирование. Всегда 0
      • param2: Игнорирование. Всегда 0
    Display. Отображение меню клиенту.
    • MenuAction_Display в SourcePawn. Это действие не срабатывает при вызове.
      • param1: Индекс клиента, которому показано меню
      • param2: Handle в меню
    Select. Выбор пункта меню.
    • MenuAction_Select в SourcePawn. Это действие срабатывает, не смотря на вызов.
      • param1: Индекс клиента, который выбрал пункт
      • param2: Индекс пункта, который выбрал клиент
    Cancel. Отмена показа меню
    • MenuAction_Cancel в SourcePawn. Это действие срабатывает, не смотря на вызов.
      • param1: Индекс клиента, который отменил показ меню
      • param2: Код причины отмены показа меню
    End. Окончание показа меню
    • MenuAction_End в SourcePawn. Это действие всегда срабатывает, то ли просьба или нет.
      • param1: Причина окончания показа меню
      • param2: Если param1 был MenuEnd_Cancelled, это содержит причину отмены показа меню

Панели
Для панелей, функции обратного вызова всегда меняются. Для SourcePawn, Menu Handle всегда будет INVALID_HANDLE.
Select. Выбор пункта в панели
  • MenuAction_Select в SourcePawn.
    • param1: Индекс клиента, который выбрал пункт
    • param2: Индекс пункта, который выбрал клиент
Cancel. Отмена показа панели
  • MenuAction_Cancel в SourcePawn.
    • param1: Индекс клиента, который отменил показ панели
    • param2: Код причины отмены показа панели

Пример
Разберем создание панели и меню с вопросами
Создание меню написал(а):
Ваш вопрос
1. Ответ 1
2. Ответ 2

Меню
PHP:
// Подключаем библиотеки
#include <sourcemod>

// Старт плагина
public OnPluginStart()
{
	// Регистрируем команду, введя которую нам отобразится меню
	RegConsoleCmd("menu", Menu)
}

// Обрабатываем команду
public Action:Menu(client, args)
{
	// Создаем Handle меню
	// MenuHandler - Значит что, нажав на один из пунктов, произойдет дальнейшее действие
	// То есть, нажав к примеру на "Да", нам выведется в консоль о том, что вы выбрали именно этот пункт
	new Handle:menu = CreateMenu(MenuHandler)
	SetMenuTitle(menu, "Вы любите Counter-Strike:Source?")
	AddMenuItem(menu, "yes", "Да")
	AddMenuItem(menu, "no", "Нет")
	SetMenuExitButton(menu, false)
	DisplayMenu(menu, client, 20)
 
	return Plugin_Handled
}

public MenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
	/* Если пункт меню был выбран, выводим в его консоль об этом */
	if (action == MenuAction_Select)
	{
		decl 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)
	}
	/* Если показ меню закончился, закрываем Handle */
	else if (action == MenuAction_End)
	{
		CloseHandle(menu)
	}
}
Обратите внимание на несколько очень важных примечаний
  • Один из пунктов Select или Cancel будет направлен в действие обрабатывания меню
  • Пункт End всегда будет направлен в действие обрабатывания меню
  • Уничтожение меню следует производить только, если клиент отменил меню, или оно закончилось
  • В меню, по умолчанию, есть пункт выхода
  • Продолжительно показа меню зависит от DisplayMenu

Панель
PHP:
// Подключаем библиотеки
#include <sourcemod>

// Старт плагина
public OnPluginStart()
{
	// Регистрируем команду, введя которую нам отобразится меню
	RegConsoleCmd("panel", Panel)
}

// Обрабатываем команду 
public Action:Panel(client, args)
{
	// Создаем Handle панели
	new Handle:panel = CreatePanel();
	SetPanelTitle(panel, "Вы любите спать?")
	DrawPanelItem(panel, "Да")
	DrawPanelItem(panel, "Нет")
 
	SendPanelToClient(panel, client, PanelHandler, 20)
 
	CloseHandle(panel)
 
	return Plugin_Handled
}

public PanelHandler(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		PrintToConsole(param1, "Вы выбрали пункт: %d", param2)
	} 
	else if (action == MenuAction_Cancel) 
	{
		PrintToServer("Клиент %d отменил показ панели. Причина: %d", param1, param2)
	}
}
Некоторые примечания
  • Есть лимит добавления элементов в панель
  • Возможность уничтожения панели после окончания отображения
  • У обработчика всегда есть информация (Отменили показ панели;Выбрали пункт)

Расширенное меню
Теперь рассмотрим более сложный пример. Меню, в котором элементы расположены на нескольких страницах.
Суть плагина: Отображение карт, с последующей сменой при выборе
PHP:
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;
	}
 
	/* Создание 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);
	}
	/* Закрытие Handle */
	CloseHandle(file);
 
	/* Заголовок */
	SetMenuTitle(menu, "Please select a map:");
 
	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, "You selected item: %d (found? %d info: %s)", param2, found, info);
 
		/* Смена карты */
		ServerCommand("changelevel %s", info);
	}
}
 
public Action:Command_ChangeMap(client, args)
{
	if (g_MapMenu == INVALID_HANDLE)
	{
		PrintToConsole(client, "The maplist.txt file was not found!");
		return Plugin_Handled;
	}	
 
	DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER);
 
	return Plugin_Handled;
}
Меню будет выглядеть так
Basic_menu_2_page1.PNG
Basic_menu_2_page2.PNG
Basic_menu_2_page3.PNG

Примечания
  • Автоматическое отображение или скрытие элементов управления
  • Время, указанное для отображения клиенту меню, не меняется при использовании меню
  • Позиции элементов управления не могут быть изменены

Голосование
Голосование в SourceMod имеет сходные черты с меню
  • MenuAction_VoteStart. Начало голосования
  • MenuAction_VoteEnd. Конец голосования. Клиенты отменили, проголосовали или оставили не выбрали один из элементов
  • MenuAction_VoteCancel. Клиент отменил показ голосования. Причина отмены будет содержаться в параметре param1
Стоить отметить некоторые примечания

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

Простое голосование
PHP:
public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Закрытие Handle, если меню стало неактивным */
		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);
}

Расширенное голосование
PHP:
public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Закрытие Handle */
		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, "Да")
	AddMenuItem(menu, "no", "Нет")
	SetMenuExitButton(menu, false)
	VoteMenuToAll(menu, 20);
}

Перевод фраз в голосовании
PHP:
public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_End)
	{
		/* Закрытие Handle */
		CloseHandle(menu);
	} 
	else if (action == MenuAction_VoteEnd) 
	{
		/* 0=yes, 1=no */
		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) 
	{
		/* Handle панели является вторым параметром */
		new Handle:panel = Handle:param2;
 
		/* Получение имени карты */
		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, "Да")
	AddMenuItem(menu, "no", "Нет")
	SetMenuExitButton(menu, false)
	VoteMenuToAll(menu, 20);
}

Источник
Оригинал
 

Серый™

CS:S Server
Сообщения
2,925
Реакции
1,376
The End Is Near..., хороший гайд, продолжай в том-же духе))
 

Sergey Grinko

Участник
Сообщения
140
Реакции
10
Как сделать чтобы после AddMenuItem можно было добавить строчку с текстом (чтобы у нее не было цифры в начале, и она была не некликабельной).

Код:
PHP:
	new String:showcd[256];
	Format(showcd,sizeof(showcd),"У вас %d кредитов", credits[userid]);
	new Handle:menu = CreateMenu(handler_menu);
	SetMenuTitle(menu, "Магазин:\n \n");
	AddMenuItem(menu, "1", "Купить бонус за кредиты", ITEMDRAW_DEFAULT);
	AddMenuItem(menu, "0", "Обмен фрагов на кредиты", ITEMDRAW_DEFAULT);
	SetMenuExitButton(menu, true);
	DisplayMenu(menu, userid, 25);
 

Scarface_slv

Участник
Сообщения
222
Реакции
47
Можно с панелью, только это уже другое меню надо DrawPanelText("текст") так не будет цифр.
"некликабельной" вместо ITEMDRAW_DEFAULT, ITEMDRAW_DISABLED
Насчет убрать цифры в твоем меню я не знаю если только перед текстом знак \n и то будут цифры наверно
 
Последнее редактирование:

The End Is Near...

Russian Roulette
Сообщения
874
Реакции
691
AddMenuItem(menu, "0", "Обмен фрагов на кредиты\nПункт без цифры", ITEMDRAW_DEFAULT);
Пробуй так.
 

white_bubblegum

Участник
Сообщения
103
Реакции
7
Как написать, чтобы меню вылазило в начале раунда и в определённой команде(например, за Т)?
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #15
@white_bubblegum, точно так же, как и всё, связанное с началом раунда.
PHP:
public void OnPluginStart() {
  HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy);
}

public void OnRoundStart(Event hEvent, const char[] szEventName, bool bDontBroadcast) {
  for (int iClient = MaxClients; iClient != 0; --iClient) {
    if (!IsClientInGame(iClient) || IsFakeClient(iClient))
      continue;

    // код, который будет выполняться в начале раунда.
    // iClient - идентификатор игрока
  }
}
 
Сверху Снизу