Начало
Игра “Shogo: Mobile Armor Division”, она же — “Ярость. Восстание на Кронусе”. В Shogo существует два вида оружия, поддерживающие режим оптического прицела (zoom). Он включается повторным выбором того же оружия. Такое управление неудобно. Попробую сделать zoom по нажатию клавиши заданной из настроек клавиатуры (или мыши) игрового меню Shogo. Беру исходники и…
Сборка
Чтение readme.txt, направило в каталог “…\ClientShellDLL” который содержит клиентскую часть Shogo. Нужно заметить, что исходники построены на C++ в лучших традициях ООП. Открываю Файл проекта (.dsp). VS 2005 любезно конвертирует его в свой новый формат. Выбираю Release. Нажимаю Ctrl+Shift+B (Build Solution) и… получаю около 1500 варнингов и около 400 ошибок. WTF?
Ошибки в исходниках, попробую править. Оказалось, что не все так страшно: разработчики забыли о типе переменных при объявлении.
for ( i=0; i < nVectorsPerShot; i++)
{
…и в одном месте компилятор не понял как привести тип переменной. После правки, ClientShell благополучно собрался. Я получил на выходе (каталог “…\Shogo”) библиотеку CShell.dll, исходники которой будут в дальнейшем правиться.
We need weapon
Поиск по содержимому исходников на предмет наличия слова “weapon”. На глазок выделяю файл “…\ClientShellDLL\WeaponModel.cpp” и лезу в нутря. Оно!
// ———————————————————————– //
//
//ROUTINE:CWeaponModel::ChangeWeapon()
//
//PURPOSE:Change to a different weapon
//
// ———————————————————————– //
void CWeaponModel::ChangeWeapon(DBYTE nCommandId)
{
В методе CWeaponModel::ChangeWeapon() есть интересующий меня код:
if (nWeaponId == m_nWeaponId)
{
// Well, okay, some weapons have toggles if selected again…
if (CanWeaponZoom(nWeaponId))
{
bDeselectWeapon = DFALSE;
}
else
{
return;
}
}
// Handle deselection of current weapon…
if (bDeselectWeapon)
{
Deselect();
// Need to wait for deselection animation to finish…Save the
// new weapon id…
m_nRequestedWeaponId = nWeaponId;
}
else
{
HandleInternalWeaponChange(nWeaponId);
}
Обращаю внимание на nWeaponId и nCommandId! Похоже, что с помощью метода DBYTE nWeaponId = GetWeaponId(nCommandId, nPlayerMode); (его нет в листинге выше) происходит стыковка id команды и id оружия. Запомню на будущее.
Непосредственно в куске кода идет проверка поддержки оружием zoom. Если поддерживает, то сброс текущего оружия отменяется. Далее вызывается метод непосредственно выбора оружия HandleInternalWeaponChange(nWeaponId); Смотрю этот метод.
// ———————————————————————– //
//
// ROUTINE: CWeaponModel::HandleInternalWeaponChange()
//
// PURPOSE: Change to a different weapon
//
// ———————————————————————– //
void CWeaponModel::HandleInternalWeaponChange(DBYTE nWeaponId)
{
if (!g_pRiotClientShell) return;
if (g_pRiotClientShell->IsPlayerDead() || g_pRiotClientShell->IsSpectatorMode()) return;
// Check to see if we are already on this weapon…
if (nWeaponId == m_nWeaponId)
{
// If the weapon can be zoomed, toggle the zoom modes…
if (CanWeaponZoom(nWeaponId))
{
if (g_pRiotClientShell->IsChaseView())
{
m_bZoomView = DFALSE;
}
else
{
m_bZoomView = !m_bZoomView;
}
}
else
{
return;
}
}
else
{
m_bZoomView = DFALSE;
}
// Change to the weapon…
DoWeaponChange(nWeaponId);
}
Вижу следующую цепочку проверок:
- Если оружие меняется на то же самое (m_nWeaponId - хранит id текущего оружия), то…
- Поддерживает ли оружие zoom? Если да, то…
- Меняет состояние режима zoom на противоположное: m_bZoomView = !m_bZoomView;
- Переключает оружие: DoWeaponChange(nWeaponId);
Как переключается zoom стало понятно. Теперь сделаю свой собственные метод. За основу возьму CWeaponModel::HandleInternalWeaponChange(DBYTE nWeaponId).
Zoom будет включаться на текущем оружии, поэтому стыковать nWeaponId и nCommandId не нужно. Id текущего оружия возьму из поля m_nWeaponId. Получилось следующее:
// ———————————————————————– //
//
// ROUTINE: CWeaponModel::ToggleZoom()
//
// PURPOSE: Toggle zoom on a current weapon
//
// ———————————————————————– //
void CWeaponModel::ToggleZoom()
{
if (!g_pRiotClientShell) return;
if (CanWeaponZoom(m_nWeaponId))
{
if (g_pRiotClientShell->IsChaseView())
{
m_bZoomView = DFALSE;
}
else
{
m_bZoomView = !m_bZoomView;
}
}
else
{
return;
}
DoWeaponChange(m_nWeaponId);
}
- Проверяю оружие на способность к zoom’у.
- Проверяю поддерживает ли камера zoom.
- Меняю состояние zoom на противоположное: m_bZoomView = !m_bZoomView;
- Переключаю оружие: DoWeaponChange(nWeaponId);
Пришло время вызвать метод.
Система команд
Кто-то вызывает метод CWeaponModel::ChangeWeapon(DBYTE nCommandId) и передает некую команду nCommandId. Ищу с помощью “Find All References”. Вижу, что есть еще один файл в котором происходит вызов: “…\ClientShellDLL\RiotClientShell.cpp”.
case COMMAND_ID_WEAPON_10 :
{
m_weaponModel.ChangeWeapon(command);
}
break;
Сделаю по аналогии:
case COMMAND_ID_TOGGLE_ZOOM :
{
m_weaponModel.ToggleZoom();
}
break;
Теперь метод вызывается, но у меня на руках никому неизвестный COMMAND_ID_TOGGLE_ZOOM. Поищу-ка, где объявляется COMMAND_ID_WEAPON_10 и добавлю туда же. “Find All References” для COMMAND_ID_WEAPON_10 и нахожу объявление в “…\shared\riotcommandids.h” Выбираю id из незанятых номеров и делаю по аналогии.
// Zoom extension…
#define COMMAND_ID_TOGGLE_ZOOM 80
Здесь я попадаю в небольшой тупик — непонятно что делать дальше: метод есть, id прописан, но о нем никто не знает. Попробую искать ссылки на другие COMMAND_ID_. Точно! Вот оно! Поиск по COMMAND_ID_FORWARD привел меня к файлу “…\ClientShellDLL\ClientUtilities.cpp”
Меню команд
В первую очередь обращаю внимание на NUM_COMMANDS из CommandID g_CommandArray[NUM_COMMANDS] = …. Число команд (NUM_COMMANDS) ограничено и прописано в заголовочном файле (.h). Добавлю сюда еще одну, следовательно наращиваем NUM_COMMANDS с 28 до 29. Добавляю новую команду в массив команд:
{ IDS_CONTROL_TOGGLEZOOM,COMMAND_ID_TOGGLE_ZOOM },
{ IDS_CONTROL_UNASSIGNED,COMMAND_ID_UNASSIGNED }// this control must always remain as the last one in the array
Обращаю внимание на комментарий “// this control must always remain as the last one in the array” и добавляю команду, перед COMMAND_ID_UNASSIGNED.
Я сделал нечто и получил на руки новый id IDS_CONTROL_TOGGLEZOOM. Припоминаю из своего опыта, что IDS_ означает строковый ресурс, который где-то должен быть прописан. Оставлю на потом, а пока продолжу исследовать ClientUtilities.cpp.
int CommandToArrayPos(int nCommand)
Добавлю сюда команду IDS_CONTROL_TOGGLEZOOM. Посчитав пальцем по экрану, получаю
case COMMAND_ID_TOGGLE_ZOOM: return 27;
case COMMAND_ID_UNASSIGNED: return 28;
Кстати, этот метод преобразует id команды в номер позиции массива команд. В дальнейшем, из массива можно будет достать связку id-команды — ids-ресурса-команды. Забегая вперед, ids-ресурса-команды — это скорее всего название команды.
И, похоже, последнее, что придется менять в этом файле:
//——————————————————————————————-
// CommandName
//
// Retrieves the command name from a command number
// Arguments:
// nCommand - command number
// Return:
// String containing the name of the action
//——————————————————————————————-
char* CommandName(int nCommand)
Теперь уже понятно, что здесь получение имени команды по ее id (это видно из комментария). Дорабатываю:
case COMMAND_ID_TOGGLE_ZOOM: nStringID = IDS_ACTIONSTRING_TOGGLEZOOM; break;
case COMMAND_ID_UNASSIGNED: nStringID = IDS_ACTIONSTRING_UNASSIGNED; break;
Получил еще один неоформленный id — IDS_ACTIONSTRING_UNASSIGNED. Разбираюсь с IDS_ ресурсами.
Ресурсы
Где-то на просторах readme.txt я приметил, что ресурсы вынесены в отдельный проект. Буду посмотреть “…\ClientRes\ClientRes.dsp”. Загружаю, смотрю в .rc файл. IDS_ обнаружены!
Добавляю по аналогии с другими командами.
IDS_ACTIONSTRING_TOGGLEZOOM = “Zoom”
IDS_CONTROL_TOGGLEZOOM = “zoom”
Запуск
Cобираю проект. Теперь у меня есть два DLL-файла в каталоге …\Shogo.
Создаю в каталоге Shogo дочерний каталог NiK. Переписываю полученные от сборки DLL’и. Запускаю лончер. В Shogo Advanced Launcher, Command-Line прописываю: -rez NiK. Запускаю Shogo.
В игровом меню настроек клавиатуры и мыши появился новый пункт Zoom. Ура! Играю! Zoom нихрена не работает. В консоле появляется надпись, что-то вроде “action is not binded”. WTF?
autoexec.cfg
В autoexec.cfg есть целый ряд команд AddAction. Может мне сюда? Попробую.
AddAction Zoom 80
Zoom - это название команды. 80 - это id команды (#define COMMAND_ID_TOGGLE_ZOOM 80).
Повторный запуск, повторная настройка клавиатуры и мыши… барабанная дробь…
Работает!
Еще немного…
Я так и не понял причину, по которой мне пришлось прописывать AddAction в autoexec.cfg. То ли это баг, то ли так и должно быть, но если удалить из autoexec.cfg другие AddAction, то другие действия перестают работать. Неужели так задумано?
Исходники и библиотеки можно посмотреть/забрать в статье — “Исходники Shogo Zoom“.