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

JDW

Мы открываем бизнес
Сообщения
376
Реакции
325
Как я понимаю метод Cancel() действует только для того объекта, на который он ссылается? Или на все существующие объекты данного класса?

---------------
1.png


Example:
void ShowMainMenu(int client)
{
    static char buffer[256];

    Menu menu = new Menu(MenuHandler_Menu);

Если хотите отменить меню для игрока, то используйте return 0; в калбеке, а можно вообще ничего не использовать, так как меню закроется при выборе пункта
 
Последнее редактирование модератором:
  • Клоун
Реакции: MaZa

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Как я понимаю метод Cancel() действует только для того объекта, на который он ссылается? Или на все существующие объекты данного класса?
Если вопрос на счёт экземпляров клиентских окон меню, то согласно доке, закрываются все они.
Но, помнится что у меня это не работало.
Вместо этого, я обычно делаю так:

C++:
if( GetClientMenu(client, null) != MenuSource_None )
{
    InternalShowMenu(client, "\10", 1); // thanks to Zira
    CancelClientMenu(client, true, null);
}
 

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
Подскажите пожалуйста, с чем может быть связана данная ошибка?

C-подобный:
"E:\public\csgo\addons\sourcemod\logs\errors_20210424.log")
L 04/24/2021 - 11:01:34: [SM] Exception reported: Menu handle 8da70b00 is invalid (error 1)
L 04/24/2021 - 11:01:34: [SM] Blaming: anti-tk (2).smx
L 04/24/2021 - 11:01:34: [SM] Call stack trace:
L 04/24/2021 - 11:01:34: [SM] [0] SetMenuTitle
L 04/24/2021 - 11:01:34: [SM] [1] Line 1057, C:\Users\Krabik\Desktop\sourcemod-1.10.0-git6502-windows\addons\sourcemod\scripting\anti-tk.sp::ForgiveMenu
L 04/24/2021 - 11:01:34: [SM] [2] Line 1026, C:\Users\Krabik\Desktop\sourcemod-1.10.0-git6502-windows\addons\sourcemod\scripting\anti-tk.sp::Event_PlayerDeath

Это меню не закрывается или он пытается открыть не то, что нужно?
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@Nekro, плагин пытается изменить/назначить название меню, которое не существует, насколько я понимаю.
Скорей всего хэндл был закрыт, но не занулён.
Точнее сказать можно только видя код плагина
 

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
@Nekro, плагин пытается изменить/назначить название меню, которое не существует, насколько я понимаю.
Скорей всего хэндл был закрыт, но не занулён.
Точнее сказать можно только видя код плагина
Строки наверное не подойдут, но эвенты те же и не тронуты
 

Вложения

  • anti-tk.sp
    70.2 КБ · Просмотры: 20

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@Nekro, OMG сделайте мне развидеть ЭТО.
Функция public Action RemoveGrenade(int client), которая должна быть void (как и ForgiveMenu(), кстати).
Кроме того удалить всё оружие можно гораздо более просто, чем выполнять while() c FindEntityByClassname() (разве что, если стоит на сервере плагин на 2 оружия на один слот, то не всё удалится тем способом, что я имею в виду).

Ещё тут в функции HudText() косяк: и терам и спецам будет показываться один и тот же текст, потому что для ShowHudText() используются одни и те же параметры, а последний должен был отличаться.
Это ещё бегло пролистал код, а если внимательно глянуть то м/б ещё косяки повсплывают.

Ещё и стили оформления кода в разных местах разные (в функциях локальные переменные и аргументы то идут полностью в нижнем регистре и без типа, то с указанием типа венгерской нотацией).

В общем нужно именно тот исходник, у которого ошибка всплывает, если функция ForgiveMenu() изменялась.
Если же в этой функции в той версии плагина, что стоит на сервере, всё то же самое, что и в предоставленном исходнике, то я не вижу причины почему может возникать такая ошибка. Разве что плагин компилился версией SM, отличной от используемой на сервере.
 

Temlik

Участник
Сообщения
668
Реакции
174
Menu hMenu = new Menu(MenuHandler_MyMenu);
Выдаёт ошибку: error 017: undefined symbol "MenuHandler_MyMenu"
 

Temlik

Участник
Сообщения
668
Реакции
174
Мне кажется тут гадалок нет, где весь код?
#include <csgo_colors>

public void OnPluginStart()
{
HookEvent("player_death", Event_PlayerDeath);
RegConsoleCmd("sm_temlik", Command_OpenMenu);
}

public Action Command_OpenMenu(int iClient, int args)
{
Menu hMenu = new Menu(MenuHandler_MyMenu);
hMenu.SetTitle("Тёмлик");
hMenu.AddItem("item1", "Задания");
hMenu.Display(iClient, 30);
hMenu.ExitButton = true;
}

public void Event_PlayerDeath(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
int iUserId = hEvent.GetInt("userid");
int iClient = GetClientOfUserId(iUserId);
CGOPrintToChat(iClient, "Вы умерли");
}
 

dvernoff

Добрая душа
Сообщения
638
Реакции
591
C-подобный:
#include <csgo_colors>

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath);
    RegConsoleCmd("sm_temlik", Command_OpenMenu);
}

public Action Command_OpenMenu(int iClient, int args)
{
    Menu hMenu = new Menu(MenuHandler_MyMenu);
    hMenu.SetTitle("Тёмлик");
    hMenu.AddItem("", "Задания");
    hMenu.Display(iClient, 30);
    hMenu.ExitButton = true;
}

public int MenuHandler_MyMenu(Menu thismenu, MenuAction action, int client, int param)
{
    if(action == MenuAction_Select)
    {
        switch(param)
        {
            case 0:    {
                // Действие если нажали в меню кнопку 1
            }
            case 1:    {
                // Действие если нажали в меню кнопку 2
            }
        }
    }
    else if(action == MenuAction_End)
    {
        thismenu.Close();
    }
}

public void Event_PlayerDeath(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
    int iClient = GetClientOfUserId(GetEventInt(hEvent, "userid"));
    CGOPrintToChat(iClient, "Вы умерли");
}
 

Temlik

Участник
Сообщения
668
Реакции
174
C-подобный:
#include <csgo_colors>

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath);
    RegConsoleCmd("sm_temlik", Command_OpenMenu);
}

public Action Command_OpenMenu(int iClient, int args)
{
    Menu hMenu = new Menu(MenuHandler_MyMenu);
    hMenu.SetTitle("Тёмлик");
    hMenu.AddItem("", "Задания");
    hMenu.Display(iClient, 30);
    hMenu.ExitButton = true;
}

public int MenuHandler_MyMenu(Menu thismenu, MenuAction action, int client, int param)
{
    if(action == MenuAction_Select)
    {
        switch(param)
        {
            case 0:    {
                // Действие если нажали в меню кнопку 1
            }
            case 1:    {
                // Действие если нажали в меню кнопку 2
            }
        }
    }
    else if(action == MenuAction_End)
    {
        thismenu.Close();
    }
}

public void Event_PlayerDeath(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
    int iClient = GetClientOfUserId(GetEventInt(hEvent, "userid"));
    CGOPrintToChat(iClient, "Вы умерли");
}
Спасибо! Как я понял, ошибка заключалась в том, что я создал меню, но не вызвал его?
Сообщения автоматически склеены:

Добавил "hMenu.ExitBackButton = true;" (чтобы была кнопка "Назад"), но при нажатии меню закрывается.
 
Последнее редактирование:

dvernoff

Добрая душа
Сообщения
638
Реакции
591
Спасибо! Как я понял, ошибка заключалась в том, что я создал меню, но не вызвал его?
Сообщения автоматически склеены:

Добавил "hMenu.ExitBackButton = true;" (чтобы была кнопка "Назад"), но при нажатии меню закрывается.
ну это в callback нужно дописать будет, и тем более ему некуда возвращаться
 

alexmy

Участник
Сообщения
284
Реакции
13
Как правильно закрыть меню, которое перебивает другое меню? Делаю sm_dump_handles и получаю утечку память IBaseMenu

PHP:
switch(action)
    {
        case MenuAction_End:
        {
            delete menu;
        }
 

DENFER

Пишу плагины за два биг тести и картошку фри..
Сообщения
260
Реакции
289
Как правильно закрыть меню, которое перебивает другое меню? Делаю sm_dump_handles и получаю утечку память IBaseMenu

PHP:
switch(action)
    {
        case MenuAction_End:
        {
            delete menu;
        }
Новое меню автоматически закрывает старое.
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@alexmy, так, как у тебя в примере, как раз правильно.
 

alexmy

Участник
Сообщения
284
Реакции
13
@Grey83, У меня одно меню вызывается множество раз, после нажатие пункта (Обновляю Меню). Делаю дамп, у меня стала забиваться память.

PHP:
0x777705b2    l4d2_upgradesreloade    IBaseMenu               152       
0x6d5005b3    l4d2_upgradesreloade    IBaseMenu               152

Разве после закрытие оно не должно полностью очищаться с память?

PHP:
public Action UpgradesMenu(int client, int args)
{
    if(GetClientTeam(client) == 2)
    {
        NightVision[client] = GetEntProp(client, Prop_Send, "m_bNightVisionOn");
        SetGlobalTransTarget(client);
        char sLaser[64], sReload[64], sAmmo[64], sNightVision[64];
        hMenu = CreateMenu(Upgrades);
        SetMenuTitle(hMenu, "%T", "MenuTitle", client, client);
    
        if(iUpgrade[client][2] == 0)
        {
            Format(sLaser, sizeof(sLaser), "%T", "laser_off", client, client);
            AddMenuItem(hMenu, "0", sLaser);
        }
        else if(iUpgrade[client][2] == 4)
        {
            Format(sLaser, sizeof(sLaser), "%T", "laser_on", client, client);
            AddMenuItem(hMenu, "0", sLaser);
        }
        
        if(iUpgrade[client][3] == 0)
        {
            Format(sReload, sizeof(sReload), "%T", "reload_off", client, client);
            AddMenuItem(hMenu, "1", sReload);
        }
        else if(iUpgrade[client][3] == 11)
        {
            Format(sReload, sizeof(sReload), "%T", "reload_on", client, client);
            AddMenuItem(hMenu, "1", sReload);
        }
        if(SpecAmmo[client] == 0)
        {
            Format(sAmmo, sizeof(sAmmo), "%T", "ammo_explosive", client, client);
            AddMenuItem(hMenu, "2", sAmmo);
        }
        else
        {
            Format(sAmmo, sizeof(sAmmo), "%T", "ammo_incendiary", client, client);
            AddMenuItem(hMenu, "2", sAmmo);
        }
        if(NightVision[client] == 1)
        {
            Format(sNightVision, sizeof(sNightVision), "%T", "nightvision_on", client, client);
            AddMenuItem(hMenu, "3", sNightVision);
        }
        else
        {
            Format(sNightVision, sizeof(sNightVision), "%T", "nightvision_off", client, client);
            AddMenuItem(hMenu, "3", sNightVision);
        }
        
        SetMenuExitButton(hMenu, true);
        DisplayMenu(hMenu, client, 20);
    }
    return Plugin_Handled;
}

public Upgrades(Handle menu, MenuAction action, int client, int option)
{
    switch(action)
    {
        case MenuAction_End:
        {
            delete menu;
        }
        case MenuAction_Select:
        {
            switch(option)
            {
                case 0:
                {
                    if(iUpgrade[client][2] == 0)
                    {
                        GiveUpgrade(client, 2);
                    }
                    else if(iUpgrade[client][2] > 0)
                    {
                        RemoveUpgrade(client, 2);
                        CheatCommand(client, "upgrade_remove", "laser_sight", "");
                    }
                }
                case 1:
                {
                    if(iUpgrade[client][3] == 0)
                    {
                        GiveUpgrade(client, 3);
                    }
                    else if(iUpgrade[client][3] > 0)
                    {
                        RemoveUpgrade(client, 3);
                    }
                }
                case 2:
                {
                    if(SpecAmmo[client] == 0)
                        SpecAmmo[client] = 1;
                    else
                        SpecAmmo[client] = 0;
                }
                case 3:
                {
                    if(NightVision[client] == 0)
                    {
                        SetEntProp(client, Prop_Send, "m_bNightVisionOn", 1);
                    }
                    else
                    {
                        SetEntProp(client, Prop_Send, "m_bNightVisionOn", 0);
                    }
                }
            }
            
            UpgradesMenu(client, 0);
        }
    }
}
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@alexmy, если имеется SetGlobalTransTarget(), то нет особого смысла использовать %T, а не %t.

Если в плагине используется глобальный хэндл hMenu для меню, где всегда одно и то же количество пунктов, то можно вообще не закрывать хэндл (а если закрываешь, то меню будет убито у всех, как я понимаю).
Тогда закрывать нужно не menu, а hMenu.

Лучше создать меню при старте плагина с флагом MENU_ACTIONS_ALL (или хотя бы добавить флаг MenuAction_DisplayItem к MENU_ACTIONS_DEFAULT), добавить нужное количество пунктов с любым текстом (всё равно при отображении они будут изменены).
При обработке каллбэка для MenuAction MenuAction_DisplayItem делаем форматирование как у тебя при создании меню и возвращаем RedrawMenuItem(buffer), где buffer - это тестовый массив с новым текстом соответствующего пункта.
А MenuAction_End не обрабатываем.

И ещё не понимаю, для чего делают таймаут у меню. Бывает что что-то отвлекает, в это время меню закрывается и снова неужно вызывать меню и лезть к нужному пункту (хорошо если меню простенькое из одной страницы, а не со сложной иерархией и из множества страниц на разных уровнях).

Вот примерно так:
C-подобный:
public void OnPluginStart()
{
    ...

    hMenu = CreateMenu(Upgrades, MENU_ACTIONS_DEFAULT|MenuAction_DisplayItem|MenuAction_Display);
    SetMenuTitle(hMenu, "UpgradesMenu");
    AddMenuItem(hMenu, "", "0");
    AddMenuItem(hMenu, "", "1");
    AddMenuItem(hMenu, "", "2");
    AddMenuItem(hMenu, "", "3");
    SetMenuExitButton(hMenu, true);
}

public Action UpgradesMenu(int client, int args)
{
    if(GetClientTeam(client) == 2) hMenu.Display(client, 0);
    return Plugin_Handled;
}

public int Upgrades(Handle menu, MenuAction action, int client, int option)
{
    switch(action)
    {
        case MenuAction_Display:
        {
            menu.SetTitle("%t", "MenuTitle", client);
            NightVision[client] = GetEntProp(client, Prop_Send, "m_bNightVisionOn");
        }
        case MenuAction_DisplayItem:
        {
            char txt[64];
            switch(option)
            {
                case 0:
                {
                    if(!iUpgrade[client][2])
                        FormatEx(txt, sizeof(txt), "%T", "laser_off", client, client);
                    else if(iUpgrade[client][2] == 4)
                        FormatEx(txt, sizeof(txt), "%T", "laser_on", client, client);
                    else return 0;    // хз что делать при других вариантах, хотя м/б делать пункт неактивным?
                }
                case 1:
                {
                    if(!iUpgrade[client][3])
                        FormatEx(txt, sizeof(txt), "%T", "reload_off", client, client);
                    else if(iUpgrade[client][3] == 11)
                        FormatEx(txt, sizeof(txt), "%T", "reload_on", client, client);
                    else return 0;    // хз что делать при других вариантах, хотя м/б делать пункт неактивным?
                }
                case 2:
                    FormatEx(txt, sizeof(txt), "%T", !SpecAmmo[client] ? "ammo_explosive" : "ammo_incendiary", client, client);
                case 3:
                    FormatEx(txt, sizeof(txt), "%T", !NightVision[client] ? "nightvision_off" : "nightvision_on", client, client);
            }
            return RedrawMenuItem(txt);
        }
        case MenuAction_Select:
        {
            switch(option)
            {
                case 0:
                {
                    if(!iUpgrade[client][2])
                        GiveUpgrade(client, 2);
                    else if(iUpgrade[client][2] > 0)
                    {
                        RemoveUpgrade(client, 2);
                        CheatCommand(client, "upgrade_remove", "laser_sight", "");
                    }
                }
                case 1:
                {
                    if(!iUpgrade[client][3])
                        GiveUpgrade(client, 3);
                    else if(iUpgrade[client][3] > 0)
                        RemoveUpgrade(client, 3);
                }
                case 2:    SpecAmmo[client] = 1 - SpecAmmo[client];
                case 3:    SetEntProp(client, Prop_Send, "m_bNightVisionOn", (1-NightVision[client]));
            }
            DisplayMenu(hMenu, client, 0);
        }
    }
    return 0
}
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@alexmy, если имеется SetGlobalTransTarget(), то нет особого смысла использовать %T, а не %t.
@Grey83 , это не является допустимым способом использования, можешь натолкнуться на undefined behaviour.
SetGlobalTransTarget() по дизайну предназначен для работы в паре только с VFormat. См. Issue #1087.

Разве после закрытие оно не должно полностью очищаться с память?
Должно, согласно твоему коду.
Для чего ты объявляешь hMenu глобально? Каким образом он у тебя еще используется по коду?

Делаю дамп, у меня стала забиваться память.
"Забиваться" - это сколько хендлов? 2 шт. из твоего лога не показатель.
Сообщения автоматически склеены:

Ты можешь получить утечку в одном случае - если юзер жмёт кнопку, которой нет в списке пунктов (особенность движка L4D - в этом случае колбек не вызывается),
но хендл все равно закроется по таймауту, либо при следующем вызове меню.
Если правильно понимаю, недавний PR #1543 в SM v.1.11.6727 должен закрывать эту проблему.
 
Последнее редактирование:

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
Если меню я создал глобально

C++:
Menu
    hMenu;

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);
}

public Action Event_PlayerDeath(Handle event, const char[] name, bool dontBroadcast)
{
    int iClient = GetClientOfUserId(GetEventInt(event, "userid"));
   
    FMenu(iClient);
}

void FMenu(int client)
{
    hMenu = new Menu(BMenu);
    char szTitle[128] = "Быть или не быть?";
    hMenu.SetTitle(szTitle, sizeof(szTitle));

    hMenu.AddItem("Yes", "Да");
    hMenu.AddItem("No", "Нет");
    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);

}

public int BMenu(Menu hMenuLocal, MenuAction action, int client, int iItem)
{
    switch(action)
    {
        case MenuAction_End:
        {
            delete hMenu;
        }
       
        case MenuAction_Select:    // Игрок выбрал пункт
        {
            if(iItem == 0)
                FunctionMenu(client);
        }
    }
}

То мне его надо удалять? Если игроки его чекают примерно 4 раза за 1 раунд. Или мне его лучше создать массивом в 65 ячеек и не удалять вовсе?
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@Nekro, не вижу создания меню: у тебя тут только его каллбэк.
И ты его удаляешь в MenuAction_End. Для чего ты вообще его хэндл сохраняешь, если для удаления это не нужно в твоём примере?
 
Сверху Снизу