From c49b4bbd50625540f528863cb8d6d07f2ad849af Mon Sep 17 00:00:00 2001 From: Filip Gawin Date: Wed, 26 Dec 2018 21:48:16 +0100 Subject: [PATCH] Further refactor of menu managment --- rwgame/MenuSystem.hpp | 22 +++--- rwgame/State.cpp | 32 ++++++++- rwgame/State.hpp | 37 ++++------ rwgame/states/DebugState.cpp | 136 ++++++++++++++++++----------------- rwgame/states/DebugState.hpp | 14 ++-- rwgame/states/MenuState.cpp | 17 +++-- rwgame/states/PauseState.cpp | 15 ++-- tests/test_Menu.cpp | 46 ++++++------ 8 files changed, 166 insertions(+), 153 deletions(-) diff --git a/rwgame/MenuSystem.hpp b/rwgame/MenuSystem.hpp index cb9330a6..dbcab907 100644 --- a/rwgame/MenuSystem.hpp +++ b/rwgame/MenuSystem.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -71,18 +72,14 @@ public: } }; - Menu(std::vector initial, int font = MenuDefaults::kFont, + Menu(std::initializer_list&& initial, + const glm::vec2& offset = glm::vec2(), int font = MenuDefaults::kFont, float size = 30.f) - : activeEntry(-1), font(font), size(size), entries(std::move(initial)) { - } - - /** - * @brief creates a menu from the given menu items - * @return optional of menu with the items - */ - static std::optional create(std::vector items, - int font = MenuDefaults::kFont, float size = 30.f) { - return std::make_optional(std::move(items), font, size); + : activeEntry(-1) + , offset(offset) + , font(font) + , size(size) + , entries(initial) { } Menu& lambda(const GameString& n, std::function callback) { @@ -100,8 +97,6 @@ public: */ int activeEntry; - glm::vec2 offset{}; - void draw(GameRenderer* r) { glm::vec2 basis(offset); for (size_t i = 0; i < entries.size(); ++i) { @@ -159,6 +154,7 @@ public: } private: + glm::vec2 offset{}; int font; float size; std::vector entries; diff --git a/rwgame/State.cpp b/rwgame/State.cpp index 4b1bd636..d7fc8604 100644 --- a/rwgame/State.cpp +++ b/rwgame/State.cpp @@ -6,8 +6,23 @@ const ViewCamera defaultView{{-250.f, -550.f, 75.f}, glm::angleAxis(glm::radians(5.f), glm::vec3(0.f, 1.f, 0.f))}; +State::State(RWGame *game) : game(game) { +} + +void State::draw(GameRenderer *r) { + auto& menu = getCurrentMenu(); + if (menu) { + menu->draw(r); + } +} + +std::optional &State::getCurrentMenu() { + refreshMenu(); + return menu; +} + void State::handleEvent(const SDL_Event& e) { - auto m = getCurrentMenu(); + auto& m = getCurrentMenu(); if (!m) return; switch (e.type) { @@ -52,3 +67,18 @@ GameWorld* State::getWorld() const { GameWindow& State::getWindow() { return game->getWindow(); } + +bool State::hasExited() const { + return hasExited_; +} + +void State::refreshMenu() { + if (nextMenu) { + menu = std::move(nextMenu); + nextMenu = std::nullopt; + } +} + +void State::done() { + hasExited_ = true; +} diff --git a/rwgame/State.hpp b/rwgame/State.hpp index 45d4a9f5..7c8d78d9 100644 --- a/rwgame/State.hpp +++ b/rwgame/State.hpp @@ -16,39 +16,25 @@ class StateManager; class State { public: - std::optional menu; - std::optional nextMenu; - RWGame* game; - State(RWGame* game) : game(game) { - } + State(RWGame* game); virtual void enter() = 0; virtual void exit() = 0; virtual void tick(float dt) = 0; - virtual void draw(GameRenderer* r) { - if (getCurrentMenu()) { - getCurrentMenu()->draw(r); - } - } + virtual void draw(GameRenderer* r); virtual ~State() = default; template - void enterMenu(T&& menu) { + void setNextMenu(T&& menu) { nextMenu = std::forward(menu); } - Menu* getCurrentMenu() { - if (nextMenu) { - menu = std::move(nextMenu); - nextMenu = std::nullopt; - } - return &*menu; - } + std::optional& getCurrentMenu(); virtual void handleEvent(const SDL_Event& e); @@ -63,17 +49,18 @@ public: GameWorld* getWorld() const; GameWindow& getWindow(); - bool hasExited() const { - return hasexited_; - } + bool hasExited() const; private: - bool hasexited_ = false; + bool hasExited_ = false; + + void refreshMenu(); protected: - void done() { - hasexited_ = true; - } + std::optional menu; + std::optional nextMenu; + + void done(); }; #endif diff --git a/rwgame/states/DebugState.cpp b/rwgame/states/DebugState.cpp index 2e49b832..6d75641e 100644 --- a/rwgame/states/DebugState.cpp +++ b/rwgame/states/DebugState.cpp @@ -33,13 +33,13 @@ static void jumpCharacter(RWGame* game, CharacterObject* player, } } -std::optional DebugState::createDebugMenu() { +Menu DebugState::createDebugMenu() { CharacterObject* player = nullptr; if (game->getWorld()->getPlayer()) { player = game->getWorld()->getPlayer()->getCharacter(); } - auto menu = Menu::create( + Menu menu{ {{"Jump to Debug Camera", [=] { jumpCharacter(game, player, @@ -47,12 +47,12 @@ std::optional DebugState::createDebugMenu() { _debugCam.rotation * glm::vec3(3.f, 0.f, 0.f), false); }}, - {"-Map", [=] { this->enterMenu(createMapMenu()); }}, - {"-Vehicles", [=] { this->enterMenu(createVehicleMenu()); }}, - {"-AI", [=] { this->enterMenu(createAIMenu()); }}, - {"-Weapons", [=] { this->enterMenu(createWeaponMenu()); }}, - {"-Weather", [=] { this->enterMenu(createWeatherMenu()); }}, - {"-Missions", [=] { this->enterMenu(createMissionsMenu()); }}, + {"-Map", [=] { setNextMenu(createMapMenu()); }}, + {"-Vehicles", [=] { setNextMenu(createVehicleMenu()); }}, + {"-AI", [=] { setNextMenu(createAIMenu()); }}, + {"-Weapons", [=] { setNextMenu(createWeaponMenu()); }}, + {"-Weather", [=] { setNextMenu(createWeatherMenu()); }}, + {"-Missions", [=] { setNextMenu(createMissionsMenu()); }}, {"Set Super Jump", [=] { player->setJumpSpeed(20.f); }}, {"Set Normal Jump", [=] { player->setJumpSpeed(CharacterObject::DefaultJumpSpeed); }}, @@ -60,14 +60,14 @@ std::optional DebugState::createDebugMenu() { {"Full Armour", [=] { player->getCurrentState().armour = 100.f; }}, {"Cull Here", [=] { game->getRenderer().setCullOverride(true, _debugCam); }}}, - kDebugFont, kDebugEntryHeight); - - menu->offset = kDebugMenuOffset; + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight}; // Optional block if the player is in a vehicle auto cv = player->getCurrentVehicle(); if (cv) { - menu->lambda("Flip vehicle", [=] { + menu.lambda("Flip vehicle", [=] { cv->setRotation(cv->getRotation() * glm::quat(glm::vec3(0.f, glm::pi(), 0.f))); }); @@ -76,14 +76,14 @@ std::optional DebugState::createDebugMenu() { return menu; } -std::optional DebugState::createMapMenu() { +Menu DebugState::createMapMenu() { CharacterObject* player = nullptr; if (game->getWorld()->getPlayer()) { player = game->getWorld()->getPlayer()->getCharacter(); } - auto menu = Menu::create( - {{"Back", [=] { enterMenu(createDebugMenu()); }}, + Menu menu{ + {{"Back", [=] { setNextMenu(createDebugMenu()); }}, {"Jump to Docks", [=] { jumpCharacter(game, player, glm::vec3(1390.f, -837.f, 100.f)); @@ -108,24 +108,24 @@ std::optional DebugState::createMapMenu() { }}, {"Unsolid garage doors", [=] { - static constexpr std::array garageDoorModels{{ - "8ballsuburbandoor", "amcogaragedoor", - "bankjobdoor", "bombdoor", - "crushercrush", "crushertop", - "door2_garage", "door3_garage", - "door4_garage", "door_bombshop", - "door_col_compnd_01", "door_col_compnd_02", - "door_col_compnd_03", "door_col_compnd_04", - "door_col_compnd_05", "door_jmsgrage", - "door_sfehousegrge", "double_garage_dr", - "impex_door", "impexpsubgrgdoor", - "ind_plyrwoor", "ind_slidedoor", - "jamesgrge_kb", "leveldoor2", - "oddjgaragdoor", "plysve_gragedoor", - "SalvGarage", "shedgaragedoor", - "Sub_sprayshopdoor", "towergaragedoor1", - "towergaragedoor2", "towergaragedoor3", - "vheistlocdoor"}}; + static constexpr std::array garageDoorModels{ + {"8ballsuburbandoor", "amcogaragedoor", + "bankjobdoor", "bombdoor", + "crushercrush", "crushertop", + "door2_garage", "door3_garage", + "door4_garage", "door_bombshop", + "door_col_compnd_01", "door_col_compnd_02", + "door_col_compnd_03", "door_col_compnd_04", + "door_col_compnd_05", "door_jmsgrage", + "door_sfehousegrge", "double_garage_dr", + "impex_door", "impexpsubgrgdoor", + "ind_plyrwoor", "ind_slidedoor", + "jamesgrge_kb", "leveldoor2", + "oddjgaragdoor", "plysve_gragedoor", + "SalvGarage", "shedgaragedoor", + "Sub_sprayshopdoor", "towergaragedoor1", + "towergaragedoor2", "towergaragedoor3", + "vheistlocdoor"}}; auto gw = game->getWorld(); for (auto& [id, instancePtr] : gw->instancePool.objects) { @@ -138,15 +138,20 @@ std::optional DebugState::createMapMenu() { } } }}}, - kDebugFont, kDebugEntryHeight); + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight}; - menu->offset = kDebugMenuOffset; return menu; } -std::optional DebugState::createVehicleMenu() { - auto menu = Menu::create({{"Back", [=] { enterMenu(createDebugMenu()); }}}, - kDebugFont, kDebugEntryHeight); +Menu DebugState::createVehicleMenu() { + Menu menu{ + {{"Back", [=] { setNextMenu(createDebugMenu()); }}}, + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight, + }; static constexpr std::array, 19> kVehicleTypes{{{"Landstalker", 90}, @@ -170,17 +175,17 @@ std::optional DebugState::createVehicleMenu() { {"Infernus", 101}}}; for (const auto& [name, id] : kVehicleTypes) { - menu->lambda(name, [this, id = id] { spawnVehicle(id); }); + menu.lambda(name, [this, id = id] { spawnVehicle(id); }); } - menu->offset = kDebugMenuOffset; return menu; } -std::optional DebugState::createAIMenu() { - auto menu = - Menu::create({{"Back", [=] { this->enterMenu(createDebugMenu()); }}}, - kDebugFont, kDebugEntryHeight); +Menu DebugState::createAIMenu() { + Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight}; static constexpr std::array, 6> kPedTypes{{ @@ -193,10 +198,10 @@ std::optional DebugState::createAIMenu() { }}; for (const auto& [name, id] : kPedTypes) { - menu->lambda(name, [this, id = id] { spawnFollower(id); }); + menu.lambda(name, [this, id = id] { spawnFollower(id); }); } - menu->lambda("Kill All Peds", [=] { + menu.lambda("Kill All Peds", [=] { for (auto& [id, pedestrianPtr] : game->getWorld()->pedestrianPool.objects) { if (pedestrianPtr->getLifetime() == GameObject::PlayerLifetime) { @@ -208,44 +213,44 @@ std::optional DebugState::createAIMenu() { } }); - menu->offset = kDebugMenuOffset; return menu; } -std::optional DebugState::createWeaponMenu() { - auto menu = - Menu::create({{"Back", [=] { this->enterMenu(createDebugMenu()); }}}, - kDebugFont, kDebugEntryHeight); +Menu DebugState::createWeaponMenu() { + Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight}; for (int i = 1; i < kMaxInventorySlots; ++i) { auto& name = getWorld()->data->weaponData[i].name; - menu->lambda(name, [=] { giveItem(i); }); + menu.lambda(name, [=] { giveItem(i); }); } - menu->offset = kDebugMenuOffset; return menu; } -std::optional DebugState::createWeatherMenu() { - auto menu = - Menu::create({{"Back", [=] { this->enterMenu(createDebugMenu()); }}}, - kDebugFont, kDebugEntryHeight); +Menu DebugState::createWeatherMenu() { + Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeight}; static constexpr std::array w{{"Sunny", "Cloudy", "Rainy", "Foggy"}}; for (std::size_t i = 0; i < w.size(); ++i) { - menu->lambda(w[i], + menu.lambda(w[i], [=] { game->getWorld()->state->basic.nextWeather = static_cast(i); }); } - menu->offset = kDebugMenuOffset; return menu; } -std::optional DebugState::createMissionsMenu() { - auto menu = - Menu::create({{"Back", [=] { this->enterMenu(createDebugMenu()); }}}, - kDebugFont, kDebugEntryHeightMissions); +Menu DebugState::createMissionsMenu() { + Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, + kDebugMenuOffset, + kDebugFont, + kDebugEntryHeightMissions}; static constexpr std::array w{{ "Intro Movie", @@ -331,7 +336,7 @@ std::optional DebugState::createMissionsMenu() { }}; for (std::size_t i = 0; i < w.size(); ++i) { - menu->lambda(w[i], [=] { + menu.lambda(w[i], [=] { ScriptMachine* vm = game->getScriptVM(); if (vm) { @@ -356,13 +361,12 @@ std::optional DebugState::createMissionsMenu() { }); } - menu->offset = kDebugMenuOffset; return menu; } DebugState::DebugState(RWGame* game, const glm::vec3& vp, const glm::quat& vd) : State(game), _invertedY(game->getConfig().getInputInvertY()) { - this->enterMenu(createDebugMenu()); + this->setNextMenu(createDebugMenu()); _debugCam.position = vp; _debugCam.rotation = vd; diff --git a/rwgame/states/DebugState.hpp b/rwgame/states/DebugState.hpp index 87b7d1a4..781f09f0 100644 --- a/rwgame/states/DebugState.hpp +++ b/rwgame/states/DebugState.hpp @@ -11,13 +11,13 @@ class DebugState final : public State { bool _sonicMode = false; bool _invertedY; - std::optional createDebugMenu(); - std::optional createMapMenu(); - std::optional createVehicleMenu(); - std::optional createAIMenu(); - std::optional createWeaponMenu(); - std::optional createWeatherMenu(); - std::optional createMissionsMenu(); + Menu createDebugMenu(); + Menu createMapMenu(); + Menu createVehicleMenu(); + Menu createAIMenu(); + Menu createWeaponMenu(); + Menu createWeatherMenu(); + Menu createMissionsMenu(); public: DebugState(RWGame* game, const glm::vec3& vp = {}, diff --git a/rwgame/states/MenuState.cpp b/rwgame/states/MenuState.cpp index 9f585a42..e2cbc6ba 100644 --- a/rwgame/states/MenuState.cpp +++ b/rwgame/states/MenuState.cpp @@ -13,7 +13,7 @@ MenuState::MenuState(RWGame* game) : State(game) { void MenuState::enterMainMenu() { auto& t = game->getGameData().texts; - auto menu = Menu::create( + Menu menu{ {{t.text(MenuDefaults::kStartGameId), [=] { StateManager::get().enter(game); }}, {t.text(MenuDefaults::kLoadGameId), [=] { enterLoadMenu(); }}, @@ -22,14 +22,14 @@ void MenuState::enterMainMenu() { {t.text(MenuDefaults::kOptionsId), [] { RW_UNIMPLEMENTED("Options Menu"); }}, {t.text(MenuDefaults::kQuitGameId), - [] { StateManager::get().clear(); }}}); - menu->offset = glm::vec2(200.f, 200.f); + [] { StateManager::get().clear(); }}}, + glm::vec2(200.f, 200.f)}; - enterMenu(menu); + setNextMenu(std::move(menu)); } void MenuState::enterLoadMenu() { - auto menu = Menu::create({{"BACK", [=] { enterMainMenu(); }}}); + Menu menu{{{"BACK", [=] { enterMainMenu(); }}}, glm::vec2(20.f, 30.f)}; auto saves = SaveGame::getAllSaveGameInfo(); for (SaveGameInfo& save : saves) { @@ -46,14 +46,13 @@ void MenuState::enterLoadMenu() { StateManager::get().enter(game, false); game->loadGame(save.savePath); }; - menu->lambda(name, loadsave); + menu.lambda(name, loadsave); } else { - menu->lambda("CORRUPT", [=] {}); + menu.lambda("CORRUPT", [=] {}); } } - menu->offset = glm::vec2(20.f, 30.f); - enterMenu(menu); + setNextMenu(std::move(menu)); } void MenuState::enter() { diff --git a/rwgame/states/PauseState.cpp b/rwgame/states/PauseState.cpp index ba4b2f4b..9a5bd7f4 100644 --- a/rwgame/states/PauseState.cpp +++ b/rwgame/states/PauseState.cpp @@ -6,14 +6,13 @@ PauseState::PauseState(RWGame* game) : State(game) { auto& t = game->getGameData().texts; - auto menu = Menu::create( - {{t.text(MenuDefaults::kResumeGameId), [&] { done(); }}, - {t.text(MenuDefaults::kOptionsId), - [] { std::cout << "Options" << std::endl; }}, - {t.text(MenuDefaults::kQuitGameId), - [] { StateManager::get().clear(); }}}); - menu->offset = glm::vec2(200.f, 200.f); - enterMenu(menu); + Menu menu{{{t.text(MenuDefaults::kResumeGameId), [&] { done(); }}, + {t.text(MenuDefaults::kOptionsId), + [] { std::cout << "Options" << std::endl; }}, + {t.text(MenuDefaults::kQuitGameId), + [] { StateManager::get().clear(); }}}, + glm::vec2(200.f, 200.f)}; + setNextMenu(menu); } void PauseState::enter() { diff --git a/tests/test_Menu.cpp b/tests/test_Menu.cpp index 1fb5186a..86a129dd 100644 --- a/tests/test_Menu.cpp +++ b/tests/test_Menu.cpp @@ -4,79 +4,77 @@ BOOST_AUTO_TEST_SUITE(MenuTests) BOOST_AUTO_TEST_CASE(menu_test_click) { - bool clickered = false; - Menu test({{"Test", [&] { clickered = true; }}}); + bool clicked = false; + Menu test({{"Test", [&] { clicked = true; }}}); - BOOST_CHECK(!clickered); + BOOST_CHECK(!clicked); // Click underneath the menu item. test.click(0.f, -1.f); - BOOST_CHECK(!clickered); + BOOST_CHECK(!clicked); float h = 30.f; test.click(0.f, h + 1.f); - BOOST_CHECK(!clickered); + BOOST_CHECK(!clicked); test.click(0.f, h / 2.f); - BOOST_CHECK(clickered); + BOOST_CHECK(clicked); } BOOST_AUTO_TEST_CASE(menu_test_click_offset) { - bool clickered = false; - Menu test({{"Test", [&] { clickered = true; }}}); - test.offset = glm::vec2(200.f, 200.f); - - BOOST_CHECK(!clickered); + bool clicked = false; + Menu test({{"Test", [&] { clicked = true; }}}, glm::vec2(200.f, 200.f)); + BOOST_CHECK(!clicked); // Click underneath the menu item. test.click(201.f, -1.f); - BOOST_CHECK(!clickered); + BOOST_CHECK(!clicked); float h = 30.f; test.click(201.f, 200.f + h + 1.f); - BOOST_CHECK(!clickered); + BOOST_CHECK(!clicked); test.click(201.f, 200.f + h / 2.f); - BOOST_CHECK(clickered); + BOOST_CHECK(clicked); } BOOST_AUTO_TEST_CASE(menu_test_active_index) { - int clickindex = -1; - Menu test({{"Test1", [&] { clickindex = 0; }}, - {"Test2", [&] { clickindex = 1; }}}); + int clickIndex = -1; + Menu test({{"Test1", [&] { clickIndex = 0; }}, + {"Test2", [&] { clickIndex = 1; }}}); test.activate(); - BOOST_CHECK(clickindex == -1); + BOOST_CHECK(clickIndex == -1); test.move(1); test.activate(); - BOOST_CHECK(clickindex == 0); + BOOST_CHECK(clickIndex == 0); test.move(1); test.activate(); - BOOST_CHECK(clickindex == 1); + BOOST_CHECK(clickIndex == 1); test.move(-1); test.activate(); - BOOST_CHECK(clickindex == 0); + BOOST_CHECK(clickIndex == 0); } BOOST_AUTO_TEST_CASE(menu_test_hover_index) { - int clickindex = -1; - Menu test({{"Test1", [&] { clickindex = 0; }}, - {"Test2", [&] { clickindex = 1; }}}); + int clickIndex = -1; + Menu test({{"Test1", [&] { clickIndex = 0; }}, + {"Test2", [&] { clickIndex = 1; }}}); test.hover(0.f, 30.f - 0.1f); BOOST_CHECK(test.activeEntry == 0);