Вероятно последняя статья про madding Shogo, т.к. больше там делать нечего. Камрад BLiTZ попросил сделать очередную доработку Shogo — приделать(binding) колесо от мышки, чтобы “скролировать” между видами вооружения.
Опыт уже есть, куда лезть понятно — …\ClientShellDLL\MouseMenu.cpp. Первое, с чем я сталкиваюсь, это перечисление параметров и возможностей мышки:
#define ID_MOUSELOOK 0
#define ID_LOOKSPRING 1
#define ID_INVERTYAXIS 2
#define ID_SENSITIVITY 3
#define ID_INPUTRATE 4
#define ID_LEFTBUTTON 5
#define ID_RIGHTBUTTON 6
#define ID_MIDDLEBUTTON 7
#define ID_UPWHEEL 8
#define ID_DOWNWHEEL 9
#define ID_BACK 10
Добавляю ID_UPWHEEL и ID_DOWNWHEEL, делаю себе крестик в Notes, что нужно где-то использовать. Тут же наталкиваюсь на следующую порцию — это присвоение глобальным переменным в конструкторе. Разбавляю своим кодом:
CMouseMenu::CMouseMenu() : CBaseMenu()
{
m_nSecondColumn = 0;
m_nLeftButtonSelection = 0;
m_nRightButtonSelection = 0;
m_nMiddleButtonSelection = 0;
m_nUpWheelSelection = 0;
m_nDownWheelSelection = 0;
}
Очередная пометка насчет глобальных переменных (они, вероятно, где-то в .h файле). Не заморачиваясь иду по файлу сверху-вниз. Натыкаюсь на инициализатор и начинаю его подробный ревьюинг.
DBOOL CMouseMenu::Init (CClientDE* pClientDE, CRiotMenu* pRiotMenu, CBaseMenu* pParent, int nScreenWidth, int nScreenHeight)
{
Инициализация глобальных переменных кодом команды unassigned. Колесо будет работать как кнопка, поэтому основное внимание уделяю работе с кнопками мышки. Пытаюсь делать по аналогии.
m_nLeftButtonSelection = NUM_COMMANDS - 1; // unassigned
m_nMiddleButtonSelection = NUM_COMMANDS - 1;
m_nRightButtonSelection = NUM_COMMANDS - 1;
m_nUpWheelSelection = NUM_COMMANDS - 1;
m_nDownWheelSelection = NUM_COMMANDS - 1;
Просканировав дальнейший кусок кода, выясняю, что здесь происходит загрузка байндинга кнопок из конфига.
// get button object names
// load the current button bindings
Интерес представил дефайн CONTROLTYPE_BUTTON, идем посмотреть.
#define CONTROLTYPE_BUTTON 8 // a mouse button
А что это у нас про wheel написано? Ага! Оказывается колёсико — это у нас ось-Z.
#define CONTROLTYPE_ZAXIS 3 // z-axis, such as a wheel on a mouse or a throttle on a joystick
Оформляю так:
if (pObj->m_ObjectType == CONTROLTYPE_BUTTON)
{
if (nButton == 0) SAFE_STRCPY(strLeftButton, pObj->m_ObjectName);
if (nButton == 1) SAFE_STRCPY(strRightButton, pObj->m_ObjectName);
if (nButton == 2) SAFE_STRCPY(strMiddleButton, pObj->m_ObjectName);
nButton++;
}
if (pObj->m_ObjectType == CONTROLTYPE_ZAXIS)
{
SAFE_STRCPY(strWheel, pObj->m_ObjectName);
}
Практически без затруднений пробежав по этому куску, я притормозил и глубоко задумался: колесо, как кнопка — одно, а действия у него два: вперед-назад. Перекур.
Камрад BLiTZ подкидывает одно из решений в FAQ’е по Shogo:
rangebind “##mouse” “Wheel” 10.000000 120.000000 “NextWeapon” -120.000000 -10.000000 “PrevWeapon”
…и многое проясняется. Одно устройство, одна кнопка. На кнопку (в данном случае — колесо) навешивается два экшена: один из которых сработает при отрицательной координате (колесо на себя), другой при положительной (колесо от себя). Я возвращаюсь немного обратно и разбавляю код загрузки настроек конфига следующим куском:
if (stricmp (pBinding->strTriggerName, strWheel) == 0 && pBinding->pActionHead)
{
if (pBinding->pActionHead->nRangeLow < 0)
m_nDownWheelSelection = CommandToArrayPos (pBinding->pActionHead->nActionCode);
else
m_nUpWheelSelection = CommandToArrayPos (pBinding->pActionHead->nActionCode);
GameAction* pAction = pBinding->pActionHead->pNext;
if (pAction)
{
if (pBinding->pActionHead->nRangeLow < 0)
m_nDownWheelSelection = CommandToArrayPos (pAction->nActionCode);
else
m_nUpWheelSelection = CommandToArrayPos (pAction->nActionCode);
}
}
Первый if — проверка наличия экшена в биндинге + проверка наш ли это биндинг. Далее:
- Блок-if — забирает первый экшен.
- Next получает второй экшен, если он есть, иначе NULL.
- Проверяет, есть ли второй экшен.
- Забираем второй экшен.
Коды команд присваиваются глобальным переменным m_nDownWheelSelection и m_nUpWheelSelection.
Немного балую себя и добавляю строчку:
pClientDE->CPrint(“ZOOM MOD v1.0rc2 — http://www.nikvoronin.com/shogo/”);
…себя не похвалишь… Заодно, будет видно — отработал мод или нет.
Меню настроек мышки в Shogo отличается по принципу работы от меню настроек клавиатуры. Здесь экшен выбирается из списка клавишами влево-вправо, т.е. приходится перебирать несколько экшенов, пока не доберешься до нужного. Перехожу к потрошению методов CMouseMenu::Left() и CMouseMenu::Right().
По аналогии с обработкой кнопок мыши добавляю обработчик колесо-вверх, колесо-вниз. BindButtonToCommand заменяю на заглушку BindWheelToCommand.
else if (m_nSelection == ID_MIDDLEBUTTON)
{
ChangeButtonSettingSurface (m_nSelection, &m_nMiddleButtonSelection, -1);
BindButtonToCommand (2, m_nMiddleButtonSelection);
}
else if (m_nSelection == ID_UPWHEEL)
{
ChangeButtonSettingSurface (m_nSelection, &m_nUpWheelSelection, -1);
BindWheelToCommand();
}
else if (m_nSelection == ID_DOWNWHEEL)
{
ChangeButtonSettingSurface (m_nSelection, &m_nDownWheelSelection, -1);
BindWheelToCommand();
}
CMouseMenu::Right() отличается от Left только значением параметра при вызове ChangeButtonSettingSurface.
Далее идут методы отрисовки меню (Draw), загрузки сурфэйсов (LoadSurface). Доделываю по аналогии с кнопками мышки и тут хлоп… ай-я-яй, как нехорошо! Использовать магические числа в движке. В циклах загрузки/отрисовки пунктов меню используется число 9, равное числу настраиваемых параметров мышки. После моих инъекций их стало 11. Ревьюю код и ввожу дефайн (9-ка встречается далеко не в одном месте):
for (int i = m_nTopItem; i < NUM_MOUSE_ID; i++)
{
if (m_MouseSettings[i].hMenuItem)
{
Это были мелочи, а теперь… я добрался до самого главного — метода:
DBOOL CMouseMenu::BindButtonToCommand (int nButton, int nSelection)
Копи-пастом добавляю BindWheelToCommand и глубоко задумываюсь. При назначении кнопок все просто: мы добавляем один биндинг для одной кнопки, иначе говоря — один пункт меню, одна кнопка, один биндинг в котором один экшен. В случае с колесом ситуация иная — два пункта меню, одна кнопка (колесо), один биндинг в котором два экшена.
Биндинги назначаются через исполнение команды консоли: m_pClientDE->RunConsoleString (str);. В случае с колесом, я не смогу сформировать строку консоли (следовательно, назначить биндинг) пока пользователь не задаст оба пункта меню. Хотя, с другой стороны, пользователь может захотеть забайндить только одно направление колеса мышки…
Несмотря на мутноватые размышление, в голове понемногу выстраивается последовательность вызовов и назначений. Я начинаю кодить.
Кажется все так. Билд. Копирование DLL. Запуск. Странности с биндингом: настройки загружаются нормально, если поменять ручками в конфиге — все равно нормально, если менять из меню — на оба направления мышки биндится один и тот же экшен. Ревьюю код. Час, другой… Хочется курить и ругаться матом. Что, впрочем, и делаю.
…
Засаду устроил метод CommandName из ClientUtilities.cpp, объявив переменную buffer как static. Оставляю “как есть” и добавляю в код SAFE_STRCPY.
// remove old binding and set new binding
char str[128];
sprintf (str, “rangebind \”%s\” \”%s\” 0 0 \”\” 0 0 \”\”", pObj->m_DeviceName, pObj->m_ObjectName);
m_pClientDE->RunConsoleString (str);
// create then set new binding
char strUp[128] = {0};
char strDown[128] = {0};
if (m_nDownWheelSelection != NUM_COMMANDS - 1 &&
m_nUpWheelSelection != NUM_COMMANDS - 1 )
{
SAFE_STRCPY(strUp, CommandName (g_CommandArray[m_nUpWheelSelection].nCommandID));
SAFE_STRCPY(strDown, CommandName (g_CommandArray[m_nDownWheelSelection].nCommandID));
sprintf (
str,
“rangebind \”%s\” \”%s\” 10.000000 120.000000 \”%s\” -120.000000 -10.000000 \”%s\”",
pObj->m_DeviceName,
pObj->m_ObjectName,
strUp,
strDown
);
}
else
if (m_nDownWheelSelection != NUM_COMMANDS - 1)
{
SAFE_STRCPY(strDown, CommandName (g_CommandArray[m_nDownWheelSelection].nCommandID));
sprintf (
str,
“rangebind \”%s\” \”%s\” -120.000000 -10.000000 \”%s\”",
pObj->m_DeviceName,
pObj->m_ObjectName,
strDown
);
}
else
if (m_nUpWheelSelection != NUM_COMMANDS - 1)
{
SAFE_STRCPY(strUp, CommandName (g_CommandArray[m_nUpWheelSelection].nCommandID));
sprintf (
str,
“rangebind \”%s\” \”%s\” 120.000000 10.000000 \”%s\”",
pObj->m_DeviceName,
pObj->m_ObjectName,
strUp
);
}
m_pClientDE->RunConsoleString (str);
Очередная сборка. Копирование. Запуск. Работает.
Я доволен, камрад BLiTZ в аналогичном состоянии, идем курить.
Исходники Shogo Zoom RC2 выложены на Shogo Madding.
По теме
- Исходники Shogo Zoom
- Включаем zoom в Shogo: Mobile Armor Division
- Shogo Madding