From 7137ec7ddbd1b0274677f62f066fedc5cc311f44 Mon Sep 17 00:00:00 2001 From: SpaghettDev <37266659+SpaghettDev@users.noreply.github.com> Date: Sun, 24 Nov 2024 17:52:54 -0500 Subject: [PATCH] update, feat(goal): Update to Geode v4.0.1 and allow resuming of a round of roulette --- CHANGELOG.md | 12 + mod.json | 15 +- src/layers/CreatorLayer.cpp | 56 ++--- src/layers/LevelInfoLayer.cpp | 8 +- src/layers/PauseLayer.cpp | 6 +- src/layers/PlayLayer.cpp | 22 +- src/listfetcher/ListFetcher.cpp | 42 +++- src/roulette/layers/RLRouletteInfoLayer.cpp | 20 +- src/roulette/layers/RLRouletteLayer.cpp | 73 ++++-- src/roulette/manager/DataManager.hpp | 259 ++++++++++++++++++++ src/roulette/manager/GameState.hpp | 13 + src/roulette/manager/RouletteManager.hpp | 150 +++++------- src/utils.hpp | 10 +- 13 files changed, 490 insertions(+), 196 deletions(-) create mode 100644 src/roulette/manager/DataManager.hpp create mode 100644 src/roulette/manager/GameState.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a5ad3d3..57c11de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [3.0.0] - 2024-11-24 + +### Added + +- Resuming round of roulette after game shutdown +- Exclamation Mark when a round of roulette is paused + +### Changed + +- Target Geode v4.0.1 +- How target percentage is displayed: it is now always the current goal, not the current percentage + 1 + ## [2.1.0] - 2024-06-15 ### Changed diff --git a/mod.json b/mod.json index 7dd147a..e08d816 100644 --- a/mod.json +++ b/mod.json @@ -1,16 +1,15 @@ { - "geode": "3.0.0-beta.4", + "geode": "4.0.1", "gd": { - "win": "2.206", - "android": "2.206", - "mac": "2.206" + "win": "2.2074", + "android": "2.2074", + "mac": "2.2074" }, - "version": "v2.1.0", + "version": "v3.0.0", "id": "spaghettdev.gd-roulette", "name": "GD-Roulette", "developer": "SpaghettDev", "description": "An in-game level roulette.", - "repository": "https://github.com/SpaghettDev/GD-Roulette", "links": { "community": "https://discord.gg/3bShQb6Jz3", "source": "https://github.com/SpaghettDev/GD-Roulette" @@ -27,12 +26,12 @@ "dependencies": [ { "id": "geode.node-ids", - "version": ">=v1.12.0", + "version": ">=v1.17.0", "importance": "required" } ], "tags": [ - "Interface", "Online" + "interface", "online" ], "settings": { "max-skips": { diff --git a/src/layers/CreatorLayer.cpp b/src/layers/CreatorLayer.cpp index 8e8d436..c371f47 100644 --- a/src/layers/CreatorLayer.cpp +++ b/src/layers/CreatorLayer.cpp @@ -9,9 +9,11 @@ class $modify(RouletteButton, CreatorLayer) { void onRouletteButton(CCObject*) { - g_rouletteManager.rouletteLayer = RLRouletteLayer::create(); - if (g_rouletteManager.rouletteLayer) + if (g_rouletteManager.rouletteLayer = RLRouletteLayer::create(); g_rouletteManager.rouletteLayer) g_rouletteManager.rouletteLayer->show(); + + if (auto exMark = this->getChildByID("center-left-menu"_spr)->getChildByID("roulette-button"_spr)->getChildByID("exclamation-mark")) + exMark->removeFromParent(); } bool init() @@ -20,34 +22,28 @@ class $modify(RouletteButton, CreatorLayer) auto winSize = CCDirector::sharedDirector()->getWinSize(); - if(Mod::get()->getSettingValue("use-big-button") && Loader::get()->isModLoaded("alphalaneous.pages_api")) { - if(auto menu = getChildByID("creator-buttons-menu")) { - auto spr = CCSprite::create("RL_btn_big.png"_spr); - spr->setScale(0.842); - auto rouletteButton = CCMenuItemSpriteExtra::create( - spr, - this, - menu_selector(RouletteButton::onRouletteButton) - ); - rouletteButton->setID("roulette-button"_spr); - menu->addChild(rouletteButton); - } - } - else { - if(auto menu = getChildByID("exit-menu")) { - auto spr = CircleButtonSprite::createWithSprite("RL_btn_001.png"_spr, 1.0f, CircleBaseColor::Green, CircleBaseSize::Small); - - auto rouletteButton = CCMenuItemSpriteExtra::create( - spr, - this, - menu_selector(RouletteButton::onRouletteButton) - ); - rouletteButton->setID("roulette-button"_spr); - menu->addChild(rouletteButton); - typeinfo_cast(menu->getLayout())->setAxisReverse(true); - menu->updateLayout(); - } - } + auto centerLeftMenu = CCMenu::create(); + centerLeftMenu->setPosition({ + this->getChildByID("exit-menu")->getPositionX(), + CCDirector::sharedDirector()->getWinSize().height / 2.f + }); + centerLeftMenu->setID("center-left-menu"_spr); + this->addChild(centerLeftMenu); + + auto spr = CircleButtonSprite::createWithSprite("RL_btn_001.png"_spr, 1.0f, CircleBaseColor::Green, CircleBaseSize::Small); + spr->setScale(1.1f); + + auto rouletteButton = CCMenuItemSpriteExtra::create( + spr, + this, + menu_selector(RouletteButton::onRouletteButton) + ); + rouletteButton->setID("roulette-button"_spr); + rouletteButton->setLayout(AnchorLayout::create()); + centerLeftMenu->addChild(rouletteButton); + + if (g_rouletteManager.gameState.levelID != 0) + g_rouletteManager.addExclamationMark(rouletteButton); return true; } diff --git a/src/layers/LevelInfoLayer.cpp b/src/layers/LevelInfoLayer.cpp index 86cae54..811b272 100644 --- a/src/layers/LevelInfoLayer.cpp +++ b/src/layers/LevelInfoLayer.cpp @@ -11,7 +11,7 @@ class $modify(LevelInfoLayer) { if (!LevelInfoLayer::init(level, p1)) return false; - if (g_rouletteManager.isPlaying && level->m_levelID.value() == g_rouletteManager.currentLevelID) + if (g_rouletteManager.isPlaying && level->m_levelID.value() == g_rouletteManager.gameState.levelID) { CCLabelBMFont* normalPercentageLabel = static_cast(this->getChildByID("normal-mode-percentage")); @@ -20,7 +20,7 @@ class $modify(LevelInfoLayer) float goalOffset = normalPercentageLabel->getContentWidth() / 2; auto goalPercentage = CCLabelBMFont::create( - fmt::format("({}%)", g_rouletteManager.levelPercentageGoal).c_str(), + fmt::format("({}%)", g_rouletteManager.currentPercentageGoal).c_str(), "bigFont.fnt" ); goalPercentage->setPosition({ normalPercentageLabel->getPositionX() + goalOffset, normalPercentageLabel->getPositionY() }); @@ -39,11 +39,11 @@ class $modify(LevelInfoLayer) if ( g_rouletteManager.isPlaying && g_rouletteManager.rouletteLayer && - this->m_level->m_levelID.value() == g_rouletteManager.currentLevelID + this->m_level->m_levelID.value() == g_rouletteManager.gameState.levelID ) static_cast( g_rouletteManager.rouletteLayer->playing_menu->getChildByID("percentage-text") - )->setString(fmt::format("{}%", g_rouletteManager.levelPercentageGoal).c_str()); + )->setString(fmt::format("{}%", g_rouletteManager.currentPercentageGoal).c_str()); LevelInfoLayer::onBack(sender); } diff --git a/src/layers/PauseLayer.cpp b/src/layers/PauseLayer.cpp index 8b41f1b..cd30341 100644 --- a/src/layers/PauseLayer.cpp +++ b/src/layers/PauseLayer.cpp @@ -10,9 +10,9 @@ class $modify(PauseLayer) { PauseLayer::customSetup(); - const PlayLayer* playLayer = GameManager::sharedState()->getPlayLayer(); + const auto* playLayer = GameManager::sharedState()->getPlayLayer(); - if (g_rouletteManager.isPlaying && playLayer->m_level->m_levelID.value() == g_rouletteManager.currentLevelID) + if (g_rouletteManager.isPlaying && playLayer->m_level->m_levelID.value() == g_rouletteManager.gameState.levelID) { CCLabelBMFont* normalPercentageLabel = static_cast(this->getChildByID("normal-progress-label")); @@ -21,7 +21,7 @@ class $modify(PauseLayer) float goalOffset = normalPercentageLabel->getContentWidth() / 2 + 7.f; auto goalPercentage = CCLabelBMFont::create( - fmt::format("({}%)", g_rouletteManager.levelPercentageGoal).c_str(), + fmt::format("({}%)", g_rouletteManager.currentPercentageGoal).c_str(), "bigFont.fnt" ); goalPercentage->setPosition({ normalPercentageLabel->getPositionX() + goalOffset, normalPercentageLabel->getPositionY() }); diff --git a/src/layers/PlayLayer.cpp b/src/layers/PlayLayer.cpp index 471ead1..273f714 100644 --- a/src/layers/PlayLayer.cpp +++ b/src/layers/PlayLayer.cpp @@ -64,16 +64,16 @@ class $modify(PlayLayerPause, PlayLayer) #endif // GEODE_IS_MACOS if ( g_rouletteManager.isPlaying && - this->m_level->m_levelID == g_rouletteManager.currentLevelID && + this->m_level->m_levelID == g_rouletteManager.gameState.levelID && !this->m_isPracticeMode && - percentage >= g_rouletteManager.levelPercentageGoal + percentage >= g_rouletteManager.gameState.levelPercentageGoal ) { if (currentDelta > .2f/* && !this->m_player1->m_isDead*/) { - g_rouletteManager.hasFinishedPreviousLevel = true; - g_rouletteManager.currentLevelPercentage = percentage; - g_rouletteManager.levelPercentageGoal = percentage + 1; - g_rouletteManager.numLevels++; + g_rouletteManager.gameState.hasReachedGoal = true; + g_rouletteManager.gameState.levelPercentage = percentage; + g_rouletteManager.gameState.levelPercentageGoal = percentage + 1; + g_rouletteManager.gameState.numLevels++; if (Mod::get()->getSettingValue("auto-pause")) { @@ -99,13 +99,13 @@ class $modify(PlayLayerPause, PlayLayer) { if ( g_rouletteManager.isPlaying && - this->m_level->m_levelID == g_rouletteManager.currentLevelID && + this->m_level->m_levelID == g_rouletteManager.gameState.levelID && !this->m_isPracticeMode ) { - g_rouletteManager.hasFinishedPreviousLevel = true; - g_rouletteManager.currentLevelPercentage = 100; - g_rouletteManager.levelPercentageGoal = 100; - g_rouletteManager.numLevels++; + g_rouletteManager.gameState.hasReachedGoal = true; + g_rouletteManager.gameState.levelPercentage = 100; + g_rouletteManager.gameState.levelPercentageGoal = 100; + g_rouletteManager.gameState.numLevels++; } PlayLayer::levelComplete(); diff --git a/src/listfetcher/ListFetcher.cpp b/src/listfetcher/ListFetcher.cpp index 44635bf..1512b76 100644 --- a/src/listfetcher/ListFetcher.cpp +++ b/src/listfetcher/ListFetcher.cpp @@ -102,19 +102,29 @@ void ListFetcher::getRandomDemonListLevel(level_pair_t& level, std::string& erro const auto& jsonResp = resp.unwrap(); - if (jsonResp.is_null()) + if (jsonResp.isNull() || !jsonResp.isArray()) { - error = "Pointercrate API returned null. Try again later."; + error = "Pointercrate API returned null or non-array. Try again later."; is_fetching = false; return; } + const auto array = jsonResp.asArray().unwrap(); + int randomIndex; do { - randomIndex = rl::utils::randomInt(0, jsonResp.as_array().size() - 1); - } while (jsonResp[randomIndex]["level_id"].is_null()); + randomIndex = rl::utils::randomInt(0, array.size() - 1); + } while (array[randomIndex]["level_id"].isNull()); + + int levelId = array[randomIndex].template get("level_id").unwrapOr(-1); + + if (levelId == -1) + { + error = "Pointercrate API returned non-number 'level_id'. Contact developer to fix this."; + is_fetching = false; + return; + } - int levelId = jsonResp[randomIndex].template get("level_id"); getLevelInfo(levelId, level, error); } else if (e->isCancelled()) @@ -150,19 +160,29 @@ void ListFetcher::getRandomChallengeListLevel(level_pair_t& level, std::string& const auto& jsonResp = resp.unwrap(); - if (jsonResp.is_null() || !jsonResp.is_array()) + if (jsonResp.isNull() || !jsonResp.isArray()) { - error = "Challenge List API returned null. Try again later."; + error = "Challenge List API returned null or non-array. Try again later."; is_fetching = false; return; } + const auto& array = jsonResp.asArray().unwrap(); + int randomIndex; do { - randomIndex = rl::utils::randomInt(0, jsonResp.as_array().size() - 1); - } while (jsonResp[randomIndex]["level_id"].is_null()); + randomIndex = rl::utils::randomInt(0, array.size() - 1); + } while (array[randomIndex]["level_id"].isNull()); + + int levelId = array[randomIndex].template get("level_id").unwrapOr(-1); + + if (levelId == -1) + { + error = "Challenge List API returned non-number 'level_id'. Contact developer to fix this."; + is_fetching = false; + return; + } - int levelId = jsonResp[randomIndex].template get("level_id"); getLevelInfo(levelId, level, error); } else if (e->isCancelled()) @@ -253,6 +273,8 @@ void ListFetcher::getRandomGDListLevel(int listID, level_pair_t& level, std::str void ListFetcher::getLevelInfo(int levelID, level_pair_t& level, std::string& error) { + is_fetching = true; + m_listener2.bind([&](web::WebTask::Event* e) { if (web::WebResponse* res = e->getValue()) { diff --git a/src/roulette/layers/RLRouletteInfoLayer.cpp b/src/roulette/layers/RLRouletteInfoLayer.cpp index 7a452b9..329dfb4 100644 --- a/src/roulette/layers/RLRouletteInfoLayer.cpp +++ b/src/roulette/layers/RLRouletteInfoLayer.cpp @@ -68,7 +68,7 @@ bool RLRouletteInfoLayer::init() ); idButton->setVisible( rl::utils::getIndexOf( - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true + DataManager::get().asVector(), true ) == 3 ); { @@ -121,24 +121,26 @@ void RLRouletteInfoLayer::onClose(CCObject*) void RLRouletteInfoLayer::onToggleButton(CCObject* sender) { auto button = static_cast(sender); - auto prevIdx = rl::utils::getIndexOf(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true); + auto prevIdx = rl::utils::getIndexOf( + DataManager::get().asVector(), true + ); const auto demonDifficultyButton = static_cast( g_rouletteManager.rouletteLayer->getDifficultyButton(GJDifficulty::Demon)->getChildByID("sprite-node") ); // yeah... auto changeListWrapper = [&](const std::function& f) { - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(button->getTag()) = false; - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(0) = true; + DataManager::set(button->getTag(), false); + DataManager::set(0, true); f(); - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(0) = false; - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(button->getTag()) = true; + DataManager::set(0, false); + DataManager::set(button->getTag(), true); }; - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(prevIdx) = false; - g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(button->getTag()) = true; + DataManager::set(prevIdx, false); + DataManager::set(button->getTag(), true); m_buttonMenu->getChildByID("list-id-button")->setVisible(button->getTag() == 3); @@ -297,7 +299,7 @@ CCMenuItemToggler* RLRouletteInfoLayer::createToggler(int tag, const std::string button->setSizeMult(1.2f); button->setTag(tag); button->setVisible(visible); - button->toggle(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array().at(tag).as()); + button->toggle(DataManager::get().at(tag)); button->setID(nodeID); m_buttonMenu->addChild(button); diff --git a/src/roulette/layers/RLRouletteLayer.cpp b/src/roulette/layers/RLRouletteLayer.cpp index 16590b2..c6fb407 100644 --- a/src/roulette/layers/RLRouletteLayer.cpp +++ b/src/roulette/layers/RLRouletteLayer.cpp @@ -128,7 +128,9 @@ bool RLRouletteLayer::init() plusButton->setSizeMult(1.2f); plusButton->setID("demon-plus-button"); plusButton->setVisible( - rl::utils::getIndexOf(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true) != 0 || + rl::utils::getIndexOf( + DataManager::get().asVector(), true + ) != 0 || m_selected_difficulty >= GJDifficulty::Demon ); main_menu->addChild(plusButton); @@ -226,7 +228,7 @@ bool RLRouletteLayer::init() } auto percentageText = CCLabelBMFont::create( - fmt::format("{}%", g_rouletteManager.levelPercentageGoal).c_str(), + fmt::format("{}%", g_rouletteManager.currentPercentageGoal).c_str(), "goldFont.fnt" ); percentageText->setPosition({ 50.f, -20.f }); @@ -327,6 +329,17 @@ bool RLRouletteLayer::init() g_rouletteManager.isPlaying = true; g_rouletteManager.isPaused = false; finishLevelRoulette(); + } else if (g_rouletteManager.gameState.levelID != 0) + { + g_rouletteManager.isPlaying = true; + + main_menu->setVisible(false); + onNextLevel(false, true); + + rl::utils::createNotificationToast(this, "Resuming round of Roulette...", 1.f, 85.f); + + m_list_fetcher.getLevelInfo(g_rouletteManager.gameState.levelID, m_level, m_list_fetcher_error); + this->scheduleUpdate(); } return true; @@ -350,6 +363,8 @@ void RLRouletteLayer::onClose(CCObject*) "Woah there!", "Would you like to quit or pause the roulette?", [&](auto cl) { + g_rouletteManager.addExclamationMark(); + this->setKeypadEnabled(false); this->removeFromParentAndCleanup(true); g_rouletteManager.isPlaying = false; @@ -395,16 +410,16 @@ void RLRouletteLayer::onInfoButton(CCObject*) void RLRouletteLayer::onDifficultyButton(CCObject* sender) { - if (rl::utils::getIndexOf(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true) != 0) + if (rl::utils::getIndexOf(DataManager::get().asVector(), true) != 0) return; auto button = static_cast(sender); auto difficulty = static_cast(sender->getTag()); - auto& array = g_rouletteManager.getFromSaveContainer("difficulty-array").as_array(); - int prevIdx = rl::utils::getIndexOf(array, true); + auto array = DataManager::get(); + int prevIdx = rl::utils::getIndexOf(array.asVector(), true); - array.at(prevIdx) = false; - array.at(rl::constants::diff_to_idx.at(difficulty)) = true; + array.set(prevIdx, false); + array.set(rl::constants::diff_to_idx.at(difficulty), true); g_rouletteManager.previousDifficulty = difficulty; m_selected_difficulty = difficulty; @@ -444,12 +459,12 @@ void RLRouletteLayer::onPlusButton(CCObject*) m_selected_demon_difficulty, [&](GJDifficulty currentDifficulty, GJDifficulty previousDifficulty) { - auto& array = g_rouletteManager.getFromSaveContainer("demon-difficulty-array").as_array(); + auto array = DataManager::get(); m_selected_demon_difficulty = currentDifficulty; - array.at(rl::constants::demon_diff_to_idx.at(previousDifficulty)) = false; - array.at(rl::constants::demon_diff_to_idx.at(currentDifficulty)) = true; + array.set(rl::constants::demon_diff_to_idx.at(previousDifficulty), false); + array.set(rl::constants::demon_diff_to_idx.at(currentDifficulty), true); g_rouletteManager.previousDemonDifficulty = currentDifficulty; static_cast( @@ -506,29 +521,31 @@ void RLRouletteLayer::onNextButton(CCObject*) if (m_list_fetcher.is_fetching) return; - if (g_rouletteManager.currentLevelPercentage == 100) + if (g_rouletteManager.gameState.levelPercentage == 100) { onNextLevel(); static_cast(finished_menu->getChildByID("skips-label"))->setString( - fmt::format("Skips Used: {}", g_rouletteManager.skipsUsed).c_str() + fmt::format("Skips Used: {}", g_rouletteManager.gameState.skipsUsed).c_str() ); static_cast(finished_menu->getChildByID("levels-played-label"))->setString( - fmt::format("Levels Played: {}", g_rouletteManager.numLevels).c_str() + fmt::format("Levels Played: {}", g_rouletteManager.gameState.numLevels).c_str() ); playing_menu->setVisible(false); finished_menu->setVisible(true); } - else if (g_rouletteManager.currentLevelPercentage != 0 && g_rouletteManager.hasFinishedPreviousLevel) + else if (g_rouletteManager.gameState.hasReachedGoal) { - g_rouletteManager.hasFinishedPreviousLevel = false; + g_rouletteManager.gameState.hasReachedGoal = false; + g_rouletteManager.gameState.levelPercentage = 0; + g_rouletteManager.currentPercentageGoal = g_rouletteManager.gameState.levelPercentageGoal; onNextLevel(false, true, 40.f); static_cast( playing_menu->getChildByID("percentage-text") - )->setString(fmt::format("{}%", g_rouletteManager.levelPercentageGoal).c_str()); + )->setString(fmt::format("{}%", g_rouletteManager.currentPercentageGoal).c_str()); getRandomListLevel( m_selected_difficulty == GJDifficulty::Demon ? m_selected_demon_difficulty : m_selected_difficulty, @@ -537,7 +554,7 @@ void RLRouletteLayer::onNextButton(CCObject*) ); } else - rl::utils::createNotificationToast(this, fmt::format("You need to get at least {}%!", g_rouletteManager.levelPercentageGoal), .5f, 85.f); + rl::utils::createNotificationToast(this, fmt::format("You need to get at least {}%!", g_rouletteManager.currentPercentageGoal), .5f, 85.f); } void RLRouletteLayer::onResetButton(CCObject*) @@ -557,7 +574,7 @@ void RLRouletteLayer::onResetButton(CCObject*) error_menu->setVisible(false); static_cast(playing_menu->getChildByID("percentage-text"))->setString( - fmt::format("{}%", g_rouletteManager.levelPercentageGoal).c_str() + fmt::format("{}%", g_rouletteManager.currentPercentageGoal).c_str() ); main_menu->getChildByID("demon-plus-button")->setPositionY(-20.f); @@ -572,16 +589,17 @@ void RLRouletteLayer::onSkipButton(CCObject*) if (m_list_fetcher.is_fetching) return; - if (g_rouletteManager.currentLevelPercentage == 100) + if (g_rouletteManager.gameState.levelPercentage == 100) { onNextButton(nullptr); return; } - if (g_rouletteManager.skipsUsed < Mod::get()->getSettingValue("max-skips")) + if (g_rouletteManager.gameState.skipsUsed < Mod::get()->getSettingValue("max-skips")) { - g_rouletteManager.skipsUsed++; - g_rouletteManager.hasFinishedPreviousLevel = false; + g_rouletteManager.gameState.skipsUsed++; + g_rouletteManager.gameState.hasReachedGoal = false; + g_rouletteManager.currentPercentageGoal = g_rouletteManager.gameState.levelPercentageGoal; onNextLevel(false, true, 40.f); @@ -605,14 +623,15 @@ void RLRouletteLayer::finishLevelRoulette() playing_menu->setVisible(false); error_menu->setVisible(true); - g_rouletteManager.isPlaying = false; + g_rouletteManager.reset(); + return; } onNextLevel(true, false, 40.f); main_menu->setVisible(false); - g_rouletteManager.currentLevelID = m_level.first.levelID; + g_rouletteManager.gameState.levelID = m_level.first.levelID; const auto& [level, creator] = m_level; static_cast( @@ -654,6 +673,8 @@ void RLRouletteLayer::finishLevelRoulette() playing_menu->getChildByID("difficulty-node")->setPositionY(40.f); playing_menu->setVisible(true); + + g_rouletteManager.saveState(); } void RLRouletteLayer::onNextLevel(bool levelTextVisible, bool enableLoadingCircle, float loadingCirclePosYOffset) @@ -684,7 +705,7 @@ CCMenuItemSpriteExtra* RLRouletteLayer::getDifficultyButton(GJDifficulty difficu void RLRouletteLayer::getRandomListLevel(GJDifficulty difficulty, ListFetcher::level_pair_t& level, std::string& error) { - int listType = rl::utils::getIndexOf(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true); + int listType = rl::utils::getIndexOf(DataManager::get().asVector(), true); switch (listType) { @@ -724,7 +745,7 @@ CCMenuItemSpriteExtra* RLRouletteLayer::createDifficultyButton( menu_selector(RLRouletteLayer::onDifficultyButton) ); if ( - rl::utils::getIndexOf(g_rouletteManager.getFromSaveContainer("selected-list-array").as_array(), true) != 0 || + rl::utils::getIndexOf(DataManager::get().asVector(), true) != 0 || m_selected_difficulty != difficulty ) button->setColor({ 125, 125, 125 }); diff --git a/src/roulette/manager/DataManager.hpp b/src/roulette/manager/DataManager.hpp new file mode 100644 index 0000000..e37f49f --- /dev/null +++ b/src/roulette/manager/DataManager.hpp @@ -0,0 +1,259 @@ +#pragma once + +#include + +#include + +#include "GameState.hpp" + +#define ADD_KEY_TRAIT(K, key_type) \ + template <> \ + struct KeyTrait \ + { using type = key_type; } + +enum DMArrayKey +{ + DIFFICULTY_ARRAY, + DEMON_DIFFICULTY_ARRAY, + SELECTED_LIST_ARRAY, +}; + +enum DMMiscKey +{ + SAVE_DATA, + GD_LIST_ID, +}; + + +namespace DataManager +{ + namespace values + { + const std::unordered_map ARRAY_KEY_TO_NAME{ + { DMArrayKey::DIFFICULTY_ARRAY, "difficulty-array" }, + { DMArrayKey::DEMON_DIFFICULTY_ARRAY, "demon-difficulty-array" }, + { DMArrayKey::SELECTED_LIST_ARRAY, "selected-list-array" }, + }; + + const std::unordered_map MISC_KEY_TO_NAME{ + { DMMiscKey::GD_LIST_ID, "gdListID" }, + }; + + struct SavedArrayInfo + { + const std::size_t size; + const std::vector default_value; + + SavedArrayInfo(std::size_t size, std::vector&& default_value) + : size(size), default_value(std::move(default_value)) + {} + }; + + const std::unordered_map ARRAY_TO_SAI{ + { DMArrayKey::DIFFICULTY_ARRAY, { 6, { true, false, false, false, false, false } } }, + { DMArrayKey::DEMON_DIFFICULTY_ARRAY, { 5, { true, false, false, false, false, false }} }, + { DMArrayKey::SELECTED_LIST_ARRAY, { 4, { true, false, false, false }} } + }; + } + + namespace traits + { + template + struct KeyTrait; + + ADD_KEY_TRAIT(DMMiscKey::SAVE_DATA, GameState); + ADD_KEY_TRAIT(DMMiscKey::GD_LIST_ID, int); + } + + struct ArrayProxy + { + private: + std::vector& m_value; + + public: + ArrayProxy(std::vector& value) + : m_value(value) + {} + + template + T&& at(std::size_t idx) + { + // at this point the value is safe to unwrap since we've already verified it beforehand + return m_value.at(idx).as().unwrap(); + } + + template + void set(std::size_t idx, const T& value) + { + m_value.at(idx) = value; + } + + std::vector& asVector() + { + return m_value; + } + }; + + + template + [[nodiscard]] std::vector& setDefaultSafe(); + template + [[nodiscard]] traits::KeyTrait::type setDefaultSafe(); + + + /** + * @brief Gets the array @tparam key array from the save container. Verifies it beforehand. + * + * @tparam key The array to get. + * @return ArrayProxy + */ + template + [[nodiscard]] ArrayProxy get() + { + return setDefaultSafe(); + } + + /** + * @brief Gets the value of @tparam key from the save container. Verifies it beforehand. + * + * @tparam key The key to get. + * @return traits::KeyTrait::type The value's type. + */ + template + [[nodiscard]] traits::KeyTrait::type get() + { + using value_t = typename traits::KeyTrait::type; + + if constexpr (key == DMMiscKey::SAVE_DATA) + { + value_t fromDisk; + std::ifstream saveDataFile(geode::Mod::get()->getSaveDir() / "save_data", std::ios::binary); + saveDataFile.read(reinterpret_cast(&fromDisk), sizeof(value_t)); + + return fromDisk; + } + + return setDefaultSafe(); + } + + /** + * @brief Sets the value of @tparam key array in the save container to @param value. + * + * @tparam key The key to set the value of. + * @param value The new value. + */ + template + void set(const std::vector& value) + { + geode::Mod::get()->setSavedValue(values::ARRAY_KEY_TO_NAME.at(key), value); + } + + /** + * @brief Sets the value of @tparam key array at index @param idx to @param value + * + * @tparam key + * @param idx + * @param value + */ + template + void set(std::size_t idx, bool value) + { + geode::Mod::get()->getSaveContainer().get( + values::ARRAY_KEY_TO_NAME.at(key) + ).unwrap().asArray().unwrap().at(idx) = value; + } + + /** + * @brief Sets the value of @tparam key in the save container to @param value. + * + * @tparam key The key to set the value of. + * @param value The new value. + */ + template + void set(const typename traits::KeyTrait::type& value) + { + if constexpr (key == DMMiscKey::SAVE_DATA) + { + std::ofstream saveDataFile(geode::Mod::get()->getSaveDir() / "save_data"); + saveDataFile.write( + reinterpret_cast(&value), + sizeof(typename traits::KeyTrait::type) + ); + } + else + { + geode::Mod::get()->setSavedValue(values::MISC_KEY_TO_NAME.at(key), value); + } + } + + + /** + * @brief Sets the value of @tparam key array to its default if it doesn't exist or there is an error getting it. + * + * @tparam key The key to set the value of. + * @return std::vector& The default value if there was an error, or the current value. + */ + template + [[maybe_unused]] std::vector& setDefaultSafe() + { + auto& container = geode::Mod::get()->getSaveContainer(); + + if (auto res = container.get(values::ARRAY_KEY_TO_NAME.at(key)); res.isOk()) + if (auto resv = res.unwrap().asArray(); resv.isOkAnd([](auto&& vec) { + return vec.size() == values::ARRAY_TO_SAI.at(key).size && + std::all_of(vec.begin(), vec.end(), [](auto& v) { return v.asBool().isOk(); }); + })) + return resv.unwrap(); + + container.set(values::ARRAY_KEY_TO_NAME.at(key), values::ARRAY_TO_SAI.at(key).default_value); + + return container.get(values::ARRAY_KEY_TO_NAME.at(key)).unwrap().asArray().unwrap(); + } + + /** + * @brief Sets the value of @tparam key to its default if it doesn't exist or there is an error getting it. + * + * @tparam key The key to set the value of. + * @return traits::KeyTrait::type The default value if there was an error, or the current value. + */ + template + [[maybe_unused]] traits::KeyTrait::type setDefaultSafe() + { + using value_t = typename traits::KeyTrait::type; + using save_container_t = std::conditional_t, uint64_t, value_t>; + + if constexpr (key == DMMiscKey::SAVE_DATA) + { + std::ifstream saveDataFileIn(geode::Mod::get()->getSaveDir() / "save_data", std::ios::binary); + saveDataFileIn.seekg(0, std::ios::end); + + if (saveDataFileIn.fail() || saveDataFileIn.tellg() != sizeof(value_t)) + { + value_t defaultValue; + + std::ofstream saveDataFileOut(geode::Mod::get()->getSaveDir() / "save_data", std::ios::binary); + saveDataFileOut.write(reinterpret_cast(&defaultValue), sizeof(value_t)); + + return defaultValue; + } + + value_t fromDisk; + saveDataFileIn.seekg(0); + saveDataFileIn.read(reinterpret_cast(&fromDisk), sizeof(value_t)); + + return fromDisk; + } + else + { + auto& container = geode::Mod::get()->getSaveContainer(); + + if (auto res = container.get(values::MISC_KEY_TO_NAME.at(key)); res.isOk()) + if (auto resv = res.unwrap().as(); resv.isOk()) + return resv.unwrap(); + + container.set(values::MISC_KEY_TO_NAME.at(key), value_t{}); + + return value_t{}; + } + } +}; diff --git a/src/roulette/manager/GameState.hpp b/src/roulette/manager/GameState.hpp new file mode 100644 index 0000000..1befb73 --- /dev/null +++ b/src/roulette/manager/GameState.hpp @@ -0,0 +1,13 @@ +#pragma once + +#pragma pack(push, 1) +struct GameState +{ + int levelID = 0; + int skipsUsed = 0; + int numLevels = 0; + int levelPercentage = 0; + int levelPercentageGoal = 1; + bool hasReachedGoal = false; +}; +#pragma pack(pop) diff --git a/src/roulette/manager/RouletteManager.hpp b/src/roulette/manager/RouletteManager.hpp index 28d348e..ea1f533 100644 --- a/src/roulette/manager/RouletteManager.hpp +++ b/src/roulette/manager/RouletteManager.hpp @@ -5,6 +5,8 @@ #include #include +#include "DataManager.hpp" +#include "GameState.hpp" #include "../layers/RLRouletteLayer.hpp" #include "../../utils.hpp" @@ -16,59 +18,15 @@ struct RouletteManager private: inline static bool m_hasInitManager = false; - struct SavedArrayInfo - { - const std::size_t size; - const std::vector default_value; - - SavedArrayInfo(std::size_t size, const std::vector& default_value) - : size(size), default_value(default_value) - {} - }; - - const std::unordered_map ARRAY_TO_SAI{ - { "difficulty-array", { 6, { true, false, false, false, false, false } } }, - { "demon-difficulty-array", { 5, { true, false, false, false, false, false }} }, - { "selected-list-array", { 4, { true, false, false, false }} } - }; - - void verifyArray(const std::string& name, matjson::Array& arr) - { - if (rl::utils::getCountOf(arr, true) != 1) - { - for (auto& elem : arr) - elem = false; - arr.at(0) = true; - } - - if ( - auto arraySaveDataInfo = ARRAY_TO_SAI.at(name); - arraySaveDataInfo.size != arr.size() - ) { - auto previousIndex = rl::utils::getIndexOf(arr, true); - - Mod::get()->setSavedValue>(name, arraySaveDataInfo.default_value); - - if (previousIndex != -1) - getFromSaveContainer(name).as_array().at(previousIndex) = true; - } - } - public: inline static RLRouletteLayer* rouletteLayer; inline static std::atomic_bool isPlaying = false; inline static std::atomic_bool isPaused = false; - inline static bool hasFinishedPreviousLevel = false; - - inline static int currentLevelID = 0; - inline static int currentLevelPercentage = 0; - inline static int levelPercentageGoal = 1; - - inline static int skipsUsed = 0; - - inline static int numLevels = 0; + inline static GameState gameState{}; + // used visually, not saved. + inline static int currentPercentageGoal = 1; inline static int gdListID = 0; inline static GJDifficulty previousDifficulty = GJDifficulty::Easy; @@ -79,55 +37,54 @@ struct RouletteManager { if (!m_hasInitManager) { - auto& saveContainer = Mod::get()->getSaveContainer().as_object(); + auto& saveContainer = Mod::get()->getSaveContainer(); - if (!Mod::get()->hasSavedValue("difficulty-array")) - Mod::get()->setSavedValue>("difficulty-array", { true, false, false, false, false, false }); - else - verifyArray("difficulty-array", getFromSaveContainer("difficulty-array").as_array()); + static_cast(DataManager::setDefaultSafe()); + static_cast(DataManager::setDefaultSafe()); + static_cast(DataManager::setDefaultSafe()); + gdListID = DataManager::setDefaultSafe(); + gameState = DataManager::setDefaultSafe(); - if (!Mod::get()->hasSavedValue("demon-difficulty-array")) - Mod::get()->setSavedValue>("demon-difficulty-array", { true, false, false, false, false }); - else - verifyArray("demon-difficulty-array", saveContainer["demon-difficulty-array"].as_array()); + if (gameState.levelID != 0) + currentPercentageGoal = gameState.levelPercentage; - - if (!Mod::get()->hasSavedValue("selected-list-array")) - Mod::get()->setSavedValue>("selected-list-array", { true, false, false, false }); - else - verifyArray("selected-list-array", saveContainer["selected-list-array"].as_array()); - - - if (!Mod::get()->hasSavedValue("gdListID")) - Mod::get()->setSavedValue("gdListID", static_cast(gdListID)); - else - gdListID = static_cast(Mod::get()->getSavedValue("gdListID")); - - - previousDifficulty = getDifficultyFromSaveContainer("difficulty-array"); - previousDemonDifficulty = getDifficultyFromSaveContainer("demon-difficulty-array"); + previousDifficulty = getDifficultyFromSaveContainer(); + previousDemonDifficulty = getDifficultyFromSaveContainer(); m_hasInitManager = true; } } - // not using getSavedValue because i want a reference and not a copy - matjson::Value& getFromSaveContainer(const std::string_view key) - { - return Mod::get()->getSaveContainer().as_object()[key]; - } - - bool getArrayState(matjson::Value& arr, std::size_t idx) + void addExclamationMark(CCMenuItemSpriteExtra* button = nullptr) { - return arr.as_array().at(idx).as(); + auto exclamationSprite = CCSprite::createWithSpriteFrameName( + "exMark_001.png" + ); + exclamationSprite->setScale(.6f); + exclamationSprite->setID("exclamation-mark"); + (button + ? button + : rouletteLayer + ->getParent() + ->getChildByType(0) + ->getChildByID("center-left-menu"_spr) + ->getChildByID("roulette-button"_spr) + )->addChild(exclamationSprite); + + exclamationSprite->setLayoutOptions( + AnchorLayoutOptions::create() + ->setAnchor(Anchor::TopRight) + ->setOffset({ -5.f, -8.f }) + ); } - GJDifficulty getDifficultyFromSaveContainer(const std::string_view key) + template + GJDifficulty getDifficultyFromSaveContainer() { - int idx = rl::utils::getIndexOf(getFromSaveContainer(key).as_array(), true); + int idx = rl::utils::getIndexOf(DataManager::get().asVector(), true); - if (key == "demon-difficulty-array") + if constexpr (key == DMArrayKey::DEMON_DIFFICULTY_ARRAY) return rl::constants::idx_to_demon_diff[idx]; else return static_cast(idx + 1); @@ -135,27 +92,40 @@ struct RouletteManager GJDifficulty getDifficultyFromSaveContainer() { - int idx = rl::utils::getIndexOf(getFromSaveContainer("difficulty-array").as_array(), true); + int idx = rl::utils::getIndexOf(DataManager::get().asVector(), true); if (auto difficulty = static_cast(idx + 1); difficulty < GJDifficulty::Demon) return difficulty; else { - int demonIdx = rl::utils::getIndexOf(getFromSaveContainer("demon-difficulty-array").as_array(), true); + int demonIdx = rl::utils::getIndexOf(DataManager::get().asVector(), true); return rl::constants::idx_to_demon_diff[demonIdx]; } } + void saveState() + { + DataManager::set(gameState); + } + + void resetState() + { + gameState.levelID = 0; + gameState.levelPercentage = 0; + gameState.levelPercentageGoal = 0; + gameState.numLevels = 0; + gameState.skipsUsed = 0; + gameState.hasReachedGoal = false; + } + void reset() { + resetState(); + saveState(); + isPlaying = false; isPaused = false; - hasFinishedPreviousLevel = false; - currentLevelID = 0; - currentLevelPercentage = 0; - levelPercentageGoal = 1; - skipsUsed = 0; - numLevels = 0; + currentPercentageGoal = 1; } }; diff --git a/src/utils.hpp b/src/utils.hpp index 2e43e93..7b763fb 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -1,5 +1,5 @@ #pragma once -#include + #include #include #include @@ -29,7 +29,7 @@ namespace rl inline std::ptrdiff_t getIndexOf(const std::vector& vec, T to_find) { auto it = std::find_if(vec.cbegin(), vec.cend(), [&](const matjson::Value& value) { - return value.as() == to_find; + return value.as().unwrapOr(T{}) == to_find; }); return it != vec.cend() ? (it - vec.cbegin()) : -1; @@ -49,7 +49,7 @@ namespace rl inline std::size_t getCountOf(const std::vector& vec, T to_find) { return std::count_if(vec.cbegin(), vec.cend(), [&](const matjson::Value arr) { - return arr.as() == to_find; + return arr.as().unwrapOr(T{}) == to_find; }); } @@ -181,9 +181,9 @@ namespace rl return button; } - inline void createNotificationToast(CCLayer* layer, const std::string& str, float time, float yPosition) + inline void createNotificationToast(CCLayer* layer, const std::string_view str, float time, float yPosition) { - auto tap = TextAlertPopup::create(str, time, .6f, 0x96, "bigFont.fnt"); + auto tap = TextAlertPopup::create(str.data(), time, .6f, 0x96, "bigFont.fnt"); tap->setPositionY(yPosition); layer->addChild(tap, 420);