Инвентарь: 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:
parent
c1f79a1fb8
commit
b29358ad12
@ -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)) |
|
||||||
|
|
||||||
## Что находилось по ходу и чинилось
|
## Что находилось по ходу и чинилось
|
||||||
|
|
||||||
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
||||||
|
|||||||
@ -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) рисуется в тот же софтверный
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
191
src/ui/menu.cpp
191
src/ui/menu.cpp
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user