Пункт UPGRADES выходил из игры; меню теперь проверяется (M26)
Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d3aa044cbe
commit
53187a7cdd
@ -51,7 +51,12 @@
|
|||||||
переменной `TILE2D_PROJECT`.
|
переменной `TILE2D_PROJECT`.
|
||||||
- Самопроверка: `squad_proto.exe --accept` печатает PASS/FAIL по всем измеримым критериям.
|
- Самопроверка: `squad_proto.exe --accept` печатает PASS/FAIL по всем измеримым критериям.
|
||||||
Содержимое проверяется отдельно: `tile2d_editor.exe --check` (комнаты и покрытие глубин),
|
Содержимое проверяется отдельно: `tile2d_editor.exe --check` (комнаты и покрытие глубин),
|
||||||
`squad_proto.exe --descend 8` (спуск, эвакуация, круговой прогон профиля).
|
`squad_proto.exe --descend 8` (спуск, эвакуация, круговой прогон профиля),
|
||||||
|
`squad_proto.exe --ui-check` (пункты меню: каждый куда-то ведёт).
|
||||||
|
- **У кнопки тоже есть код возврата.** Пункт UPGRADES четыре вехи закрывал игру
|
||||||
|
вместо открытия экрана: в обработчике стоял `default: quit`, а новый пункт
|
||||||
|
добавили четвёртым. Все прогоны меряли симуляцию, меню не проверял никто —
|
||||||
|
теперь проверяет `--ui-check`, и новый экран обязан появиться в нём.
|
||||||
- **Унести можно только то, что на ВЫЖИВШИХ.** Павший теряет своё, вайп теряет всё,
|
- **Унести можно только то, что на ВЫЖИВШИХ.** Павший теряет своё, вайп теряет всё,
|
||||||
достигнутая глубина не теряется никогда ([17-extraction.md](docs/17-extraction.md)).
|
достигнутая глубина не теряется никогда ([17-extraction.md](docs/17-extraction.md)).
|
||||||
- **`NUM_AGENTS` — это вместимость, а не размер отряда.** Сколько слотов занято,
|
- **`NUM_AGENTS` — это вместимость, а не размер отряда.** Сколько слотов занято,
|
||||||
|
|||||||
@ -110,6 +110,7 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64
|
|||||||
| Аргумент | Что делает |
|
| Аргумент | Что делает |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--accept` | прогоняет сценарии критериев приёмки (спек 11) и печатает PASS/FAIL |
|
| `--accept` | прогоняет сценарии критериев приёмки (спек 11) и печатает PASS/FAIL |
|
||||||
|
| `--ui-check` | пункты меню без окна: каждый ведёт куда-то, выходит только QUIT |
|
||||||
| `--headless <сек>` | прогон по маршруту без окна + метрики каждые 10 с |
|
| `--headless <сек>` | прогон по маршруту без окна + метрики каждые 10 с |
|
||||||
| `--probe` | к `--headless`: подробности по каждому агенту на стоянках |
|
| `--probe` | к `--headless`: подробности по каждому агенту на стоянках |
|
||||||
| `--no-solver` | отключить решатель огневых позиций (A/B сравнение) |
|
| `--no-solver` | отключить решатель огневых позиций (A/B сравнение) |
|
||||||
@ -142,6 +143,7 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
squad_proto.exe --accept
|
squad_proto.exe --accept
|
||||||
|
squad_proto.exe --ui-check
|
||||||
squad_proto.exe --headless 300
|
squad_proto.exe --headless 300
|
||||||
squad_proto.exe --headless 300 --no-solver --no-laneavoid # каким прототип был бы без ядра
|
squad_proto.exe --headless 300 --no-solver --no-laneavoid # каким прототип был бы без ядра
|
||||||
squad_proto.exe --headless 300 --melee 0 # чистый отряд стрелков
|
squad_proto.exe --headless 300 --melee 0 # чистый отряд стрелков
|
||||||
|
|||||||
@ -40,6 +40,8 @@
|
|||||||
|
|
||||||
| **M25** | редкости и аффиксы: экземпляр предмета вместо вида (`ItemInstance`), четыре редкости, десять тегов, бюджет и тир по глубине; выкатанное ложится в те же четыре числа бойца и в стихии оружия; инструменты `--tool drops` и `--tool affix` | замеры поймали ТРИ расхождения дизайн-документа с самим собой: формула редкости противоречила своей же таблице (2.7% против 22% на первом этаже), бюджет пускал восемь высших аффиксов вместо обещанных шести, а предельная вещь собиралась к семидесятому этажу вместо сотого — числа пересчитаны, документ исправлен; проверка «доезжает до бойца» показала урон 12→18, здоровье 100→140, броню 0.10→0.25, конверсию KIN 1.00→0.60 при сумме долей ровно 1.000; `--accept` PASS без изменений ([22-loot.md](22-loot.md)) |
|
| **M25** | редкости и аффиксы: экземпляр предмета вместо вида (`ItemInstance`), четыре редкости, десять тегов, бюджет и тир по глубине; выкатанное ложится в те же четыре числа бойца и в стихии оружия; инструменты `--tool drops` и `--tool affix` | замеры поймали ТРИ расхождения дизайн-документа с самим собой: формула редкости противоречила своей же таблице (2.7% против 22% на первом этаже), бюджет пускал восемь высших аффиксов вместо обещанных шести, а предельная вещь собиралась к семидесятому этажу вместо сотого — числа пересчитаны, документ исправлен; проверка «доезжает до бойца» показала урон 12→18, здоровье 100→140, броню 0.10→0.25, конверсию KIN 1.00→0.60 при сумме долей ровно 1.000; `--accept` PASS без изменений ([22-loot.md](22-loot.md)) |
|
||||||
|
|
||||||
|
| **M26** | `--ui-check`: пункты меню проверяются кодом возврата, как всё остальное | нашлось по жалобе «игра крашится при открытии улучшений»: пункт UPGRADES не крашил, а ВЫХОДИЛ из игры — в обработчике стоял `default: quit`, а экран улучшений добавили четвёртым пунктом, и обработчик о нём не узнал; проверка сперва воспроизведена на сломанной сборке (код 1, «пункт UPGRADES не делает ничего») и только потом объявлена рабочей ([01-build-and-run.md](01-build-and-run.md)) |
|
||||||
|
|
||||||
## Что находилось по ходу и чинилось
|
## Что находилось по ходу и чинилось
|
||||||
|
|
||||||
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
||||||
|
|||||||
@ -5,8 +5,10 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "ui/menu.h"
|
||||||
|
|
||||||
// Перевод экранных осей ввода в мировые — единственное место,
|
// Перевод экранных осей ввода в мировые — единственное место,
|
||||||
// где симуляционный слой касается изометрии (см. docs/02-architecture.md).
|
// где симуляционный слой касается изометрии (см. docs/02-architecture.md).
|
||||||
@ -1557,3 +1559,90 @@ int RunDescend(const RoomLibrary& rooms, const EntityCatalog& catalog, uint32_t
|
|||||||
std::printf("\nитог: этажей %d, опыта %.0f, ошибок %d\n", floorsDone, double(xpEarned), bad);
|
std::printf("\nитог: этажей %d, опыта %.0f, ошибок %d\n", floorsDone, double(xpEarned), bad);
|
||||||
return bad == 0 ? 0 : 1;
|
return bad == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// --ui-check: меню без окна.
|
||||||
|
//
|
||||||
|
// Проверяет ровно одно утверждение, зато для каждого пункта: НАЖАТИЕ ВЕДЁТ
|
||||||
|
// КУДА-ТО. Пункт, который не меняет ни экран, ни состояние игры, — это
|
||||||
|
// неработающая кнопка, и выглядит она в точности как работающая.
|
||||||
|
//
|
||||||
|
// Проверка появилась после конкретной поломки: пункт UPGRADES четыре вехи
|
||||||
|
// закрывал игру. В обработчике стоял `default: quit`, экран улучшений добавили
|
||||||
|
// четвёртым пунктом, и обработчик о нём не узнал. Для игрока это выглядело как
|
||||||
|
// падение при открытии улучшений — то есть как ошибка совсем другого рода.
|
||||||
|
//
|
||||||
|
// Раньше поймать это было нечем: все прогоны в проекте меряют СИМУЛЯЦИЮ, а
|
||||||
|
// меню не проверял никто. Отсюда и правило: у поверхности, по которой игрок
|
||||||
|
// щёлкает, тоже должен быть код возврата.
|
||||||
|
//
|
||||||
|
// Окно не открывается: UiActivate ничего из raylib не трогает, он только
|
||||||
|
// переключает экраны и зовёт методы игры.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
int RunUiCheck()
|
||||||
|
{
|
||||||
|
Profile profile;
|
||||||
|
profile.ResetLoadouts();
|
||||||
|
profile.bankedXp = 100000.0f; // чтобы покупки на экранах реально проходили
|
||||||
|
|
||||||
|
Game game;
|
||||||
|
game.profile = &profile;
|
||||||
|
game.Init();
|
||||||
|
|
||||||
|
int bad = 0;
|
||||||
|
const int count = UiMenuItemCount();
|
||||||
|
|
||||||
|
std::printf("%-4s %-12s %-10s %-10s %s\n", "N", "ПУНКТ", "ЭКРАН ДО", "ПОСЛЕ", "ИТОГ");
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
Ui ui;
|
||||||
|
ui.started = true; // «продолжить» имеет смысл только в начатом забеге
|
||||||
|
const Screen before = ui.screen;
|
||||||
|
|
||||||
|
UiActivate(ui, game, UiPart::MENU_ITEM, i);
|
||||||
|
|
||||||
|
const char* name = UiMenuItemName(i);
|
||||||
|
const bool isQuit = std::strcmp(name, "QUIT") == 0;
|
||||||
|
const bool moved = ui.screen != before;
|
||||||
|
const bool ok = isQuit ? (ui.quit && !moved) : (moved && !ui.quit);
|
||||||
|
|
||||||
|
std::printf("%-4d %-12s %-10d %-10d %s\n", i, name, int(before), int(ui.screen),
|
||||||
|
ok ? "ok" : "ПОЛОМКА");
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
if (ui.quit && !isQuit)
|
||||||
|
std::printf(" [FAIL] пункт `%s` выходит из игры\n", name);
|
||||||
|
else if (!moved)
|
||||||
|
std::printf(" [FAIL] пункт `%s` не делает ничего\n", name);
|
||||||
|
else
|
||||||
|
std::printf(" [FAIL] пункт `%s` ведёт себя не как QUIT\n", name);
|
||||||
|
++bad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экран улучшений — тот самый, из-за которого проверка и появилась.
|
||||||
|
// Мало открыть его: покупка обязана работать с той же кнопки.
|
||||||
|
{
|
||||||
|
Ui ui;
|
||||||
|
const int sizeBefore = profile.squadSize;
|
||||||
|
UiActivate(ui, game, UiPart::UPGRADE_ITEM, int(Upgrade::COUNT)); // строка найма
|
||||||
|
const bool hired = profile.squadSize > sizeBefore;
|
||||||
|
|
||||||
|
const int lvlBefore = profile.UpgradeLevel(Upgrade::AMMO);
|
||||||
|
UiActivate(ui, game, UiPart::UPGRADE_ITEM, int(Upgrade::AMMO));
|
||||||
|
const bool bought = profile.UpgradeLevel(Upgrade::AMMO) > lvlBefore;
|
||||||
|
|
||||||
|
std::printf("\nулучшения: найм %s, покупка %s\n", hired ? "работает" : "НЕ работает",
|
||||||
|
bought ? "работает" : "НЕ работает");
|
||||||
|
if (!hired || !bought)
|
||||||
|
{
|
||||||
|
std::printf(" [FAIL] кнопки экрана улучшений не покупают\n");
|
||||||
|
++bad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("\nитог: пунктов %d, поломок %d\n", count, bad);
|
||||||
|
return bad == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -32,3 +32,8 @@ struct HarnessOptions
|
|||||||
|
|
||||||
int RunHeadless(const HarnessOptions& opt);
|
int RunHeadless(const HarnessOptions& opt);
|
||||||
int RunAccept();
|
int RunAccept();
|
||||||
|
|
||||||
|
// --ui-check: пункты меню без окна. Проверяет, что каждый пункт КУДА-ТО ведёт и
|
||||||
|
// что из игры выходит ровно один — QUIT. Появилась после того, как UPGRADES
|
||||||
|
// четыре вехи молча закрывал игру (ui/menu.h).
|
||||||
|
int RunUiCheck();
|
||||||
|
|||||||
@ -123,6 +123,7 @@ int main(int argc, char** argv)
|
|||||||
int shotFrame = -1;
|
int shotFrame = -1;
|
||||||
const char* shotFile = "shot.png";
|
const char* shotFile = "shot.png";
|
||||||
bool accept = false;
|
bool accept = false;
|
||||||
|
bool uiCheck = false;
|
||||||
bool clean = false; // --clean: стартовать без оверлея (для скриншотов)
|
bool clean = false; // --clean: стартовать без оверлея (для скриншотов)
|
||||||
bool sfTiles = false; // --sf-tiles: пол и стены из библиотеки SpriteForge
|
bool sfTiles = false; // --sf-tiles: пол и стены из библиотеки SpriteForge
|
||||||
bool sfEnemies = false; // --sf-enemies: анимированные твари оттуда же
|
bool sfEnemies = false; // --sf-enemies: анимированные твари оттуда же
|
||||||
@ -205,6 +206,7 @@ int main(int argc, char** argv)
|
|||||||
if (i + 1 < argc && argv[i + 1][0] != '-') projectArg = argv[++i];
|
if (i + 1 < argc && argv[i + 1][0] != '-') projectArg = argv[++i];
|
||||||
}
|
}
|
||||||
else if (std::strcmp(argv[i], "--accept") == 0) accept = true;
|
else if (std::strcmp(argv[i], "--accept") == 0) accept = true;
|
||||||
|
else if (std::strcmp(argv[i], "--ui-check") == 0) uiCheck = true;
|
||||||
else if (std::strcmp(argv[i], "--probe") == 0) harness.probe = true;
|
else if (std::strcmp(argv[i], "--probe") == 0) harness.probe = true;
|
||||||
else if (std::strcmp(argv[i], "--no-solver") == 0) harness.solver = false;
|
else if (std::strcmp(argv[i], "--no-solver") == 0) harness.solver = false;
|
||||||
else if (std::strcmp(argv[i], "--no-laneavoid") == 0) harness.laneAvoid = false;
|
else if (std::strcmp(argv[i], "--no-laneavoid") == 0) harness.laneAvoid = false;
|
||||||
@ -288,6 +290,7 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
if (sfReport) return RunSpriteReport();
|
if (sfReport) return RunSpriteReport();
|
||||||
if (accept) return RunAccept();
|
if (accept) return RunAccept();
|
||||||
|
if (uiCheck) return RunUiCheck();
|
||||||
if (harness.seconds > 0.0f) return RunHeadless(harness);
|
if (harness.seconds > 0.0f) return RunHeadless(harness);
|
||||||
if (descendFloors > 0)
|
if (descendFloors > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -681,11 +681,15 @@ void ApplyDrop(Ui& ui, Game& game, UiPart to, int toIndex)
|
|||||||
EquipWeapon(game.world, a, inv->weapon);
|
EquipWeapon(game.world, a, inv->weapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Действия кнопок. Один вход и для клика, и для ENTER: иначе мышь и клавиатура
|
// Действия кнопок. Один вход и для клика, и для ENTER: иначе мышь и клавиатура
|
||||||
// начнут делать «почти одно и то же».
|
// начнут делать «почти одно и то же».
|
||||||
|
//
|
||||||
|
// Не в анонимном пространстве: этот же вход дёргает `--ui-check` (ui/menu.h).
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
void Activate(Ui& ui, Game& game, UiPart part, int index)
|
void UiActivate(Ui& ui, Game& game, UiPart part, int index)
|
||||||
{
|
{
|
||||||
switch (part)
|
switch (part)
|
||||||
{
|
{
|
||||||
@ -709,7 +713,14 @@ void Activate(Ui& ui, Game& game, UiPart part, int index)
|
|||||||
ui.screen = Screen::DEPTH;
|
ui.screen = Screen::DEPTH;
|
||||||
break;
|
break;
|
||||||
case 2: ui.screen = Screen::SQUAD; break;
|
case 2: ui.screen = Screen::SQUAD; break;
|
||||||
default: ui.quit = true; break;
|
case 3: ui.screen = Screen::UPGRADE; break;
|
||||||
|
// QUIT — последний пункт, и попасть сюда можно только им.
|
||||||
|
// Раньше здесь стоял default, и любой НЕ перечисленный пункт
|
||||||
|
// выходил из игры: экран улучшений появился четвёртым, а
|
||||||
|
// обработчик о нём не узнал — нажатие UPGRADES закрывало игру,
|
||||||
|
// и снаружи это выглядело как падение.
|
||||||
|
case 4: ui.quit = true; break;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -749,6 +760,9 @@ void Activate(Ui& ui, Game& game, UiPart part, int index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
bool Pressed(int key) { return IsKeyPressed(key); }
|
bool Pressed(int key) { return IsKeyPressed(key); }
|
||||||
|
|
||||||
// Все списки листаются одинаково: стрелки и WS.
|
// Все списки листаются одинаково: стрелки и WS.
|
||||||
@ -762,6 +776,13 @@ int Step()
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
int UiMenuItemCount() { return MENU_COUNT; }
|
||||||
|
|
||||||
|
const char* UiMenuItemName(int index)
|
||||||
|
{
|
||||||
|
return (index >= 0 && index < MENU_COUNT) ? MENU_ITEMS[index] : "?";
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Ввод
|
// Ввод
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -833,34 +854,34 @@ bool Ui::Update(Game& game, const ScreenRect& fit, float dt)
|
|||||||
if (menuIndex == 0 && !started && d >= 0) menuIndex = 1;
|
if (menuIndex == 0 && !started && d >= 0) menuIndex = 1;
|
||||||
|
|
||||||
if (lmbDown && hot && hot->enabled)
|
if (lmbDown && hot && hot->enabled)
|
||||||
Activate(*this, game, hot->part, hot->index);
|
UiActivate(*this, game, hot->part, hot->index);
|
||||||
else if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_KP_ENTER))
|
else if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_KP_ENTER))
|
||||||
Activate(*this, game, UiPart::MENU_ITEM, menuIndex);
|
UiActivate(*this, game, UiPart::MENU_ITEM, menuIndex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Screen::PAUSE:
|
case Screen::PAUSE:
|
||||||
if (lmbDown && hot) { Activate(*this, game, hot->part, hot->index); return true; }
|
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
|
||||||
if (Pressed(KEY_ESCAPE)) { screen = Screen::GAME; return true; }
|
if (Pressed(KEY_ESCAPE)) { screen = Screen::GAME; return true; }
|
||||||
if (Pressed(KEY_I) || Pressed(KEY_TAB)) { screen = Screen::SQUAD; return true; }
|
if (Pressed(KEY_I) || Pressed(KEY_TAB)) { screen = Screen::SQUAD; return true; }
|
||||||
if (Pressed(KEY_Q)) { screen = Screen::MENU; return true; }
|
if (Pressed(KEY_Q)) { screen = Screen::MENU; return true; }
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Screen::DEFEAT:
|
case Screen::DEFEAT:
|
||||||
if (lmbDown && hot) { Activate(*this, game, hot->part, hot->index); return true; }
|
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
|
||||||
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
|
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
|
||||||
Activate(*this, game, UiPart::DEFEAT_ITEM, 0);
|
UiActivate(*this, game, UiPart::DEFEAT_ITEM, 0);
|
||||||
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
|
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Screen::SUMMARY:
|
case Screen::SUMMARY:
|
||||||
if (lmbDown && hot) { Activate(*this, game, hot->part, hot->index); return true; }
|
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
|
||||||
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_ESCAPE))
|
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_ESCAPE))
|
||||||
Activate(*this, game, UiPart::SUMMARY_ITEM, 0);
|
UiActivate(*this, game, UiPart::SUMMARY_ITEM, 0);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Screen::UPGRADE:
|
case Screen::UPGRADE:
|
||||||
if (lmbDown && hot) Activate(*this, game, hot->part, hot->index);
|
if (lmbDown && hot) UiActivate(*this, game, hot->part, hot->index);
|
||||||
if (Pressed(KEY_ESCAPE) || Pressed(KEY_U)) screen = Screen::MENU;
|
if (Pressed(KEY_ESCAPE) || Pressed(KEY_U)) screen = Screen::MENU;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -874,11 +895,11 @@ bool Ui::Update(Game& game, const ScreenRect& fit, float dt)
|
|||||||
if (d) depthIndex = (depthIndex + d + shown) % shown;
|
if (d) depthIndex = (depthIndex + d + shown) % shown;
|
||||||
if (depthIndex < 0) depthIndex = 0;
|
if (depthIndex < 0) depthIndex = 0;
|
||||||
|
|
||||||
if (lmbDown && hot) { Activate(*this, game, hot->part, hot->index); return true; }
|
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
|
||||||
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
|
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
|
||||||
{
|
{
|
||||||
const Widget& pick = w.item[depthIndex];
|
const Widget& pick = w.item[depthIndex];
|
||||||
Activate(*this, game, pick.part, pick.index);
|
UiActivate(*this, game, pick.part, pick.index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
|
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
|
||||||
|
|||||||
@ -107,3 +107,22 @@ struct Ui
|
|||||||
// Поверх готового кадра. fit — куда лёг фреймбуфер в окне.
|
// Поверх готового кадра. fit — куда лёг фреймбуфер в окне.
|
||||||
void Draw(const Game& game, const ScreenRect& fit) const;
|
void Draw(const Game& game, const ScreenRect& fit) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Нажать пункт так, как будто по нему кликнули. Наружу вынесено ради ОДНОЙ
|
||||||
|
// проверки — `--ui-check`, — и вынесено не для полноты API.
|
||||||
|
//
|
||||||
|
// Причина конкретная: пункт меню UPGRADES четыре вехи выходил из игры. В
|
||||||
|
// обработчике стоял `default: quit`, экран улучшений появился четвёртым
|
||||||
|
// пунктом, и обработчик о нём просто не узнал. Снаружи это выглядело как
|
||||||
|
// падение игры при открытии улучшений.
|
||||||
|
//
|
||||||
|
// Ни один прогон этого не ловил, потому что все проверки в проекте меряют
|
||||||
|
// СИМУЛЯЦИЮ, а меню не проверял никто. Теперь его щупают так же, как всё
|
||||||
|
// остальное: код возврата, а не глаз.
|
||||||
|
void UiActivate(Ui& ui, Game& game, UiPart part, int index);
|
||||||
|
|
||||||
|
// Сколько пунктов в главном меню и как называется i-й. Нужны той же проверке:
|
||||||
|
// она обязана пройти по ВСЕМ пунктам, а не по тем, о которых знает.
|
||||||
|
int UiMenuItemCount();
|
||||||
|
const char* UiMenuItemName(int index);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user