Инвентарь: SHIFT+клик, сортировка схрона, сравнение с надетым (M38)

Перетаскивание мышью в кадре 480x270 — это попадание в ячейку 74x18 с зажатой
кнопкой, и делать так тридцать раз после каждой вылазки нельзя.

* SHIFT + клик отправляет вещь ТУДА, ГДЕ ЕЙ МЕСТО: ствол в руки, плита в броню,
  брелок в карман, расходник в сумку; из слота — обратно в схрон. Обмен идёт
  через тот же ApplyDrop, что и перетаскивание: своя копия правил означала бы
  ещё одно место, где вещь может потеряться.
* Кнопка SORT раскладывает схрон: класс -> редкость -> аффиксы -> имя.
* Подсказка вещи из схрона дописывает блок NOW: что стоит в этом слоте сейчас
  и чем новое отличается.

--ui-check меряет сохранность, а не факт вызова: вещей было 5 — стало 5, ствол
в руках, прежний в схроне, профиль о переодевании знает. Проверка сперва
воспроизведена на сломанной сборке: без SaveLoadout печатает «профиль НЕ знает».

Замеры: --ui-check поломок 0, --accept PASS, --descend 8 ошибок 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
z.kirill 2026-08-18 18:42:28 +03:00
parent c1f79a1fb8
commit b29358ad12
5 changed files with 286 additions and 2 deletions

View File

@ -62,6 +62,8 @@
| **M37** | **поле путей**: отставший боец возвращается в строй по геометрии уровня, а не по прямой; обход включается только тому, кто упёрся и потерял строй | это был тот самый «отряд не проходит там, где одиночка проходит»: `--play 6 5` вставал на первом этаже на всех восьми сидах, стало 47 из 48 (единственная потеря — вайп в бою); по дороге вскрылись ДВЕ неверные мерки: критерий 11.6 награждал брошенных за стеной бойцов (растянутый отряд = «колонна»), а `--wall` читал «стены не нашлось» как «стена на нулевом этаже» и объявлял лучший результат провалом; обе переписаны и проверены на сломанной сборке; шаг симуляции 6.1 мкс; `--accept` PASS ([05-squad-ai.md](05-squad-ai.md), [08-decisions.md](08-decisions.md)) | | **M37** | **поле путей**: отставший боец возвращается в строй по геометрии уровня, а не по прямой; обход включается только тому, кто упёрся и потерял строй | это был тот самый «отряд не проходит там, где одиночка проходит»: `--play 6 5` вставал на первом этаже на всех восьми сидах, стало 47 из 48 (единственная потеря — вайп в бою); по дороге вскрылись ДВЕ неверные мерки: критерий 11.6 награждал брошенных за стеной бойцов (растянутый отряд = «колонна»), а `--wall` читал «стены не нашлось» как «стена на нулевом этаже» и объявлял лучший результат провалом; обе переписаны и проверены на сломанной сборке; шаг симуляции 6.1 мкс; `--accept` PASS ([05-squad-ai.md](05-squad-ai.md), [08-decisions.md](08-decisions.md)) |
| **M38** | инвентарь перестал быть перетаскиванием: SHIFT+клик отправляет вещь туда, где ей место, кнопка SORT раскладывает схрон, подсказка показывает, что заменится | `--ui-check` меряет не «функция вызвалась», а СОХРАННОСТЬ: вещей было 5 — стало 5, ствол в руках, прежний в схроне, профиль знает; проверка воспроизведена на сломанной сборке (без записи в профиль печатает «профиль НЕ знает») ([13-combat.md](13-combat.md)) |
## Что находилось по ходу и чинилось ## Что находилось по ходу и чинилось
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта, Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,

View File

@ -213,6 +213,35 @@ dev-режим без профиля и песочница приёмки.
HP, броню, скорость, обзор. `ENTER` меняет местами оружие в руках и выбранную HP, броню, скорость, обзор. `ENTER` меняет местами оружие в руках и выбранную
ячейку сумки — отряд не носит воздух, у него всегда что-то в руках. ячейку сумки — отряд не носит воздух, у него всегда что-то в руках.
### Как перекладываются вещи
Три способа, и все три ведут в один и тот же обмен (`ApplyDrop`): своя копия
правил в каждом означала бы три места, где вещь может потеряться.
| Способ | Что делает |
|---|---|
| **SHIFT + клик** | отправляет вещь ТУДА, ГДЕ ЕЙ МЕСТО: ствол в руки, плита в броню, брелок в карман, расходник в сумку; из слота — обратно в схрон |
| перетаскивание | точный перенос в конкретную ячейку (порядок схрона — единственное, чем игрок им управляет) |
| ПКМ по вещи | выбросить под ноги; из рук нельзя — безоружный боец не боец |
SHIFT + клик появился потому, что кадр 480x270: ячейка схрона 74x18 пикселей, и
попадать в неё зажатой кнопкой тридцать раз после каждой вылазки невозможно.
Кнопка **SORT** раскладывает схрон: класс → редкость → число аффиксов → имя.
Именно в таком порядке, а не по имени: игрок ищет «во что переодеться», то есть
сначала вид вещи, а внутри вида — лучшее.
Подсказка вещи из схрона или сумки дописывает блок **NOW**: что стоит в том же
слоте сейчас и чем новое от него отличается. Без него выбор снаряжения — это
запоминание чисел: навёл на плиту, увидел «ARMOR +18%», ушёл смотреть на бойца
и уже не помнишь первое.
Всё это проверяется кодом возврата, а не глазом: `--ui-check` меряет
СОХРАННОСТЬ (сколько вещей было в схроне и слотах, столько и осталось), доехало
ли переодевание до профиля и не теряет ли вещи сортировка. Проверка сперва
воспроизведена на сломанной сборке — без записи в профиль она печатает
«профиль НЕ знает» и падает.
## HUD ## HUD
[src/render/hud.cpp](../src/render/hud.cpp) рисуется в тот же софтверный [src/render/hud.cpp](../src/render/hud.cpp) рисуется в тот же софтверный

View File

@ -2583,6 +2583,67 @@ int RunUiCheck()
} }
} }
// --- быстрый перенос и сортировка схрона ---------------------------------
//
// Перекладывание вещей — самое дорогое место инвентаря: здесь предмет может
// ПРОПАСТЬ, и до этой проверки он ровно так и пропадал (M36, пистолет).
// Меряем не «функция вызвалась», а сохранность: сколько вещей было в сумме
// по схрону и слотам, столько и осталось.
{
Ui ui;
ui.agentIndex = 0;
ui.started = false; // сборы НА БАЗЕ: сумки нет, схрон есть
profile.stash.clear();
profile.StashPush(ItemInstance{ItemId::PISTOL});
profile.StashPush(ItemInstance{ItemId::PLATE});
profile.StashPush(ItemInstance{ItemId::SABER});
Inventory* inv = game.world.Get<Inventory>(game.squad.agents[0].self);
const ItemId handsBefore = inv ? ItemId(inv->weapon) : ItemId::NONE;
const int totalBefore = profile.StashCount() + (inv && !inv->weapon.Empty() ? 1 : 0) +
(inv && !inv->armor.Empty() ? 1 : 0);
// Пистолет из схрона обязан оказаться В РУКАХ, а прежнее оружие — в
// схроне. Именно этот обмен игрок делает после каждой вылазки.
UiQuickMoveForTest(ui, game, UiPart::STASH, 0);
const bool inHands = inv && ItemId(inv->weapon) == ItemId::PISTOL;
const bool oldBack = [&]() {
for (int k = 0; k < profile.StashCount(); ++k)
if (ItemId(profile.stash[size_t(k)]) == handsBefore) return true;
return false;
}();
const int totalAfter = profile.StashCount() + (inv && !inv->weapon.Empty() ? 1 : 0) +
(inv && !inv->armor.Empty() ? 1 : 0);
// И профиль обязан узнать о переодевании: без этого вылазка соберёт
// бойца заново из профиля и вернёт ему старый ствол (M36).
const bool saved = ItemId(profile.loadout[0].weapon) == ItemId::PISTOL;
std::printf("\nбыстрый перенос: в руках %s, прежнее в схроне %s, профиль %s, "
"вещей было %d стало %d\n",
inHands ? "да" : "НЕТ", oldBack ? "да" : "НЕТ", saved ? "знает" : "НЕ знает",
totalBefore, totalAfter);
if (!inHands || !oldBack || !saved || totalAfter != totalBefore)
{
std::printf(" [FAIL] SHIFT+клик теряет вещь или не доводит её до профиля\n");
++bad;
}
// Сортировка не имеет права ничего потерять: это перестановка, а не
// операция над вещами.
const int sortBefore = profile.StashCount();
UiActivate(ui, game, UiPart::STASH_SORT, 0);
const int sortAfter = profile.StashCount();
std::printf("сортировка схрона: было %d, стало %d\n", sortBefore, sortAfter);
if (sortBefore != sortAfter)
{
std::printf(" [FAIL] сортировка схрона теряет вещи\n");
++bad;
}
}
std::printf("\nитог: пунктов %d, поломок %d\n", count, bad); std::printf("\nитог: пунктов %d, поломок %d\n", count, bad);
return bad == 0 ? 0 : 1; return bad == 0 ? 0 : 1;
} }

View File

@ -156,6 +156,9 @@ constexpr int SQ_HINT_Y = 250, SQ_HINT_STEP = 10;
// две ячейки в ряду: прокрутка есть, а угадывать вещь по обрубку — нет. // две ячейки в ряду: прокрутка есть, а угадывать вещь по обрубку — нет.
constexpr int SQ_STASH_X = 16, SQ_STASH_Y = 206, SQ_STASH_W = 74, SQ_STASH_H = 18; constexpr int SQ_STASH_X = 16, SQ_STASH_Y = 206, SQ_STASH_W = 74, SQ_STASH_H = 18;
constexpr int SQ_STASH_GX = 4, SQ_STASH_GY = 3, SQ_STASH_COLS = 6, SQ_STASH_ROWS = 2; constexpr int SQ_STASH_GX = 4, SQ_STASH_GY = 3, SQ_STASH_COLS = 6, SQ_STASH_ROWS = 2;
// Кнопка «разложить»: справа от заголовка схрона, там же, где подсказка о
// прокрутке. Схрон на 48 ячеек, вещи валятся в порядке подбора.
constexpr int SQ_SORT_X = 168, SQ_SORT_W = 34, SQ_SORT_H = 10;
constexpr int SQ_STASH_CELLS = SQ_STASH_COLS * SQ_STASH_ROWS; constexpr int SQ_STASH_CELLS = SQ_STASH_COLS * SQ_STASH_ROWS;
// Мастерская. Слева схрон той же сеткой, что и на экране отряда (привычка // Мастерская. Слева схрон той же сеткой, что и на экране отряда (привычка
@ -348,6 +351,42 @@ Tip ItemTip(const ItemInstance& it)
return t; return t;
} }
// Сравнение с надетым. Без него выбор снаряжения — это запоминание чисел:
// игрок наводит на найденную плиту, видит «ARMOR +18%», уходит смотреть, что
// на бойце, возвращается и уже не помнит первое. Дописываем прямо в ту же
// подсказку, отдельным блоком после аффиксов.
//
// Сравнивается ТО, ЧТО ЗАМЕНИТСЯ: класс вещи выбирает слот, слот даёт текущую
// вещь. Если слот пуст или это та же самая вещь, блока нет вовсе.
void AddCompare(Tip& t, const ItemInstance& it, const Inventory& inv)
{
const ItemDef d = ItemDefOf(it.id);
const ItemInstance* now = nullptr;
if (IsWeaponItem(it.id)) now = &inv.weapon;
else if (d.kind == ItemKind::ARMOR) now = &inv.armor;
else if (d.kind == ItemKind::TRINKET) now = &inv.trinket;
if (!now || now->Empty() || now->id == it.id) return;
const ItemDef c = ItemDefOf(now->id);
t.Add("NOW: %s", c.name);
// Только те строки, где есть РАЗНИЦА: «ARMOR +0%» не говорит ничего.
const int dArmor = int((d.armor - c.armor) * 100.0f + (d.armor >= c.armor ? 0.5f : -0.5f));
const int dSpeed = int((d.speedMul - c.speedMul) * 100.0f +
(d.speedMul >= c.speedMul ? 0.5f : -0.5f));
const int dHp = int(d.hpBonus - c.hpBonus);
const int dSight = int((d.sightMul - c.sightMul) * 100.0f +
(d.sightMul >= c.sightMul ? 0.5f : -0.5f));
const float dWeight = d.weight - c.weight;
if (dArmor != 0) t.Add(" ARMOR %+d%%", dArmor);
if (dSpeed != 0) t.Add(" SPEED %+d%%", dSpeed);
if (dHp != 0) t.Add(" HP %+d", dHp);
if (dSight != 0) t.Add(" SIGHT %+d%%", dSight);
if (dWeight < -0.05f || dWeight > 0.05f) t.Add(" WEIGHT %+.1f", double(dWeight));
}
Tip StatTip(int line, const Agent& a) Tip StatTip(int line, const Agent& a)
{ {
Tip t; Tip t;
@ -576,6 +615,8 @@ void BuildWidgets(const Ui& ui, const Game& game, const Layout& l, Widgets& out)
// (продолжение ниже) // (продолжение ниже)
if (game.profile) if (game.profile)
{ {
out.Add(UiPart::STASH_SORT, 0,
UiRect{SQ_SORT_X, SQ_STASH_Y - 12, SQ_SORT_W, SQ_SORT_H});
for (int c = 0; c < SQ_STASH_CELLS; ++c) for (int c = 0; c < SQ_STASH_CELLS; ++c)
{ {
const int k = ui.stashScroll + c; const int k = ui.stashScroll + c;
@ -748,6 +789,113 @@ void DropToFloor(Ui& ui, Game& game, UiPart part, int index)
SaveLoadout(game, ui.agentIndex); SaveLoadout(game, ui.agentIndex);
} }
// -----------------------------------------------------------------------------
// Быстрый перенос: SHIFT + клик.
//
// Перетаскивание мышью в кадре 480x270 — это точное попадание в ячейку 74x18
// с зажатой кнопкой, и делать так тридцать раз подряд после каждой вылазки
// невозможно. SHIFT + клик отправляет вещь ТУДА, ГДЕ ЕЙ МЕСТО, и обратно:
//
// схрон -> подходящий слот бойца (что было в слоте, уезжает в схрон);
// слот -> схрон.
//
// Слот выбирается по КЛАССУ вещи, а не по номеру: ствол в руки, плита в броню,
// брелок в карман, расходник в сумку. Это ровно те же правила, что и у
// перетаскивания (SlotAccepts), просто игроку больше не надо их отмерять рукой.
UiPart SlotForItem(ItemId id, bool started)
{
if (IsWeaponItem(id)) return UiPart::HANDS;
switch (ItemDefOf(id).kind)
{
case ItemKind::ARMOR: return UiPart::ARMOR;
case ItemKind::TRINKET: return UiPart::POCKET;
// Расходник — в сумку, но сумка принадлежит ВЫЛАЗКЕ: вне забега её нет,
// и вещь, положенная туда из меню, исчезла бы вместе с миром.
default: return started ? UiPart::BAG : UiPart::NONE;
}
}
// Первая пустая ячейка сумки, иначе последняя. Возвращает -1, если сумки нет.
int FreeBagCell(const SquadBag& bag)
{
for (int i = 0; i < SQUAD_BAG_SLOTS; ++i)
if (bag.slots[i].Empty()) return i;
return -1;
}
void ApplyDrop(Ui& ui, Game& game, UiPart to, int toIndex);
void QuickMove(Ui& ui, Game& game, UiPart part, int index)
{
if (!game.profile) return;
Agent& a = game.squad.agents[ui.agentIndex];
Inventory* inv = game.world.Get<Inventory>(a.self);
if (!inv) return;
Profile& pr = *game.profile;
if (part == UiPart::STASH)
{
if (index < 0 || index >= pr.StashCount()) return;
const ItemInstance moved = pr.stash[size_t(index)];
const UiPart dest = SlotForItem(moved, ui.started);
if (dest == UiPart::NONE) return;
int destIndex = 0;
if (dest == UiPart::BAG)
{
destIndex = FreeBagCell(game.squad.bag);
if (destIndex < 0) return; // сумка полна — молча терять вещь нельзя
}
// Дальше — обычный обмен, тот же самый код, что и у перетаскивания.
// Своя копия правил обмена здесь означала бы два места, где вещь может
// потеряться, вместо одного.
ui.dragFrom = UiPart::STASH;
ui.dragIndex = index;
ui.dragItem = moved;
ApplyDrop(ui, game, dest, destIndex);
ui.dragFrom = UiPart::NONE;
ui.dragIndex = -1;
ui.dragItem = ItemId::NONE;
return;
}
// Обратно в схрон. Из РУК не отправляем: безоружный боец — не боец, и то же
// правило уже стоит на выбрасывании под ноги (DropToFloor).
if (part == UiPart::HANDS || !IsSlot(part)) return;
ItemInstance* slot = SlotPtr(*inv, game.squad.bag, part, index);
if (!slot || slot->Empty()) return;
if (pr.StashCount() >= STASH_CAP) return; // схрон полон
ui.dragFrom = part;
ui.dragIndex = index;
ui.dragItem = *slot;
ApplyDrop(ui, game, UiPart::STASH, pr.StashCount());
ui.dragFrom = UiPart::NONE;
ui.dragIndex = -1;
ui.dragItem = ItemId::NONE;
}
// -----------------------------------------------------------------------------
// Сортировка схрона. Схрон на сорок восемь ячеек, вещи в него валятся в порядке
// подбора, и найти в нём нужное — отдельная работа, которой игрок не просил.
//
// Порядок: класс -> редкость (сначала лучшее) -> число аффиксов -> имя.
// Именно в таком, а не по имени: игрок ищет «во что переодеться», то есть
// сначала вид вещи, а внутри вида — лучшее.
void SortStash(Profile& pr)
{
std::stable_sort(pr.stash.begin(), pr.stash.end(),
[](const ItemInstance& x, const ItemInstance& y) {
const ItemDef dx = ItemDefOf(x.id), dy = ItemDefOf(y.id);
if (dx.kind != dy.kind) return int(dx.kind) < int(dy.kind);
if (x.rarity != y.rarity) return int(x.rarity) > int(y.rarity);
if (x.Count() != y.Count()) return x.Count() > y.Count();
return std::strcmp(dx.name, dy.name) < 0;
});
}
void ApplyDrop(Ui& ui, Game& game, UiPart to, int toIndex) void ApplyDrop(Ui& ui, Game& game, UiPart to, int toIndex)
{ {
Agent& a = game.squad.agents[ui.agentIndex]; Agent& a = game.squad.agents[ui.agentIndex];
@ -876,6 +1024,11 @@ void UiActivate(Ui& ui, Game& game, UiPart part, int index)
ui.screen = Screen::GAME; ui.screen = Screen::GAME;
return; return;
case UiPart::STASH_SORT:
if (game.profile) SortStash(*game.profile);
ui.stashScroll = 0;
return;
case UiPart::HUD_BUTTON: case UiPart::HUD_BUTTON:
switch (index) switch (index)
{ {
@ -996,6 +1149,11 @@ void UiApplyDropForTest(Ui& ui, Game& game, UiPart to, int toIndex)
ApplyDrop(ui, game, to, toIndex); ApplyDrop(ui, game, to, toIndex);
} }
void UiQuickMoveForTest(Ui& ui, Game& game, UiPart part, int index)
{
QuickMove(ui, game, part, index);
}
int UiMenuItemCount() { return MENU_COUNT; } int UiMenuItemCount() { return MENU_COUNT; }
const char* UiMenuItemName(int index) const char* UiMenuItemName(int index)
@ -1155,6 +1313,19 @@ bool Ui::Update(Game& game, const ScreenRect& fit, float dt)
// --- мышь: выбор бойца и перетаскивание предметов --- // --- мышь: выбор бойца и перетаскивание предметов ---
if (lmbDown && hot) if (lmbDown && hot)
{ {
// SHIFT + клик — быстрый перенос: вещь уходит туда, где ей
// место, без прицеливания мышью в ячейку 74x18.
const bool quick = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
if (quick && IsSlot(hot->part) && hot->item != ItemId::NONE)
{
QuickMove(*this, game, hot->part, hot->index);
return true;
}
if (hot->part == UiPart::STASH_SORT)
{
UiActivate(*this, game, hot->part, hot->index);
return true;
}
if (hot->part == UiPart::AGENT) if (hot->part == UiPart::AGENT)
{ {
agentIndex = hot->index; agentIndex = hot->index;
@ -1386,6 +1557,13 @@ void DrawSquadScreen(const Layout& l, const Game& game, const Ui& ui, const Widg
char cap[32]; char cap[32];
std::snprintf(cap, sizeof(cap), "STASH %d/%d", game.profile->StashCount(), STASH_CAP); std::snprintf(cap, sizeof(cap), "STASH %d/%d", game.profile->StashCount(), STASH_CAP);
Text(l, cap, SQ_STASH_X, SQ_STASH_Y - 12, 10, COL_TEXT); Text(l, cap, SQ_STASH_X, SQ_STASH_Y - 12, 10, COL_TEXT);
if (const Widget* sortBtn = w.Find(UiPart::STASH_SORT, 0))
{
const bool hot = Hovered(ui, *sortBtn);
BoxLines(l, sortBtn->rect, hot ? COL_TEXT : COL_EDGE);
Text(l, "SORT", sortBtn->rect.x + 5, sortBtn->rect.y + 1, 8,
hot ? COL_TEXT : COL_FAINT);
}
// Схрон длиннее сетки: без явной подсказки прокрутка не существует. // Схрон длиннее сетки: без явной подсказки прокрутка не существует.
if (game.profile->StashCount() > SQ_STASH_CELLS) if (game.profile->StashCount() > SQ_STASH_CELLS)
Text(l, "Q/E SCROLL", SQ_STASH_X + 92, SQ_STASH_Y - 11, 8, COL_FAINT); Text(l, "Q/E SCROLL", SQ_STASH_X + 92, SQ_STASH_Y - 11, 8, COL_FAINT);
@ -1409,7 +1587,7 @@ void DrawSquadScreen(const Layout& l, const Game& game, const Ui& ui, const Widg
std::snprintf(line, sizeof(line), "SIGHT %d%%", int(a.sightMul * 100.0f + 0.5f)); std::snprintf(line, sizeof(line), "SIGHT %d%%", int(a.sightMul * 100.0f + 0.5f));
Text(l, line, SQ_STAT_X, SQ_STAT_Y + SQ_STAT_STEP * 3, 8, COL_FAINT); Text(l, line, SQ_STAT_X, SQ_STAT_Y + SQ_STAT_STEP * 3, 8, COL_FAINT);
Text(l, "DRAG ITEMS WITH MOUSE RMB ON ITEM DROPS IT", SQ_STAT_X, SQ_HINT_Y, 8, Text(l, "SHIFT+CLICK MOVES ITEM DRAG ALSO WORKS RMB DROPS", SQ_STAT_X, SQ_HINT_Y, 8,
COL_FAINT); COL_FAINT);
Text(l, "A/D FIGHTER W/S SLOT ENTER TAKE ESC BACK", SQ_STAT_X, Text(l, "A/D FIGHTER W/S SLOT ENTER TAKE ESC BACK", SQ_STAT_X,
SQ_HINT_Y + SQ_HINT_STEP, 8, COL_FAINT); SQ_HINT_Y + SQ_HINT_STEP, 8, COL_FAINT);
@ -1826,7 +2004,16 @@ void Ui::Draw(const Game& game, const ScreenRect& fit) const
if (hoverPart == UiPart::STAT) if (hoverPart == UiPart::STAT)
DrawTip(l, StatTip(hoverIndex, game.squad.agents[agentIndex]), int(mx), int(my)); DrawTip(l, StatTip(hoverIndex, game.squad.agents[agentIndex]), int(mx), int(my));
else if (hov->item != ItemId::NONE) else if (hov->item != ItemId::NONE)
DrawTip(l, ItemTip(hov->item), int(mx), int(my)); {
Tip t = ItemTip(hov->item);
// Сравниваем только то, что ещё НЕ надето: наводя на слот
// бойца, игрок и так смотрит на надетое.
if (hoverPart == UiPart::STASH || hoverPart == UiPart::BAG)
if (const Inventory* inv =
game.world.Get<Inventory>(game.squad.agents[agentIndex].self))
AddCompare(t, hov->item, *inv);
DrawTip(l, t, int(mx), int(my));
}
} }
} }

View File

@ -55,6 +55,7 @@ enum class UiPart : uint8_t
CRAFT_SLOT, // ячейка схрона в мастерской: что дорабатываем CRAFT_SLOT, // ячейка схрона в мастерской: что дорабатываем
CRAFT_AFFIX, // строка аффикса на верстаке: цель направленной операции CRAFT_AFFIX, // строка аффикса на верстаке: цель направленной операции
HUD_BUTTON, // кнопка нижней панели в бою: открывает экран HUD_BUTTON, // кнопка нижней панели в бою: открывает экран
STASH_SORT, // кнопка «разложить схрон по порядку»
}; };
// Прямоугольник в координатах КАДРА (480x270), а не окна: кадр лежит в окне // Прямоугольник в координатах КАДРА (480x270), а не окна: кадр лежит в окне
@ -148,5 +149,9 @@ void UiActivate(Ui& ui, Game& game, UiPart part, int index);
// нельзя, окна в прогоне нет. // нельзя, окна в прогоне нет.
void UiApplyDropForTest(Ui& ui, Game& game, UiPart to, int toIndex); void UiApplyDropForTest(Ui& ui, Game& game, UiPart to, int toIndex);
// То же для быстрого переноса (SHIFT + клик): проверка обязана убедиться, что
// вещь доехала до слота, а не пропала по дороге.
void UiQuickMoveForTest(Ui& ui, Game& game, UiPart part, int index);
int UiMenuItemCount(); int UiMenuItemCount();
const char* UiMenuItemName(int index); const char* UiMenuItemName(int index);