From db1cb59a20896afa905476f749ea39738b8560a0 Mon Sep 17 00:00:00 2001 From: Alex Uskov Date: Fri, 3 Jan 2025 06:45:12 +0400 Subject: [PATCH 01/28] Update build instructions to build release on release only --- .github/workflows/main.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- CMakeLists.txt | 20 +++++++++----------- build.ps1 | 18 +++++++++++++++--- copy.ps1 | 5 ++++- createqmod.ps1 | 5 ++++- include/UI/ViewControllers/SongList.hpp | 2 +- qpm.json | 2 +- qpm.shared.json | 10 +++------- src/UI/ViewControllers/SongList.cpp | 8 ++++---- src/UI/ViewControllers/SongListCell.cpp | 1 - 11 files changed, 45 insertions(+), 34 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db1edb0..d590c5c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,12 +35,12 @@ jobs: - name: Build run: | cd ${GITHUB_WORKSPACE} - pwsh -Command ./build.ps1 + pwsh -Command ./build.ps1 -release qpm qmod build - name: Create Qmod run: | - pwsh -Command ./createqmod.ps1 ${{env.qmodName}} + pwsh -Command ./createqmod.ps1 ${{env.qmodName}} -release - name: Get Library Name id: libname diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ec3d080..ca5bd08 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -51,12 +51,12 @@ jobs: - name: Build run: | cd ${GITHUB_WORKSPACE} - pwsh -Command ./build.ps1 + pwsh -Command ./build.ps1 -release qpm qmod build - name: Create Qmod run: | - pwsh -Command ./createqmod.ps1 ${{env.qmodName}} + pwsh -Command ./createqmod.ps1 ${{env.qmodName}} -release - name: Get Library Name and Build ID id: libname diff --git a/CMakeLists.txt b/CMakeLists.txt index 4594cc6..f9c0d91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,14 +5,6 @@ cmake_minimum_required(VERSION 3.21) project(${COMPILE_ID}) include(${EXTERN_DIR}/includes/kaleb/shared/cmake/assets.cmake) -# Enable link time optimization -# In my experience, this can be highly unstable but it nets a huge size optimization and likely performance -# However, the instability was seen using Android.mk/ndk-build builds. With Ninja + CMake, this problem seems to have been solved. -# As always, test thoroughly -# - Fern -# Reduces linking speed, disable in development for faster builds -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - # Hot reload (DO NOT USE IN PRODUCTION) # add_compile_options(-DHotReload) @@ -30,9 +22,15 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_link_options(-Wl,--exclude-libs,ALL) add_compile_options(-frtti -fPIE -fPIC -fexceptions -fvisibility=hidden) -add_compile_options(-O3) -# Uncomment for faster builds (DO NOT USE IN PRODUCTION, it slows down the search 6 times) -# add_compile_options(-O0) +if(${CMAKE_BUILD_TYPE} STREQUAL "RELEASE" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo" OR ${CMAKE_BUILD_TYPE} STREQUAL "MinSizeRel") + + # Better optimizations + add_compile_options(-O3) + + # LTO + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + add_compile_options(-flto) +endif() # get git info execute_process(COMMAND git config user.name OUTPUT_VARIABLE GIT_USER) diff --git a/build.ps1 b/build.ps1 index f135cbf..13a7a26 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,6 +1,8 @@ Param( [Parameter(Mandatory=$false)] - [Switch]$clean + [Switch]$clean, + [Parameter(Mandatory=$false)] + [Switch]$release ) # if user specified clean, remove all build files @@ -12,6 +14,14 @@ if ($clean.IsPresent) } } +$buildType = "Debug" +if ($release.IsPresent) { + $buildType = "RelWithDebInfo" + echo "Building release" +} else { + echo "Building debug" +} + $NDKPath = Get-Content $PSScriptRoot/ndkpath.txt if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) @@ -19,5 +29,7 @@ if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) $out = new-item -Path build -ItemType Directory } -& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" . -B build -& cmake --build ./build \ No newline at end of file +& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="$buildType" . -B build +& cmake --build ./build +$ExitCode = $LastExitCode +exit $ExitCode \ No newline at end of file diff --git a/copy.ps1 b/copy.ps1 index a146b81..942cf61 100644 --- a/copy.ps1 +++ b/copy.ps1 @@ -5,6 +5,9 @@ Param( [Parameter(Mandatory=$false)] [Switch] $log, + [Parameter(Mandatory=$false)] + [Switch]$release, + [Parameter(Mandatory=$false)] [Switch] $useDebug, @@ -39,7 +42,7 @@ if ($help -eq $true) { exit } -& $PSScriptRoot/build.ps1 -clean:$clean +& $PSScriptRoot/build.ps1 -clean:$clean -release:$release if ($LASTEXITCODE -ne 0) { echo "Failed to build, exiting..." diff --git a/createqmod.ps1 b/createqmod.ps1 index 5b57653..33e4fdd 100644 --- a/createqmod.ps1 +++ b/createqmod.ps1 @@ -3,6 +3,9 @@ Param( [Parameter(Mandatory=$false)] [Switch] $clean, + + [Parameter(Mandatory=$false)] + [Switch]$release, [Parameter(Mandatory=$false)] [Switch] $help @@ -26,7 +29,7 @@ if ($qmodName -eq "") exit } -& $PSScriptRoot/build.ps1 -clean:$clean +& $PSScriptRoot/build.ps1 -clean:$clean -release:$release if ($LASTEXITCODE -ne 0) { Write-Output "Failed to build, exiting..." diff --git a/include/UI/ViewControllers/SongList.hpp b/include/UI/ViewControllers/SongList.hpp index 1abd563..4cb53ce 100644 --- a/include/UI/ViewControllers/SongList.hpp +++ b/include/UI/ViewControllers/SongList.hpp @@ -255,6 +255,6 @@ DECLARE_CLASS_CODEGEN_INTERFACES(BetterSongSearch::UI::ViewControllers, SongList // Event receivers void SongDataDone(); - void SongDataError(); + void SongDataError(std::string message); ) diff --git a/qpm.json b/qpm.json index ec9a72e..e25d275 100644 --- a/qpm.json +++ b/qpm.json @@ -42,7 +42,7 @@ }, { "id": "song-details", - "versionRange": "^0.3.6", + "versionRange": "^1.0.0", "additionalData": {} }, { diff --git a/qpm.shared.json b/qpm.shared.json index 090f5f5..be647b7 100644 --- a/qpm.shared.json +++ b/qpm.shared.json @@ -50,7 +50,7 @@ }, { "id": "song-details", - "versionRange": "^0.3.6", + "versionRange": "^1.0.0", "additionalData": {} }, { @@ -195,17 +195,13 @@ { "dependency": { "id": "song-details", - "versionRange": "=0.3.7", + "versionRange": "=1.0.0", "additionalData": { - "soLink": "https://github.com/bsq-ports/SongDetails/releases/download/v0.3.7/libsongdetails.so", - "debugSoLink": "https://github.com/bsq-ports/SongDetails/releases/download/v0.3.7/debug_libsongdetails.so", "overrideSoName": "libsongdetails.so", - "modLink": "https://github.com/bsq-ports/SongDetails/releases/download/v0.3.7/SongDetails.qmod", - "branchName": "version/v0_3_7", "cmake": false } }, - "version": "0.3.7" + "version": "1.0.0" }, { "dependency": { diff --git a/src/UI/ViewControllers/SongList.cpp b/src/UI/ViewControllers/SongList.cpp index b499356..6743927 100644 --- a/src/UI/ViewControllers/SongList.cpp +++ b/src/UI/ViewControllers/SongList.cpp @@ -1437,7 +1437,7 @@ void ViewControllers::SongListController::DownloadSongList() { if (!DataHolder::songDetails->songs.get_isDataAvailable()) { - this->SongDataError(); + this->SongDataError("Failed to load song data"); } else { this->SongDataDone(); } @@ -1485,7 +1485,7 @@ void ViewControllers::SongListController::SongDataDone() { }); } -void ViewControllers::SongListController::SongDataError() { +void ViewControllers::SongListController::SongDataError(std::string message) { DEBUG("SongDataError"); // Set state flags @@ -1495,9 +1495,9 @@ void ViewControllers::SongListController::SongDataError() { DataHolder::invalid = true; DataHolder::needsRefresh = false; - BSML::MainThreadScheduler::Schedule([this] { + BSML::MainThreadScheduler::Schedule([this, message] { if (fcInstance != nullptr) { - fcInstance->FilterViewController->datasetInfoLabel->set_text("Failed to load, click to retry"); + fcInstance->FilterViewController->datasetInfoLabel->set_text(fmt::format("{}, click to retry", message)); } }); } \ No newline at end of file diff --git a/src/UI/ViewControllers/SongListCell.cpp b/src/UI/ViewControllers/SongListCell.cpp index 73dee6a..00e7bee 100644 --- a/src/UI/ViewControllers/SongListCell.cpp +++ b/src/UI/ViewControllers/SongListCell.cpp @@ -57,7 +57,6 @@ namespace BetterSongSearch::UI::ViewControllers this->levelAuthorName->set_text(entry->levelAuthorName()); this->songLengthAndRating->set_text(fmt::format("Length: {:%M:%S} Upvotes: {}, Downvotes: {}", std::chrono::seconds(entry->songDurationSeconds), entry->upvotes, entry->downvotes)); this->uploadDateFormatted->set_text(fmt::format("{:%d. %b %Y}", fmt::localtime(entry->uploadTimeUnix))); - auto ranked = entry->rankedStatus == SongDetailsCache::RankedStatus::Ranked; bool downloaded = fcInstance->DownloadHistoryViewController->CheckIsDownloaded(entry->hash()); Sombrero::FastColor songColor = Sombrero::FastColor::white(); From 7551b6872e76841f8a391f80d6f1045d2bc6dd1c Mon Sep 17 00:00:00 2001 From: Alex Uskov Date: Fri, 3 Jan 2025 07:46:36 +0400 Subject: [PATCH 02/28] Sync search algo with upstream --- src/UI/ViewControllers/SongList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/ViewControllers/SongList.cpp b/src/UI/ViewControllers/SongList.cpp index 6743927..4ed00d0 100644 --- a/src/UI/ViewControllers/SongList.cpp +++ b/src/UI/ViewControllers/SongList.cpp @@ -589,7 +589,7 @@ void ViewControllers::SongListController::_UpdateSearchedSongsList() { * The 8 character limitation for this is so that super short words like "those" dont end * up triggering this */ - if (songName.length() > 7 && songName.length() == posInName) { + if (songName.length() >= 6 && songName.length() == posInName) { resultWeight += 3; } else { // If we did match the beginning, check if we matched an entire word. Get the end index as indicated by our needle From 7aa80079ae7c626fead02ae47daa15bdeea593ac Mon Sep 17 00:00:00 2001 From: Alex Uskov Date: Sat, 4 Jan 2025 13:11:21 +0400 Subject: [PATCH 03/28] WIP Adding presets support and refactoring --- include/FilterOptions.hpp | 320 +++++----------------- include/PluginConfig.hpp | 130 ++++++++- include/UI/ViewControllers/FilterView.hpp | 8 +- include/UI/ViewControllers/SongList.hpp | 20 +- include/Util/TextUtil.hpp | 3 + include/internal_macros.hpp | 1 - include/main.hpp | 4 +- src/FilterOptions.cpp | 167 +++++++++++ src/UI/Modals/Settings.cpp | 8 +- src/UI/ViewControllers/FilterView.cpp | 285 +++++++------------ src/UI/ViewControllers/SongList.cpp | 84 +++--- src/UI/ViewControllers/SongListCell.cpp | 4 +- src/Util/SongUtil.cpp | 12 +- src/Util/TextUtil.cpp | 11 + src/main.cpp | 52 +--- 15 files changed, 550 insertions(+), 559 deletions(-) delete mode 100644 include/internal_macros.hpp create mode 100644 src/FilterOptions.cpp diff --git a/include/FilterOptions.hpp b/include/FilterOptions.hpp index 525b23a..3ba9474 100644 --- a/include/FilterOptions.hpp +++ b/include/FilterOptions.hpp @@ -2,260 +2,70 @@ #include #include -#include "main.hpp" -#include "song-details/shared/Data/MapCharacteristic.hpp" -#include "song-details/shared/Data/MapDifficulty.hpp" -#include "song-details/shared/Data/RankedStates.hpp" -#include "song-details/shared/Data/MapMods.hpp" -#include "song-details/shared/Data/SongDifficulty.hpp" - - - -class FilterOptions -{ -public: - // FilterOptions(FilterOptions const&) = delete; // no accidental copying - // FilterOptions() = default; - - enum class DownloadFilterType - { - All, - OnlyDownloaded, - HideDownloaded - }; - enum class LocalScoreFilterType - { - All, - HidePassed, - OnlyPassed - }; - enum class RankedFilterType - { - ShowAll, - ScoreSaberRanked, - BeatLeaderRanked, - ScoreSaberQualified, - BeatLeaderQualified - }; - enum class DifficultyFilterType - { - All, - Easy, - Normal, - Hard, - Expert, - ExpertPlus - }; - enum class CharFilterType - { - All, - Custom, - Standard, - OneSaber, - NoArrows, - NinetyDegrees, - ThreeSixtyDegrees, - LightShow, - Lawless, - }; - - enum class RequirementType { - Any, - NoodleExtensions, - MappingExtensions, - Chroma, - Cinema, - None - }; - - static inline const float SONG_LENGTH_FILTER_MAX = 15.0f; - static inline const float STAR_FILTER_MAX = 18.0f; - static inline const float NJS_FILTER_MAX = 25.0f; - static inline const float NPS_FILTER_MAX = 12.0f; - static inline const int64_t BEATSAVER_EPOCH = 1525136400; - - //General - DownloadFilterType downloadType = DownloadFilterType::All; - LocalScoreFilterType localScoreType = LocalScoreFilterType::All; - float minLength = 0, maxLength = 900; - - //Mapping - float minNJS = 0, maxNJS = NJS_FILTER_MAX; - float minNPS = 0, maxNPS = NPS_FILTER_MAX; - - // Ranked - RankedFilterType rankedType = RankedFilterType::ShowAll; - float minStars = 0, maxStars = STAR_FILTER_MAX; - - //BeatSaver - int minUploadDate = BEATSAVER_EPOCH; - int minUploadDateInMonths = 0; - float minRating = 0; - int minVotes = 0; - std::vector uploaders; - bool uploadersBlackList = false; - - //Difficulty - DifficultyFilterType difficultyFilter = DifficultyFilterType::All; - CharFilterType charFilter = CharFilterType::All; - - //Mods - RequirementType modRequirement = RequirementType::Any; -}; - -enum class SortMode { - Newest, - Oldest, - Latest_Ranked, - Most_Stars, - Least_Stars, - Best_rated, - Worst_rated -}; - -enum class PreferredLeaderBoard { - ScoreSaber = 0, - BeatLeader = 1 -}; - - - -// Map for characteristics -static const std::unordered_map charMap = { - {FilterOptions::CharFilterType::Custom, SongDetailsCache::MapCharacteristic::Custom}, - {FilterOptions::CharFilterType::Standard, SongDetailsCache::MapCharacteristic::Standard}, - {FilterOptions::CharFilterType::OneSaber, SongDetailsCache::MapCharacteristic::OneSaber}, - {FilterOptions::CharFilterType::NoArrows, SongDetailsCache::MapCharacteristic::NoArrows}, - {FilterOptions::CharFilterType::NinetyDegrees, SongDetailsCache::MapCharacteristic::NinetyDegree}, - {FilterOptions::CharFilterType::ThreeSixtyDegrees, SongDetailsCache::MapCharacteristic::ThreeSixtyDegree}, - {FilterOptions::CharFilterType::LightShow, SongDetailsCache::MapCharacteristic::LightShow}, - {FilterOptions::CharFilterType::Lawless, SongDetailsCache::MapCharacteristic::Lawless} -}; -// Map for difficulties -static const std::unordered_map diffMap = { - {FilterOptions::DifficultyFilterType::Easy, SongDetailsCache::MapDifficulty::Easy}, - {FilterOptions::DifficultyFilterType::Normal, SongDetailsCache::MapDifficulty::Normal}, - {FilterOptions::DifficultyFilterType::Hard, SongDetailsCache::MapDifficulty::Hard}, - {FilterOptions::DifficultyFilterType::Expert, SongDetailsCache::MapDifficulty::Expert}, - {FilterOptions::DifficultyFilterType::ExpertPlus, SongDetailsCache::MapDifficulty::ExpertPlus} -}; - - -// Map for ranked states -static const std::unordered_map rankMap = { - {FilterOptions::RankedFilterType::ScoreSaberRanked, SongDetailsCache::RankedStates::ScoresaberRanked}, - {FilterOptions::RankedFilterType::BeatLeaderRanked, SongDetailsCache::RankedStates::BeatleaderRanked}, - {FilterOptions::RankedFilterType::ScoreSaberQualified, SongDetailsCache::RankedStates::ScoresaberQualified}, - {FilterOptions::RankedFilterType::BeatLeaderQualified, SongDetailsCache::RankedStates::BeatleaderQualified} -}; - -// Map for preferred leaderboard -static const std::unordered_map leaderBoardMap = { - {"Scoresaber", PreferredLeaderBoard::ScoreSaber}, - {"Beatleader", PreferredLeaderBoard::BeatLeader} -}; - -class FilterOptionsCache -{ -public: - FilterOptionsCache(FilterOptionsCache const&) = delete; // no accidental copying - FilterOptionsCache() = default; - - void cache(FilterOptions s){ - downloadType=s.downloadType; - localScoreType=s.localScoreType; - minLength=s.minLength; - if (s.maxLength / 60 >= FilterOptions::SONG_LENGTH_FILTER_MAX) { maxLength=std::numeric_limits::infinity(); } else { maxLength=s.maxLength;} - minNJS=s.minNJS; - if (s.maxNJS >= FilterOptions::NJS_FILTER_MAX) { maxNJS=std::numeric_limits::infinity(); } else { maxNJS=s.maxNJS;} - minNPS=s.minNPS; - if (s.maxNPS >= FilterOptions::NPS_FILTER_MAX) { maxNPS=std::numeric_limits::infinity(); } else { maxNPS=s.maxNPS;} - rankedType=s.rankedType; - minStars=s.minStars; - if (s.maxStars >= FilterOptions::STAR_FILTER_MAX) { maxStars=std::numeric_limits::infinity(); } else { maxStars=s.maxStars;} - minUploadDate=s.minUploadDate; - minRating=s.minRating; - minVotes=s.minVotes; - uploaders=s.uploaders; - uploadersBlackList=s.uploadersBlackList; - difficultyFilter=s.difficultyFilter; - charFilter=s.charFilter; - modRequirement=s.modRequirement; - - // Process char filter - if (s.charFilter != FilterOptions::CharFilterType::All) { - charFilterPreprocessed = charMap.at(s.charFilter); - }; - - // Map difficulty filter - if (s.difficultyFilter != FilterOptions::DifficultyFilterType::All) { - difficultyFilterPreprocessed = diffMap.at(s.difficultyFilter); - }; - - // Check if filter is needed at all - skipFilter = false; - skipFilter = ( - downloadType == FilterOptions::DownloadFilterType::All && - localScoreType == FilterOptions::LocalScoreFilterType::All && - (s.maxLength / 60 >= FilterOptions::SONG_LENGTH_FILTER_MAX) && - (s.minLength == 0) && - minNJS == 0 && - s.maxNJS >= FilterOptions::NJS_FILTER_MAX && - s.minNPS == 0 && - s.maxNPS >= FilterOptions::NPS_FILTER_MAX && - rankedType == FilterOptions::RankedFilterType::ShowAll && - minStars == 0 && - s.maxStars >= FilterOptions::STAR_FILTER_MAX && - s.minUploadDateInMonths == 0 && - minRating == 0 && - minVotes == 0 && - uploaders.size() == 0 && - difficultyFilter == FilterOptions::DifficultyFilterType::All && - charFilter == FilterOptions::CharFilterType::All && - modRequirement == FilterOptions::RequirementType::Any - ); - - // Do infinity checks for songs that are out of bounds - if (s.maxStars >= FilterOptions::STAR_FILTER_MAX) { maxStars=std::numeric_limits::infinity(); } - if (s.maxNJS >= FilterOptions::NJS_FILTER_MAX) { maxNJS=std::numeric_limits::infinity(); } - if (s.maxNPS >= FilterOptions::NPS_FILTER_MAX) { maxNPS=std::numeric_limits::infinity(); } - if (s.maxLength / 60 >= FilterOptions::SONG_LENGTH_FILTER_MAX) { maxLength=std::numeric_limits::infinity(); } - } - - bool skipFilter = false; - - - //General - FilterOptions::DownloadFilterType downloadType = FilterOptions::DownloadFilterType::All; - FilterOptions::LocalScoreFilterType localScoreType = FilterOptions::LocalScoreFilterType::All; - float minLength = 0, maxLength = 900; - - //Mapping - float minNJS = 0, maxNJS = FilterOptions::NJS_FILTER_MAX; - float minNPS = 0, maxNPS = FilterOptions::NPS_FILTER_MAX; - - //ScoreSaber - FilterOptions::RankedFilterType rankedType = FilterOptions::RankedFilterType::ShowAll; - float minStars = 0, maxStars = FilterOptions::STAR_FILTER_MAX; - - //BeatSaver - int minUploadDate = FilterOptions::BEATSAVER_EPOCH; - float minRating = 0; - int minVotes = 0; - std::vector uploaders; - bool uploadersBlackList = false; - - //Difficulty - FilterOptions::DifficultyFilterType difficultyFilter = FilterOptions::DifficultyFilterType::All; - SongDetailsCache::MapDifficulty difficultyFilterPreprocessed; - - - /// @brief Char filter for gui - FilterOptions::CharFilterType charFilter = FilterOptions::CharFilterType::All; - /// @brief Used to speedup filtering, only if not All - SongDetailsCache::MapCharacteristic charFilterPreprocessed = SongDetailsCache::MapCharacteristic::Custom; +#include "rapidjson-macros/shared/macros.hpp" +#include "song-details/shared/Data/MapMods.hpp" +#include "PluginConfig.hpp" + +using namespace SongDetailsCache; + +namespace BetterSongSearch { + DECLARE_JSON_CLASS(FilterProfile, + VALUE_DEFAULT(FilterTypes::DownloadFilter, downloadType, FilterTypes::DownloadFilter::All); + VALUE_DEFAULT(FilterTypes::LocalScoreFilter, localScoreType, FilterTypes::LocalScoreFilter::All); + VALUE_DEFAULT(FilterTypes::RankedFilter, rankedType, FilterTypes::RankedFilter::ShowAll); + VALUE_DEFAULT(FilterTypes::DifficultyFilter, difficultyFilter, FilterTypes::DifficultyFilter::All); + VALUE_DEFAULT(FilterTypes::CharFilter, charFilter, FilterTypes::CharFilter::All); + VALUE_DEFAULT(FilterTypes::Requirement, modRequirement, FilterTypes::Requirement::Any); + + VALUE_DEFAULT(float, minLength, 0); + VALUE_DEFAULT(float, maxLength, 900); + VALUE_DEFAULT(float, minNJS, 0); + VALUE_DEFAULT(float, maxNJS, NJS_FILTER_MAX); + VALUE_DEFAULT(float, minNPS, 0); + VALUE_DEFAULT(float, maxNPS, NPS_FILTER_MAX); + + VALUE_DEFAULT(float, minStars, 0); + VALUE_DEFAULT(float, maxStars, STAR_FILTER_MAX); + VALUE_DEFAULT(int, minUploadDate, BEATSAVER_EPOCH); + VALUE_DEFAULT(int, minUploadDateInMonths, 0); + VALUE_DEFAULT(float, minRating, 0); + VALUE_DEFAULT(int, minVotes, 0); + + VECTOR_DEFAULT(std::string, uploaders, {}); + VALUE_DEFAULT(bool, uploadersBlackList, false); + + public: + SongDetailsCache::MapCharacteristic charFilterPreprocessed = SongDetailsCache::MapCharacteristic::Custom; + SongDetailsCache::MapDifficulty difficultyFilterPreprocessed = SongDetailsCache::MapDifficulty::Easy; + + bool isDefaultPreprocessed = true; + + // @brief Checks if the profile is the default profile (no filters) + bool IsDefault(); + + // @brief Recalculates preprocessed values + void RecalculatePreprocessedValues(); + + // @brief Loads the profile from the mod config + void LoadFromConfig(); + + // @brief Saves the profile to the mod config + void SaveToConfig(); + + // @brief Saves the profile to a preset + // @param presetName The name of the preset to save + // @return True if the preset was saved successfully + bool SaveToPreset(std::string presetName) const; + + // @brief Gets a list of all available presets + // @return A list of all available presets + static std::vector GetPresetList(); + + // @brief Loads the profile from a preset + // @param presetName The name of the preset to load + // @return The loaded profile + static std::optional LoadFromPreset(std::string presetName); + ) +} - //Mods - FilterOptions::RequirementType modRequirement = FilterOptions::RequirementType::Any; -}; diff --git a/include/PluginConfig.hpp b/include/PluginConfig.hpp index 36f9ec4..d5c51ba 100644 --- a/include/PluginConfig.hpp +++ b/include/PluginConfig.hpp @@ -1,15 +1,127 @@ #pragma once -#include "config-utils/shared/config-utils.hpp" - #include #include -#include "FilterOptions.hpp" -#include "main.hpp" +#include "song-details/shared/Data/MapCharacteristic.hpp" +#include "song-details/shared/Data/MapDifficulty.hpp" +#include "song-details/shared/Data/RankedStates.hpp" +#include "song-details/shared/Data/SongDifficulty.hpp" -DECLARE_CONFIG(PluginConfig, +#include "config-utils/shared/config-utils.hpp" + +static inline const float SONG_LENGTH_FILTER_MAX = 15.0f; +static inline const float STAR_FILTER_MAX = 18.0f; +static inline const float NJS_FILTER_MAX = 25.0f; +static inline const float NPS_FILTER_MAX = 12.0f; +static inline const int64_t BEATSAVER_EPOCH = 1525136400; +static inline const std::chrono::system_clock::time_point BEATSAVER_EPOCH_TIME_POINT{std::chrono::seconds(BEATSAVER_EPOCH)}; + +namespace FilterTypes { + enum class DownloadFilter + { + All, + OnlyDownloaded, + HideDownloaded + }; + enum class LocalScoreFilter + { + All, + HidePassed, + OnlyPassed + }; + enum class RankedFilter + { + ShowAll, + ScoreSaberRanked, + BeatLeaderRanked, + ScoreSaberQualified, + BeatLeaderQualified + }; + enum class DifficultyFilter + { + All, + Easy, + Normal, + Hard, + Expert, + ExpertPlus + }; + enum class CharFilter + { + All, + Custom, + Standard, + OneSaber, + NoArrows, + NinetyDegrees, + ThreeSixtyDegrees, + LightShow, + Lawless, + }; + + enum class Requirement { + Any, + NoodleExtensions, + MappingExtensions, + Chroma, + Cinema, + None + }; + + enum class SortMode { + Newest, + Oldest, + Latest_Ranked, + Most_Stars, + Least_Stars, + Best_rated, + Worst_rated + }; + enum class PreferredLeaderBoard { + ScoreSaber = 0, + BeatLeader = 1 + }; + +} + +// Map for characteristics +static const std::unordered_map CHARACTERISTIC_MAP = { + {FilterTypes::CharFilter::Custom, SongDetailsCache::MapCharacteristic::Custom}, + {FilterTypes::CharFilter::Standard, SongDetailsCache::MapCharacteristic::Standard}, + {FilterTypes::CharFilter::OneSaber, SongDetailsCache::MapCharacteristic::OneSaber}, + {FilterTypes::CharFilter::NoArrows, SongDetailsCache::MapCharacteristic::NoArrows}, + {FilterTypes::CharFilter::NinetyDegrees, SongDetailsCache::MapCharacteristic::NinetyDegree}, + {FilterTypes::CharFilter::ThreeSixtyDegrees, SongDetailsCache::MapCharacteristic::ThreeSixtyDegree}, + {FilterTypes::CharFilter::LightShow, SongDetailsCache::MapCharacteristic::LightShow}, + {FilterTypes::CharFilter::Lawless, SongDetailsCache::MapCharacteristic::Lawless} +}; + +// Map for difficulties +static const std::unordered_map DIFFICULTY_MAP = { + {FilterTypes::DifficultyFilter::Easy, SongDetailsCache::MapDifficulty::Easy}, + {FilterTypes::DifficultyFilter::Normal, SongDetailsCache::MapDifficulty::Normal}, + {FilterTypes::DifficultyFilter::Hard, SongDetailsCache::MapDifficulty::Hard}, + {FilterTypes::DifficultyFilter::Expert, SongDetailsCache::MapDifficulty::Expert}, + {FilterTypes::DifficultyFilter::ExpertPlus, SongDetailsCache::MapDifficulty::ExpertPlus} +}; + +// Map for ranked states +static const std::unordered_map RANK_MAP = { + {FilterTypes::RankedFilter::ScoreSaberRanked, SongDetailsCache::RankedStates::ScoresaberRanked}, + {FilterTypes::RankedFilter::BeatLeaderRanked, SongDetailsCache::RankedStates::BeatleaderRanked}, + {FilterTypes::RankedFilter::ScoreSaberQualified, SongDetailsCache::RankedStates::ScoresaberQualified}, + {FilterTypes::RankedFilter::BeatLeaderQualified, SongDetailsCache::RankedStates::BeatleaderQualified} +}; + +// Map for preferred leaderboard +static const std::unordered_map LEADERBOARD_MAP = { + {"Scoresaber", FilterTypes::PreferredLeaderBoard::ScoreSaber}, + {"Beatleader", FilterTypes::PreferredLeaderBoard::BeatLeader} +}; + +DECLARE_CONFIG(PluginConfig, CONFIG_VALUE(ReturnToBSS, bool, "Return to BSS from Solo", true); CONFIG_VALUE(LoadSongPreviews, bool, "Load song previews", true); CONFIG_VALUE(SmallerFontSize, bool, "Smaller font size", true); @@ -19,14 +131,14 @@ DECLARE_CONFIG(PluginConfig, CONFIG_VALUE(MinLength, float, "Minimum Song Length", 0); CONFIG_VALUE(MaxLength, float, "Maximum Song Length", 900); CONFIG_VALUE(MinNJS, float, "Minimum Note Jump Speed", 0); - CONFIG_VALUE(MaxNJS, float, "Maximum Note Jump Speed", 25); + CONFIG_VALUE(MaxNJS, float, "Maximum Note Jump Speed", SONG_LENGTH_FILTER_MAX); CONFIG_VALUE(MinNPS, float, "Minimum Notes Per Second", 0); - CONFIG_VALUE(MaxNPS, float, "Maximum Note Per Second", 12); + CONFIG_VALUE(MaxNPS, float, "Maximum Note Per Second", NPS_FILTER_MAX); CONFIG_VALUE(RankedType, int, "Ranked Type", 0); CONFIG_VALUE(MinStars, float, "Minimum Ranked Stars", 0); - CONFIG_VALUE(MaxStars, float, "Maximum Ranked Stars", 18); + CONFIG_VALUE(MaxStars, float, "Maximum Ranked Stars", STAR_FILTER_MAX); CONFIG_VALUE(MinUploadDateInMonths, int, "Minimum Upload Date In Months", 0); //TEMPORARY UNTIL I FIX CONVERTING UNIX -> MONTHS SINCE BEAT SAVER - CONFIG_VALUE(MinUploadDate, int, "Minimum Upload Date", FilterOptions::BEATSAVER_EPOCH); + CONFIG_VALUE(MinUploadDate, int, "Minimum Upload Date", BEATSAVER_EPOCH); CONFIG_VALUE(MinRating, float, "Minimum Rating", 0); CONFIG_VALUE(MinVotes, int, "Minimum Votes", 0); CONFIG_VALUE(DifficultyType, int, "Difficulty Type", 0); diff --git a/include/UI/ViewControllers/FilterView.hpp b/include/UI/ViewControllers/FilterView.hpp index b2ebec5..ebadc75 100644 --- a/include/UI/ViewControllers/FilterView.hpp +++ b/include/UI/ViewControllers/FilterView.hpp @@ -31,7 +31,13 @@ DECLARE_CLASS_CODEGEN(BetterSongSearch::UI::ViewControllers, FilterViewControlle DECLARE_INSTANCE_METHOD(void, CloseSponsorModal); DECLARE_INSTANCE_METHOD(void, OpenSponsorsLink); DECLARE_INSTANCE_METHOD(void, TryToDownloadDataset); - + + // @brief Update the global state from the filter settings withouth updating the UI + DECLARE_INSTANCE_METHOD(void, UpdateLocalState); + + DECLARE_INSTANCE_METHOD(void, ForceRefreshUI); + + DECLARE_INSTANCE_METHOD(void, ForceFormatValues); // Header buttons DECLARE_INSTANCE_METHOD(void, ClearFilters); diff --git a/include/UI/ViewControllers/SongList.hpp b/include/UI/ViewControllers/SongList.hpp index 4cb53ce..3118e1b 100644 --- a/include/UI/ViewControllers/SongList.hpp +++ b/include/UI/ViewControllers/SongList.hpp @@ -45,6 +45,7 @@ DECLARE_OVERRIDE_METHOD(retval, method, il2cpp_utils::il2cpp_type_check::MetadataGetter::get(), __VA_ARGS__) #endif +using namespace BetterSongSearch; #define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() @@ -66,30 +67,29 @@ namespace BetterSongSearch::UI { inline static std::vector sortedSongList; // State variables to be globally accessible - inline static SortMode currentSort = SortMode::Newest; + inline static FilterTypes::SortMode currentSort = FilterTypes::SortMode::Newest; inline static std::string currentSearch = ""; inline static std::unordered_set songsWithScores; - inline static FilterOptions filterOptions; - inline static FilterOptionsCache filterOptionsCache; + inline static FilterProfile filterOptions; + inline static FilterProfile filterOptionsCache; /// @brief Player data model to get the scores inline static UnityW playerDataModel = nullptr; // Preferred Leaderboard - inline static PreferredLeaderBoard preferredLeaderboard = PreferredLeaderBoard::ScoreSaber; + inline static FilterTypes::PreferredLeaderBoard preferredLeaderboard = FilterTypes::PreferredLeaderBoard::ScoreSaber; /// @brief Song data is loaded inline static bool loaded = false; /// @brief Song data failed to load inline static bool failed = false; - // Song data is loading + // Song data is loading inline static bool loading = false; /// @brief Flag to say that the song list needs a refresh when the user opens the BSS because the data got updated inline static bool needsRefresh = false; // Song list is invalid (means that we should not touch anything in the song list rn) inline static bool invalid = false; - }; #define PROP_GET(jsonName, varName) \ @@ -127,7 +127,7 @@ namespace BetterSongSearch::UI { } using SortFunction = std::function< float (SongDetailsCache::Song const*)>; -extern std::unordered_map sortFunctionMap; +extern std::unordered_map sortFunctionMap; #ifdef HotReload DECLARE_CLASS_CUSTOM_INTERFACES(BetterSongSearch::UI::ViewControllers, SongListController, BSML::HotReloadViewController, classof(HMUI::TableView::IDataSource*), @@ -228,7 +228,7 @@ DECLARE_CLASS_CODEGEN_INTERFACES(BetterSongSearch::UI::ViewControllers, SongList BetterSongSearch::Util::RatelimitCoroutine* limitedUpdateSearchedSongsList = nullptr; - void SortAndFilterSongs(SortMode sort, std::string_view search, bool resetTable); + void SortAndFilterSongs(FilterTypes::SortMode sort, std::string_view search, bool resetTable); void ResetTable(); const SongDetailsCache::Song* currentSong = nullptr; void UpdateDetails(); @@ -236,10 +236,10 @@ DECLARE_CLASS_CODEGEN_INTERFACES(BetterSongSearch::UI::ViewControllers, SongList // Temp values std::string search = ""; - SortMode sort = (SortMode) 0; + FilterTypes::SortMode sort = (FilterTypes::SortMode) 0; // Prev values std::string prevSearch = ""; - SortMode prevSort = (SortMode) 0; + FilterTypes::SortMode prevSort = (FilterTypes::SortMode) 0; bool filterChanged = true; diff --git a/include/Util/TextUtil.hpp b/include/Util/TextUtil.hpp index 7f26a21..f0d628b 100644 --- a/include/Util/TextUtil.hpp +++ b/include/Util/TextUtil.hpp @@ -8,6 +8,9 @@ namespace BetterSongSearch::Util { // this hurts std::vector split(std::string_view buffer, const std::string_view delimeter = " "); + // @brief Joins a vector of strings into a single string, separated by a delimeter + std::string join(std::vector strings, const std::string_view delimeter = " "); + /** * Removes special characters from a string */ diff --git a/include/internal_macros.hpp b/include/internal_macros.hpp deleted file mode 100644 index ccdf7ba..0000000 --- a/include/internal_macros.hpp +++ /dev/null @@ -1 +0,0 @@ -// FIXME: Dummy file cause Red included it for some reason, to be removed in the next release \ No newline at end of file diff --git a/include/main.hpp b/include/main.hpp index 9143659..9837af6 100644 --- a/include/main.hpp +++ b/include/main.hpp @@ -9,4 +9,6 @@ #include "beatsaber-hook/shared/utils/hooking.hpp" #include "beatsaber-hook/shared/config/config-utils.hpp" #include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" -#include "UnityEngine/GameObject.hpp" \ No newline at end of file +#include "UnityEngine/GameObject.hpp" + +static inline modloader::ModInfo modInfo = {MOD_ID, VERSION, GIT_COMMIT}; // Stores the ID and version of our mod, and is sent to the modloader upon startup diff --git a/src/FilterOptions.cpp b/src/FilterOptions.cpp new file mode 100644 index 0000000..b165b2c --- /dev/null +++ b/src/FilterOptions.cpp @@ -0,0 +1,167 @@ +#include "FilterOptions.hpp" + +#include "Util/TextUtil.hpp" +#include "main.hpp" +#include "logging.hpp" + +using namespace rapidjson; +using namespace UnityEngine; +using namespace BetterSongSearch::Util; + + +bool BetterSongSearch::FilterProfile::IsDefault(){ + return ( + downloadType == FilterTypes::DownloadFilter::All && + localScoreType == FilterTypes::LocalScoreFilter::All && + (maxLength / 60 >= SONG_LENGTH_FILTER_MAX) && + (minLength == 0) && + minNJS == 0 && + maxNJS >= NJS_FILTER_MAX && + minNPS == 0 && + maxNPS >= NPS_FILTER_MAX && + rankedType == FilterTypes::RankedFilter::ShowAll && + minStars == 0 && + maxStars >= STAR_FILTER_MAX && + minUploadDateInMonths == 0 && + minRating == 0 && + minVotes == 0 && + uploaders.empty() && + difficultyFilter == FilterTypes::DifficultyFilter::All && + charFilter == FilterTypes::CharFilter::All && + modRequirement == FilterTypes::Requirement::Any + ); +} + +void BetterSongSearch::FilterProfile::LoadFromConfig() { + downloadType = (FilterTypes::DownloadFilter) getPluginConfig().DownloadType.GetValue(); + localScoreType = (FilterTypes::LocalScoreFilter) getPluginConfig().LocalScoreType.GetValue(); + minLength = getPluginConfig().MinLength.GetValue(); + maxLength = getPluginConfig().MaxLength.GetValue(); + minNJS = getPluginConfig().MinNJS.GetValue(); + maxNJS = getPluginConfig().MaxNJS.GetValue(); + minNPS = getPluginConfig().MinNPS.GetValue(); + maxNPS = getPluginConfig().MaxNPS.GetValue(); + rankedType = (FilterTypes::RankedFilter) getPluginConfig().RankedType.GetValue(); + minStars = getPluginConfig().MinStars.GetValue(); + maxStars = getPluginConfig().MaxStars.GetValue(); + minUploadDate = getPluginConfig().MinUploadDate.GetValue(); + minRating = getPluginConfig().MinRating.GetValue(); + minVotes = getPluginConfig().MinVotes.GetValue(); + charFilter = (FilterTypes::CharFilter) getPluginConfig().CharacteristicType.GetValue(); + difficultyFilter = (FilterTypes::DifficultyFilter) getPluginConfig().DifficultyType.GetValue(); + modRequirement = (FilterTypes::Requirement) getPluginConfig().RequirementType.GetValue(); + minUploadDateInMonths = getPluginConfig().MinUploadDateInMonths.GetValue(); + + // Custom string loader + auto uploadersString = getPluginConfig().Uploaders.GetValue(); + if (!uploadersString.empty()) { + if (uploadersString[0] == '!') { + uploadersString.erase(0,1); + uploadersBlackList = true; + } else { + uploadersBlackList = false; + } + uploaders = split(toLower(uploadersString), " "); + } else { + uploaders.clear(); + } +} + +void BetterSongSearch::FilterProfile::SaveToConfig() { + getPluginConfig().DownloadType.SetValue((int) downloadType, false); + getPluginConfig().LocalScoreType.SetValue((int) localScoreType, false); + getPluginConfig().MinLength.SetValue(minLength, false); + getPluginConfig().MaxLength.SetValue(maxLength, false); + getPluginConfig().MinNJS.SetValue(minNJS, false); + getPluginConfig().MaxNJS.SetValue(maxNJS, false); + getPluginConfig().MinNPS.SetValue(minNPS, false); + getPluginConfig().MaxNPS.SetValue(maxNPS, false); + getPluginConfig().RankedType.SetValue((int) rankedType, false); + getPluginConfig().MinStars.SetValue(minStars, false); + getPluginConfig().MaxStars.SetValue(maxStars, false); + getPluginConfig().MinUploadDate.SetValue(minUploadDate, false); + getPluginConfig().MinRating.SetValue(minRating, false); + getPluginConfig().MinVotes.SetValue(minVotes, false); + getPluginConfig().CharacteristicType.SetValue((int) charFilter, false); + getPluginConfig().DifficultyType.SetValue((int) difficultyFilter, false); + getPluginConfig().RequirementType.SetValue((int) modRequirement, false); + getPluginConfig().MinUploadDateInMonths.SetValue(minUploadDateInMonths, false); + + getPluginConfig().Uploaders.SetValue((uploadersBlackList ? "!" : "") + join(uploaders, " "), false); + + getPluginConfig().Save(); +} + +std::optional BetterSongSearch::FilterProfile::LoadFromPreset(std::string presetName){ + std::string presetsDir = getDataDir(modInfo) + "/Presets/"; + + // Ensure the directory exists + if (!direxists(presetsDir)) { + mkpath(presetsDir); + } + + std::string path = presetsDir + presetName + ".json"; + + if (!fileexists(path)) { + return std::nullopt; + } + + try { + return ReadFromFile(path); + } catch (const std::exception &e) { + ERROR("Failed to load preset: {}", e.what()); + return std::nullopt; + } +} + +bool BetterSongSearch::FilterProfile::SaveToPreset(std::string presetName) const { + std::string presetsDir = getDataDir(modInfo) + "/Presets/"; + + // Ensure the directory exists + if (!direxists(presetsDir)) { + mkpath(presetsDir); + } + + std::string path = presetsDir + presetName + ".json"; + + try { + WriteToFile(path, *this); + return true; + } catch (const std::exception &e) { + ERROR("Failed to save preset: {}", e.what()); + return false; + } +} + +std::vector BetterSongSearch::FilterProfile::GetPresetList() { + std::vector presetNames; + + std::string presetsDir = getDataDir(modInfo) + "/Presets/"; + + // Ensure the directory exists + if (!direxists(presetsDir)) { + mkpath(presetsDir); + } + + if(!std::filesystem::is_directory(presetsDir)) return presetNames; + + std::error_code ec; + auto directory_iterator = std::filesystem::directory_iterator(presetsDir, std::filesystem::directory_options::none, ec); + for (auto const& entry : directory_iterator) { + if(!entry.is_regular_file()) continue; + std::string file_extension = entry.path().extension().string(); + std::string raw_file_name = entry.path().filename().replace_extension().string(); + if (file_extension == ".json") presetNames.push_back(raw_file_name); + } + std::sort(presetNames.begin(), presetNames.end(), [](std::string& a, std::string& b) { + return a < b; + }); + + return presetNames; +} + +void BetterSongSearch::FilterProfile::RecalculatePreprocessedValues(){ + charFilterPreprocessed = CHARACTERISTIC_MAP.at(charFilter); + difficultyFilterPreprocessed = DIFFICULTY_MAP.at(difficultyFilter); + isDefaultPreprocessed = IsDefault(); +} diff --git a/src/UI/Modals/Settings.cpp b/src/UI/Modals/Settings.cpp index f17a4de..f8e4a2c 100644 --- a/src/UI/Modals/Settings.cpp +++ b/src/UI/Modals/Settings.cpp @@ -69,18 +69,18 @@ void Modals::Settings::set_smallerFontSize(bool value) { StringW Modals::Settings::get_preferredLeaderboard() { // Preferred Leaderboard std::string preferredLeaderboard = getPluginConfig().PreferredLeaderboard.GetValue(); - if (leaderBoardMap.contains(preferredLeaderboard)) { + if (LEADERBOARD_MAP.contains(preferredLeaderboard)) { return preferredLeaderboard; } else { - DataHolder::preferredLeaderboard = PreferredLeaderBoard::ScoreSaber; + DataHolder::preferredLeaderboard = FilterTypes::PreferredLeaderBoard::ScoreSaber; getPluginConfig().PreferredLeaderboard.SetValue("Scoresaber"); return "Scoresaber"; } } void Modals::Settings::set_preferredLeaderboard(StringW value) { - if (leaderBoardMap.contains(value)) { - DataHolder::preferredLeaderboard = leaderBoardMap.at(value); + if (LEADERBOARD_MAP.contains(value)) { + DataHolder::preferredLeaderboard = LEADERBOARD_MAP.at(value); getPluginConfig().PreferredLeaderboard.SetValue(value); auto controller = fcInstance->SongListController; controller->filterChanged = true; diff --git a/src/UI/ViewControllers/FilterView.cpp b/src/UI/ViewControllers/FilterView.cpp index 2386737..ca6aa88 100644 --- a/src/UI/ViewControllers/FilterView.cpp +++ b/src/UI/ViewControllers/FilterView.cpp @@ -25,12 +25,12 @@ using namespace BetterSongSearch::Util; using namespace BetterSongSearch::UI; using namespace BetterSongSearch::UI::Util::BSMLStuff; -static const std::chrono::system_clock::time_point BEATSAVER_EPOCH_TIME_POINT{std::chrono::seconds(FilterOptions::BEATSAVER_EPOCH)}; + DEFINE_TYPE(BetterSongSearch::UI::ViewControllers, FilterViewController); #define coro(coroutine) BSML::SharedCoroutineStarter::get_instance()->StartCoroutine(custom_types::Helpers::CoroutineHelper::New(coroutine)) -#define SAVE_STRING_CONFIG(value, options, configName, filterProperty ) \ +#define SAVE_STRING_CONFIG(value, options, configName ) \ if (value != nullptr) { \ int index = get_##options()->IndexOf(reinterpret_cast (value.convert())); \ if (index < 0 ) { \ @@ -39,24 +39,21 @@ DEFINE_TYPE(BetterSongSearch::UI::ViewControllers, FilterViewController); if (index != getPluginConfig().configName.GetValue()) { \ filtersChanged = true; \ getPluginConfig().configName.SetValue(index); \ - DataHolder::filterOptions.filterProperty = (typeof(DataHolder::filterOptions.filterProperty)) index; \ } \ }\ } -#define SAVE_NUMBER_CONFIG(value, configName, filterProperty) \ +#define SAVE_NUMBER_CONFIG(value, configName) \ if (value != getPluginConfig().configName.GetValue()) { \ filtersChanged = true; \ getPluginConfig().configName.SetValue(value); \ - DataHolder::filterOptions.filterProperty = (typeof(DataHolder::filterOptions.filterProperty)) value; \ } \ // TODO: Fix saving last saved -#define SAVE_INTEGER_CONFIG(value, configName, filterProperty) \ +#define SAVE_INTEGER_CONFIG(value, configName) \ if (static_cast(value) != getPluginConfig().configName.GetValue()) { \ filtersChanged = true; \ getPluginConfig().configName.SetValue(static_cast(value)); \ - DataHolder::filterOptions.filterProperty = static_cast(value); \ } \ @@ -67,45 +64,29 @@ custom_types::Helpers::Coroutine ViewControllers::FilterViewController::_UpdateF // WARNING: There is a bug with bsml update, it runs before the value is changed for some reason bool filtersChanged = false; -// if (this->existingSongs != nullptr) { -// int index = get_downloadedFilterOptions()->IndexOf(reinterpret_cast (this->existingSongs.convert())); -// if (index < 0) { -// getLogger().fmtLog("WE HAVE A BUG WITH SAVING VALUE {}", -// (std::string) this->existingSongs); -// } -// else { -// if (index != getPluginConfig().DownloadType.GetValue()) { -// filtersChanged = true; -// getPluginConfig().DownloadType.SetValue(index); -// DataHolder::filterOptions.downloadType = (typeof(DataHolder::filterOptions.downloadType)) index; -// } -// } -// } - SAVE_STRING_CONFIG(this->existingSongs, downloadedFilterOptions, DownloadType, downloadType); - SAVE_STRING_CONFIG(this->existingScore, scoreFilterOptions, LocalScoreType , localScoreType); - - SAVE_STRING_CONFIG(this->characteristic, characteristics, CharacteristicType, charFilter); - SAVE_STRING_CONFIG(this->difficulty, difficulties, DifficultyType, difficultyFilter); - SAVE_STRING_CONFIG(this->rankedState, rankedFilterOptions, RankedType, rankedType); - SAVE_STRING_CONFIG(this->mods, modOptions, RequirementType, modRequirement); - SAVE_NUMBER_CONFIG(this->minimumNjs, MinNJS, minNJS); - SAVE_NUMBER_CONFIG(this->maximumNjs,MaxNJS, maxNJS); - SAVE_NUMBER_CONFIG(this->minimumNps, MinNPS, minNPS); - SAVE_NUMBER_CONFIG(this->maximumNps,MaxNPS, maxNPS); - SAVE_NUMBER_CONFIG(this->minimumStars,MinStars, minStars); - SAVE_NUMBER_CONFIG(this->maximumStars,MaxStars, maxStars); - SAVE_NUMBER_CONFIG(this->minimumRating, MinRating, minRating); - SAVE_INTEGER_CONFIG(this->minimumVotes,MinVotes, minVotes); + + SAVE_STRING_CONFIG(this->existingSongs, downloadedFilterOptions, DownloadType); + SAVE_STRING_CONFIG(this->existingScore, scoreFilterOptions, LocalScoreType); + + SAVE_STRING_CONFIG(this->characteristic, characteristics, CharacteristicType); + SAVE_STRING_CONFIG(this->difficulty, difficulties, DifficultyType); + SAVE_STRING_CONFIG(this->rankedState, rankedFilterOptions, RankedType); + SAVE_STRING_CONFIG(this->mods, modOptions, RequirementType); + SAVE_NUMBER_CONFIG(this->minimumNjs, MinNJS); + SAVE_NUMBER_CONFIG(this->maximumNjs, MaxNJS); + SAVE_NUMBER_CONFIG(this->minimumNps, MinNPS); + SAVE_NUMBER_CONFIG(this->maximumNps, MaxNPS); + SAVE_NUMBER_CONFIG(this->minimumStars, MinStars); + SAVE_NUMBER_CONFIG(this->maximumStars, MaxStars); + SAVE_NUMBER_CONFIG(this->minimumRating, MinRating); + SAVE_INTEGER_CONFIG(this->minimumVotes, MinVotes); // Special case for saving date if (this->hideOlderThan != getPluginConfig().MinUploadDateInMonths.GetValue()) { filtersChanged = true; - auto timestamp = GetDateAfterMonths(DataHolder::filterOptions.BEATSAVER_EPOCH, this->hideOlderThan).count(); - - DataHolder::filterOptions.minUploadDate = timestamp; - DataHolder::filterOptions.minUploadDateInMonths = this->hideOlderThan; - DEBUG("Date {}", GetDateAfterMonths(DataHolder::filterOptions.BEATSAVER_EPOCH, this->hideOlderThan)); + auto timestamp = GetDateAfterMonths(BEATSAVER_EPOCH, this->hideOlderThan).count(); + DEBUG("Date {}", GetDateAfterMonths(BEATSAVER_EPOCH, this->hideOlderThan)); getPluginConfig().MinUploadDate.SetValue(timestamp); getPluginConfig().MinUploadDateInMonths.SetValue(this->hideOlderThan); @@ -116,7 +97,6 @@ custom_types::Helpers::Coroutine ViewControllers::FilterViewController::_UpdateF int seconds = minimumSongLength * 60; filtersChanged = true; - DataHolder::filterOptions.minLength = seconds; getPluginConfig().MinLength.SetValue(seconds); } @@ -125,7 +105,6 @@ custom_types::Helpers::Coroutine ViewControllers::FilterViewController::_UpdateF int seconds = maximumSongLength * 60; filtersChanged = true; - DataHolder::filterOptions.maxLength = seconds; getPluginConfig().MaxLength.SetValue(seconds); } @@ -135,48 +114,20 @@ custom_types::Helpers::Coroutine ViewControllers::FilterViewController::_UpdateF // Save to config getPluginConfig().Uploaders.SetValue(this->uploadersString); - - // Apply to filters - std::string copy = uploadersString; - if (copy.size() > 0) { - if (copy[0] == '!') { - copy.erase(0,1); - DataHolder::filterOptions.uploadersBlackList = true; - } else { - DataHolder::filterOptions.uploadersBlackList = false; - } - DataHolder::filterOptions.uploaders = split(toLower(copy), " "); - } else { - DataHolder::filterOptions.uploaders.clear(); - } } - - std::function uploadersStringFormat = [](std::string value) { - bool blacklist = false; - if (value.size() > 0) { - if (value[0] == '!') { - value.erase(0,1); - blacklist = true; - } - } else { - return (std::string) ""; - } - auto uploaders = split(value, " "); - - return fmt::format("{} {} uploader", (blacklist ? "Hiding": "Show only"), uploaders.size(), (uploaders.size() == 1 ? "" : "s") ); - }; - if (filtersChanged) { DEBUG("Filters changed"); + + // Update filter options state + DataHolder::filterOptions.LoadFromConfig(); + auto controller = fcInstance->SongListController; controller->filterChanged = true; controller->SortAndFilterSongs(controller->sort, controller->search, true); } else { DEBUG("Filters did not change"); } - - } UnityEngine::Sprite* GetBGSprite(std::string str) @@ -199,29 +150,10 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo coro(this->_UpdateFilterSettings()); }, 0.2f); - INFO("Filter View contoller activated"); - - // Get settings and set stuff - this->existingSongs=this->get_downloadedFilterOptions()->get_Item((int) DataHolder::filterOptions.downloadType); - this->existingScore=this->get_scoreFilterOptions()->get_Item((int) DataHolder::filterOptions.localScoreType); - this->minimumSongLength=DataHolder::filterOptions.minLength / 60.0f; - this->maximumSongLength=DataHolder::filterOptions.maxLength / 60.0f; - this->minimumNjs = DataHolder::filterOptions.minNJS; - this->maximumNjs = DataHolder::filterOptions.maxNJS; - this->minimumNps = DataHolder::filterOptions.minNPS; - this->maximumNps = DataHolder::filterOptions.maxNPS; - this->minimumStars = DataHolder::filterOptions.minStars; - this->maximumStars = DataHolder::filterOptions.maxStars; - this->minimumRating = DataHolder::filterOptions.minRating; - this->minimumVotes = DataHolder::filterOptions.minVotes; - this->hideOlderThan = getPluginConfig().MinUploadDateInMonths.GetValue(); + INFO("Filter View controller activated"); - // Custom string loader - this->uploadersString = getPluginConfig().Uploaders.GetValue(); - this->characteristic = this->get_characteristics()->get_Item((int) DataHolder::filterOptions.charFilter); - this->difficulty = this->get_difficulties()->get_Item((int) DataHolder::filterOptions.difficultyFilter); - this->rankedState = this->get_rankedFilterOptions()->get_Item((int) DataHolder::filterOptions.rankedType); - this->mods = this->get_modOptions()->get_Item((int) DataHolder::filterOptions.modRequirement); + // Load the values from the config + UpdateLocalState(); // Create bsml view BSML::parse_and_construct(Assets::FilterView_bsml, this->get_transform(), this); @@ -229,15 +161,14 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo auto x = this->get_gameObject()->get_transform().cast(); x->set_offsetMax(UnityEngine::Vector2(20.0f, 22.0f)); - auto maxUploadDate = BetterSongSearch::GetMonthsSinceDate(FilterOptions::BEATSAVER_EPOCH); + auto maxUploadDate = BetterSongSearch::GetMonthsSinceDate(BEATSAVER_EPOCH); coro(BetterSongSearch::UI::Util::BSMLStuff::MergeSliders(this->get_gameObject())); - // Apply formatter functions Manually cause Red did not implement parsing for them in bsml std::function DateTimeToStr = [](float monthsSinceFirstUpload) { - auto val = BetterSongSearch::GetTimepointAfterMonths(FilterOptions::BEATSAVER_EPOCH,monthsSinceFirstUpload); + auto val = BetterSongSearch::GetTimepointAfterMonths(BEATSAVER_EPOCH,monthsSinceFirstUpload); return fmt::format("{:%b:%Y}", fmt::localtime(system_clock::to_time_t(val))); }; @@ -265,7 +196,7 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo } // Format other values - std::function minLengthSliderFormatFunction = [](float value) { + std::function minLengthSliderFormatFunction = [](float value) { float totalSeconds = value * 60; int minutes = ((int)totalSeconds % 3600) / 60; int seconds = (int)totalSeconds % 60; @@ -276,12 +207,12 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo minimumSongLengthSlider->formatter = minLengthSliderFormatFunction; // Max length format - std::function maxLengthSliderFormatFunction = [](float value) { + std::function maxLengthSliderFormatFunction = [](float value) { float totalSeconds = value * 60; int minutes = ((int)totalSeconds % 3600) / 60; int seconds = (int)totalSeconds % 60; - if (value >= DataHolder::filterOptions.SONG_LENGTH_FILTER_MAX) { + if (value >= SONG_LENGTH_FILTER_MAX) { return (std::string) "Unlimited"; } else { return fmt::format("{:02}:{:02}", minutes, seconds); @@ -290,17 +221,17 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo maximumSongLengthSlider->formatter = maxLengthSliderFormatFunction; // Min rating format - std::function minRatingSliderFormatFunction = [](float value) { + std::function minRatingSliderFormatFunction = [](float value) { return fmt::format("{:.1f}%", value*100); }; minimumRatingSlider->formatter = minRatingSliderFormatFunction; // NJS format - std::function minNJSFormat = [](float value) { + std::function minNJSFormat = [](float value) { return fmt::format("{:.1f}", value); }; - std::function maxNJSFormat = [](float value) { - if (value >= DataHolder::filterOptions.NJS_FILTER_MAX) { + std::function maxNJSFormat = [](float value) { + if (value >= NJS_FILTER_MAX) { return (std::string) "Unlimited"; } return fmt::format("{:.1f}", value); @@ -309,11 +240,11 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo maximumNjsSlider->formatter = maxNJSFormat; // NPS format - std::function minNPSFormat = [](float value) { + std::function minNPSFormat = [](float value) { return fmt::format("{:.1f}", value); }; - std::function maxNPSFormat = [](float value) { - if (value >= DataHolder::filterOptions.NPS_FILTER_MAX) { + std::function maxNPSFormat = [](float value) { + if (value >= NPS_FILTER_MAX) { return (std::string) "Unlimited"; } return fmt::format("{:.1f}", value); @@ -322,18 +253,18 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo maximumNpsSlider->formatter = maxNPSFormat; // Stars formatting - std::function minStarFormat = [](float value) { + std::function minStarFormat = [](float value) { return fmt::format("{:.1f}", value); }; - std::function maxStarFormat = [](float value) { - if (value >= FilterOptions::STAR_FILTER_MAX) { + std::function maxStarFormat = [](float value) { + if (value >= STAR_FILTER_MAX) { return (std::string) "Unlimited"; } return fmt::format("{:.1f}", value); }; minStarsSetting->formatter = minStarFormat; maxStarsSetting->formatter = maxStarFormat; - std::function minimumVotesFormat = [](float value) { + std::function minimumVotesFormat = [](float value) { return fmt::format("{}", (int) value); }; minimumVotesSlider->formatter = minimumVotesFormat; @@ -356,6 +287,18 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo uploadersStringControl->formatter = uploadersStringFormat; + ForceFormatValues(); + + // I hate BSML sometimes + auto m = modsRequirementDropdown->dropdown->____modalView; + m->get_transform().cast()->set_pivot(Vector2(0.5f, 0.3f)); + + #ifdef HotReload + fileWatcher->filePath = "/sdcard/FilterView.bsml"; + #endif +} + +void ViewControllers::FilterViewController::ForceFormatValues() { // Force format values FormatSliderSettingValue(this->minStarsSetting); FormatSliderSettingValue(this->maxStarsSetting); @@ -368,14 +311,6 @@ void ViewControllers::FilterViewController::DidActivate(bool firstActivation, bo FormatSliderSettingValue(this->minimumRatingSlider); FormatSliderSettingValue(this->minimumVotesSlider); FormatStringSettingValue(this->uploadersStringControl); - - // I hate BSML some times - auto m = modsRequirementDropdown->dropdown->____modalView; - m->get_transform().cast()->set_pivot(UnityEngine::Vector2(0.5f, 0.3f)); - - #ifdef HotReload - fileWatcher->filePath = "/sdcard/FilterView.bsml"; - #endif } void ViewControllers::FilterViewController::UpdateFilterSettings() @@ -399,67 +334,9 @@ void ViewControllers::FilterViewController::OpenSponsorsLink() } -// Top buttons -void ViewControllers::FilterViewController::ClearFilters() -{ - DEBUG("ClearFilters FIRED"); - - // Reset config - getPluginConfig().DownloadType.SetValue(getPluginConfig().DownloadType.GetDefaultValue()); - getPluginConfig().LocalScoreType.SetValue(getPluginConfig().LocalScoreType.GetDefaultValue()); - getPluginConfig().CharacteristicType.SetValue(getPluginConfig().CharacteristicType.GetDefaultValue()); - getPluginConfig().RankedType.SetValue(getPluginConfig().RankedType.GetDefaultValue()); - getPluginConfig().DifficultyType.SetValue(getPluginConfig().DifficultyType.GetDefaultValue()); - getPluginConfig().RequirementType.SetValue(getPluginConfig().RequirementType.GetDefaultValue()); - - getPluginConfig().MinLength.SetValue(getPluginConfig().MinLength.GetDefaultValue()); - getPluginConfig().MaxLength.SetValue(getPluginConfig().MaxLength.GetDefaultValue()); - getPluginConfig().MinNJS.SetValue(getPluginConfig().MinNJS.GetDefaultValue()); - getPluginConfig().MaxNJS.SetValue(getPluginConfig().MaxNJS.GetDefaultValue()); - getPluginConfig().MinNPS.SetValue(getPluginConfig().MinNPS.GetDefaultValue()); - getPluginConfig().MaxNPS.SetValue(getPluginConfig().MaxNPS.GetDefaultValue()); - getPluginConfig().MinStars.SetValue(getPluginConfig().MinStars.GetDefaultValue()); - getPluginConfig().MaxStars.SetValue(getPluginConfig().MaxStars.GetDefaultValue()); - getPluginConfig().MinUploadDate.SetValue(getPluginConfig().MinUploadDate.GetDefaultValue()); - getPluginConfig().MinRating.SetValue(getPluginConfig().MinRating.GetDefaultValue()); - getPluginConfig().MinVotes.SetValue(getPluginConfig().MinVotes.GetDefaultValue()); - getPluginConfig().Uploaders.SetValue(getPluginConfig().Uploaders.GetDefaultValue()); - getPluginConfig().MinUploadDateInMonths.SetValue(getPluginConfig().MinUploadDateInMonths.GetDefaultValue()); - getPluginConfig().MinUploadDate.SetValue(getPluginConfig().MinUploadDate.GetDefaultValue()); - - - // Load to dataHolder - DataHolder::filterOptions.downloadType = (FilterOptions::DownloadFilterType) getPluginConfig().DownloadType.GetValue(); - DataHolder::filterOptions.localScoreType = (FilterOptions::LocalScoreFilterType) getPluginConfig().LocalScoreType.GetValue(); - DataHolder::filterOptions.charFilter = (FilterOptions::CharFilterType) getPluginConfig().CharacteristicType.GetValue(); - DataHolder::filterOptions.rankedType = (FilterOptions::RankedFilterType) getPluginConfig().RankedType.GetValue(); - DataHolder::filterOptions.difficultyFilter = (FilterOptions::DifficultyFilterType) getPluginConfig().DifficultyType.GetValue(); - DataHolder::filterOptions.modRequirement = (FilterOptions::RequirementType) getPluginConfig().RequirementType.GetValue(); - DataHolder::filterOptions.minLength = getPluginConfig().MinLength.GetValue(); - DataHolder::filterOptions.maxLength = getPluginConfig().MaxLength.GetValue(); - DataHolder::filterOptions.minNJS = getPluginConfig().MinNJS.GetValue(); - DataHolder::filterOptions.maxNJS = getPluginConfig().MaxNJS.GetValue(); - DataHolder::filterOptions.minNPS = getPluginConfig().MinNPS.GetValue(); - DataHolder::filterOptions.maxNPS = getPluginConfig().MaxNPS.GetValue(); - DataHolder::filterOptions.minStars = getPluginConfig().MinStars.GetValue(); - DataHolder::filterOptions.maxStars = getPluginConfig().MaxStars.GetValue(); - DataHolder::filterOptions.minUploadDate = getPluginConfig().MinUploadDate.GetValue(); - DataHolder::filterOptions.minRating = getPluginConfig().MinRating.GetValue(); - DataHolder::filterOptions.minVotes = getPluginConfig().MinVotes.GetValue(); - auto uploadersString = getPluginConfig().Uploaders.GetValue(); - if (uploadersString.size() > 0) { - if (uploadersString[0] == '!') { - uploadersString.erase(0,1); - DataHolder::filterOptions.uploadersBlackList = true; - } else { - DataHolder::filterOptions.uploadersBlackList = false; - } - DataHolder::filterOptions.uploaders = split(toLower(uploadersString), " "); - } else { - DataHolder::filterOptions.uploaders.clear(); - } - // Load to UI +void ViewControllers::FilterViewController::UpdateLocalState() { + // Load from dataHolder this->existingSongs=this->get_downloadedFilterOptions()->get_Item((int) DataHolder::filterOptions.downloadType); this->existingScore=this->get_scoreFilterOptions()->get_Item((int) DataHolder::filterOptions.localScoreType); this->characteristic = this->get_characteristics()->get_Item((int) DataHolder::filterOptions.charFilter); @@ -477,10 +354,13 @@ void ViewControllers::FilterViewController::ClearFilters() this->maximumStars = DataHolder::filterOptions.maxStars; this->minimumRating = DataHolder::filterOptions.minRating; this->minimumVotes = DataHolder::filterOptions.minVotes; + + // TODO: Maybe save it to the preset too this->hideOlderThan = getPluginConfig().MinUploadDateInMonths.GetValue(); this->uploadersString = getPluginConfig().Uploaders.GetValue(); +} - +void ViewControllers::FilterViewController::ForceRefreshUI() { // Refresh UI // Force format values SetSliderSettingValue(this->minimumSongLengthSlider, this->minimumSongLength); @@ -501,6 +381,43 @@ void ViewControllers::FilterViewController::ClearFilters() characteristicDropdown->set_Value(reinterpret_cast (this->characteristic.convert())); difficultyDropdown->set_Value(reinterpret_cast (this->difficulty.convert())); modsRequirementDropdown->set_Value(reinterpret_cast (this->mods.convert())); +} + +// Top buttons +void ViewControllers::FilterViewController::ClearFilters() +{ + DEBUG("ClearFilters FIRED"); + + // Reset config + getPluginConfig().DownloadType.SetValue(getPluginConfig().DownloadType.GetDefaultValue()); + getPluginConfig().LocalScoreType.SetValue(getPluginConfig().LocalScoreType.GetDefaultValue()); + getPluginConfig().CharacteristicType.SetValue(getPluginConfig().CharacteristicType.GetDefaultValue()); + getPluginConfig().RankedType.SetValue(getPluginConfig().RankedType.GetDefaultValue()); + getPluginConfig().DifficultyType.SetValue(getPluginConfig().DifficultyType.GetDefaultValue()); + getPluginConfig().RequirementType.SetValue(getPluginConfig().RequirementType.GetDefaultValue()); + getPluginConfig().MinLength.SetValue(getPluginConfig().MinLength.GetDefaultValue()); + getPluginConfig().MaxLength.SetValue(getPluginConfig().MaxLength.GetDefaultValue()); + getPluginConfig().MinNJS.SetValue(getPluginConfig().MinNJS.GetDefaultValue()); + getPluginConfig().MaxNJS.SetValue(getPluginConfig().MaxNJS.GetDefaultValue()); + getPluginConfig().MinNPS.SetValue(getPluginConfig().MinNPS.GetDefaultValue()); + getPluginConfig().MaxNPS.SetValue(getPluginConfig().MaxNPS.GetDefaultValue()); + getPluginConfig().MinStars.SetValue(getPluginConfig().MinStars.GetDefaultValue()); + getPluginConfig().MaxStars.SetValue(getPluginConfig().MaxStars.GetDefaultValue()); + getPluginConfig().MinUploadDate.SetValue(getPluginConfig().MinUploadDate.GetDefaultValue()); + getPluginConfig().MinRating.SetValue(getPluginConfig().MinRating.GetDefaultValue()); + getPluginConfig().MinVotes.SetValue(getPluginConfig().MinVotes.GetDefaultValue()); + getPluginConfig().Uploaders.SetValue(getPluginConfig().Uploaders.GetDefaultValue()); + getPluginConfig().MinUploadDateInMonths.SetValue(getPluginConfig().MinUploadDateInMonths.GetDefaultValue()); + getPluginConfig().MinUploadDate.SetValue(getPluginConfig().MinUploadDate.GetDefaultValue()); + + // Load to dataHolder + DataHolder::filterOptions.LoadFromConfig(); + + // Refresh FilterView state from settings and DataHolder + UpdateLocalState(); + + // Force refresh UI + ForceRefreshUI(); DEBUG("Filters changed"); auto controller = fcInstance->SongListController; @@ -512,8 +429,6 @@ void ViewControllers::FilterViewController::ShowPresets() DEBUG("ShowPresets FIRED"); } - - // StringW ViewControllers::FilterViewController::DateTimeToStr(int d) { // // FilterView.hideOlderThanOptions[d].ToString("MMM yyyy", CultureInfo.InvariantCulture); // } diff --git a/src/UI/ViewControllers/SongList.cpp b/src/UI/ViewControllers/SongList.cpp index 4ed00d0..7241317 100644 --- a/src/UI/ViewControllers/SongList.cpp +++ b/src/UI/ViewControllers/SongList.cpp @@ -87,21 +87,18 @@ const std::vector CHAR_FILTER_OPTIONS = {"Any", "Custom", "Standard const std::vector DIFFS = {"Easy", "Normal", "Hard", "Expert", "Expert+"}; const std::vector REQUIREMENTS = {"Any", "Noodle Extensions", "Mapping Extensions", "Chroma", "Cinema"}; -const std::chrono::system_clock::time_point BEATSAVER_EPOCH_TIME_POINT{ - std::chrono::seconds(FilterOptions::BEATSAVER_EPOCH)}; - std::string prevSearch; -SortMode prevSort = (SortMode) 0; +FilterTypes::SortMode prevSort = FilterTypes::SortMode::Newest; using SortFunction = std::function; //////////////////// UTILS ////////////////////// bool BetterSongSearch::UI::MeetsFilter(const SongDetailsCache::Song *song) { - auto const &filterOptions = DataHolder::filterOptionsCache; + auto& filterOptions = DataHolder::filterOptionsCache; std::string songHash = song->hash(); - if (filterOptions.uploaders.size() != 0) { + if (!filterOptions.uploaders.empty()) { if (std::find(filterOptions.uploaders.begin(), filterOptions.uploaders.end(), removeSpecialCharacter(toLower(song->uploaderName()))) != filterOptions.uploaders.end()) { if (filterOptions.uploadersBlackList) @@ -121,23 +118,23 @@ bool BetterSongSearch::UI::MeetsFilter(const SongDetailsCache::Song *song) { if (((int) song->upvotes + (int) song->downvotes) < filterOptions.minVotes) return false; // Skip if not needed - if (filterOptions.localScoreType != FilterOptions::LocalScoreFilterType::All) { + if (filterOptions.localScoreType != FilterTypes::LocalScoreFilter::All) { bool hasLocalScore = false; if (DataHolder::songsWithScores.contains(songHash)) { hasLocalScore = true; } if (hasLocalScore) { - if (filterOptions.localScoreType == FilterOptions::LocalScoreFilterType::HidePassed) + if (filterOptions.localScoreType == FilterTypes::LocalScoreFilter::HidePassed) return false; } else { - if (filterOptions.localScoreType == FilterOptions::LocalScoreFilterType::OnlyPassed) + if (filterOptions.localScoreType == FilterTypes::LocalScoreFilter::OnlyPassed) return false; } } - if (filterOptions.rankedType != FilterOptions::RankedFilterType::ShowAll) { + if (filterOptions.rankedType != FilterTypes::RankedFilter::ShowAll) { // if not the ranked that we want, skip - if (!hasFlags(song->rankedStates, rankMap.at(filterOptions.rankedType))) { + if (!hasFlags(song->rankedStates, RANK_MAP.at(filterOptions.rankedType))) { return false; } } @@ -160,13 +157,13 @@ bool BetterSongSearch::UI::MeetsFilter(const SongDetailsCache::Song *song) { // This is the most heavy filter, check it last - if (filterOptions.downloadType != FilterOptions::DownloadFilterType::All) { + if (filterOptions.downloadType != FilterTypes::DownloadFilter::All) { bool downloaded = SongCore::API::Loading::GetLevelByHash(songHash) != nullptr; if (downloaded) { - if (filterOptions.downloadType == FilterOptions::DownloadFilterType::HideDownloaded) + if (filterOptions.downloadType == FilterTypes::DownloadFilter::HideDownloaded) return false; } else { - if (filterOptions.downloadType == FilterOptions::DownloadFilterType::OnlyDownloaded) + if (filterOptions.downloadType == FilterTypes::DownloadFilter::OnlyDownloaded) return false; } } @@ -177,20 +174,20 @@ bool BetterSongSearch::UI::MeetsFilter(const SongDetailsCache::Song *song) { bool BetterSongSearch::UI::DifficultyCheck(const SongDetailsCache::SongDifficulty *diff, const SongDetailsCache::Song *song) { auto const ¤tFilter = DataHolder::filterOptionsCache; - if (currentFilter.skipFilter) { + if (currentFilter.isDefaultPreprocessed) { return true; } - if (currentFilter.rankedType != FilterOptions::RankedFilterType::ShowAll) { + if (currentFilter.rankedType != FilterTypes::RankedFilter::ShowAll) { // if not the ranked that we want, skip - if (!hasFlags(song->rankedStates, rankMap.at(currentFilter.rankedType))) { + if (!hasFlags(song->rankedStates, RANK_MAP.at(currentFilter.rankedType))) { return false; } } // Min and max stars - if (currentFilter.maxStars != FilterOptions::STAR_FILTER_MAX) { + if (currentFilter.maxStars != STAR_FILTER_MAX) { if (getStars(diff) > currentFilter.maxStars) { return false; } @@ -201,13 +198,13 @@ bool BetterSongSearch::UI::DifficultyCheck(const SongDetailsCache::SongDifficult } } - if (currentFilter.difficultyFilter != FilterOptions::DifficultyFilterType::All) { + if (currentFilter.difficultyFilter != FilterTypes::DifficultyFilter::All) { if (diff->difficulty != currentFilter.difficultyFilterPreprocessed) { return false; } } - if (currentFilter.charFilter != FilterOptions::CharFilterType::All) { + if (currentFilter.charFilter != FilterTypes::CharFilter::All) { if (diff->characteristic != currentFilter.charFilterPreprocessed) { return false; } @@ -216,21 +213,21 @@ bool BetterSongSearch::UI::DifficultyCheck(const SongDetailsCache::SongDifficult if (diff->njs < currentFilter.minNJS || diff->njs > currentFilter.maxNJS) return false; - if (currentFilter.modRequirement != FilterOptions::RequirementType::Any) { + if (currentFilter.modRequirement != FilterTypes::Requirement::Any) { switch (currentFilter.modRequirement) { - case FilterOptions::RequirementType::Chroma: + case FilterTypes::Requirement::Chroma: if (!hasFlags(diff->mods, MapMods::Chroma)) return false; break; - case FilterOptions::RequirementType::Cinema: + case FilterTypes::Requirement::Cinema: if (!hasFlags(diff->mods, MapMods::Cinema)) return false; break; - case FilterOptions::RequirementType::MappingExtensions: + case FilterTypes::Requirement::MappingExtensions: if (!hasFlags(diff->mods, MapMods::MappingExtensions)) return false; break; - case FilterOptions::RequirementType::NoodleExtensions: + case FilterTypes::Requirement::NoodleExtensions: if (!hasFlags(diff->mods, MapMods::NoodleExtensions)) return false; break; - case FilterOptions::RequirementType::None: + case FilterTypes::Requirement::None: if (!((diff->mods & (MapMods::NE | MapMods::ME)) == MapMods::None)) return false; break; default: @@ -249,23 +246,23 @@ bool BetterSongSearch::UI::DifficultyCheck(const SongDetailsCache::SongDifficult } -std::unordered_map sortFunctionMap = { - {SortMode::Newest, [](const SongDetailsCache::Song *x) // Newest +std::unordered_map sortFunctionMap = { + {FilterTypes::SortMode::Newest, [](const SongDetailsCache::Song *x) // Newest { return (x->uploadTimeUnix); }}, - {SortMode::Oldest, [](const SongDetailsCache::Song *x) // Oldest + {FilterTypes::SortMode::Oldest, [](const SongDetailsCache::Song *x) // Oldest { return (std::numeric_limits::max() - x->uploadTimeUnix); }}, - {SortMode::Latest_Ranked, [](const SongDetailsCache::Song *x) // Latest Ranked + {FilterTypes::SortMode::Latest_Ranked, [](const SongDetailsCache::Song *x) // Latest Ranked { return (hasFlags(x->rankedStates, (SongDetailsCache::RankedStates::BeatleaderRanked | SongDetailsCache::RankedStates::ScoresaberRanked))) ? x->rankedChangeUnix : 0.0f; }}, - {SortMode::Most_Stars, [](const SongDetailsCache::Song *x) // Most Stars + {FilterTypes::SortMode::Most_Stars, [](const SongDetailsCache::Song *x) // Most Stars { return x->max([x](const auto &diff) { bool passesFilter = DifficultyCheck(&diff, x); @@ -276,7 +273,7 @@ std::unordered_map sortFunctionMap = { } }); }}, - {SortMode::Least_Stars, [](const SongDetailsCache::Song *x) // Least Stars + {FilterTypes::SortMode::Least_Stars, [](const SongDetailsCache::Song *x) // Least Stars { return 420.0f - x->min([x](const auto &diff) { bool passesFilter = DifficultyCheck(&diff, x); @@ -287,11 +284,11 @@ std::unordered_map sortFunctionMap = { } }); }}, - {SortMode::Best_rated, [](const SongDetailsCache::Song *x) // Best rated + {FilterTypes::SortMode::Best_rated, [](const SongDetailsCache::Song *x) // Best rated { return x->rating(); }}, - {SortMode::Worst_rated, [](const SongDetailsCache::Song *x)//Worst rated + {FilterTypes::SortMode::Worst_rated, [](const SongDetailsCache::Song *x)//Worst rated { return 420.0f - (x->rating() != 0 ? x->rating() : 420.0f); }} @@ -323,7 +320,7 @@ void ViewControllers::SongListController::_UpdateSearchedSongsList() { bool currentSearchChanged = prevSearch != search; // Take a snapshot of current filter options - DataHolder::filterOptionsCache.cache(DataHolder::filterOptions); + DataHolder::filterOptionsCache = DataHolder::filterOptions; DEBUG("SEARCHING Cache"); DEBUG("Sort: {}", SortToString((int) sort)); @@ -422,7 +419,7 @@ void ViewControllers::SongListController::_UpdateSearchedSongsList() { DEBUG("Filtering"); int totalSongs = DataHolder::songDetails->songs.size(); DataHolder::filteredSongList.clear(); - if (DataHolder::filterOptionsCache.skipFilter) { + if (DataHolder::filterOptionsCache.IsDefault()) { DEBUG("Filtering skipped"); DataHolder::filteredSongList.reserve(totalSongs); for (auto &song: DataHolder::songDetails->songs) { @@ -874,7 +871,7 @@ void ViewControllers::SongListController::DidActivate(bool firstActivation, bool auto sortMode = getPluginConfig().SortMode.GetValue(); if (sortMode < get_sortModeSelections()->get_Count()) { selectedSortMode = get_sortModeSelections()->get_Item(sortMode); - sort = (SortMode) sortMode; + sort = (FilterTypes::SortMode) sortMode; } BSML::parse_and_construct(Assets::SongList_bsml, this->get_transform(), this); @@ -1015,7 +1012,7 @@ custom_types::Helpers::Coroutine ViewControllers::SongListController::UpdateData bool filtersChanged = false; - SortMode sort = prevSort; + FilterTypes::SortMode sort = prevSort; if (selectedSortMode != nullptr) { int index = get_sortModeSelections()->IndexOf(reinterpret_cast (selectedSortMode.convert())); if (index < 0) {} @@ -1023,7 +1020,7 @@ custom_types::Helpers::Coroutine ViewControllers::SongListController::UpdateData if (index != getPluginConfig().SortMode.GetValue()) { filtersChanged = true; getPluginConfig().SortMode.SetValue(index); - sort = (SortMode) index; + sort = (FilterTypes::SortMode) index; } } } @@ -1387,15 +1384,12 @@ void ViewControllers::SongListController::UpdateSearch() { } -void -ViewControllers::SongListController::SortAndFilterSongs(SortMode sort, std::string_view const search, bool resetTable) { +void ViewControllers::SongListController::SortAndFilterSongs(FilterTypes::SortMode sort, std::string_view const search, bool resetTable) { // Skip if not active - if (get_isActiveAndEnabled() == false) { - return; - } + if (!get_isActiveAndEnabled()) return; + this->sort = sort; this->search = search; - this->UpdateSearchedSongsList(); } diff --git a/src/UI/ViewControllers/SongListCell.cpp b/src/UI/ViewControllers/SongListCell.cpp index 00e7bee..3ff075a 100644 --- a/src/UI/ViewControllers/SongListCell.cpp +++ b/src/UI/ViewControllers/SongListCell.cpp @@ -91,7 +91,7 @@ namespace BetterSongSearch::UI::ViewControllers }); // If most stars - if (DataHolder::currentSort == SortMode::Most_Stars) { + if (DataHolder::currentSort == FilterTypes::SortMode::Most_Stars) { std::stable_sort(sortedDiffs.begin(), sortedDiffs.end(), [entry](const DiffIndex& a, const DiffIndex& b) { auto diff1 = - a.stars; @@ -102,7 +102,7 @@ namespace BetterSongSearch::UI::ViewControllers }); } // If least stars - if (DataHolder::currentSort == SortMode::Least_Stars) { + if (DataHolder::currentSort == FilterTypes::SortMode::Least_Stars) { std::stable_sort(sortedDiffs.begin(), sortedDiffs.end(), [entry](const DiffIndex& a, const DiffIndex& b) { auto diff1 = a.stars > 0 ? a.stars: -420.0f; auto diff2 = b.stars > 0 ? b.stars: -420.0f; diff --git a/src/Util/SongUtil.cpp b/src/Util/SongUtil.cpp index ef7c046..ca8841b 100644 --- a/src/Util/SongUtil.cpp +++ b/src/Util/SongUtil.cpp @@ -60,17 +60,19 @@ namespace BetterSongSearch::Util { SongDetailsCache::RankedStates GetTargetedRankLeaderboardService(const SongDetailsCache::SongDifficulty* diff) { auto& rStates = diff->song().rankedStates; + FilterProfile filterOptions = UI::DataHolder::filterOptionsCache; + // If song is scoresaber ranked - if (hasFlags(rStates, SongDetailsCache::RankedStates::ScoresaberRanked) && + if (hasFlags(rStates, RankedStates::ScoresaberRanked) && // And Not Filtering by BeatLeader ranked - UI::DataHolder::filterOptionsCache.rankedType != FilterOptions::RankedFilterType::BeatLeaderRanked && + UI::DataHolder::filterOptionsCache.rankedType != FilterTypes::RankedFilter::BeatLeaderRanked && ( // Beatleader is not preferred leaderboard - BetterSongSearch::UI::DataHolder::preferredLeaderboard != PreferredLeaderBoard::BeatLeader || + BetterSongSearch::UI::DataHolder::preferredLeaderboard != FilterTypes::PreferredLeaderBoard::BeatLeader || // Song has no BeatLeader rank - !hasFlags(rStates, SongDetailsCache::RankedStates::BeatleaderRanked) || + !hasFlags(rStates, RankedStates::BeatleaderRanked) || // Filtering by SS ranked - UI::DataHolder::filterOptionsCache.rankedType == FilterOptions::RankedFilterType::ScoreSaberRanked + UI::DataHolder::filterOptionsCache.rankedType == FilterTypes::RankedFilter::ScoreSaberRanked ) ) { return SongDetailsCache::RankedStates::ScoresaberRanked; diff --git a/src/Util/TextUtil.cpp b/src/Util/TextUtil.cpp index b332503..6364c86 100644 --- a/src/Util/TextUtil.cpp +++ b/src/Util/TextUtil.cpp @@ -19,6 +19,17 @@ namespace BetterSongSearch::Util { return ret; } + std::string join(std::vector strings, const std::string_view delimeter) { + if (strings.empty()) return ""; + + std::string ret; + for (size_t i = 0; i < strings.size(); i++) { + ret += strings[i]; + if (i != strings.size() - 1) ret += delimeter; + } + return ret; + } + /** * Removes special characters from a string */ diff --git a/src/main.cpp b/src/main.cpp index 42a99dc..c520b6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,9 +29,7 @@ #define coro(coroutine) BSML::SharedCoroutineStarter::get_instance()->StartCoroutine(custom_types::Helpers::CoroutineHelper::New(coroutine)) using namespace BetterSongSearch::Util; - -inline modloader::ModInfo modInfo = {MOD_ID, VERSION, GIT_COMMIT}; // Stores the ID and version of our mod, and is sent to the modloader upon startup - +using namespace BetterSongSearch; // Called at the early stages of game loading BSS_EXPORT_FUNC void setup(CModInfo& info) { @@ -40,51 +38,23 @@ BSS_EXPORT_FUNC void setup(CModInfo& info) { modInfo.assign(info); getPluginConfig().Init(modInfo); + INFO("Completed setup!"); std::thread([]{ - auto& filterOptions = DataHolder::filterOptions; INFO("setting config values"); - filterOptions.downloadType = (FilterOptions::DownloadFilterType) getPluginConfig().DownloadType.GetValue(); - filterOptions.localScoreType = (FilterOptions::LocalScoreFilterType) getPluginConfig().LocalScoreType.GetValue(); - filterOptions.minLength = getPluginConfig().MinLength.GetValue(); - filterOptions.maxLength = getPluginConfig().MaxLength.GetValue(); - filterOptions.minNJS = getPluginConfig().MinNJS.GetValue(); - filterOptions.maxNJS = getPluginConfig().MaxNJS.GetValue(); - filterOptions.minNPS = getPluginConfig().MinNPS.GetValue(); - filterOptions.maxNPS = getPluginConfig().MaxNPS.GetValue(); - filterOptions.rankedType = (FilterOptions::RankedFilterType) getPluginConfig().RankedType.GetValue(); - filterOptions.minStars = getPluginConfig().MinStars.GetValue(); - filterOptions.maxStars = getPluginConfig().MaxStars.GetValue(); - filterOptions.minUploadDate = getPluginConfig().MinUploadDate.GetValue(); - filterOptions.minRating = getPluginConfig().MinRating.GetValue(); - filterOptions.minVotes = getPluginConfig().MinVotes.GetValue(); - filterOptions.charFilter = (FilterOptions::CharFilterType) getPluginConfig().CharacteristicType.GetValue(); - filterOptions.difficultyFilter = (FilterOptions::DifficultyFilterType) getPluginConfig().DifficultyType.GetValue(); - filterOptions.modRequirement = (FilterOptions::RequirementType) getPluginConfig().RequirementType.GetValue(); - filterOptions.minUploadDateInMonths = getPluginConfig().MinUploadDateInMonths.GetValue(); - + + // Load configs + DataHolder::filterOptions.LoadFromConfig(); + DataHolder::filterOptionsCache = DataHolder::filterOptions; + // Preferred Leaderboard std::string preferredLeaderboard = getPluginConfig().PreferredLeaderboard.GetValue(); - if (leaderBoardMap.contains(preferredLeaderboard)) { - DataHolder::preferredLeaderboard = leaderBoardMap.at(preferredLeaderboard); - } else { - DataHolder::preferredLeaderboard = PreferredLeaderBoard::ScoreSaber; - getPluginConfig().PreferredLeaderboard.SetValue("Scoresaber"); - } - - // Custom string loader - auto uploadersString = getPluginConfig().Uploaders.GetValue(); - if (!uploadersString.empty()) { - if (uploadersString[0] == '!') { - uploadersString.erase(0,1); - filterOptions.uploadersBlackList = true; - } else { - filterOptions.uploadersBlackList = false; - } - filterOptions.uploaders = split(toLower(uploadersString), " "); + if (LEADERBOARD_MAP.contains(preferredLeaderboard)) { + DataHolder::preferredLeaderboard = LEADERBOARD_MAP.at(preferredLeaderboard); } else { - filterOptions.uploaders.clear(); + DataHolder::preferredLeaderboard = FilterTypes::PreferredLeaderBoard::ScoreSaber; + getPluginConfig().PreferredLeaderboard.SetValue("Scoresaber"); } }).detach(); } From ee9066f9d48d6776a6863d509923f6ab1177c3b9 Mon Sep 17 00:00:00 2001 From: Alex Uskov Date: Mon, 6 Jan 2025 18:01:26 +0400 Subject: [PATCH 04/28] Implement presets, remove using from all header files, remove main.hpp import in files that don't need it --- CMakeLists.txt | 3 + assets/FilterView.bsml | 2 +- assets/Presets.bsml | 23 +- include/BeatSaverRegionManager.hpp | 6 +- include/DateUtils.hpp | 20 +- include/FilterOptions.hpp | 7 +- .../BetterSongSearchFlowCoordinator.hpp | 2 - include/UI/Manager.hpp | 4 - include/UI/Modals/Settings.hpp | 3 +- include/UI/Modals/UploadDetails.hpp | 2 +- .../UI/ViewControllers/DownloadHistory.hpp | 7 +- .../ViewControllers/DownloadListTableData.hpp | 3 - include/UI/ViewControllers/FilterView.hpp | 4 + include/UI/ViewControllers/SongList.hpp | 7 +- include/UI/ViewControllers/SongListCell.hpp | 2 - .../ViewControllers/SongListCellTableData.hpp | 2 - include/Util/BSMLStuff.hpp | 3 +- include/Util/Debug.hpp | 20 +- include/Util/RatelimitCoroutine.hpp | 1 - include/assets.hpp | 1 + src/FilterOptions.cpp | 25 ++ src/UI/Modals/MultiDL.cpp | 5 +- src/UI/Modals/Settings.cpp | 6 +- src/UI/Modals/UploadDetails.cpp | 1 - src/UI/ViewControllers/DownloadHistory.cpp | 4 +- src/UI/ViewControllers/FilterView.cpp | 16 +- src/UI/ViewControllers/SongList.cpp | 8 +- src/Util/BSMLStuff.cpp | 2 + src/Util/Debug.cpp | 413 +++++++++--------- src/Util/SongUtil.cpp | 3 + src/main.cpp | 1 + 31 files changed, 302 insertions(+), 304 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9c0d91..4d85a9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ if(${CMAKE_BUILD_TYPE} STREQUAL "RELEASE" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWi # LTO set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) add_compile_options(-flto) +else() + # Debug options + add_compile_options(-O0 -g) endif() # get git info diff --git a/assets/FilterView.bsml b/assets/FilterView.bsml index 1946565..c753a53 100644 --- a/assets/FilterView.bsml +++ b/assets/FilterView.bsml @@ -6,7 +6,7 @@