16 августа.
Всем привет от Omni, сегодня будет стена текста.
В целом денёк был занятой, в основном Omni и Bartwe играли вдвоём и убивали найденные баги (должен быть стрим на твитче). Это скучно. Кратко, что получилось, перед тем, как перейти к интересным вещам: Omni солгал, сказав, что всё ограничилось только багфиксами - с его подачи в игре появился вторичный инвентарь для автоматической сортировки материалов, так что теперь у игрока 92 доступных слота, не считая слотов экипировки. Он просто об этом забыл, денёк длинный выдался. Итак, поехали.
Сначала Bartwe сделал несколько изменений в методе отлова ошибок под Windows, пытаясь сделать трассировку стеков, чтобы получать более продуктивный отчёт при падении игры. Это поможет в будущих отладках и не является багом. Далее он убрал проблемные цифры, вылетающие при нанесении невероятно низкого урона (из стримов: снос некоторых растений). После он поправил код опасного окружения, чтобы оно умело наносить значения урона, отличные от 100% показателя здоровья игрока и поправил несколько недоглядок в вышеупомянутой фиче. Omni занимался пропущенными логическими элементами из раздела функционализма. А ещё были поправлены частицы при заносе противников - теперь они не будут генерироваться, когда это не нужно. Ну и общая чистка кода и опечаток.
Omni начал день с добавления второстепенного инвентаря. Затем он пошёл разбираться с глюками инструмента под названием BeamAxe (лучевой топор), в частности с методом рендера его лазера - смещения были неверные. Далее он поправил недосмотр в коде GUI, который мог привести к тому, что элементы Panes (панели) регистрировались в PaneManager, хотя на самом деле формально никогда не регистрировались нигде, что приводило к определённым глюкам. После этого последовали исправления в система крафта - рецепты не учитывали наличие материалов в новом инвентаре, а после того, как это было исправлено, пришлось исправлять новый баг, когда материалы не исчезали из этого инвентаря после создания вещей, ы!
Вы когда-нибудь замечали, просматривая стримы, что когда используется BeamAxe, рука персонажа начинает вести себя странно, дико вращаясь вокруг или дёргаясь, а затем медленно возвращаясь в исходное положение? Исправлено. Да, при слишком близком положении курсора встречался похожий глюк с любыми вещами - тоже поправлено. Ещё были поправлены некоторые крайние методы обработки инвентаря, о которых будет сказано позже, и очередная проблема с UI, когда не отображалось содержимое слотов "сейчас в руках" после перезапуска файла сохранения.
Позже поступили свежие баги от George, который сообщил, что навигационное меню не пропадает и не возвращается на экран корректно. Пришлось заняться и этим. Панель не получала статус как фактически существующая, к тому же её код жрал слишком много строк, так что сразу два бага поправилось. Наконец, Omni ввёл фичу, которая ему давно нужна была, да и вообще полезна - шифт+клик переносит вещи с инвентаря на панель быстрого доступа и обратно. Всё ещё определяется горячая клавиша для быстрого перемещения выбранного предмета в конкретную руку, есть идеи?
Длинный день, что сказать.
Итак... чтобы сделать всё это менее напоминающим спискоту и безвкусицу, давайте поговорим про панель быстрого доступа и обработку инвентаря в целом. В последнее время эти элементы подверглись самому серьёзному изменению, а исходя из огромного числа взаимодействий с другими элементами, сложно вот так сходу взять и сделать всё правильно.
Вот панель доступа от Omni. Таких полно, но это лично его панель.

Наверняка вы заметили, что теперь слоты L и R это реальные слоты, а не просто отображение вещей, которые выбраны где-то ещё на панели. Да, есть и двуручные предметы, как же быть с ними? Ну, это просто! Второй слот отключается и обесцвечивается:

Это решение выглядит куда более элегантно, чем форсированное удаление предмета из слота и помещение его в инвентарь, где может и не быть места. "Но!" - уже слышны вопли недовольных, "Этот метод какой-то громоздкий! Где же возможность быстро переключаться между слотами? Придётся физически таскать предметы в эти L и R..."
"Не так быстро!" - отвечает Omni, "Вы всё ещё можете переключать слоты достаточно быстро".

И это работает с двуручными предметами!

А теперь о том, как это всё устроено.
Панель быстрого доступа управляется файлом /source/frontend/StarActionBar.hpp, совместно с cpp-файлом определяет объект, который будет обрабатываться через MainInterface, source/frontend/StarMainInterface.hpp. MainInterface отвечает за весь GUI. За инвентарь игрока отвечает класс /source/game/StarPlayerInventory.hpp, который тоже надо неплохо редактировать, чтобы всё это вообще работало.
Переходим к изменениям высокоро уровня, начнём с инвентаря игрока.
Класс инвентаря получил два существенных изменения - во-первых, там были прописаны два экстра слота, которые существуют вне остальной панели для более лёгкого внедрения. Называются они просто, "LeftHand" и "RightHand". Во-вторых, тут имеется концепт слота "сейчас в руках". Ранее использовалось отслеживание предмета, который был помечен на панели как выбранный, но для этого применялось лишь смещение значения size_t в сумке, вместо определения через InventorySlot уникальной ячейки из всего инвентаря. Пока что это не используется, но эта штука позволит пометить абсолютно любой слот в инвентаре как "сейчас в руках", вместо потенциально кривого смещения в какой-нибудь лишней сумке.
Итак, у нас есть 4 разных предмета, которые можно экипировать разными методами, у двух предметов есть 3 возможных значения, и 4 у оставшихся двух. Слоты левой и правой "рук" могут быть либо двуручными, либо одноручными, либо пустыми. Занятые, эти слоты могут быть двуручными, одноручными, выбранными (но пустыми), или просто не выбранными.
Что приводит к вопросу - какой предмет будет отображаться в левой руке, а какой в правой? Сложный вопрос - тут имеется 144 возможных комбинации двуручных, одноручных, пустых, не выбранных, и так далее. Так что единственный метод внести сюда здравый смысл, это пронумеровать каждый возможный случай. Так же это позволит поймать баги, так как тут всё прозрачно и линейно.

Текущий варианты матрицы выбора для левой руки.
// The following is a grid of the possibilities here
// Source:
// AE = Alt hEld Item, AH = Alt Hand Item
// PE = Pri hEld Item, PH = Pri Hand Item
// 2 = two handed item
// 1 = one handed item
// 0 = empty slot (selected)
// N = not set (Held items only)
//
// Result:
// X = invalid / don't care
// P = primaryHeldSlot();
// p = leftHand();
// A = altHeldSlot();
// a = rightHand();
// 0 = none();
//
///////////////////////////////////////////////////////////
// XX |AE | 2 | 2 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | N | N | N |
// XX |AH | 2 | 1 | 0 | 2 | 1 | 0 | 2 | 1 | 0 | 2 | 1 | 0 |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// PP | | | | | | | | | | | | | |
// EH | | | | | | | | | | | | | |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 22 | | X | X | X | X | X | X | X | X | X | P | P | P |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 21 | | X | X | X | X | X | X | X | X | X | P | P | P |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 20 | | X | X | X | X | X | X | X | X | X | P | P | P |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 12 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 11 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 10 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 02 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 01 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// 00 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// N2 | | A | A | A | A | A | A | p | p | p | p | p | p |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// N1 | | A | A | A | A | A | A | 0 | 0 | 0 | 0 | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// N0 | | A | A | A | A | A | A | 0 | 0 | 0 | a | a | a |
// ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Понять не трудно, так как описание присутствуюет и похоже на табличку.

То же самое в виде кода
auto priHand = underlyingPrimaryHandItem();
auto altHand = underlyingAltHandItem();
// If we have a two handed primary held item, return it
// Covers 0,0 to 2,11 submatrix
if (safeTwoHanded(primaryHeldItem()))
return primaryHeldSlot();
// If we have a alt held item, it takes priority
// Covers 0,0 to 11,5 submatrix
if (altHeldItem())
return altHeldSlot();
// if we're flagged as holding an empty item in altHeld
// and a two-handed item in our priHand, return priHand
// Covers 9,6 to 9,8 submatrix
if (validInventorySlot(altHeldSlot()) &&
!altHeldItem() &&
!validInventorySlot(primaryHeldSlot()) &&
safeTwoHanded(priHand))
return InventorySlot::leftHand();
// If we have a alt held slot selected, but not item...
// Covers 3,6 to 11,8 submatrix
if (validInventorySlot(altHeldSlot())) // implicit !altHeldItem()
return InventorySlot::none();
// If we have a no selected primary held item and primaryHandItem is two handed, use that
// Covers 9,9 to 9,11 submatrix
if (!validInventorySlot(primaryHeldSlot()) &&
safeTwoHanded(priHand))
return InventorySlot::leftHand();
// If we have no item in primaryHeldSlot, but it's selected, and we're two handed, use rightHand
// Covers 6,9 to 8,9 submatrix
if (validInventorySlot(primaryHeldSlot()) &&
!primaryHandItem() &&
safeTwoHanded(altHand))
return InventorySlot::rightHand();
// If we have have a valid one handed primary hand item, and we're two handed, return none
// Covers 3,9 to 5,9 submatrix
if (validInventorySlot(primaryHeldSlot()) &&
safeOneHanded(primaryHeldItem()) &&
safeTwoHanded(altHand))
return InventorySlot::none();
// If we have a one handed primary hand item, and a two handed alt hand item, return none
// Covers 10,9 cell
if (safeTwoHanded(altHand) &&
safeOneHanded(priHand))
return InventorySlot::none();
// Covers remainder
return InventorySlot::rightHand();
Вот это ничего себе, верно? На самом деле это достаточно трудно сделать "правильно", особенно когда это определение не так явно. Например, в случае, когда главная рука выбрана как активная, но в ней всё же ничего нет, а в слоте второй руки лежит двуручный предмет. Кажется, что верным решением будет выбрать отключение обоих слотов, но эт не самый лучший дизайн, так что альтернативная рука в данном варианте перехватывает первенство у главной, не смотря на то, что это нарушение обычного хода вещей, но это "правильно".
Далее, про область панели быстрого доступа. Теперь, когда готова почва для поддержки в игре инвентаря, надо бы добавить несколько дополнительных штук в панельку. Сначала, добавить интерактивные слоты на места L и R. Они должны что-то делать, если на них кликнуть - например, убрать выбор текущего выбранного предмета, или выстрелить из пушки, если оная в слоте и может стрелять.
А что насчёт других предметов на панельке? Это уже сложнее. Есть три способа выбрать предмет, который будет помечен как "взятый", но только ОДИН действительно отметится на интерфейсе панели. Остальные два пройдут обработку через MainInterface, потому что он отвечает за перехваты нажатия клавиш и опрашивает мышь, так что вся эта ерунда не должна быть где-то в оболочке самой панели.
Однако метод, который напрямую за это отвечает, не так прост! Всего есть три штуки для UI, которые отслеживаются, как и число методов выбора:



Эти три штуки учитываются, как одна. Далее - можно скроллить колесо, чтобы выбирать положение иконки для левой и для правой руки - значения хранятся в MainInterface, так как на него все ссылки. И объект под курсором определяется через InventorySlot, и тут значения тоже хранятся в MainInterface. Все они похожи, но всё же отличаются, так как следуют разным правилам, хотя бы потому, что отслеживаются независимо друг от друга.
Например, каждый раз, когда Omni говорит "обнови то и то", логика этих контроллеров срабатывает на всех трёх вариантах, но только если это применимо к текущей ситуации, которые ещё не слишком определены, чтобы о них чётко рассказать, но всё вроде бы работает как надо - до первой серьёзной профилактики, ага.
С кликом на слотах панели есть парочка вариантов. Если кликнуть с зажатым шифтом, то будет инициирована попытка отправить предмет в контейнер, если он существует в мире и открыт. Если нет, предмет пытается отправиться в инвентарь. Если шифт не зажат, идёт проверка на открытый инвентарь, и если он открыт, предмет перемещается в "слот для перемещений", о котором ещё не было речи. Но если кликнуть правой кнопкой мыши, то предмет из этого "слота" будет применён - например, так используются предметы друг на друге, скажем, для ремонта. Если же инвентарь не открыт, предмет перемещается в главную руку и помечается как "взятый", если нажать правой кнопкой мыши - то в другую руку с тем же результатом. Затем проверка на то, что объект не двуручный, и если он двуручный, то быстренько обновляется информация по трём методам, упомянутым ранее. Если одноручный, то само собой, помещается в слот согласно ситуации.
Когда крутится колесо мыши вверх или вниз, или нажимаются кнопки 1-9+0 на клавиатуре, MainInterface берёт на себя заботы по этим вопросам. Код почти такой же, но проще, так как вариантов действий меньше, но всё равно проверяются все три позиции.
Все, кто смотрели стрим на этой неделе, видели 90% того, что выше написано. Началось всё не так гладко, потому заняло порядком времени. Всё работает и не нервирует никого, что прекрасно.

А теперь Omni затыкается и награждает всех прочитавших сие медалькой!
//Хочу заметить, что порой понять Omni и перевести мысль дословно мне трудно
если у кого есть конструктивные предложения по качеству - вещайте. Стараюсь переводить так, чтобы был понятен принцип - всё и правда несколько сложнее, чем выглядит на первый взгляд.