diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08aa7d6..f3bc2eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -74,10 +74,10 @@ jobs: run: | cd ${GITHUB_WORKSPACE} ./QPM/qpm qmod build - pwsh -Command ./build.ps1 + pwsh -Command ./scripts/build.ps1 - name: Create Qmod run: | - pwsh -Command ./buildQMOD.ps1 ${{env.qmodName}} + pwsh -Command ./scripts/createqmod.ps1 ${{env.qmodName}} - name: Get Library Name id: libname run: | diff --git a/.gitignore b/.gitignore index 6a96cd6..1c5e905 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ ndkpath.txt *.log log.txt cmake-build-debug/ +.cache \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ede5b84..0b246c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ project(${COMPILE_ID}) # c++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED 20) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # define that stores the actual source directory set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) @@ -27,7 +28,6 @@ add_compile_options(-frtti -fexceptions) add_compile_options(-O3) # compile definitions used add_compile_definitions(VERSION=\"${MOD_VERSION}\") -add_compile_definitions(ID=\"${MOD_ID}\") add_compile_definitions(MOD_ID=\"${MOD_ID}\") add_compile_definitions(USE_CODEGEN_FIELDS) diff --git a/build.ps1 b/build.ps1 deleted file mode 100755 index f16df1d..0000000 --- a/build.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -Param( - [Parameter(Mandatory=$false)] - [Switch] $clean, - - [Parameter(Mandatory=$false)] - [Switch] $help -) - -if ($help -eq $true) { - echo "`"Build`" - Copiles your mod into a `".so`" or a `".a`" library" - echo "`n-- Arguments --`n" - - echo "-Clean `t`t Deletes the `"build`" folder, so that the entire library is rebuilt" - - exit -} - -# if user specified clean, remove all build files -if ($clean.IsPresent) -{ - if (Test-Path -Path "build") - { - remove-item build -R - } -} - - -if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) -{ - $out = new-item -Path build -ItemType Directory -} - -cd build -& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" ../ -& cmake --build . -cd .. \ No newline at end of file diff --git a/buildQMOD.ps1 b/buildQMOD.ps1 deleted file mode 100644 index 67584ac..0000000 --- a/buildQMOD.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -Param( - [String] $qmodname="BeatLeader", - - [Parameter(Mandatory=$false)] - [Switch] $clean, - - [Parameter(Mandatory=$false)] - [Switch] $help -) - -if ($help -eq $true) { - echo "`"BuildQmod `" - Copiles your mod into a `".so`" or a `".a`" library" - echo "`n-- Parameters --`n" - echo "qmodName `t The file name of your qmod" - - echo "`n-- Arguments --`n" - - echo "-Clean `t`t Performs a clean build on both your library and the qmod" - - exit -} - -if ($qmodName -eq "") -{ - echo "Give a proper qmod name and try again" - exit -} - -& $PSScriptRoot/build.ps1 -clean:$clean - -if ($LASTEXITCODE -ne 0) { - echo "Failed to build, exiting..." - exit $LASTEXITCODE -} - -echo "Creating qmod from mod.json" - -$mod = "./mod.json" -$modJson = Get-Content $mod -Raw | ConvertFrom-Json - -$filelist = @($mod) - -$cover = "./" + $modJson.coverImage -if ((-not ($cover -eq "./")) -and (Test-Path $cover)) -{ - $filelist += ,$cover -} - -foreach ($mod in $modJson.modFiles) -{ - $path = "./build/" + $mod - if (-not (Test-Path $path)) - { - $path = "./extern/libs/" + $mod - } - $filelist += $path -} - -foreach ($lib in $modJson.libraryFiles) -{ - $path = "./build/" + $lib - if (-not (Test-Path $path)) - { - $path = "./extern/libs/" + $lib - } - $filelist += $path -} - -$zip = $qmodName + ".zip" -$qmod = $qmodName + ".qmod" - -if ((-not ($clean.IsPresent)) -and (Test-Path $qmod)) -{ - echo "Making Clean Qmod" - Move-Item $qmod $zip -Force -} - -Compress-Archive -Path $filelist -DestinationPath $zip -Update -Move-Item $zip $qmod -Force \ No newline at end of file diff --git a/copy.ps1 b/copy.ps1 deleted file mode 100644 index 7ad4ce8..0000000 --- a/copy.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -Param( - [Parameter(Mandatory=$false)] - [Switch] $clean, - - [Parameter(Mandatory=$false)] - [Switch] $log, - - [Parameter(Mandatory=$false)] - [Switch] $useDebug, - - [Parameter(Mandatory=$false)] - [Switch] $self, - - [Parameter(Mandatory=$false)] - [Switch] $all, - - [Parameter(Mandatory=$false)] - [String] $custom="", - - [Parameter(Mandatory=$false)] - [Switch] $file, - - [Parameter(Mandatory=$false)] - [Switch] $help -) - -if ($help -eq $true) { - echo "`"Copy`" - Builds and copies your mod to your quest, and also starts Beat Saber with optional logging" - echo "`n-- Arguments --`n" - - echo "-Clean `t`t Performs a clean build (equvilant to running `"Build -clean`")" - echo "-UseDebug `t Copied the debug version of the mod to your quest" - echo "-Log `t`t Logs Beat Saber using the `"Start-Logging`" command" - - echo "`n-- Logging Arguments --`n" - - & $PSScriptRoot/start-logging.ps1 -help -excludeHeader - - exit -} - -& $PSScriptRoot/build.ps1 -clean:$clean - -if ($LASTEXITCODE -ne 0) { - echo "Failed to build, exiting..." - exit $LASTEXITCODE -} - -if ($useDebug -eq $true) { - $fileName = Get-ChildItem lib*.so -Path "build/debug" -Name -} else { - $fileName = Get-ChildItem lib*.so -Path "build/" -Name -} - -& adb push build/$fileName /sdcard/Android/data/com.beatgames.beatsaber/files/mods/$fileName - -& $PSScriptRoot/restart-game.ps1 - -if ($log -eq $true) { & $PSScriptRoot/start-logging.ps1 -self:$self -all:$all -custom:$custom -file:$file } \ No newline at end of file diff --git a/include/Assets/Gif.hpp b/include/Assets/Gif.hpp index 27ac197..6a01e2f 100644 --- a/include/Assets/Gif.hpp +++ b/include/Assets/Gif.hpp @@ -118,10 +118,10 @@ struct Gif if (frame->RasterBits[loc] == ext->Bytes[3] && ext->Bytes[0]) { - pixelData[locWithinFrame] = Color32(0xff, 0xff, 0xff, 0); + pixelData[locWithinFrame] = Color32(0, 0xff, 0xff, 0xff, 0); } else { color = &colorMap->Colors[frame->RasterBits[loc]]; - pixelData[locWithinFrame] = Color32(color->Red, color->Green, color->Blue, 0xff); + pixelData[locWithinFrame] = Color32(0, color->Red, color->Green, color->Blue, 0xff); } } @@ -149,9 +149,6 @@ struct Gif ::ArrayW frames = ArrayW(length); ArrayW timings = ArrayW (length); - // FrameBuffer - TextureColor *pixelData = new TextureColor[width * height]; - // Persist data from the previous frame GifColorType* color; SavedImage* frame; @@ -161,6 +158,7 @@ struct Gif // Graphic control ext block GraphicsControlBlock GCB; int GCBResult = GIF_ERROR; + ::ArrayW pixelData = nullptr; for (int idx = 0; idx < length; idx++) { int x, y, j, loc; @@ -184,9 +182,12 @@ struct Gif if (ext != nullptr) { GCBResult = DGifExtensionToGCB(ext->ByteCount, (const GifByteType*)ext->Bytes, &GCB); } + // gif->SWidth is not neccesarily the same as FrameInfo->Width due to a frame possibly describing a smaller block of pixels than the entire gif size - UnityEngine::Texture2D* texture = UnityEngine::Texture2D::New_ctor(width, height, UnityEngine::TextureFormat::RGBA32, false); + if (!pixelData) { + pixelData = texture->GetPixels32(); + } // This is the same size as the entire size of the gif :) // offset into the entire image, might need to also have it's y value flipped? need to test long flippedFrameTop = height - frameInfo->Top - frameInfo->Height; @@ -202,14 +203,12 @@ struct Gif // Checks if the pixel is transparent if (GCB.TransparentColor >=0 && frame->RasterBits[loc] == ext->Bytes[3] && ext->Bytes[0]) { - pixelData[locWithinFrame] = { - 0xff, 0xff, 0xff, 0 - }; + if (GCB.DisposalMode == 2) { + pixelData[locWithinFrame] = Color32(0, 0xff, 0xff, 0xff, 0); + } } else { color = &colorMap->Colors[frame->RasterBits[loc]]; - pixelData[locWithinFrame] = { - color->Red, color->Green, color->Blue, 0xff - }; + pixelData[locWithinFrame] = Color32(0, color->Red, color->Green, color->Blue, 0xff); } } @@ -219,18 +218,13 @@ struct Gif } // Copy raw pixel data to texture - texture->LoadRawTextureData(pixelData, width * height * 4); - // texture->set_filterMode(UnityEngine::FilterMode::Trilinear); - // Compress texture - texture->Compress(false); + texture->SetAllPixels32(pixelData, 0); // Upload to GPU texture->Apply(); frames[idx] = texture; timings[idx] = static_cast(GCB.DelayTime); }; - // Clear FrameBuffer to not leak things - delete[] pixelData; return { frames, timings }; @@ -258,7 +252,7 @@ struct Gif } vectorwrapbuf(Array* arr) { - this->std::basic_streambuf::setg(arr->values, arr->values, arr->values + arr->Length()); + this->std::basic_streambuf::setg(arr->_values, arr->_values, arr->_values + arr->get_Length()); } }; diff --git a/include/Enhancers/MapEnhancer.hpp b/include/Enhancers/MapEnhancer.hpp index 370ee65..3b4546a 100644 --- a/include/Enhancers/MapEnhancer.hpp +++ b/include/Enhancers/MapEnhancer.hpp @@ -1,7 +1,7 @@ #pragma once -#include "GlobalNamespace/IDifficultyBeatmap.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapKey.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" #include "GlobalNamespace/PlayerSpecificSettings.hpp" #include "GlobalNamespace/GameplayModifiers.hpp" #include "GlobalNamespace/PracticeSettings.hpp" @@ -15,8 +15,8 @@ using namespace GlobalNamespace; class MapEnhancer { public: - IDifficultyBeatmap* difficultyBeatmap; - IPreviewBeatmapLevel* previewBeatmapLevel; + BeatmapKey difficultyBeatmap; + BeatmapLevel* beatmapLevel; GameplayModifiers* gameplayModifiers; PlayerSpecificSettings* playerSpecificSettings; PracticeSettings* practiceSettings; diff --git a/include/UI/CaptorClanUI.hpp b/include/UI/CaptorClanUI.hpp index f8a17d8..361d23f 100644 --- a/include/UI/CaptorClanUI.hpp +++ b/include/UI/CaptorClanUI.hpp @@ -2,8 +2,8 @@ #include "include/Models/Clan.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "TMPro/TextMeshProUGUI.hpp" diff --git a/include/UI/Components/ExternalComponents.hpp b/include/UI/Components/ExternalComponents.hpp new file mode 100644 index 0000000..31b556b --- /dev/null +++ b/include/UI/Components/ExternalComponents.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include "UnityEngine/MonoBehaviour.hpp" +#include "UnityEngine/Component.hpp" + +#include "custom-types/shared/macros.hpp" + +DECLARE_CLASS_CODEGEN(QuestUI, ExternalComponents, UnityEngine::MonoBehaviour, + + private: + std::vector components; + public: + template + T Get() { + return (T)GetByType(csTypeOf(T)); + } + + DECLARE_INSTANCE_METHOD(void, Add, UnityEngine::Component* component); + DECLARE_INSTANCE_METHOD(UnityEngine::Component*, GetByType, Il2CppReflectionType* type); + + DECLARE_DEFAULT_CTOR(); + + DECLARE_SIMPLE_DTOR(); + +) \ No newline at end of file diff --git a/include/UI/Components/SmoothHoverController.hpp b/include/UI/Components/SmoothHoverController.hpp index 7905bf2..101429a 100644 --- a/include/UI/Components/SmoothHoverController.hpp +++ b/include/UI/Components/SmoothHoverController.hpp @@ -11,7 +11,7 @@ #include using namespace std; -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN_INTERFACES( BeatLeader, @@ -34,8 +34,8 @@ DECLARE_CLASS_CODEGEN_INTERFACES( HoverStateChangedEvent & get_hoverStateChangedEvent(); HoverStateChangedEvent hoverStateChangedEvent; - DECLARE_OVERRIDE_METHOD(void, OnPointerEnter, il2cpp_utils::il2cpp_type_check::MetadataGetter<&UnityEngine::EventSystems::IPointerEnterHandler::OnPointerEnter>::get(), UnityEngine::EventSystems::PointerEventData*); - DECLARE_OVERRIDE_METHOD(void, OnPointerExit, il2cpp_utils::il2cpp_type_check::MetadataGetter<&UnityEngine::EventSystems::IPointerExitHandler::OnPointerExit>::get(), UnityEngine::EventSystems::PointerEventData*); + DECLARE_OVERRIDE_METHOD(void, OnPointerEnter, il2cpp_utils::il2cpp_type_check::MetadataGetter<&UnityEngine::EventSystems::IPointerEnterHandler::OnPointerEnter>::methodInfo(), UnityEngine::EventSystems::PointerEventData*); + DECLARE_OVERRIDE_METHOD(void, OnPointerExit, il2cpp_utils::il2cpp_type_check::MetadataGetter<&UnityEngine::EventSystems::IPointerExitHandler::OnPointerExit>::methodInfo(), UnityEngine::EventSystems::PointerEventData*); DECLARE_INSTANCE_METHOD(void, OnDisable); DECLARE_INSTANCE_METHOD(void, Update); diff --git a/include/UI/LeaderboardUI.hpp b/include/UI/LeaderboardUI.hpp index dca58fa..c1a9bad 100644 --- a/include/UI/LeaderboardUI.hpp +++ b/include/UI/LeaderboardUI.hpp @@ -4,7 +4,7 @@ #include #include "include/Utils/ReplayManager.hpp" #include "include/UI/LevelInfoUI.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" #include "UnityEngine/Transform.hpp" #include "UnityEngine/UI/Toggle.hpp" #include "UnityEngine/Vector2.hpp" @@ -21,7 +21,7 @@ namespace LeaderboardUI { void updateStatus(ReplayUploadStatus status, string description, float progress, bool showRestart); void updateVotingButton(); - tuple getLevelDetails(GlobalNamespace::IPreviewBeatmapLevel* levelData); + tuple getLevelDetails(GlobalNamespace::BeatmapKey levelData); void setVotingButtonsState(int state); void initSettingsModal(UnityEngine::Transform* parent); void initContextsModal(UnityEngine::Transform* parent); diff --git a/include/UI/LinksContainer.hpp b/include/UI/LinksContainer.hpp index 1b0c965..ebf5eeb 100644 --- a/include/UI/LinksContainer.hpp +++ b/include/UI/LinksContainer.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" namespace BeatLeader { class LinksContainerPopup { @@ -12,13 +12,14 @@ namespace BeatLeader { TMPro::TextMeshProUGUI* versionText; - QuestUI::ClickableImage* profile; - QuestUI::ClickableImage* discord; - QuestUI::ClickableImage* patreon; + BSML::ClickableImage* profile; + BSML::ClickableImage* discord; + BSML::ClickableImage* patreon; UnityEngine::UI::Button* nominated; UnityEngine::UI::Button* qualified; UnityEngine::UI::Button* ranked; }; void initLinksContainerPopup(LinksContainerPopup** modalUI, UnityEngine::Transform* parent); + void SetButtonSize(UnityEngine::UI::Button* button, UnityEngine::Vector2 sizeDelta); } \ No newline at end of file diff --git a/include/UI/LogoAnimation.hpp b/include/UI/LogoAnimation.hpp index 82f8f5f..63a136a 100644 --- a/include/UI/LogoAnimation.hpp +++ b/include/UI/LogoAnimation.hpp @@ -5,7 +5,7 @@ #include "custom-types/shared/macros.hpp" -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN(BeatLeader, LogoAnimation, UnityEngine::MonoBehaviour, DECLARE_INSTANCE_FIELD(HMUI::ImageView*, imageView); diff --git a/include/UI/PlayerAvatar.hpp b/include/UI/PlayerAvatar.hpp index a7808e9..0586582 100644 --- a/include/UI/PlayerAvatar.hpp +++ b/include/UI/PlayerAvatar.hpp @@ -11,7 +11,7 @@ #include using namespace std; -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN(BeatLeader, PlayerAvatar, UnityEngine::MonoBehaviour, DECLARE_INSTANCE_FIELD(HMUI::ImageView*, imageView); diff --git a/include/UI/PreferencesViewController.hpp b/include/UI/PreferencesViewController.hpp index 4a0765e..bceb5dd 100644 --- a/include/UI/PreferencesViewController.hpp +++ b/include/UI/PreferencesViewController.hpp @@ -1,7 +1,6 @@ #pragma once #include "UnityEngine/MonoBehaviour.hpp" -#include "HMUI/TableView_IDataSource.hpp" #include "custom-types/shared/macros.hpp" #include "HMUI/ViewController.hpp" @@ -9,13 +8,9 @@ #include "HMUI/TableCell.hpp" #include "System/Object.hpp" #include "TMPro/TextMeshProUGUI.hpp" -#include "questui/shared/CustomTypes/Components/List/QuestUITableView.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/TableView.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() - - -DECLARE_CLASS_CODEGEN(BeatLeader, PreferencesViewController, HMUI::ViewController, - DECLARE_OVERRIDE_METHOD(void, DidActivate, GET_FIND_METHOD(&HMUI::ViewController::DidActivate), bool firstActivation, bool addedToHeirarchy, bool screenSystemDisabling); - DECLARE_OVERRIDE_METHOD(void, DidDeactivate, GET_FIND_METHOD(&HMUI::ViewController::DidDeactivate), bool removedFromHierarchy, bool screenSystemDisabling); -) \ No newline at end of file +namespace BeatLeader::PreferencesViewController { + void DidActivate(HMUI::ViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling); +} \ No newline at end of file diff --git a/include/UI/QuestUI.hpp b/include/UI/QuestUI.hpp new file mode 100644 index 0000000..57fc37e --- /dev/null +++ b/include/UI/QuestUI.hpp @@ -0,0 +1,135 @@ +#pragma once +#include "beatsaber-hook/shared/utils/utils.h" +#include "beatsaber-hook/shared/utils/utils-functions.h" + +#include "GlobalNamespace/MainFlowCoordinator.hpp" +#include "GlobalNamespace/GameplayModifierToggle.hpp" +#include "GlobalNamespace/ColorChangeUIEventType.hpp" +#include "GlobalNamespace/IVRPlatformHelper.hpp" + +#include "UnityEngine/GameObject.hpp" +#include "UnityEngine/RectTransform.hpp" +#include "UnityEngine/Vector2.hpp" +#include "UnityEngine/Sprite.hpp" +#include "UnityEngine/UI/Button.hpp" +#include "UnityEngine/UI/Image.hpp" +#include "UnityEngine/UI/Toggle.hpp" +#include "UnityEngine/UI/LayoutElement.hpp" +#include "UnityEngine/UI/GridLayoutGroup.hpp" +#include "UnityEngine/UI/HorizontalLayoutGroup.hpp" +#include "UnityEngine/UI/VerticalLayoutGroup.hpp" +#include "UnityEngine/UI/ContentSizeFitter.hpp" + +#include "HMUI/ViewController.hpp" +#include "HMUI/FlowCoordinator.hpp" +#include "HMUI/HoverHint.hpp" +#include "HMUI/InputFieldView.hpp" +#include "HMUI/ModalView.hpp" +#include "HMUI/ImageView.hpp" +#include "HMUI/SimpleTextDropdown.hpp" + +#include "TMPro/TextMeshProUGUI.hpp" +#include "TMPro/TMP_FontAsset.hpp" + +#include +#include + +namespace QuestUI { + void SetupPersistentObjects(); + void ClearCache(); + + /// @brief creates a modal that can be used to display information + /// @param parent what to parent it to + /// @param onBlockerClicked callback that gets called when clicking next to the modal, leaving it empty makes it just dismiss the modal + /// @param sizeDelta size of the object + /// @param anchoredPosition position of the modal + /// @param dismissOnBlockerClicked whether to auto dismiss when the blocker (outside) is clicked + /// @return created modal + HMUI::ModalView* CreateModal(UnityEngine::Transform* parent, UnityEngine::Vector2 sizeDelta, UnityEngine::Vector2 anchoredPosition, std::function onBlockerClicked, bool dismissOnBlockerClicked = true); + + /// @brief Creates a text object + /// @param parent what to parent it to + /// @param text the string to display + /// @param italic should the text be italic + TMPro::TextMeshProUGUI* CreateText(UnityEngine::Transform* parent, StringW text, bool italic = true); + + /// @brief Creates a text object + /// @param parent what to parent it to + /// @param text the string to display + /// @param anchoredPosition the location of the text + /// @param sizeDelta the size of the text + TMPro::TextMeshProUGUI* CreateText(UnityEngine::Transform* parent, StringW text, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta); + + /// @brief Creates a text object + /// @param parent what to parent it to + /// @param text the string to display + /// @param anchoredPosition the location of the text + TMPro::TextMeshProUGUI* CreateText(UnityEngine::Transform* parent, StringW text, UnityEngine::Vector2 anchoredPosition); + + /// @brief Creates a text object + /// @param parent what to parent it to + /// @param text the string to display + /// @param italic should the text be italic + /// @param anchoredPosition the location of the text + TMPro::TextMeshProUGUI* CreateText(UnityEngine::Transform* parent, StringW text, bool italic, UnityEngine::Vector2 anchoredPosition); + + /// @brief Creates a text object + /// @param parent what to parent it to + /// @param text the string to display + /// @param italicc should the text be italic + /// @param anchoredPosition the location of the text + /// @param sizeDelta the size of the text + TMPro::TextMeshProUGUI* CreateText(UnityEngine::Transform* parent, StringW text, bool italic, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, std::function onClick = nullptr); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param buttonTemplate the name of a button to use as a base + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, std::string_view buttonTemplate, std::function onClick = nullptr); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param buttonTemplate the name of a button to use as a base + /// @param anchoredPosition position of the button + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, std::string_view buttonTemplate, UnityEngine::Vector2 anchoredPosition, std::function onClick = nullptr); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param buttonTemplate the name of a button to use as a base + /// @param anchoredPosition position of the button + /// @param sizeDelta size of the button + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, std::string_view buttonTemplate, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta, std::function onClick = nullptr); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param anchoredPosition position of the button + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, UnityEngine::Vector2 anchoredPosition, std::function onClick = nullptr); + + /// @brief Creates a button to click and have an action happen + /// @param parent what to parent it to + /// @param buttonText the text the butotn displays + /// @param anchoredPosition position of the button + /// @param sizeDelta size of the button + /// @param onClick callback to run when the button is clicked + UnityEngine::UI::Button* CreateUIButton(UnityEngine::Transform* parent, StringW buttonText, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta, std::function onClick = nullptr); + + /// @brief Creates an image + /// @param parent what to parent it to + /// @param sprite the sprite to display + /// @param anchoredPosition the position of the image + /// @param sizeDelta the size of the image + HMUI::ImageView* CreateImage(UnityEngine::Transform* parent, UnityEngine::Sprite* sprite, UnityEngine::Vector2 anchoredPosition = {}, UnityEngine::Vector2 sizeDelta = {}); +} \ No newline at end of file diff --git a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.hpp b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.hpp index f6ba768..ac3a04e 100644 --- a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.hpp +++ b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.hpp @@ -28,8 +28,8 @@ #include "UnityEngine/Transform.hpp" #include "UnityEngine/Time.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "VRUIControls/VRPointer.hpp" @@ -37,7 +37,7 @@ using namespace std; -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN(BeatLeader, AccuracyGraph, UnityEngine::EventSystems::UIBehaviour, DECLARE_INSTANCE_FIELD(AccuracyGraphLine*, graphLine); diff --git a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphLine.hpp b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphLine.hpp index 9f2db90..c85cf11 100644 --- a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphLine.hpp +++ b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphLine.hpp @@ -11,7 +11,7 @@ using namespace std; -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN(BeatLeader, AccuracyGraphLine, UnityEngine::UI::Graphic, DECLARE_INSTANCE_FIELD(UnityEngine::Rect, viewRect); diff --git a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp index 475d259..17ac1a1 100644 --- a/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp +++ b/include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp @@ -76,7 +76,7 @@ namespace AccuracyGraphUtils { float yMin = 1000000; float yMax = -1000000; - int length = points.Length(); + int length = points.size(); result.reserve(length); for (auto i = 0; i < length; i++) { diff --git a/include/UI/ScoreDetails/AdditionalScoreDetails.hpp b/include/UI/ScoreDetails/AdditionalScoreDetails.hpp index e5537b1..9a5bedc 100644 --- a/include/UI/ScoreDetails/AdditionalScoreDetails.hpp +++ b/include/UI/ScoreDetails/AdditionalScoreDetails.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/ScoreStats.hpp" diff --git a/include/UI/ScoreDetails/GeneralScoreDetails.hpp b/include/UI/ScoreDetails/GeneralScoreDetails.hpp index db85ebc..9c783f5 100644 --- a/include/UI/ScoreDetails/GeneralScoreDetails.hpp +++ b/include/UI/ScoreDetails/GeneralScoreDetails.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/Score.hpp" #include "include/UI/PlayerAvatar.hpp" diff --git a/include/UI/ScoreDetails/MiniProfileButton.hpp b/include/UI/ScoreDetails/MiniProfileButton.hpp index f49a332..e600b10 100644 --- a/include/UI/ScoreDetails/MiniProfileButton.hpp +++ b/include/UI/ScoreDetails/MiniProfileButton.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/UI/Components/SmoothHoverController.hpp" @@ -18,9 +18,9 @@ namespace BeatLeader { class MiniProfileButton { public: MiniProfileButton() = default; - MiniProfileButton(std::string hint, UnityEngine::Color glowColor, bool labelOnLeft, QuestUI::ClickableImage* button) noexcept; + MiniProfileButton(std::string hint, UnityEngine::Color glowColor, bool labelOnLeft, BSML::ClickableImage* button) noexcept; - QuestUI::ClickableImage* button; + BSML::ClickableImage* button; HMUI::HoverHint* hint; BeatLeader::SmoothHoverController* hoverController; MiniProfileButtonState state; diff --git a/include/UI/ScoreDetails/PlayerButtons.hpp b/include/UI/ScoreDetails/PlayerButtons.hpp index d01d2d7..e657920 100644 --- a/include/UI/ScoreDetails/PlayerButtons.hpp +++ b/include/UI/ScoreDetails/PlayerButtons.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/Score.hpp" #include "include/Models/Player.hpp" diff --git a/include/UI/ScoreDetails/ScoreDetailsUI.hpp b/include/UI/ScoreDetails/ScoreDetailsUI.hpp index 65a198a..07b68ad 100644 --- a/include/UI/ScoreDetails/ScoreDetailsUI.hpp +++ b/include/UI/ScoreDetails/ScoreDetailsUI.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/Score.hpp" #include "include/UI/PlayerAvatar.hpp" @@ -34,13 +34,13 @@ namespace BeatLeader { ScoreStatsGrid grid; ScoreStatsGraph graph; - QuestUI::ClickableImage* generalButton; - QuestUI::ClickableImage* additionalButton; - QuestUI::ClickableImage* overviewButton; - QuestUI::ClickableImage* gridButton; - QuestUI::ClickableImage* graphButton; + BSML::ClickableImage* generalButton; + BSML::ClickableImage* additionalButton; + BSML::ClickableImage* overviewButton; + BSML::ClickableImage* gridButton; + BSML::ClickableImage* graphButton; - QuestUI::ClickableImage* replayButton; + BSML::ClickableImage* replayButton; TMPro::TextMeshProUGUI* loadingText; diff --git a/include/UI/ScoreDetails/ScoreStatsGraph.hpp b/include/UI/ScoreDetails/ScoreStatsGraph.hpp index 9861319..f7f1fe7 100644 --- a/include/UI/ScoreDetails/ScoreStatsGraph.hpp +++ b/include/UI/ScoreDetails/ScoreStatsGraph.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/ScoreStats.hpp" diff --git a/include/UI/ScoreDetails/ScoreStatsGrid.hpp b/include/UI/ScoreDetails/ScoreStatsGrid.hpp index 0fb7b75..df2ac4a 100644 --- a/include/UI/ScoreDetails/ScoreStatsGrid.hpp +++ b/include/UI/ScoreDetails/ScoreStatsGrid.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/ScoreStats.hpp" #include "include/UI/PlayerAvatar.hpp" diff --git a/include/UI/ScoreDetails/ScoreStatsGridCell.hpp b/include/UI/ScoreDetails/ScoreStatsGridCell.hpp index e325da4..885a683 100644 --- a/include/UI/ScoreDetails/ScoreStatsGridCell.hpp +++ b/include/UI/ScoreDetails/ScoreStatsGridCell.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/Score.hpp" #include "include/UI/PlayerAvatar.hpp" diff --git a/include/UI/ScoreDetails/ScoreStatsOverview.hpp b/include/UI/ScoreDetails/ScoreStatsOverview.hpp index 2ca071d..58f3be2 100644 --- a/include/UI/ScoreDetails/ScoreStatsOverview.hpp +++ b/include/UI/ScoreDetails/ScoreStatsOverview.hpp @@ -2,8 +2,8 @@ #include "HMUI/ModalView.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "include/Models/ScoreStats.hpp" #include "include/UI/PlayerAvatar.hpp" diff --git a/include/UI/UIUtils.hpp b/include/UI/UIUtils.hpp index 6d09838..19657b9 100644 --- a/include/UI/UIUtils.hpp +++ b/include/UI/UIUtils.hpp @@ -13,9 +13,8 @@ #include "HMUI/Screen.hpp" #include "HMUI/ViewController.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/ArrayUtil.hpp" -#include "questui/shared/BeatSaberUI.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" inline void move(UnityEngine::Component* label, float x, float y) { UnityEngine::RectTransform* transform = label->GetComponent(); @@ -34,9 +33,9 @@ inline void resize(UnityEngine::Component* label, float x, float y) { } inline UnityEngine::GameObject* CreateCustomScreen(HMUI::ViewController* rootView, UnityEngine::Vector2 screenSize, UnityEngine::Vector3 position, float curvatureRadius) { - auto gameObject = QuestUI::BeatSaberUI::CreateCanvas(); + auto gameObject = BSML::Lite::CreateCanvas(); auto screen = gameObject->AddComponent(); - screen->rootViewController = rootView; + screen->_rootViewController = rootView; auto curvedCanvasSettings = gameObject->AddComponent(); curvedCanvasSettings->SetRadius(curvatureRadius); diff --git a/include/UI/VotingButton.hpp b/include/UI/VotingButton.hpp index 70c5706..23b1fb8 100644 --- a/include/UI/VotingButton.hpp +++ b/include/UI/VotingButton.hpp @@ -5,17 +5,17 @@ #include "custom-types/shared/macros.hpp" -#include "questui/shared/CustomTypes/Components/ClickableImage.hpp" +#include "bsml/shared/BSML/Components/ClickableImage.hpp" #include "HMUI/HoverHint.hpp" -#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::get() +#define GET_FIND_METHOD(mPtr) il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() DECLARE_CLASS_CODEGEN(BeatLeader, VotingButton, UnityEngine::MonoBehaviour, - DECLARE_INSTANCE_FIELD(QuestUI::ClickableImage*, imageView); + DECLARE_INSTANCE_FIELD(BSML::ClickableImage*, imageView); DECLARE_INSTANCE_FIELD(UnityEngine::Material*, materialInstance); DECLARE_INSTANCE_FIELD(HMUI::HoverHint*, hoverHint); DECLARE_INSTANCE_FIELD(int, state); - DECLARE_INSTANCE_METHOD(void, Init, QuestUI::ClickableImage* imageView); + DECLARE_INSTANCE_METHOD(void, Init, BSML::ClickableImage* imageView); DECLARE_INSTANCE_METHOD(void, SetState, int state); ) \ No newline at end of file diff --git a/include/UI/VotingUI.hpp b/include/UI/VotingUI.hpp index b8e9ff7..8b7163f 100644 --- a/include/UI/VotingUI.hpp +++ b/include/UI/VotingUI.hpp @@ -2,13 +2,13 @@ #include "custom-types/shared/macros.hpp" #include "custom-types/shared/register.hpp" -#include "modloader/shared/modloader.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/QuestUI.hpp" +#include "scotland2/shared/loader.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML.hpp" #include "HMUI/ModalView.hpp" #include "UnityEngine/RectOffset.hpp" #include "GlobalNamespace/ScoreFormatter.hpp" -#include "GlobalNamespace/IDifficultyBeatmap.hpp" +#include "GlobalNamespace/BeatmapKey.hpp" #include "GlobalNamespace/PlayerLevelStatsData.hpp" #include "GlobalNamespace/BeatmapData.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" @@ -34,7 +34,7 @@ namespace BeatLeader { UnityEngine::UI::Button* noButton; TMPro::TextMeshProUGUI* header2; - QuestUI::SliderSetting* starSlider; + BSML::SliderSetting* starSlider; TMPro::TextMeshProUGUI* subheader2; UnityEngine::UI::Button* accButton; @@ -42,7 +42,6 @@ namespace BeatLeader { UnityEngine::UI::Button* midspeedButton; UnityEngine::UI::Button* speedButton; - UnityEngine::UI::Button* cancelButton; UnityEngine::UI::Button* voteButton; UnityEngine::UI::Button* leftButton; UnityEngine::UI::Button* rightButton; diff --git a/include/Utils/FormatUtils.hpp b/include/Utils/FormatUtils.hpp index feac027..6d4225f 100644 --- a/include/Utils/FormatUtils.hpp +++ b/include/Utils/FormatUtils.hpp @@ -69,13 +69,13 @@ namespace FormatUtils { } inline string FormatUserName(string_view userName) { - return "" + userName + ""; + return "" + string(userName) + ""; } const string ModifiersColor = ""; inline string FormatModifiers(string_view modifiers) { - return modifiers.length() == 0 ? "" : "" + modifiers; + return modifiers.length() == 0 ? "" : "" + string(modifiers); } static Color lowAccColor = UnityEngine::Color(0.93, 1, 0.62, 1); @@ -102,7 +102,7 @@ namespace FormatUtils { } inline string FormatClanTag(string_view tag) { - return "." + tag + "."; + return "." + string(tag) + "."; } inline string FormatNameWithClans(Player const& player, int limit, bool withClans) { diff --git a/include/Utils/RecorderUtils.hpp b/include/Utils/RecorderUtils.hpp deleted file mode 100644 index 90b6442..0000000 --- a/include/Utils/RecorderUtils.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -class RecorderUtils -{ -public: - static bool shouldRecord; - static void StartRecorderUtils(); -}; diff --git a/include/_config.hpp b/include/_config.hpp new file mode 100644 index 0000000..fcde51d --- /dev/null +++ b/include/_config.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define MOD_EXPORT extern "C" __attribute__((visibility("default"))) \ No newline at end of file diff --git a/include/main.hpp b/include/main.hpp index b657455..cf26567 100644 --- a/include/main.hpp +++ b/include/main.hpp @@ -1,7 +1,8 @@ #pragma once -// Include the modloader header, which allows us to tell the modloader which mod this is, and the version etc. -#include "modloader/shared/modloader.hpp" +#include "_config.hpp" + +#include "scotland2/shared/loader.hpp" // beatsaber-hook is a modding framework that lets us call functions and fetch field values from in the game // It also allows creating objects, configuration, and importantly, hooking methods to modify their values @@ -10,6 +11,8 @@ #include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" #include "beatsaber-hook/shared/utils/hooking.hpp" -Logger& getLogger(); +#include "paper/shared/logger.hpp" + +inline modloader::ModInfo modInfo = {MOD_ID, VERSION, 0}; -extern ModInfo modInfo; \ No newline at end of file +constexpr auto BeatLeaderLogger = Paper::ConstLoggerContext("BeatLeader"); \ No newline at end of file diff --git a/mod.json b/mod.json index be3920f..6299665 100644 --- a/mod.json +++ b/mod.json @@ -2,44 +2,47 @@ "_QPVersion": "0.1.1", "name": "BeatLeader", "id": "BeatLeader", - "description": "beatleader.xyz | In-game leaderboards for custom and OST maps | Score replays | Clans, events, playlists and much more", + "modloader": "Scotland2", "author": "NSGolova", - "version": "0.7.1", + "version": "0.8.0", "packageId": "com.beatgames.beatsaber", - "packageVersion": "1.28.0_4124311467", + "packageVersion": "1.35.0_8016709773", + "description": "beatleader.xyz | In-game leaderboards for custom and OST maps | Score replays | Clans, events, playlists and much more", "coverImage": "cover.png", "dependencies": [ { - "version": "^0.7.0", - "id": "bs-utils", - "downloadIfMissing": "https://github.com/sc2ad/QuestBS-Utils/releases/download/v0.7.2/BS-Utils.qmod" + "version": "^0.17.0", + "id": "custom-types", + "downloadIfMissing": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.17.7/CustomTypes.qmod" }, { - "version": "^0.17.11", - "id": "questui", - "downloadIfMissing": "https://github.com/darknight1050/QuestUI/releases/download/v0.17.12/QuestUI.qmod" + "version": "^1.1.1", + "id": "songcore", + "downloadIfMissing": "https://github.com/raineio/Quest-SongCore/releases/download/v1.1.9/SongCore.qmod" }, { - "version": "^0.10.15", - "id": "songloader", - "downloadIfMissing": "https://github.com/darknight1050/SongLoader/releases/download/v0.10.17/SongLoader.qmod" + "version": "^0.8.0", + "id": "bs-utils", + "downloadIfMissing": "https://github.com/RedBrumbler/QuestBS-Utils/releases/download/v0.8.2/BS-Utils.qmod" }, { - "version": "^0.15.23", - "id": "custom-types", - "downloadIfMissing": "https://github.com/sc2ad/Il2CppQuestTypePatching/releases/download/v0.15.24/CustomTypes.qmod" + "version": "^3.6.3", + "id": "paper", + "downloadIfMissing": "https://github.com/Fernthedev/paperlog/releases/download/v3.6.3/paperlog.qmod" }, { - "version": "^0.33.0", - "id": "codegen", - "downloadIfMissing": "https://github.com/sc2ad/BeatSaber-Quest-Codegen/releases/download/v0.33.0/Codegen.qmod" + "version": "^0.4.24", + "id": "bsml", + "downloadIfMissing": "https://github.com/RedBrumbler/Quest-BSML/releases/download/v0.4.24/BSML.qmod" } ], - "modFiles": [ + "modFiles": [], + "lateModFiles": [ "libbl.so" ], "libraryFiles": [ - "libbeatsaber-hook_3_14_0.so" + "libbeatsaber-hook_5_1_6.so", + "libsl2.so" ], "fileCopies": [], "copyExtensions": [] diff --git a/mod.template.json b/mod.template.json index a4345fa..e3d4770 100644 --- a/mod.template.json +++ b/mod.template.json @@ -4,10 +4,14 @@ "name": "BeatLeader", "version": "${version}", "author": "NSGolova", + "modloader": "Scotland2", "packageId": "com.beatgames.beatsaber", - "packageVersion": "1.28.0_4124311467", + "packageVersion": "1.35.0_8016709773", "coverImage": "cover.png", "description": "beatleader.xyz | In-game leaderboards for custom and OST maps | Score replays | Clans, events, playlists and much more", "modFiles": [], + "lateModFiles": [ + "${binary}" + ], "libraryFiles": [] } diff --git a/ndk-stack.ps1 b/ndk-stack.ps1 deleted file mode 100644 index 0bc1a69..0000000 --- a/ndk-stack.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -if (Test-Path "./ndkpath.txt") -{ - $NDKPath = Get-Content ./ndkpath.txt -} else { - $NDKPath = $ENV:ANDROID_NDK_HOME -} - - - -$stackScript = "$NDKPath/ndk-stack" -if (-not ($PSVersionTable.PSEdition -eq "Core")) { - $stackScript += ".cmd" -} - -if ($args.Count -eq 0) { - Get-Content ./log.txt | & $stackScript -sym ./build/debug/ > log_unstripped.log -} else { - Get-Content $args[0] | & $stackScript -sym ./build/debug/ > "$($args[0])_unstripped.txt" -} diff --git a/qpm.json b/qpm.json index 6278df8..cf5fc44 100644 --- a/qpm.json +++ b/qpm.json @@ -1,19 +1,23 @@ { + "version": "0.1.0", "sharedDir": "shared", "dependenciesDir": "extern", "info": { "name": "BeatLeader", "id": "BeatLeader", - "version": "0.7.1", + "version": "0.8.0", "url": null, "additionalData": { "overrideSoName": "libbl.so" } }, + "workspace": { + "scripts": {} + }, "dependencies": [ { "id": "beatsaber-hook", - "versionRange": "^3.14.0", + "versionRange": "^5.1.6", "additionalData": { "extraFiles": [ "src/inline-hook" @@ -21,28 +25,23 @@ } }, { - "id": "modloader", - "versionRange": "^1.2.3", + "id": "scotland2", + "versionRange": "^0.1.4", "additionalData": {} }, { - "id": "codegen", - "versionRange": "^0.33.0", - "additionalData": {} - }, - { - "id": "questui", - "versionRange": "^0.17.11", + "id": "bs-cordl", + "versionRange": "3500.*", "additionalData": {} }, { "id": "custom-types", - "versionRange": "^0.15.23", + "versionRange": "^0.17.0", "additionalData": {} }, { "id": "config-utils", - "versionRange": "^1.0.1", + "versionRange": "^1.4.2", "additionalData": {} }, { @@ -57,12 +56,12 @@ }, { "id": "sombrero", - "versionRange": "^0.1.30", + "versionRange": "^0.1.40", "additionalData": {} }, { - "id": "songloader", - "versionRange": "^0.10.15", + "id": "songcore", + "versionRange": "^1.1.1", "additionalData": {} }, { @@ -72,9 +71,18 @@ }, { "id": "bs-utils", - "versionRange": "^0.7.0", + "versionRange": "^0.8.0", + "additionalData": {} + }, + { + "id": "paper", + "versionRange": "^3.6.3", + "additionalData": {} + }, + { + "id": "bsml", + "versionRange": "^0.4.24", "additionalData": {} } - ], - "additionalData": {} + ] } \ No newline at end of file diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..2059550 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,31 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $clean, + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Build`" - Copiles your mod into a `".so`" or a `".a`" library" + Write-Output "`n-- Arguments --`n" + + Write-Output "-Clean `t`t Deletes the `"build`" folder, so that the entire library is rebuilt" + + exit +} + +# if user specified clean, remove all build files +if ($clean.IsPresent) { + if (Test-Path -Path "build") { + remove-item build -R + } +} + + +if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) { + new-item -Path build -ItemType Directory +} + +& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -B build +& cmake --build ./build diff --git a/scripts/copy.ps1 b/scripts/copy.ps1 new file mode 100644 index 0000000..b449573 --- /dev/null +++ b/scripts/copy.ps1 @@ -0,0 +1,76 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $clean, + + [Parameter(Mandatory=$false)] + [Switch] $log, + + [Parameter(Mandatory=$false)] + [Switch] $useDebug, + + [Parameter(Mandatory=$false)] + [Switch] $self, + + [Parameter(Mandatory=$false)] + [Switch] $all, + + [Parameter(Mandatory=$false)] + [String] $custom="", + + [Parameter(Mandatory=$false)] + [String] $file="", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Copy`" - Builds and copies your mod to your quest, and also starts Beat Saber with optional logging" + Write-Output "`n-- Arguments --`n" + + Write-Output "-Clean `t`t Performs a clean build (equvilant to running `"build -clean`")" + Write-Output "-UseDebug `t Copies the debug version of the mod to your quest" + Write-Output "-Log `t`t Logs Beat Saber using the `"Start-Logging`" command" + + Write-Output "`n-- Logging Arguments --`n" + + & $PSScriptRoot/start-logging.ps1 -help -excludeHeader + + exit +} + +& $PSScriptRoot/build.ps1 -clean:$clean + +if ($LASTEXITCODE -ne 0) { + Write-Output "Failed to build, exiting..." + exit $LASTEXITCODE +} + +& $PSScriptRoot/validate-modjson.ps1 +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$modJson = Get-Content "./mod.json" -Raw | ConvertFrom-Json + +foreach ($fileName in $modJson.modFiles) { + if ($useDebug -eq $true) { + & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName + } else { + & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName + } +} + +foreach ($fileName in $modJson.lateModFiles) { + if ($useDebug -eq $true) { + & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName + } else { + & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName + } +} + +& $PSScriptRoot/restart-game.ps1 + +if ($log -eq $true) { + & adb logcat -c + & $PSScriptRoot/start-logging.ps1 -self:$self -all:$all -custom:$custom -file:$file +} diff --git a/scripts/createqmod.ps1 b/scripts/createqmod.ps1 new file mode 100644 index 0000000..9bd5551 --- /dev/null +++ b/scripts/createqmod.ps1 @@ -0,0 +1,78 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $qmodName="", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"createqmod`" - Creates a .qmod file with your compiled libraries and mod.json." + Write-Output "`n-- Arguments --`n" + + Write-Output "-QmodName `t The file name of your qmod" + + exit +} + +$mod = "./mod.json" + +& $PSScriptRoot/validate-modjson.ps1 +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$modJson = Get-Content $mod -Raw | ConvertFrom-Json + +if ($qmodName -eq "") { + $qmodName = $modJson.name +} + +$filelist = @($mod) + +$cover = "./" + $modJson.coverImage +if ((-not ($cover -eq "./")) -and (Test-Path $cover)) { + $filelist += ,$cover +} + +foreach ($mod in $modJson.modFiles) { + $path = "./build/" + $mod + if (-not (Test-Path $path)) { + $path = "./extern/libs/" + $mod + } + if (-not (Test-Path $path)) { + Write-Output "Error: could not find dependency: $path" + exit 1 + } + $filelist += $path +} + +foreach ($mod in $modJson.lateModFiles) { + $path = "./build/" + $mod + if (-not (Test-Path $path)) { + $path = "./extern/libs/" + $mod + } + if (-not (Test-Path $path)) { + Write-Output "Error: could not find dependency: $path" + exit 1 + } + $filelist += $path +} + +foreach ($lib in $modJson.libraryFiles) { + $path = "./build/" + $lib + if (-not (Test-Path $path)) { + $path = "./extern/libs/" + $lib + } + if (-not (Test-Path $path)) { + Write-Output "Error: could not find dependency: $path" + exit 1 + } + $filelist += $path +} + +$zip = $qmodName + ".zip" +$qmod = $qmodName + ".qmod" + +Compress-Archive -Path $filelist -DestinationPath $zip -Update +Start-Sleep 1 +Move-Item $zip $qmod -Force diff --git a/scripts/get-backup.ps1 b/scripts/get-backup.ps1 new file mode 100644 index 0000000..d322eeb --- /dev/null +++ b/scripts/get-backup.ps1 @@ -0,0 +1,18 @@ +Write-Output "Pulling playlists from Quest" + +if (Test-Path Backup/Playlists) { + rm -r Backup/Playlists +} +if (Test-Path Backup/PlaylistBackups) { + rm -r Backup/PlaylistBackups +} + +adb pull /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/Playlists/ Backup/Playlists/ | Out-Null +if ($LASTEXITCODE -ne 0) { + mkdir Backup/Playlists | Out-Null +} + +adb pull /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/PlaylistBackups/ Backup/PlaylistBackups/ | Out-Null +if ($LASTEXITCODE -ne 0) { + mkdir Backup/PlaylistBackups | Out-Null +} \ No newline at end of file diff --git a/scripts/ndk-stack.ps1 b/scripts/ndk-stack.ps1 new file mode 100644 index 0000000..b247dbc --- /dev/null +++ b/scripts/ndk-stack.ps1 @@ -0,0 +1,29 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $logName = "log.log", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"NDK-Stack`" - Processes a tombstone using the debug .so to find file locations" + Write-Output "`n-- Arguments --`n" + + Write-Output "LogName `t`t The file name of the tombstone to process" + + exit +} + +if (Test-Path "./ndkpath.txt") { + $NDKPath = Get-Content ./ndkpath.txt +} else { + $NDKPath = $ENV:ANDROID_NDK_HOME +} + +$stackScript = "$NDKPath/ndk-stack" +if (-not ($PSVersionTable.PSEdition -eq "Core")) { + $stackScript += ".cmd" +} + +Get-Content $logName | & $stackScript -sym ./build/debug/ > "$($logName)_processed.log" diff --git a/scripts/pull-tombstone.ps1 b/scripts/pull-tombstone.ps1 new file mode 100644 index 0000000..17d3f83 --- /dev/null +++ b/scripts/pull-tombstone.ps1 @@ -0,0 +1,52 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $fileName = "RecentCrash.log", + + [Parameter(Mandatory=$false)] + [Switch] $analyze, + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Pull-Tombstone`" - Finds and pulls the most recent tombstone from your quest, optionally analyzing it with ndk-stack" + Write-Output "`n-- Arguments --`n" + + Write-Output "-FileName `t The name for the output file, defaulting to RecentCrash.log" + Write-Output "-Analyze `t Runs ndk-stack on the file after pulling" + + exit +} + +$global:currentDate = get-date +$global:recentDate = $Null +$global:recentTombstone = $Null + +for ($i = 0; $i -lt 3; $i++) { + $stats = & adb shell stat /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$i + $date = (Select-String -Input $stats -Pattern "(?<=Modify: )\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?=.\d{9})").Matches.Value + if([string]::IsNullOrEmpty($date)) { + Write-Output "Failed to pull tombstone, exiting..." + exit 1; + } + $dateObj = [datetime]::ParseExact($date, "yyyy-MM-dd HH:mm:ss", $Null) + $difference = [math]::Round(($currentDate - $dateObj).TotalMinutes) + if ($difference -eq 1) { + Write-Output "Found tombstone_0$i $difference minute ago" + } else { + Write-Output "Found tombstone_0$i $difference minutes ago" + } + if (-not $recentDate -or $recentDate -lt $dateObj) { + $recentDate = $dateObj + $recentTombstone = $i + } +} + +Write-Output "Latest tombstone was tombstone_0$recentTombstone" + +& adb pull /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$recentTombstone $fileName + +if ($analyze) { + & $PSScriptRoot/ndk-stack.ps1 -logName:$fileName +} diff --git a/restart-game.ps1 b/scripts/restart-game.ps1 similarity index 100% rename from restart-game.ps1 rename to scripts/restart-game.ps1 diff --git a/scripts/restore-backup.ps1 b/scripts/restore-backup.ps1 new file mode 100644 index 0000000..5c6f473 --- /dev/null +++ b/scripts/restore-backup.ps1 @@ -0,0 +1,13 @@ +Write-Output "Pushing playlist backups to Quest" + +$dir = adb shell ls /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/ + +if ($dir.Contains("Playlists")) { + adb shell rm -r /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/Playlists/ | Out-Null +} +if ($dir.Contains("PlaylistBackups")) { + adb shell rm -r /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/PlaylistBackups/ | Out-Null +} + +adb push Backup/Playlists/. /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/Playlists | Out-Null +adb push Backup/PlaylistBackups/. /sdcard/ModData/com.beatgames.beatsaber/Mods/PlaylistManager/PlaylistBackups | Out-Null \ No newline at end of file diff --git a/scripts/start-logging.ps1 b/scripts/start-logging.ps1 new file mode 100644 index 0000000..c7401fb --- /dev/null +++ b/scripts/start-logging.ps1 @@ -0,0 +1,84 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $self, + + [Parameter(Mandatory=$false)] + [Switch] $all, + + [Parameter(Mandatory=$false)] + [String] $custom="", + + [Parameter(Mandatory=$false)] + [String] $file="", + + [Parameter(Mandatory=$false)] + [Switch] $help, + + [Parameter(Mandatory=$false)] + [Switch] $trim, + + [Parameter(Mandatory=$false)] + [Switch] $excludeHeader +) + +if ($help -eq $true) { + if ($excludeHeader -eq $false) { + Write-Output "`"Start-Logging`" - Logs Beat Saber using `"adb logcat`"" + Write-Output "`n-- Arguments --`n" + } + + Write-Output "-Self `t`t Only logs from your mod and crashes" + Write-Output "-All `t`t Logs everything, including from non Beat Saber processes" + Write-Output "-Custom `t Specify a specific logging pattern, e.g `"custom-types|questui`"" + Write-Output "`t`t NOTE: The paterent `"AndriodRuntime|CRASH`" is always appended to a custom pattern" + Write-Output "-Trim `t Removes time, level, and mod from the start of lines`"" + Write-Output "-File `t`t Saves the output of the log to the file name given" + + exit +} + +$bspid = adb shell pidof com.beatgames.beatsaber +$command = "adb logcat " + +if ($all -eq $false) { + $loops = 0 + while ([string]::IsNullOrEmpty($bspid) -and $loops -lt 3) { + Start-Sleep -Milliseconds 100 + $bspid = adb shell pidof com.beatgames.beatsaber + $loops += 1 + } + + if ([string]::IsNullOrEmpty($bspid)) { + Write-Output "Could not connect to adb, exiting..." + exit 1 + } + + $command += "--pid $bspid" +} + +if ($all -eq $false) { + $pattern = "(" + if ($self -eq $true) { + $modID = (Get-Content "./mod.json" -Raw | ConvertFrom-Json).id + $pattern += "$modID|" + } + if (![string]::IsNullOrEmpty($custom)) { + $pattern += "$custom|" + } + if ($pattern -eq "(") { + $pattern = "(QuestHook|modloader|" + } + $pattern += "AndroidRuntime|CRASH)" + $command += " | Select-String -pattern `"$pattern`"" +} + +if ($trim -eq $true) { + $command += " | % {`$_ -replace `"^(?(?=.*\]:).*?\]: |.*?: )`", `"`"}" +} + +if (![string]::IsNullOrEmpty($file)) { + $command += " | Out-File -FilePath $PSScriptRoot\$file" +} + +Write-Output "Logging using Command `"$command`"" +Invoke-Expression $command diff --git a/scripts/validate-modjson.ps1 b/scripts/validate-modjson.ps1 new file mode 100644 index 0000000..094541c --- /dev/null +++ b/scripts/validate-modjson.ps1 @@ -0,0 +1,36 @@ +$mod = "./mod.json" + +if (-not (Test-Path -Path $mod)) { + if (Test-Path -Path ".\mod.template.json") { + & qpm qmod build + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + else { + Write-Output "Error: mod.json and mod.template.json were not present" + exit 1 + } +} + +$psVersion = $PSVersionTable.PSVersion.Major +if ($psVersion -ge 6) { + $schemaUrl = "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json" + Invoke-WebRequest $schemaUrl -OutFile ./mod.schema.json + + $schema = "./mod.schema.json" + $modJsonRaw = Get-Content $mod -Raw + $modSchemaRaw = Get-Content $schema -Raw + + Remove-Item $schema + + Write-Output "Validating mod.json..." + if (-not ($modJsonRaw | Test-Json -Schema $modSchemaRaw)) { + Write-Output "Error: mod.json is not valid" + exit 1 + } +} +else { + Write-Output "Could not validate mod.json with schema: powershell version was too low (< 6)" +} +exit diff --git a/src/API/PlayerController.cpp b/src/API/PlayerController.cpp index 804ce50..904c333 100644 --- a/src/API/PlayerController.cpp +++ b/src/API/PlayerController.cpp @@ -86,7 +86,7 @@ void PlayerController::SignUp(string login, string password, const function::New_ctor(); sprites = System::Collections::Generic::Dictionary_2::New_ctor(); - for (size_t i = 0; i < allnames.Length(); i++) + for (size_t i = 0; i < allnames.size(); i++) { StringW name = allnames[i]; auto material = assetBundle->LoadAsset(name); diff --git a/src/Assets/Sprites.cpp b/src/Assets/Sprites.cpp index ea94953..b9d6301 100644 --- a/src/Assets/Sprites.cpp +++ b/src/Assets/Sprites.cpp @@ -7,8 +7,8 @@ #include "UnityEngine/SpriteMeshType.hpp" #include "UnityEngine/ImageConversion.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "main.hpp" @@ -38,7 +38,7 @@ void Sprites::get_Icon(string url, const std::function bytes(data.begin(), data.end()); bytes.push_back('\0'); @@ -94,7 +94,7 @@ void Sprites::get_AnimatedIcon(string url, const std::function(UpB64)); + return BSML::Lite::Base64ToSprite(const_cast(UpB64)); } const string Sprites::DownB64 = "iVBORw0KGgoAAAANSUhEUgAAAJYAAABfCAYAAAAK5j6eAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAGAAAAABAAAAYAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAlqADAAQAAAABAAAAXwAAAADvKUNOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAASiklEQVR4Ae2dW2wc1RnHvb6GxDE2EBS1QmpVKGoDpDRcU5w4VxKb3JNSCr2CeKtU9aGiqtTyVB76UIk+VEK9pLSlTdNcnWAIJHEu3BtIgASUSuWBi4DmDiS+u7//eL7N8Xg33nW8u3N2d6TxjGdnZs/5vv/5f//zzZmziYrslkRLS0tVZ2dnP5cN2qVr1qyZ2tfX1zQwMNBUWVn5WSKROF5VVfXx+vXre+wcrquOXmeflbf5sQB+qpo2bdrgww8/PGDfuGLFiiv7+/ub8NdlbLuqq6tPNDQ0fLR27douO0e+Yx1wr7PP0m0T6T6IHuemlYcPH04AFoGqggLNZrOIdR7rF1gnAaiJg4ODveyfYXuM//ey7qCg26ygIcD6OKe85MkC8h2NWmtg95UrV94MCbTim/kU4RpW+a4en8m3Z1hPsP882x01NTXt+Pw0+xXZ+C4jYLk3BFB38B2/gJkWgO4KChisFKRCKwVMrpyj8lT09va+zeaRTZs2Pa7/H3zwwZrHHntMACwvObaAWMohg+l83c/x0RoAk6nv3sWvvz516tTvBMxMfTcqsFxQLV++/DeA6ccCDGARnQrhQo/u497LwmRAudBsNWtFT0/Pbuj2nq1bt36UaQG5b3kZowVc30EID3GbR2pra+UH3VENO/CdyADwmP/S+e4g598NORzNxHd2M33RiMVusHTp0smAaX1dXd2dYaFUstoRF6Q/IAoepFI1XP8elVi+efPmA7SmWleHpb+8/Em2FnBAlQBUj2P7+yADAaibe9VlcT+RRz8MV8v1Z4hQq7Zs2fKsYSPdfdICy5zO9jKEeScFux5QqFDVrFXpbniB44O0jG6YawL362J//saNG59bvHhxXUdHh+5bXsbJAgYqaatDhw51QAgLu7u7jaHG4juVTL6rI+JofwnMtc0wogPRZUgERY4KjWKStra2JkCwB7QaqIT0sRYsQWsRqHoELu7z7OrVq2cKVCpgpAjlf8doAQMVlycA1VMQgkClCDNWQrCSCFS9oW5upwPQJoyk890IYBnFCVQUSqC6DgpUwbKhTytMqm0tdKoCTqCgO5ctWzbzQgVMdYPysdQWcEClXrtAtYAoY7IlbXRKfbeUR2vwXZ/ABUlsW7VqVat8J8xEzx72ZQYqRHojoUqguiEEVS4YReBSQc9RqHlQ6wvlsBh1T+b/O6BK4D+FP+lhA1XmN8rgTLDRyyrfqWfZhuZ60rBjlyeBZQXDuQ0TJkzYF4IqW6Fn9810q7BYC3Odo6Bz0VwvXihuZ3rTUjvPfKd6w1QdMNWiXIHKsa2Yq1rggr1a6Yx1uOCyUJiw5Bmg2gjaxVS5BpXKKFD1Aq5LKNwu4vZt5bDouC6D3QKBSiWrxmeB5oIU2mHJGcpNWlgMGAuWCJJofPgooPpRHtAeNZnAVQPIztIK5m7YsOGlMnNFTTTy/wKCyi1MD7nNWjpl73LwJiTNx8JTZejAfql8wp9ApaTmCDHm3ikH+wKVwuJEqHUXgv5WMZc0Vw6+qyhu6YIK323PU/hLZTtFnS4I6SoY7FGdoOeRSY1FbH4J5N2i0MQJ+QaWFdg011nKMIe4/XKZucw057cuqIgy23FqawGizPkCDQ1IUJ6yEr81w1r7A40FqNYIVNDZAB8o31GoJUhFiLkIidJcN5c113BXxBBUKqAIagAMaV+PjoJnRVL1y3Gk/tejlySL6UC+F8qisChROImwuNvAVQ6Lw0cXQAbbYsBULjyqICb934yUuUoa61IobCbO1EHrJWq/kEuguWgBkwDaLhJxN5V6ht5lKkDVjqZqK3D4G4EPfDVAtGlgba4krXA1wPpCzIClQlsqol7Mpe5sqYZFF1Qw+FZAdReg0rO/XCSuRwAmwwNBOFTkA2C3SmxdAcJ0rXqDBQ2DKSoQhEXKVw/4SxJcLqhoXFvpuS8JmapQHawUbhpxaCoAq/wcTtMnGocTN2CpXJaKmCxw0WK/XiqpCBdUhL8taCoDVZyYSj6yJaFMPMvnxVh9rPZBXLcKi0pFTKasnQJXsWuuCKg2E/6Wxk1TpQJLSFJ9ley8H54gtoozwkxzCVy7EfQ3FqvmSgGqZT6ASvgJgfWeeoHHw26i9uMMLIoXhEU9/mmAwTrRHF8rtrDoggpm3gRT+QIq+SdYRFaVdOmP8t9bYXIryDmEn8d1Y6mIBioQgEthsRjyXC6o0FQbEerLPRDqhhORUpWyC0SU5ytp8RoPtV/dRJa4M5bKqMU0l3JwnThhuu+aKwoqmGoFoLKUQhw7VUOeOP93EAxJvCsCPmcJ0fVhOFRvI5D158+P7Z5prkspYQAuXzVXBFQbQlBpkF6cUwpRYPSHaavt7e3txwSsBA8NnwFpT0G9OtmHcGiVslREIwc6EfQ3+Ka5XFChqf4FqFZ6FP4CPxA1ghExJNv7Ya1HdLCSgVnBk0Pi4i/FWpwkdPnCWqqDhcVGGscenlNd70tYdEFFOF9Pw17lWfiT/aWpesmxafdRxtK9HYzH0qg/VVBDVDjhJ6GIF7B80VuqkIXFRlpMAK64h8UoqGCq1SGoCjm6RLbMdummQdR1dXW9dPLkyZ/q4mHjsfg/yGNBx7+nkveHrwyJvXwQjqqPlmAkKsx7EuadRYh/U71FMdjQx/H4GwHVP7H3Gif8+WRvGz36Ib3BGbzh/sGMGTNqDhw40GvivQL6CvZ5oeEBQLWFykrIq1fiE3NZKqKJcu8hvFwXt1REBFTrQlDJzr414mBQJo34GBJknkCl8e4CFXUZzkaRSgtceoxgldb5vizWkk4QGmcR9w/TcAr+On/EvgLVN53w5yNTHQdUs3j964hA5U70MqIykcr7DK5e9GINLSpZ+UKCy7UrTwz+gdi921dQkVaQpj0BqJoFqlR2HQEsUZJrBDRXdKhGymtiSmU2hj4JrkJoLteehOe/w1Tf8h1UFgnS2TOpsVxg2DxIOobmUjjchjFMc7mnxn1fLasb5rpcvUWc+pV8pyIioHqiGECF02ebvEjXMbog+0SMInBpOKyPmsvC4jGMot7iW6noe7xbCd/hTnomUN3jOVOdDO335mj2uyCwZOg04LLHDaNeP97Ouoj7Gbj+F9L42+lo/CK+I3mpCyo01d/QVN/2HFSnQqH+xmigkhEyAoblJnQBRorDe2wqylgWm+Mpp+CKNMa/wlT3eg6qkzTGFsLf65k2xpQaK+ox5SbUndRxMvQKh5p4QppLzOXTojmelIqYQuvbw0yF14635ioiUCnZLI16ykAlpkqnqaIgyIix7KKI0Z4EW4vDlhiAzs7zYGth8WPKqjd3j2ZC76PVyw1/dBT+gn3u85SpbC6N06FseD1b+2QFLBnWAZfmthS4bMoc7zLHMJcms0iCK1OaTwUwF1TIhcfRVN/xFFSWotEU3LNpdIfGYpeMQqFrSCcVMciXtvL452nA5WUqAlApFXEl9dvLkJtrxhoW1dh46B0MN6Kx/dljUFn4O82AhBaBSkyVafhzcZI1Y9nFjqBPkETt4Am3ZpDzMRVhj38+DGn/P9nQvstUAhVt7LueMpWFv08A1Sy09MFs7GC4sG3WjGUXStCrpfL/IElUaa0dGFXh0DdBr3AozTUVQb8XQX91pkNuIqBaWwygQqzPvlhQCSNjBpYudsPi9OnTBa5nMK7CYqyGqaisoyx6pqiwOJWe0D4Dl7RFuutcUKGp/kS1v+c5U51hqFELoxReU73VuNLVPZPjFwUsfYFND8ic4gOAS0Je4JJDLqpgmRR+nM+pA1wKiwLXXsDypXSaKwoqNNX3PQeVwp/mgH1V4W8smirqizFrrOiNrLeoSesPHjz4NMaeHxrb11TEB2iuZpKC/5WxrQW7oEJT/ZFG9APGemuyOsmCcbNn1L45+N801acS6oS/cf2lkHE1hIFLxqf1q7c4D3CJuXxNRQwDF/Xod3p/f6B+PyyDKjXkxxVY+gob8BUBVz5mYE5dw7EfleZSpv59QmMzgHqHW8legxq+TS/4ft9BhaZS+HvFZeSxm2v4leMOLN0+Ai71FueGzCVh79NiqYgPYOA7eF/uHQt/1EfT12n+p5zYMEdGCsIfvd/PuP+cXIFKZc+ZUSwsatvU1CRwzfEUXL2wk3617Agt/DV274Wp9BaTbJcz+8k547yYpjqr3l8uQaVy59QwxlwRcPkYFoNfYUDMV8BcyrCrN51T28k547gkmQq2motQz/ls1GP9Ja+M6kwSdUDgomfVN2nSpCfq6+ubYa6rNcKAG+T0uzMqYOYnaR6xfpwipvK193eW4s9jjHrOQSWzXnQeazTf2AuxytRPmTJlISFlD+Dy8dmiGoJvL5Na+DsnpgJUefvFj5wDS8BThl7hUCADXPqpM4HLx8c/o7WjOH2eBJV6f/kElYyQF2DpiwQu01x048Vce0Pm8u3xj6oT9yUJKgo6D6Ge919Vyxuw5Al7/KMsNuBawJCbfYDLx8c/cQaWCfUugYqhLy/kIk81mgEK0rNRWBSD6WEnP2OnZ4vNMJgEvW95rtHsm+/P7cdFu0NN9XwhQKVK55WxzMoWFvWwk1lKpLn2l8OiWWfM2wBU9F71Q+7q/RUMVKpBQRjLTGetKWSunYDrG2XmMutktU0ylUCFpnrObJvVXcbx5ILmko4cOdIvQb9u3bqeqVOnPgGw5rB+0cM81zi6JOtbGagkJeajqQoOKtWgoIxlJjTNxXZCY2PjTobczCwzl1nnglsXVBLq+wvNVFbagmgs+3LbSnPJIGy7AJaG2rzAMzkfR6JalfKxTYIKoS6mig2oVPlYMJZ5wVob20t4JifNdXuZucw6w7YGql401Xw01V6z3bCzCvhPrIAlO9jbPyG4dgGu28rgGoaQAFT0/vRGVCxBpdLGIhS6ZrPX+UminoO1FBZfBFzlsDhkpCRTAawFcWQq82XsGMsKZtS+ZMmSiYzgFHPdyjioUk6iJpkKTbWQoS+dZiOzWZy2sWMsM44e+ygVwajNs6Qf5gKql0PmErhKbTGm0tCd2INKzoktYxlyTHMtXLiQIV2TxFy3lJLmQpxLoNcAqD62Cwl/u+PMVOa32DKWFVCaS4bcsWPHZywaO/9KCaUikqDCHnf6Air5LvbAUiEVFg1cjIpQWBS4in1URDL8IdQXkafa5QNTyV9avACWCmqai+2nAhfM9e8i1lwaY6/wNwCo7kSo7/QJVPJX7DWWCukuNlgQQ9eTjuiEuWbAYD6+oOFWy90PXtwAUxLqixil8KxvoFJlvAOWCm2GZvKOyaQidhcRuCz86aUNhb9nrK6qt0+LN6HQNaqFRWZG+YRUxBwY61XfNZd6f2H4028XeQ0q+cpLxjKQWSqC8VwNjIjoZL3R01SEhT8k1eAiNNUOX5nKfOMlY1nhLRXBSNQzjJ9vYdWbyl49/gmZqlo6nXVxMYBK/vEaWKqApSIELkClsHjQo7BoeSr9UPdihPrTvjOVfKLFe2CpEqa52J4mFdESgkvMFefHP5ZSUBVaeVu8aEClCnmtsVQBd3FSEZeSitgDc02PaSrCNJWKv5je31PFwlTmj6ICliplDmKqx0b0i8B1Q5zAJU3FquSnfqS7FU3VYWU2pxTDtuiA5YKrra2tiey8kqgCV8GH3LigAlhtaKonixFU8kFRaKxoCzfNtX379pOkH6S53gBchdZcGp0QMBVCvahBJX8UJWMZ0IwNQuZSWLy+QGExqakA112MUthuZbOyFtu2KBnLnGSpCDFX2Ft8E3DldVREGP6Up1KxlpQCqFTRogaWKmhhke0JwDUbxjqcx7CoeUptOvIl9P62FTtTyeZaijoUDlVx6K+lIpic9nKOKCxOy7Ggl6YKJmrjeeZShHp7qYBKFi96xhqC1dAUSnIsrHGcY2KuIyFz5WJ+riSo+K5lApWALfa08hT7tmQYyxxprMHbP1cQGsVcXx1nQR/kqfR95KmWA+Qt9p1WhlLYlgxjmTNNc/H2zzGy84HmItdlgl5DVi5m6dHQF93AQFVqTGXGKzlgqeI2s6DAhf7Ra/wacqM8lxhcbxhryu1MQabunkR6v0IrvT9NJNsqpjJdx2clt5RcKHQ97DqenzH5FSzzM8ChudzFOAJX8Kup4TVmKxdwOlZDSK1AqFcQUvfx/wOA6qh77/D6ktqYsUqq0m5l0T9VhMcAQAx1vpGhzg/x+V0AbKLAAsCC022rY7aEYNI5r7L+lud+a/WZDUC080pxe95KpVj783VOMDdXlaZT0iFSEl9mM5f1dsBzLaCRbqpn1UR1n7L2cewj9NSL7O8h6SmmChDoApVj5aVsgaEfl9LvLaayheaQ0Msb6T5Xz4/ryg01NN7/AVc/vUYekbmIAAAAAElFTkSuQmCC"; UnityEngine::Sprite* Sprites::get_DownIcon() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(DownB64)); + return BSML::Lite::Base64ToSprite(const_cast(DownB64)); } const string Sprites::StarB64 = "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAB5lBMVEX////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ZkfksAAAAoXRSTlMAAQIDBAUGCAkKCwwNEBESExUWFxgZGxweHyQlJicrLC0vMTQ2Nzg5Oj9AQUJFSElKS0xOUlNUVldYWlxeX2BhYmRlaGtsbm9xcnR1dnd6e35/hYeIiouOk5SWmJmcn6Gio6Slp6ipra6xsrO1t7u8vb6/wcLDxMXGx8jJysvO0NHW19jZ2tvd3t/i5OXm6Ovt7/Dx8vP09fb3+Pn6+/z9/nFfuYEAAAQpSURBVHgB7dkJN1RhAwfw/8VQSCVFjaRQoSQtKbQv0hJlSdGuRKSlSEUhRUiNlAzm/03f88qZY5Z7Z5773Pvc9z2n39f44Z//byk3R0dvpsAxrmck+cwFp1RyUSUcEjvCRSOxcMZ+LtkPZ7zikldwRA79cuCE+/S7Dwds8NLPuwHqXeMy16DcykkuM7kSqp1igFNQTBtkgEENahUzSDHU6mSQTii1hSG2QKVmhmiGQmtnGGJmLdSpZhjVUCZ+gmFMxEOVCoZVAVX6GVY/FCmkjkKo0UYdbVDC7aMOnxsqXKeu61Bg1TR1Ta+C/S7QwAXYLm6UBkbjYLdSGiqF3XppqBc2284ItsNeDxnBQ9gqY54RzGfATvWMqB42SvrOiL4nwT5nGIUzsE3MMKMwHAO77GNU9sEWcem53YxKd256HKyipW7be7Kmqb1vYoECFib62ptqTu7dlqrBlJSs3RWXG1t7R72U5B3tbW28XLE7KwWRJboLjlQ1tLz8/Js2+P35ZUtD1ZECdyKCZOw8eLb2bteAh4p4Brru1p49uDMD/1X+gY75UA4U+uggXyHe0FFvMERHDaGLjupC9hwdNJcNHJ6lY2YPA8AuDx3i2YVFW8fpiPGtWLJxgA4Y2Ai/NT1UrmcNllnxmIo9XoEAsU1UqikWwaqpUDXCODFPReZPIKySX1TiVwl05H2jAt/yoCvzC233JRMG0vpos740GErupK06kxGB6x5tdM+FiLQ62qZOQzTO+WgL3zlE6dAf2uDPIUSt4Act96MAArK/0mJfsyEk4yMt9TEDglJe00KvUyAsoZWWaU2ACTE3aJEbMTDnCi1xBaYdm6O0uWOQsGeakqb3QEoxJRVDTikllUJOAyU1QE43JXVDimuGkmZckJFDaTmQcZrSTkPGHUq7AxmfKO0TJKymBVbDvCJaoAjm1dACNTCvgxbogHmTtMAkTHPTEm6YVUZLlMGsRlqiEWa9pyXew6QELy3hTYA5O2iRHTCnkhaphDkttEgLzBmhRUZgyjpGwetlFNbZlvUdmzd32Bb6VxnJcAkAlAwzkqsw4ymN/bwUj0Xxl37S2FOYEOOhEd/t9fBbf9tHI54YiMuikXd5CJD3jkayIO449U0c1RBEOzpBfcchrol6vPXJCCO53ks9TRDXTx1PMqEj8wl19ENY4jzDGiqGgeIhhjWfCFH5DGeqKh6G4qumGE4+RF1kKN+tNESUdsvHUBch6hFD9OYiKrm9DPEIosYYZLxcQ5S08nEGGYOgdAaarU2GgOTaWQZKh5gDDNC+CYI2tTPAAYip4zKDRTChaJDL1EHMc/p5zrtgiuu8h37PIeYFlyw0p8K01OYFLnkBMQ/4V08OpOT08K8HEOOeIsmxMg2StLIxkpxyQ1B+2/Tb6iRYIKn67XRbPv5X/fMfwXx1itpIs6EAAAAASUVORK5CYII="; Sprite* Sprites::get_StarIcon() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(StarB64)); + return BSML::Lite::Base64ToSprite(const_cast(StarB64)); } const string Sprites::PixelB64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAANSURBVBhXY2BgYGAAAAAFAAGKM+MAAAAAAElFTkSuQmCC"; Sprite* Sprites::get_TransparentPixel() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(PixelB64)); + return BSML::Lite::Base64ToSprite(const_cast(PixelB64)); } const string Sprites::GraphB64 = "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"; Sprite* Sprites::get_GraphIcon() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(GraphB64)); + return BSML::Lite::Base64ToSprite(const_cast(GraphB64)); } const string Sprites::ClipboardB64 = "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"; Sprite* Sprites::get_ClipboardIcon() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(ClipboardB64)); + return BSML::Lite::Base64ToSprite(const_cast(ClipboardB64)); } const string Sprites::ArrowsB64 = "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"; Sprite* Sprites::get_ArrowIcon() { - return QuestUI::BeatSaberUI::Base64ToSprite(const_cast(ArrowsB64)); + return BSML::Lite::Base64ToSprite(const_cast(ArrowsB64)); } \ No newline at end of file diff --git a/src/Core/ReplayRecorder.cpp b/src/Core/ReplayRecorder.cpp index 3bd0c9d..9fc53bc 100644 --- a/src/Core/ReplayRecorder.cpp +++ b/src/Core/ReplayRecorder.cpp @@ -12,7 +12,6 @@ #include "include/Enhancers/UserEnhancer.hpp" #include "include/Utils/ReplayManager.hpp" -#include "include/Utils/RecorderUtils.hpp" #include "include/Utils/ModConfig.hpp" #include "include/API/PlayerController.hpp" @@ -31,7 +30,6 @@ #include "GlobalNamespace/BeatmapData.hpp" #include "GlobalNamespace/GameplayModifiers.hpp" #include "GlobalNamespace/StandardLevelScenesTransitionSetupDataSO.hpp" -#include "GlobalNamespace/BeatmapEnvironmentHelper.hpp" #include "GlobalNamespace/OverrideEnvironmentSettings.hpp" #include "GlobalNamespace/GameplayCoreInstaller.hpp" #include "GlobalNamespace/AudioTimeSyncController.hpp" @@ -39,9 +37,7 @@ #include "GlobalNamespace/BeatmapObjectData.hpp" #include "GlobalNamespace/NoteData.hpp" #include "GlobalNamespace/CutScoreBuffer.hpp" -#include "GlobalNamespace/BeatmapObjectSpawnMovementData_NoteSpawnData.hpp" #include "GlobalNamespace/ObstacleData.hpp" -#include "GlobalNamespace/BeatmapObjectSpawnMovementData_ObstacleSpawnData.hpp" #include "GlobalNamespace/NoteController.hpp" #include "GlobalNamespace/ObstacleController.hpp" #include "GlobalNamespace/BeatmapObjectManager.hpp" @@ -57,12 +53,10 @@ #include "GlobalNamespace/SaberSwingRatingCounter.hpp" #include "GlobalNamespace/PlayerHeightDetector.hpp" #include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" #include "GlobalNamespace/IBeatmapLevelData.hpp" -#include "GlobalNamespace/IDifficultyBeatmapSet.hpp" #include "GlobalNamespace/IReadonlyBeatmapData.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" #include "GlobalNamespace/SaberMovementData.hpp" #include "GlobalNamespace/SaberSwingRating.hpp" #include "GlobalNamespace/GameplayCoreSceneSetupData.hpp" @@ -124,12 +118,12 @@ namespace ReplayRecorder { chrono::steady_clock::time_point _pauseStartTime; System::Action_1* _heightEvent; System::Action_1* _scoreEvent; - System::Action_1* _wallEvent; + System::Action_1>* _wallEvent; void collectMapData(StandardLevelScenesTransitionSetupDataSO* self) { - mapEnhancer.difficultyBeatmap = self->difficultyBeatmap; - mapEnhancer.previewBeatmapLevel = self->gameplayCoreSceneSetupData->previewBeatmapLevel; + mapEnhancer.difficultyBeatmap = self->beatmapKey; + mapEnhancer.beatmapLevel = self->beatmapLevel; mapEnhancer.gameplayModifiers = self->gameplayModifiers; mapEnhancer.playerSpecificSettings = self->gameplayCoreSceneSetupData->playerSpecificSettings; mapEnhancer.practiceSettings = self->practiceSettings; @@ -140,14 +134,14 @@ namespace ReplayRecorder { } void collectMultiplayerMapData(MultiplayerLevelScenesTransitionSetupDataSO* self) { - GameplayCoreSceneSetupData* gameplayCoreSceneSetupData = reinterpret_cast(self->sceneSetupDataArray->get(2)); + GameplayCoreSceneSetupData* gameplayCoreSceneSetupData = reinterpret_cast(self->gameplayCoreSceneSetupData); - mapEnhancer.difficultyBeatmap = self->difficultyBeatmap; - mapEnhancer.previewBeatmapLevel = gameplayCoreSceneSetupData->previewBeatmapLevel; + mapEnhancer.difficultyBeatmap = self->beatmapKey; + mapEnhancer.beatmapLevel = gameplayCoreSceneSetupData->beatmapLevel; mapEnhancer.gameplayModifiers = gameplayCoreSceneSetupData->gameplayModifiers; mapEnhancer.playerSpecificSettings = gameplayCoreSceneSetupData->playerSpecificSettings; mapEnhancer.practiceSettings = NULL; - mapEnhancer.environmentInfo = self->multiplayerEnvironmentInfo; + mapEnhancer.environmentInfo = self->_loadedMultiplayerEnvironmentInfo; mapEnhancer.colorScheme = self->colorScheme; automaticPlayerHeight = gameplayCoreSceneSetupData->playerSpecificSettings->automaticPlayerHeight; @@ -188,9 +182,7 @@ namespace ReplayRecorder { if (replay == nullopt) return; - _heightEvent = il2cpp_utils::MakeDelegate *>( - classof(System::Action_1*), - static_cast(nullptr), OnPlayerHeightChange); + _heightEvent = custom_types::MakeDelegate*>((std::function)OnPlayerHeightChange); self->add_playerHeightDidChangeEvent(_heightEvent); } @@ -225,6 +217,7 @@ namespace ReplayRecorder { switch (results->playerLevelEndReason) { case MultiplayerLevelCompletionResults::MultiplayerPlayerLevelEndReason::Cleared: + { auto results = levelCompletionResults->localPlayerResultData->multiplayerLevelCompletionResults->levelCompletionResults; auto playEndData = PlayEndData(results, replay->info.speed); @@ -234,6 +227,9 @@ namespace ReplayRecorder { mapEnhancer.Enhance(replay.value()); replayCallback(*replay, playEndData, false); break; + } + default: + break; } } } @@ -317,9 +313,9 @@ namespace ReplayRecorder { return real < 1 ? real : max(real, unclamped); } - void onObstacle(ObstacleController* obstacle) { + void onObstacle(UnityW obstacle) { if (_currentWallEvent == nullopt) { - WallEvent& wallEvent = _wallEventCache.at(_wallCache[obstacle]); + WallEvent& wallEvent = _wallEventCache.at(_wallCache[obstacle.ptr()]); wallEvent.time = audioTimeSyncController->songTime; replay->walls.emplace_back(wallEvent); _currentWallEvent.emplace(wallEvent); @@ -352,29 +348,29 @@ namespace ReplayRecorder { } else if (il2cpp_utils::try_cast(scoringElement) != nullopt) { GoodCutScoringElement* goodCut = il2cpp_utils::try_cast(scoringElement).value(); - CutScoreBuffer* cutScoreBuffer = goodCut->cutScoreBuffer; - SaberSwingRatingCounter* saberSwingRatingCounter = cutScoreBuffer->saberSwingRatingCounter; + CutScoreBuffer* cutScoreBuffer = goodCut->_cutScoreBuffer; + SaberSwingRatingCounter* saberSwingRatingCounter = cutScoreBuffer->_saberSwingRatingCounter; ReplayNoteCutInfo& noteCutInfo = noteEvent.noteCutInfo; PopulateNoteCutInfo(noteCutInfo, cutScoreBuffer->noteCutInfo, _noteControllerCache[noteId]); - noteCutInfo.beforeCutRating = ChooseSwingRating(saberSwingRatingCounter->beforeCutRating, _preSwingContainer[(SaberMovementData *)saberSwingRatingCounter->saberMovementData]); + noteCutInfo.beforeCutRating = ChooseSwingRating(saberSwingRatingCounter->beforeCutRating, _preSwingContainer[(SaberMovementData *)saberSwingRatingCounter->_saberMovementData]); noteCutInfo.afterCutRating = ChooseSwingRating(saberSwingRatingCounter->afterCutRating, _postSwingContainer[saberSwingRatingCounter]); - _preSwingContainer[(SaberMovementData *)saberSwingRatingCounter->saberMovementData] = 0; + _preSwingContainer[(SaberMovementData *)saberSwingRatingCounter->_saberMovementData] = 0; _postSwingContainer[saberSwingRatingCounter] = 0; } } MAKE_HOOK_MATCH(ScoreControllerLateUpdate, &ScoreController::LateUpdate, void, ScoreController* self) { - auto sortedScoringElementsWithoutMultiplier = self->sortedScoringElementsWithoutMultiplier; - auto sortedNoteTimesWithoutScoringElements = self->sortedNoteTimesWithoutScoringElements; + auto sortedScoringElementsWithoutMultiplier = self->_sortedScoringElementsWithoutMultiplier; + auto sortedNoteTimesWithoutScoringElements = self->_sortedNoteTimesWithoutScoringElements; - if (replay != nullopt - && sortedScoringElementsWithoutMultiplier != NULL - && sortedNoteTimesWithoutScoringElements != NULL - && self->audioTimeSyncController != NULL) { - auto songTime = self->audioTimeSyncController->songTime; + if (replay + && sortedScoringElementsWithoutMultiplier + && sortedNoteTimesWithoutScoringElements + && self->_audioTimeSyncController) { + auto songTime = self->_audioTimeSyncController->songTime; float nearestNotCutNoteTime = sortedNoteTimesWithoutScoringElements->get_Count() > 0 ? sortedNoteTimesWithoutScoringElements->get_Item(0) : 10000000; float skipAfter = songTime + 0.15f; @@ -404,29 +400,25 @@ namespace ReplayRecorder { ScoreControllerStart(self); if (replay == nullopt) return; - _scoreEvent = il2cpp_utils::MakeDelegate *>( - classof(System::Action_1*), - static_cast(nullptr), scoringElementFinished); + _scoreEvent = custom_types::MakeDelegate *>((std::function)scoringElementFinished); self->add_scoringForNoteFinishedEvent(_scoreEvent); - _wallEvent = il2cpp_utils::MakeDelegate *>( - classof(System::Action_1*), - static_cast(nullptr), onObstacle); - self->playerHeadAndObstacleInteraction->add_headDidEnterObstacleEvent(_wallEvent); + _wallEvent = custom_types::MakeDelegate> *>((std::function)>)onObstacle); + self->_playerHeadAndObstacleInteraction->add_headDidEnterObstacleEvent(_wallEvent); - phoi = self->playerHeadAndObstacleInteraction; + phoi = self->_playerHeadAndObstacleInteraction; - audioTimeSyncController = self->audioTimeSyncController; + audioTimeSyncController = self->_audioTimeSyncController; } MAKE_HOOK_MATCH(ComputeSwingRating, static_cast(&SaberMovementData::ComputeSwingRating), float, SaberMovementData* self, bool overrideSegmenAngle, float overrideValue) { float result = ComputeSwingRating(self, overrideSegmenAngle, overrideValue); if (replay == nullopt) return result; - auto _data = self->data; - int _nextAddIndex = self->nextAddIndex; - int _validCount = self->validCount; + auto _data = self->_data; + int _nextAddIndex = self->_nextAddIndex; + int _validCount = self->_validCount; - int length = _data.Length(); + int length = _data.size(); int index = _nextAddIndex - 1; if (index < 0) index += length; @@ -461,24 +453,24 @@ namespace ReplayRecorder { } MAKE_HOOK_MATCH(ProcessNewSwingData, &SaberSwingRatingCounter::ProcessNewData, void, SaberSwingRatingCounter* self, BladeMovementDataElement newData, BladeMovementDataElement prevData, bool prevDataAreValid) { - bool alreadyCut = self->notePlaneWasCut; + bool alreadyCut = self->_notePlaneWasCut; ProcessNewSwingData(self, newData, prevData, prevDataAreValid); if (replay == nullopt) return; float postSwing = _postSwingContainer[self]; - if (!alreadyCut && !self->notePlane.SameSide(newData.topPos, prevData.topPos)) + if (!alreadyCut && !self->_notePlane.SameSide(newData.topPos, prevData.topPos)) { - float angleDiff = UnityEngine::Vector3::Angle(self->cutTopPos - self->cutBottomPos, self->afterCutTopPos - self->afterCutBottomPos); + float angleDiff = UnityEngine::Vector3::Angle(UnityEngine::Vector3::op_Subtraction(self->_cutTopPos, self->_cutBottomPos), UnityEngine::Vector3::op_Subtraction(self->_afterCutTopPos, self->_afterCutBottomPos)); - if (self->rateAfterCut) + if (self->_rateAfterCut) { postSwing = SaberSwingRating::AfterCutStepRating(angleDiff, 0.0f); } } else { - float normalDiff = UnityEngine::Vector3::Angle(newData.segmentNormal, self->cutPlaneNormal); - if (self->rateAfterCut) + float normalDiff = UnityEngine::Vector3::Angle(newData.segmentNormal, self->_cutPlaneNormal); + if (self->_rateAfterCut) { postSwing += SaberSwingRating::AfterCutStepRating(newData.segmentAngle, normalDiff); } @@ -534,7 +526,7 @@ namespace ReplayRecorder { } if (_currentWallEvent != nullopt) { - if (phoi->intersectingObstacles->get_Count() == 0) + if (phoi->_intersectingObstacles->get_Count() == 0) { WallEvent& wallEvent = replay->walls[replay->walls.size() - 1]; wallEvent.energy = audioTimeSyncController->songTime; @@ -546,27 +538,26 @@ namespace ReplayRecorder { void StartRecording( function const &started, function const &callback) { - LoggerContextObject logger = getLogger().WithContext("load"); - - getLogger().info("Installing ReplayRecorder hooks..."); - - INSTALL_HOOK(logger, ProcessResultsSolo); - INSTALL_HOOK(logger, SinglePlayerInstallBindings); - INSTALL_HOOK(logger, SpawnNote); - INSTALL_HOOK(logger, SpawnObstacle); - INSTALL_HOOK(logger, BeatMapStart); - INSTALL_HOOK(logger, LevelPause); - INSTALL_HOOK(logger, LevelUnpause); - INSTALL_HOOK(logger, Tick); - INSTALL_HOOK(logger, ComputeSwingRating); - INSTALL_HOOK(logger, ProcessNewSwingData); - INSTALL_HOOK(logger, PlayerHeightDetectorStart); - INSTALL_HOOK(logger, ScoreControllerStart); - INSTALL_HOOK(logger, ScoreControllerLateUpdate); - INSTALL_HOOK(logger, ProcessResultsMultiplayer); - INSTALL_HOOK(logger, HandleNoteControllerNoteWasCut); - - getLogger().info("Installed all ReplayRecorder hooks!"); + + BeatLeaderLogger.info("Installing ReplayRecorder hooks..."); + + INSTALL_HOOK(BeatLeaderLogger, ProcessResultsSolo); + INSTALL_HOOK(BeatLeaderLogger, SinglePlayerInstallBindings); + INSTALL_HOOK(BeatLeaderLogger, SpawnNote); + INSTALL_HOOK(BeatLeaderLogger, SpawnObstacle); + INSTALL_HOOK(BeatLeaderLogger, BeatMapStart); + INSTALL_HOOK(BeatLeaderLogger, LevelPause); + INSTALL_HOOK(BeatLeaderLogger, LevelUnpause); + INSTALL_HOOK(BeatLeaderLogger, Tick); + INSTALL_HOOK(BeatLeaderLogger, ComputeSwingRating); + INSTALL_HOOK(BeatLeaderLogger, ProcessNewSwingData); + INSTALL_HOOK(BeatLeaderLogger, PlayerHeightDetectorStart); + INSTALL_HOOK(BeatLeaderLogger, ScoreControllerStart); + INSTALL_HOOK(BeatLeaderLogger, ScoreControllerLateUpdate); + INSTALL_HOOK(BeatLeaderLogger, ProcessResultsMultiplayer); + INSTALL_HOOK(BeatLeaderLogger, HandleNoteControllerNoteWasCut); + + BeatLeaderLogger.info("Installed all ReplayRecorder hooks!"); startedCallback = started; replayCallback = callback; diff --git a/src/Enhancers/MapEnhancer.cpp b/src/Enhancers/MapEnhancer.cpp index f3c7063..1194bbb 100644 --- a/src/Enhancers/MapEnhancer.cpp +++ b/src/Enhancers/MapEnhancer.cpp @@ -1,10 +1,8 @@ #include "include/Enhancers/MapEnhancer.hpp" #include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" -#include "GlobalNamespace/IDifficultyBeatmapSet.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" #include #include @@ -12,14 +10,13 @@ void MapEnhancer::Enhance(Replay &replay) { ReplayInfo& info = replay.info; - info.hash = regex_replace((string)previewBeatmapLevel->get_levelID(), basic_regex("custom_level_"), ""); - IPreviewBeatmapLevel* levelData = reinterpret_cast(difficultyBeatmap->get_level()); - info.songName = (string)levelData->get_songName(); - info.mapper = (string)levelData->get_levelAuthorName(); - info.difficulty = DiffName(difficultyBeatmap->get_difficulty().value); + info.hash = regex_replace((string)beatmapLevel->levelID, basic_regex("custom_level_"), ""); + info.songName = (string)beatmapLevel->songName; + info.mapper = (string)beatmapLevel->songAuthorName; + info.difficulty = DiffName(difficultyBeatmap.difficulty.value__); - info.mode = (string)difficultyBeatmap->get_parentDifficultyBeatmapSet()->get_beatmapCharacteristic()->serializedName; - info.environment = (string)environmentInfo->get_environmentName(); + info.mode = (string)difficultyBeatmap.beatmapCharacteristic->serializedName; + info.environment = (string)environmentInfo->environmentName; info.modifiers = Join(Modifiers()); info.leftHanded = playerSpecificSettings->leftHanded; info.height = playerSpecificSettings->automaticPlayerHeight ? 0 : playerSpecificSettings->playerHeight; diff --git a/src/Enhancers/UserEnhancer.cpp b/src/Enhancers/UserEnhancer.cpp index 6f0f6e5..d3eeded 100644 --- a/src/Enhancers/UserEnhancer.cpp +++ b/src/Enhancers/UserEnhancer.cpp @@ -2,8 +2,6 @@ #include "include/API/PlayerController.hpp" #include "GlobalNamespace/OVRPlugin.hpp" -#include "GlobalNamespace/OVRPlugin_Controller.hpp" -#include "GlobalNamespace/OVRPlugin_SystemHeadset.hpp" void UserEnhancer::Enhance(Replay& replay) { @@ -29,23 +27,14 @@ void UserEnhancer::Enhance(Replay& replay) replay.info.hmd = "Oculus Quest"; break; case GlobalNamespace::OVRPlugin::SystemHeadset::Oculus_Quest_2: - switch (sysconf(_SC_NPROCESSORS_CONF)) - { - case 4: - replay.info.hmd = "Oculus Quest"; - break; - case 8: - replay.info.hmd = "Oculus Quest 2"; - break; - case 6: - replay.info.hmd = "Meta Quest 3"; - break; - default: - replay.info.hmd = "Unknown"; - break; - } + replay.info.hmd = "Oculus Quest 2"; + break; + case GlobalNamespace::OVRPlugin::SystemHeadset::Meta_Quest_Pro: + replay.info.hmd = "Meta Quest Pro"; + break; + case GlobalNamespace::OVRPlugin::SystemHeadset::Meta_Quest_3: + replay.info.hmd = "Meta Quest 3"; break; - default: replay.info.hmd = "Unknown"; break; diff --git a/src/Models/Replay.cpp b/src/Models/Replay.cpp index 238a8f5..d8d1943 100644 --- a/src/Models/Replay.cpp +++ b/src/Models/Replay.cpp @@ -13,7 +13,7 @@ enum struct StructType { }; void Replay::Encode(ofstream& s) const { - getLogger().info("Started encode"); + BeatLeaderLogger.info("Started encode"); Encode((int)0x442d3d69, s); Encode((char)1, s); @@ -24,27 +24,27 @@ void Replay::Encode(ofstream& s) const { switch (type) { case StructType::info: - getLogger().info("Started encode info"); + BeatLeaderLogger.info("Started encode info"); Encode(info, s); break; case StructType::frames: - getLogger().info("Started encode frames"); + BeatLeaderLogger.info("Started encode frames"); Encode(frames, s); break; case StructType::notes: - getLogger().info("Started encode notes"); + BeatLeaderLogger.info("Started encode notes"); Encode(notes, s); break; case StructType::walls: - getLogger().info("Started encode walls"); + BeatLeaderLogger.info("Started encode walls"); Encode(walls, s); break; case StructType::heights: - getLogger().info("Started encode heights"); + BeatLeaderLogger.info("Started encode heights"); Encode(heights, s); break; case StructType::pauses: - getLogger().info("Started encode pauses"); + BeatLeaderLogger.info("Started encode pauses"); Encode(pauses, s); break; } diff --git a/src/UI/CaptorClanUI.cpp b/src/UI/CaptorClanUI.cpp index de44264..e94cea3 100644 --- a/src/UI/CaptorClanUI.cpp +++ b/src/UI/CaptorClanUI.cpp @@ -3,12 +3,10 @@ #include "GlobalNamespace/LevelSelectionNavigationController.hpp" #include "GlobalNamespace/StandardLevelDetailViewController.hpp" #include "GlobalNamespace/StandardLevelDetailView.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" -#include "GlobalNamespace/IDifficultyBeatmap.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapKey.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" -#include "GlobalNamespace/IDifficultyBeatmapSet.hpp" #include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" #include "GlobalNamespace/BeatmapCharacteristicSegmentedControlController.hpp" #include "GlobalNamespace/LevelParamsPanel.hpp" #include "GlobalNamespace/ColorExtensions.hpp" @@ -35,10 +33,9 @@ #include "TMPro/TMP_Text.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/ArrayUtil.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include #include @@ -47,8 +44,8 @@ using namespace GlobalNamespace; using namespace std; -using namespace QuestUI; -using namespace BeatSaberUI; +using namespace BSML; +using namespace Lite; namespace CaptorClanUI { bool showClanRanking = false; @@ -63,7 +60,7 @@ namespace CaptorClanUI { TMPro::TextMeshProUGUI* clanTag; TMPro::TextMeshProUGUI* captorClanStatus; - QuestUI::ClickableImage* backgroundImage; + BSML::ClickableImage* backgroundImage; HMUI::ImageView* clanImage; HMUI::HoverHint* clanHint; @@ -103,29 +100,29 @@ namespace CaptorClanUI { void initCaptorClan(UnityEngine::GameObject* headerPanel, TMPro::TextMeshProUGUI* headerPanelText) { headerText = headerPanelText; - backgroundImage = CreateClickableImage(headerPanel->get_transform(), Sprites::get_TransparentPixel(), {0, 0}, {17, 6}, []() { + backgroundImage = CreateClickableImage(headerPanel->get_transform(), Sprites::get_TransparentPixel(), []() { showClanRanking = !showClanRanking; showClanRankingCallback(); - }); + }, {0, 0}, {17, 6}); backgroundImage->set_material(BundleLoader::bundle->clanTagBackgroundMaterial); backgroundImage->set_color(BackgroundColor()); - backgroundImage->get_onPointerEnterEvent() += [](auto _){ + backgroundImage->onEnter = [](){ backgroundImage->set_color(BackgroundHoverColor()); }; - backgroundImage->get_onPointerExitEvent() += [](auto _){ + backgroundImage->onExit = [](){ backgroundImage->set_color(BackgroundColor()); }; - mainPanel = BeatSaberUI::CreateHorizontalLayoutGroup(backgroundImage->get_transform()); + mainPanel = Lite::CreateHorizontalLayoutGroup(backgroundImage->get_transform()); mainPanel->set_padding(RectOffset::New_ctor(1, 1, 0, 0)); mainPanel->set_spacing(3); mainPanel->GetComponentInChildren()->set_horizontalFit(UnityEngine::UI::ContentSizeFitter::FitMode::PreferredSize); mainPanel->GetComponentInChildren()->set_preferredHeight(5.0f); clanHint = AddHoverHint(mainPanel, ""); - captorClanStatus = BeatSaberUI::CreateText(mainPanel->get_transform(), ""); + captorClanStatus = Lite::CreateText(mainPanel->get_transform(), ""); captorClanStatus->set_fontSize(3.0f); captorClanStatus->set_alignment(TMPro::TextAlignmentOptions::Midline); EmojiSupport::AddSupport(captorClanStatus); @@ -133,7 +130,7 @@ namespace CaptorClanUI { clanImage = UIUtils::CreateRoundRectImage(mainPanel->get_transform(), {0, 0}, {8, 2.5}); clanImage->set_material(BundleLoader::bundle->clanTagBackgroundMaterial); - clanTag = CreateText(clanImage->get_transform(), "", UnityEngine::Vector2(0.0, 0.0)); + clanTag = CreateText(clanImage->get_transform(), "", {0, 0}, {30, 5}); clanTag->set_enableAutoSizing(true); clanTag->set_fontSizeMin(0.1); clanTag->set_fontSizeMax(3.0); @@ -185,7 +182,7 @@ namespace CaptorClanUI { string text = "👑 "; captorClanStatus->set_text(text); - captorClanStatus->set_faceColor(UnityEngine::Color32(255, 215, 0, 255)); + captorClanStatus->set_faceColor(UnityEngine::Color32(0, 255, 215, 0, 255)); clanHint->set_text("Map is captured by \r\n" + clan->name + "\r\n They have the highest weighted PP on this leaderboard"); backgroundWidth = WidthPerCharacter * clan->tag.length() + WidthPerCharacter * text.length() / 2; @@ -193,7 +190,7 @@ namespace CaptorClanUI { } else if (clanStatus.clanRankingContested) { string text = "⚔ Contested "; captorClanStatus->set_text(text); - captorClanStatus->set_faceColor(UnityEngine::Color32(192, 192, 192, 255)); + captorClanStatus->set_faceColor(UnityEngine::Color32(0, 192, 192, 192, 255)); clanHint->set_text("Several clans claim equal rights to capture this map! Set a score to break the tie"); backgroundWidth = WidthPerCharacter * text.length() / 2; @@ -201,7 +198,7 @@ namespace CaptorClanUI { } else { string text = "👑 Uncaptured "; captorClanStatus->set_text(text); - captorClanStatus->set_faceColor(UnityEngine::Color32(255, 255, 255, 255)); + captorClanStatus->set_faceColor(UnityEngine::Color32(0, 255, 255, 255, 255)); clanHint->set_text("Map is not captured! Set a score to capture it for your clan"); backgroundWidth = WidthPerCharacter * text.length() / 2; diff --git a/src/UI/Components/ExternalComponents.cpp b/src/UI/Components/ExternalComponents.cpp new file mode 100644 index 0000000..7c81590 --- /dev/null +++ b/src/UI/Components/ExternalComponents.cpp @@ -0,0 +1,19 @@ +#include "beatsaber-hook/shared/utils/utils.h" +#include "UI/Components/ExternalComponents.hpp" + +DEFINE_TYPE(QuestUI, ExternalComponents); + +void QuestUI::ExternalComponents::Add(UnityEngine::Component* component) { + components.push_back(component); +} + +UnityEngine::Component* QuestUI::ExternalComponents::GetByType(Il2CppReflectionType* type) { + if(!type) return nullptr; + Il2CppClass* clazz = il2cpp_functions::class_from_system_type(type); + for(UnityEngine::Component* component : components) + { + if (il2cpp_functions::class_is_assignable_from(clazz, il2cpp_functions::object_get_class(component))) + return component; + } + return nullptr; +} \ No newline at end of file diff --git a/src/UI/Components/SmoothHoverController.cpp b/src/UI/Components/SmoothHoverController.cpp index f633cd4..63e9f6e 100644 --- a/src/UI/Components/SmoothHoverController.cpp +++ b/src/UI/Components/SmoothHoverController.cpp @@ -1,8 +1,8 @@ #include "HMUI/Touchable.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "UnityEngine/Application.hpp" #include "UnityEngine/GUIUtility.hpp" @@ -22,7 +22,7 @@ #include "main.hpp" -using namespace QuestUI; +using namespace BSML; using namespace UnityEngine; static float lerpCoefficient = 10.0f; @@ -41,7 +41,8 @@ void BeatLeader::SmoothHoverController::ctor() { void BeatLeader::SmoothHoverController::OnDisable() { IsHovered = false; - Progress = _targetValue = 0.0f; + Progress = 0.0f; + _targetValue = 0.0f; _set = false; hoverStateChangedEvent.invoke(IsHovered, Progress); diff --git a/src/UI/EmojiSupport.cpp b/src/UI/EmojiSupport.cpp index f3088ae..c2604a3 100644 --- a/src/UI/EmojiSupport.cpp +++ b/src/UI/EmojiSupport.cpp @@ -13,7 +13,9 @@ #include "UnityEngine/Graphics.hpp" #include "UnityEngine/TextCore/GlyphMetrics.hpp" #include "UnityEngine/TextCore/GlyphRect.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "UnityEngine/TextCore/Text/FontAssetUtilities.hpp" +#include "UnityEngine/TextureFormat.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "beatsaber-hook/shared/utils/hooking.hpp" #include "beatsaber-hook/shared/utils/logging.hpp" @@ -23,6 +25,7 @@ #include "TMPro/TMP_TextUtilities.hpp" #include "TMPro/TMP_SpriteGlyph.hpp" #include "TMPro/TMP_SpriteCharacter.hpp" +#include "TMPro/TMP_FontAssetUtilities.hpp" #include "main.hpp" @@ -62,7 +65,7 @@ TMPro::TMP_SpriteAsset* CreateTMP_SpriteAsset() { texture->Apply(false, true); TMPro::TMP_SpriteAsset* spriteAsset = ScriptableObject::CreateInstance(); - spriteAsset->fallbackSpriteAssets = System::Collections::Generic::List_1::New_ctor(); + spriteAsset->fallbackSpriteAssets = System::Collections::Generic::List_1>::New_ctor(); spriteAsset->spriteInfoList = System::Collections::Generic::List_1::New_ctor(); spriteAsset->spriteSheet = texture; spriteAsset->material = UnityEngine::Material::New_ctor(BundleLoader::bundle->TMP_SpriteCurved); @@ -129,15 +132,12 @@ TMPro::TMP_SpriteGlyph* PushSprite(int unicode) { return sprite; } -MAKE_HOOK_MATCH(SearchForSpriteByUnicode, &TMPro::TMP_SpriteAsset::SearchForSpriteByUnicode, TMPro::TMP_SpriteAsset*, TMPro::TMP_SpriteAsset* spriteAsset, uint unicode, bool includeFallbacks, ByRef spriteIndex) { - TMPro::TMP_SpriteAsset* result = SearchForSpriteByUnicode(spriteAsset, unicode, includeFallbacks, spriteIndex); - + +MAKE_HOOK_MATCH(GetSpriteCharacterFromSpriteAsset, &TMPro::TMP_FontAssetUtilities::GetSpriteCharacterFromSpriteAsset, TMPro::TMP_SpriteCharacter*, uint32_t unicode, TMPro::TMP_SpriteAsset* spriteAsset, bool includeFallbacks) { + auto result = GetSpriteCharacterFromSpriteAsset(unicode, spriteAsset, includeFallbacks); if (result == NULL) { auto glyph = PushSprite(unicode); - *spriteIndex = currentEmojiIndex; - result = currentEmojiAsset; - int indexToUse = currentEmojiIndex; auto assetToUse = currentEmojiAsset; loadingCount++; @@ -146,13 +146,13 @@ MAKE_HOOK_MATCH(SearchForSpriteByUnicode, &TMPro::TMP_SpriteAsset::SearchForSpri if (sprite != NULL) { DrawSprite((UnityEngine::Texture*)sprite->get_texture(), indexToUse, glyph, assetToUse); } else { - getLogger().info("%s", (WebUtils::API_URL + "unicode/" + utf8ToHex(unicode) + ".png").c_str()); + BeatLeaderLogger.info("%s", (WebUtils::API_URL + "unicode/" + utf8ToHex(unicode) + ".png").c_str()); } loadingCount--; if (loadingCount == 0) { for (auto const& i : textToUpdate) { - i->ForceMeshUpdate(); + i->ForceMeshUpdate(false, false); } textToUpdate = {}; } @@ -160,9 +160,10 @@ MAKE_HOOK_MATCH(SearchForSpriteByUnicode, &TMPro::TMP_SpriteAsset::SearchForSpri }, true); currentEmojiIndex++; + result = GetSpriteCharacterFromSpriteAsset(unicode, spriteAsset, includeFallbacks); } - if (result == currentEmojiAsset && loadingCount > 0) { + if (spriteAsset == currentEmojiAsset && loadingCount > 0) { textToUpdate.push_back(lastText); } @@ -187,14 +188,13 @@ void EmojiSupport::AddSupport(TMPro::TextMeshProUGUI* text) { currentEmojiIndex = 0; } if (!hooksInstalled) { - LoggerContextObject logger = getLogger().WithContext("load"); - INSTALL_HOOK(logger, SearchForSpriteByUnicode); - INSTALL_HOOK(logger, SetArraySizes); + INSTALL_HOOK(BeatLeaderLogger, GetSpriteCharacterFromSpriteAsset); + INSTALL_HOOK(BeatLeaderLogger, SetArraySizes); hooksInstalled = true; } - text->set_spriteAsset(rootEmojiAsset); + text->m_spriteAsset = rootEmojiAsset; } void EmojiSupport::RemoveSupport(TMPro::TextMeshProUGUI* text) { diff --git a/src/UI/LeaderboardUI.cpp b/src/UI/LeaderboardUI.cpp index 8517861..70d01a6 100644 --- a/src/UI/LeaderboardUI.cpp +++ b/src/UI/LeaderboardUI.cpp @@ -33,20 +33,16 @@ #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" #include "beatsaber-hook/shared/rapidjson/include/rapidjson/filereadstream.h" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/ArrayUtil.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "HMUI/TableView.hpp" #include "HMUI/TableCell.hpp" #include "HMUI/IconSegmentedControl.hpp" -#include "HMUI/IconSegmentedControl_DataItem.hpp" #include "HMUI/CurvedCanvasSettings.hpp" #include "HMUI/Screen.hpp" #include "HMUI/ViewController.hpp" -#include "HMUI/ViewController_AnimationType.hpp" -#include "HMUI/ViewController_AnimationDirection.hpp" #include "HMUI/CurvedTextMeshPro.hpp" #include "System/Action.hpp" @@ -77,18 +73,16 @@ #include "GlobalNamespace/LocalLeaderboardViewController.hpp" #include "GlobalNamespace/LoadingControl.hpp" #include "GlobalNamespace/LeaderboardTableView.hpp" -#include "GlobalNamespace/LeaderboardTableView_ScoreData.hpp" -#include "GlobalNamespace/PlatformLeaderboardsModel_ScoresScope.hpp" #include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" #include "GlobalNamespace/IBeatmapLevelData.hpp" -#include "GlobalNamespace/IDifficultyBeatmapSet.hpp" #include "GlobalNamespace/IReadonlyBeatmapData.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" #include "GlobalNamespace/LeaderboardTableCell.hpp" #include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp" #include "GlobalNamespace/SoloFreePlayFlowCoordinator.hpp" +#include "GlobalNamespace/BoolSettingsController.hpp" +#include "BGLib/Polyglot/LocalizedTextMeshProUGUI.hpp" #include "TMPro/TMP_Sprite.hpp" #include "TMPro/TMP_SpriteGlyph.hpp" @@ -110,7 +104,7 @@ using namespace GlobalNamespace; using namespace HMUI; -using namespace QuestUI; +using namespace BSML; using namespace BeatLeader; using namespace std; using UnityEngine::Resources; @@ -133,9 +127,9 @@ namespace LeaderboardUI { UnityEngine::UI::Button* retryButton = NULL; BeatLeader::LogoAnimation* logoAnimation = NULL; - QuestUI::ClickableImage* upPageButton = NULL; - QuestUI::ClickableImage* downPageButton = NULL; - QuestUI::ClickableImage* contextsButton = NULL; + BSML::ClickableImage* upPageButton = NULL; + BSML::ClickableImage* downPageButton = NULL; + BSML::ClickableImage* contextsButton = NULL; BeatLeader::VotingButton* votingButton = NULL; HMUI::HoverHint* contextsButtonHover; UnityEngine::GameObject* parentScreen = NULL; @@ -160,7 +154,7 @@ namespace LeaderboardUI { map avatars; map cellBackgrounds; - map cellHighlights; + map cellHighlights; map cellScores; map imageRows; @@ -222,10 +216,10 @@ namespace LeaderboardUI { float ppChange = player->pp - player->lastPP; // Actually set the labels - globalRank->SetText("#" + to_string(player->rank) + (rankChange != 0 ? getColoredChange(rankChange) + to_string(rankChange) : "")); + globalRank->SetText("#" + to_string(player->rank) + (rankChange != 0 ? getColoredChange(rankChange) + to_string(rankChange) : ""), true); auto rankText = "#" + to_string(player->countryRank) + " " + (countryRankChange != 0 ? getColoredChange(countryRankChange) + to_string(countryRankChange) : "") + " " + to_string_wprecision(player->pp, 2) + "pp " + (ppChange != 0 ? getColoredChange(ppChange) + to_string_wprecision(ppChange, 2) + "pp" : ""); - countryRankAndPp->SetText(rankText); + countryRankAndPp->SetText(rankText, true); if (rankText.length() > 60) { countryRankAndPp->set_fontSize(3); @@ -245,7 +239,7 @@ namespace LeaderboardUI { updatePlayerRank(); playerName->set_alignment(TMPro::TextAlignmentOptions::Center); - playerName->SetText(FormatUtils::FormatNameWithClans(PlayerController::currentPlayer.value(), 25, false)); + playerName->SetText(FormatUtils::FormatNameWithClans(PlayerController::currentPlayer.value(), 25, false), true); auto params = GetAvatarParams(player.value(), false); playerAvatar->SetPlayer(player->avatar, params.baseMaterial, params.hueShift, params.saturation); @@ -253,31 +247,31 @@ namespace LeaderboardUI { if (plvc != NULL) { auto sprite = BundleLoader::bundle->GetCountryIcon(player->country); if (!ssInstalled) { - auto countryControl = plvc->scopeSegmentedControl->dataItems.get(3); + auto countryControl = plvc->_scopeSegmentedControl->_dataItems.get(3); countryControl->set_hintText("Country"); - plvc->scopeSegmentedControl->dataItems.get(3)->set_icon(sprite); - plvc->scopeSegmentedControl->SetData(plvc->scopeSegmentedControl->dataItems); + plvc->_scopeSegmentedControl->_dataItems.get(3)->set_icon(sprite); + plvc->_scopeSegmentedControl->SetData(plvc->_scopeSegmentedControl->_dataItems); } if (countryRankIcon) { countryRankIcon->set_sprite(sprite); - RectTransform* rectTransform = (RectTransform*)countryRankIcon->get_transform(); + RectTransform* rectTransform = countryRankIcon->get_transform().cast(); rectTransform->set_sizeDelta({3.2, sprite->get_bounds().get_size().y * 10}); } } } else { - playerName->SetText(player->name + ", play something!"); + playerName->SetText(player->name + ", play something!", true); } } else { - globalRank->SetText("#0"); - countryRankAndPp->SetText("#0"); + globalRank->SetText("#0", true); + countryRankAndPp->SetText("#0", true); playerAvatar->HideImage(); if (countryRankIcon) { countryRankIcon->set_sprite(BundleLoader::bundle->globeIcon); } - playerName->SetText(""); + playerName->SetText("", true); } } @@ -307,20 +301,20 @@ namespace LeaderboardUI { void, SegmentedControl* self, ::HMUI::SelectableCell* selectableCell, - ::HMUI::SelectableCell::TransitionType transitionType, - ::Il2CppObject* changeOwner) { + ::HMUI::__SelectableCell__TransitionType transitionType, + ::System::Object* changeOwner) { SegmentedControlHandleCellSelection(self, selectableCell, transitionType, changeOwner); if (plvc && leaderboardLoaded && - self == plvc->scopeSegmentedControl) { + self == plvc->_scopeSegmentedControl.ptr()) { cachedSelector = -1; } } MAKE_HOOK_MATCH(LeaderboardDeactivate, &PlatformLeaderboardViewController::DidDeactivate, void, PlatformLeaderboardViewController* self, bool removedFromHierarchy, bool screenSystemDisabling) { if (ssInstalled && plvc) { - cachedSelector = plvc->scopeSegmentedControl->selectedCellNumber; + cachedSelector = plvc->_scopeSegmentedControl->selectedCellNumber; leaderboardLoaded = false; } @@ -337,13 +331,13 @@ namespace LeaderboardUI { setVotingButtonsState(0); hideVotingUIs(); if (plvc) { - auto [hash, difficulty, mode] = getLevelDetails(reinterpret_cast(plvc->difficultyBeatmap->get_level())); + auto [hash, difficulty, mode] = getLevelDetails(plvc->_beatmapKey); string votingStatusUrl = WebUtils::API_URL + "votestatus/" + hash + "/" + difficulty + "/" + mode; lastVotingStatusUrl = votingStatusUrl; WebUtils::GetAsync(votingStatusUrl, [votingStatusUrl](long status, string response) { if (votingStatusUrl == lastVotingStatusUrl && status == 200) { - QuestUI::MainThreadScheduler::Schedule([response] { + BSML::MainThreadScheduler::Schedule([response] { setVotingButtonsState(stoi(response)); }); } @@ -360,19 +354,19 @@ namespace LeaderboardUI { } } - tuple getLevelDetails(IPreviewBeatmapLevel* levelData) + tuple getLevelDetails(BeatmapKey levelData) { - string hash = regex_replace((string)levelData->get_levelID(), basic_regex("custom_level_"), ""); - string difficulty = MapEnhancer::DiffName(plvc->difficultyBeatmap->get_difficulty().value); - string mode = (string)plvc->difficultyBeatmap->get_parentDifficultyBeatmapSet()->get_beatmapCharacteristic()->serializedName; + string hash = regex_replace((string)levelData.levelId, basic_regex("custom_level_"), ""); + string difficulty = MapEnhancer::DiffName(levelData.difficulty.value__); + string mode = (string)levelData.beatmapCharacteristic->serializedName; return make_tuple(hash, difficulty, mode); } void refreshFromTheServerScores() { - auto [hash, difficulty, mode] = getLevelDetails(reinterpret_cast(plvc->difficultyBeatmap->get_level())); + auto [hash, difficulty, mode] = getLevelDetails(plvc->_beatmapKey); string url = WebUtils::API_URL + "v3/scores/" + hash + "/" + difficulty + "/" + mode + "/" + contextToUrlString[static_cast(getModConfig().Context.GetValue())]; - int selectedCellNumber = cachedSelector != -1 ? cachedSelector : plvc->scopeSegmentedControl->selectedCellNumber; + int selectedCellNumber = cachedSelector != -1 ? cachedSelector : plvc->_scopeSegmentedControl->selectedCellNumber; switch (selectedCellNumber) { @@ -403,20 +397,20 @@ namespace LeaderboardUI { return; } - QuestUI::MainThreadScheduler::Schedule([status, stringResult] { + BSML::MainThreadScheduler::Schedule([status, stringResult] { rapidjson::Document result; result.Parse(stringResult.c_str()); if (result.HasParseError() || !result.HasMember("data")) return; auto scores = result["data"].GetArray(); - plvc->scores->Clear(); + plvc->_scores->Clear(); if ((int)scores.Size() == 0) { - plvc->loadingControl->Hide(); - plvc->hasScoresData = false; - plvc->loadingControl->ShowText("No scores were found!", true); + plvc->_loadingControl->Hide(); + plvc->_hasScoresData = false; + plvc->_loadingControl->ShowText("No scores were found!", true); - plvc->leaderboardTableView->tableView->SetDataSource((HMUI::TableView::IDataSource *)plvc->leaderboardTableView, true); + plvc->_leaderboardTableView->_tableView->SetDataSource(plvc->_leaderboardTableView.cast(), true); return; } @@ -448,10 +442,10 @@ namespace LeaderboardUI { FormatUtils::FormatPlayerScore(currentScore), currentScore.rank, false); - plvc->scores->Add(scoreData); + plvc->_scores->Add(scoreData); } } - plvc->leaderboardTableView->rowHeight = 6; + plvc->_leaderboardTableView->_rowHeight = 6; if (selectedScore > 9 && !result["selection"].IsNull()) { Score currentScore = Score(result["selection"]); @@ -462,7 +456,7 @@ namespace LeaderboardUI { false); if (currentScore.rank > topRank) { - plvc->scores->Add(scoreData); + plvc->_scores->Add(scoreData); scoreVector[10] = currentScore; selectedScore = 10; } else { @@ -470,25 +464,25 @@ namespace LeaderboardUI { { scoreVector[i] = scoreVector[i - 1]; } - plvc->scores->Insert(0, scoreData); + plvc->_scores->Insert(0, scoreData); scoreVector[0] = currentScore; selectedScore = 0; } - if (plvc->scores->get_Count() > 10) { - plvc->leaderboardTableView->rowHeight = 5.5; + if (plvc->_scores->get_Count() > 10) { + plvc->_leaderboardTableView->_rowHeight = 5.5; } } - plvc->leaderboardTableView->scores = plvc->scores; - plvc->leaderboardTableView->specialScorePos = 12; + plvc->_leaderboardTableView->_scores = plvc->_scores; + plvc->_leaderboardTableView->_specialScorePos = 12; if (upPageButton != NULL) { upPageButton->get_gameObject()->SetActive(pageNum != 1); downPageButton->get_gameObject()->SetActive(pageNum * perPage < total); } - plvc->loadingControl->Hide(); - plvc->hasScoresData = true; - plvc->leaderboardTableView->tableView->SetDataSource((HMUI::TableView::IDataSource *)plvc->leaderboardTableView, true); + plvc->_loadingControl->Hide(); + plvc->_hasScoresData = true; + plvc->_leaderboardTableView->_tableView->SetDataSource(plvc->_leaderboardTableView.cast(), true); }); }); @@ -498,11 +492,11 @@ namespace LeaderboardUI { updateVotingButton(); } - plvc->loadingControl->ShowText("Loading", true); + plvc->_loadingControl->ShowText("Loading", true); } void refreshFromTheServerClans() { - auto [hash, difficulty, mode] = getLevelDetails(reinterpret_cast(plvc->difficultyBeatmap->get_level())); + auto [hash, difficulty, mode] = getLevelDetails(plvc->_beatmapKey); string url = WebUtils::API_URL + "v1/clanScores/" + hash + "/" + difficulty + "/" + mode + "/page"; url += "?page=" + to_string(page); @@ -517,20 +511,20 @@ namespace LeaderboardUI { return; } - QuestUI::MainThreadScheduler::Schedule([status, stringResult] { + BSML::MainThreadScheduler::Schedule([status, stringResult] { rapidjson::Document result; result.Parse(stringResult.c_str()); if (result.HasParseError() || !result.HasMember("data")) return; auto scores = result["data"].GetArray(); - plvc->scores->Clear(); + plvc->_scores->Clear(); if ((int)scores.Size() == 0) { - plvc->loadingControl->Hide(); - plvc->hasScoresData = false; - plvc->loadingControl->ShowText("No clan rankings were found!", true); + plvc->_loadingControl->Hide(); + plvc->_hasScoresData = false; + plvc->_loadingControl->ShowText("No clan rankings were found!", true); - plvc->leaderboardTableView->tableView->SetDataSource((HMUI::TableView::IDataSource *)plvc->leaderboardTableView, true); + plvc->_leaderboardTableView->_tableView->SetDataSource(plvc->_leaderboardTableView.cast(), true); return; } @@ -548,33 +542,33 @@ namespace LeaderboardUI { ClanScore currentScore = ClanScore(score); clanScoreVector[index] = currentScore; - getLogger().info("ClanScore"); + BeatLeaderLogger.info("ClanScore"); LeaderboardTableView::ScoreData* scoreData = LeaderboardTableView::ScoreData::New_ctor( currentScore.modifiedScore, FormatUtils::FormatClanScore(currentScore), currentScore.rank, false); - plvc->scores->Add(scoreData); + plvc->_scores->Add(scoreData); } } - plvc->leaderboardTableView->rowHeight = 6; + plvc->_leaderboardTableView->_rowHeight = 6; - plvc->leaderboardTableView->scores = plvc->scores; - plvc->leaderboardTableView->specialScorePos = 12; + plvc->_leaderboardTableView->_scores = plvc->_scores; + plvc->_leaderboardTableView->_specialScorePos = 12; if (upPageButton != NULL) { upPageButton->get_gameObject()->SetActive(pageNum != 1); downPageButton->get_gameObject()->SetActive(pageNum * perPage < total); } - plvc->loadingControl->Hide(); - plvc->hasScoresData = true; - plvc->leaderboardTableView->tableView->SetDataSource((HMUI::TableView::IDataSource *)plvc->leaderboardTableView, true); + plvc->_loadingControl->Hide(); + plvc->_hasScoresData = true; + plvc->_leaderboardTableView->_tableView->SetDataSource(plvc->_leaderboardTableView.cast(), true); }); }); - plvc->loadingControl->ShowText("Loading", true); + plvc->_loadingControl->ShowText("Loading", true); } void updateModifiersButton() { @@ -616,7 +610,7 @@ namespace LeaderboardUI { WebUtils::PostJSONAsync(votingUrl + rankableString + starsString + typeString, "", [currentVotingUrl, rankable, type](long status, string response) { if (votingUrl != currentVotingUrl) return; - QuestUI::MainThreadScheduler::Schedule([status, response, rankable, type] { + BSML::MainThreadScheduler::Schedule([status, response, rankable, type] { if (status == 200) { setVotingButtonsState(stoi(response)); LevelInfoUI::addVoteToCurrentLevel(rankable, type); @@ -642,11 +636,11 @@ namespace LeaderboardUI { void clearTable() { selectedScore = 11; - if (plvc->leaderboardTableView->scores != NULL) { - plvc->leaderboardTableView->scores->Clear(); + if (plvc->_leaderboardTableView->_scores != NULL) { + plvc->_leaderboardTableView->_scores->Clear(); } - if (plvc->leaderboardTableView && plvc->leaderboardTableView->tableView) { - plvc->leaderboardTableView->tableView->SetDataSource((HMUI::TableView::IDataSource *)plvc->leaderboardTableView, true); + if (plvc->_leaderboardTableView && plvc->_leaderboardTableView->_tableView) { + plvc->_leaderboardTableView->_tableView->SetDataSource(plvc->_leaderboardTableView.cast(), true); } } @@ -686,15 +680,15 @@ namespace LeaderboardUI { } ssElements = vector(); ArrayW transforms = plvc->get_gameObject()->get_transform()->FindObjectsOfType(); - for (size_t i = 0; i < transforms.Length(); i++) + for (size_t i = 0; i < transforms.size(); i++) { auto transform = transforms[i]; auto name = transform->get_name(); - bool infoIcon = to_utf8(csstrtostr(name)) == "ScoreSaberClickableImage"; - bool header = (to_utf8(csstrtostr(name)) == "QuestUIHorizontalLayoutGroup" || to_utf8(csstrtostr(name)) == "QuestUIVerticalLayoutGroup") && + bool infoIcon = name == "ScoreSaberClickableImage"; + bool header = (name == "bsmlHorizontalLayoutGroup" || name == "bsmlVerticalLayoutGroup") && transform->get_parent() && transform->get_parent()->get_parent() && - to_utf8(csstrtostr(transform->get_parent()->get_parent()->get_name())) == "PlatformLeaderboardViewController"; + transform->get_parent()->get_parent()->get_name() == "PlatformLeaderboardViewController"; if (infoIcon || header) { transform->get_gameObject()->SetActive(false); ssElements.push_back(transform); @@ -703,11 +697,11 @@ namespace LeaderboardUI { } if (PlayerController::currentPlayer == std::nullopt) { - self->loadingControl->Hide(); + self->_loadingControl->Hide(); - if (preferencesButton == NULL) { - loginPrompt = ::QuestUI::BeatSaberUI::CreateText(plvc->get_transform(), "Please sign up or log in to post scores!", false, UnityEngine::Vector2(4, 10)); - preferencesButton = ::QuestUI::BeatSaberUI::CreateUIButton(plvc->get_transform(), "Open settings", UnityEngine::Vector2(0, 0), [](){ + if (loginPrompt == NULL) { + loginPrompt = ::BSML::Lite::CreateText(plvc->get_transform(), "Please log in or sign up in settings to post scores!", {24, 0}, {100, 4}); + preferencesButton = ::BSML::Lite::CreateUIButton(plvc->get_transform(), "Open settings", {65, -50}, [](){ UIUtils::OpenSettings(); }); } @@ -717,7 +711,7 @@ namespace LeaderboardUI { return; } - if (preferencesButton != NULL) { + if (loginPrompt != NULL) { loginPrompt->get_gameObject()->SetActive(false); preferencesButton->get_gameObject()->SetActive(false); } @@ -728,14 +722,14 @@ namespace LeaderboardUI { ArrayW scoreScopes = ArrayW(4); for (int index = 0; index < 3; ++index) { - dataItems[index] = self->scopeSegmentedControl->dataItems.get(index); - scoreScopes[index] = self->scoreScopes.get(index); + dataItems[index] = self->_scopeSegmentedControl->_dataItems.get(index); + scoreScopes[index] = self->_scoreScopes.get(index); } - dataItems[3] = HMUI::IconSegmentedControl::DataItem::New_ctor(self->friendsLeaderboardIcon, "Country"); + dataItems[3] = HMUI::IconSegmentedControl::DataItem::New_ctor(self->_friendsLeaderboardIcon, "Country"); scoreScopes[3] = PlatformLeaderboardsModel::ScoresScope(3); - plvc->scopeSegmentedControl->SetData(dataItems); - plvc->scoreScopes = scoreScopes; + plvc->_scopeSegmentedControl->SetData(dataItems); + plvc->_scoreScopes = scoreScopes; } parentScreen = CreateCustomScreen(self, UnityEngine::Vector2(480, 160), self->screen->get_transform()->get_position(), 140); @@ -750,54 +744,54 @@ namespace LeaderboardUI { BeatLeader::initLinksContainerPopup(&linkContainer, self->get_transform()); BeatLeader::initVotingPopup(&votingUI, self->get_transform(), voteCallback); - auto playerAvatarImage = ::QuestUI::BeatSaberUI::CreateImage(parentScreen->get_transform(), plvc->aroundPlayerLeaderboardIcon, UnityEngine::Vector2(180, 51), UnityEngine::Vector2(16, 16)); + auto playerAvatarImage = ::BSML::Lite::CreateImage(parentScreen->get_transform(), plvc->_aroundPlayerLeaderboardIcon, UnityEngine::Vector2(180, 51), UnityEngine::Vector2(16, 16)); playerAvatar = playerAvatarImage->get_gameObject()->AddComponent(); playerAvatar->Init(playerAvatarImage); - playerName = ::QuestUI::BeatSaberUI::CreateText(parentScreen->get_transform(), "", false, UnityEngine::Vector2(140, 53), UnityEngine::Vector2(60, 10)); + playerName = ::BSML::Lite::CreateText(parentScreen->get_transform(), "", UnityEngine::Vector2(140, 53), UnityEngine::Vector2(60, 10)); playerName->set_fontSize(6); EmojiSupport::AddSupport(playerName); - auto rankLayout = ::QuestUI::BeatSaberUI::CreateHorizontalLayoutGroup(parentScreen->get_transform()); + auto rankLayout = ::BSML::Lite::CreateHorizontalLayoutGroup(parentScreen->get_transform()); rankLayout->set_spacing(3); EnableHorizontalFit(rankLayout); - auto rectTransform = (RectTransform*)rankLayout->get_transform(); + auto rectTransform = rankLayout->get_transform().cast(); rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchoredPosition({138, 45}); - auto globalLayout = ::QuestUI::BeatSaberUI::CreateHorizontalLayoutGroup(rankLayout->get_transform()); + auto globalLayout = ::BSML::Lite::CreateHorizontalLayoutGroup(rankLayout->get_transform()); globalLayout->set_spacing(1); EnableHorizontalFit(globalLayout); - globalRankIcon = ::QuestUI::BeatSaberUI::CreateImage(globalLayout->get_transform(), BundleLoader::bundle->globeIcon); - globalRank = ::QuestUI::BeatSaberUI::CreateText(globalLayout->get_transform(), "", false); + globalRankIcon = ::BSML::Lite::CreateImage(globalLayout->get_transform(), BundleLoader::bundle->globeIcon); + globalRank = ::BSML::Lite::CreateText(globalLayout->get_transform(), ""); - auto countryLayout = ::QuestUI::BeatSaberUI::CreateHorizontalLayoutGroup(rankLayout->get_transform()); + auto countryLayout = ::BSML::Lite::CreateHorizontalLayoutGroup(rankLayout->get_transform()); countryLayout->set_spacing(1); EnableHorizontalFit(countryLayout); - countryRankIcon = ::QuestUI::BeatSaberUI::CreateImage(countryLayout->get_transform(), BundleLoader::bundle->globeIcon); - countryRankAndPp = ::QuestUI::BeatSaberUI::CreateText(countryLayout->get_transform(), "", false); + countryRankIcon = ::BSML::Lite::CreateImage(countryLayout->get_transform(), BundleLoader::bundle->globeIcon); + countryRankAndPp = ::BSML::Lite::CreateText(countryLayout->get_transform(), ""); if (PlayerController::currentPlayer != std::nullopt) { updatePlayerInfoLabel(); } - auto websiteLink = ::QuestUI::BeatSaberUI::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, UnityEngine::Vector2(100, 50), UnityEngine::Vector2(16, 16), []() { + auto websiteLink = ::BSML::Lite::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, []() { linkContainer->modal->Show(true, true, nullptr); - }); + }, UnityEngine::Vector2(100, 50), UnityEngine::Vector2(16, 16)); logoAnimation = websiteLink->get_gameObject()->AddComponent(); logoAnimation->Init(websiteLink); - websiteLink->get_onPointerEnterEvent() += [](auto _){ + websiteLink->onEnter = [](){ logoAnimation->SetGlowing(true); }; - websiteLink->get_onPointerExitEvent() += [](auto _){ + websiteLink->onExit = [](){ logoAnimation->SetGlowing(false); }; if (retryButton) UnityEngine::GameObject::Destroy(retryButton); - retryButton = ::QuestUI::BeatSaberUI::CreateUIButton(parentScreen->get_transform(), "Retry", UnityEngine::Vector2(105, 63), UnityEngine::Vector2(15, 8), [](){ + retryButton = ::BSML::Lite::CreateUIButton(parentScreen->get_transform(), "Retry", UnityEngine::Vector2(105, 63), UnityEngine::Vector2(15, 8), [](){ retryButton->get_gameObject()->SetActive(false); showRetryButton = false; retryCallback(); @@ -805,48 +799,48 @@ namespace LeaderboardUI { retryButton->get_gameObject()->SetActive(false); retryButton->GetComponentInChildren()->set_alignment(TMPro::TextAlignmentOptions::Left); - if(uploadStatus) UnityEngine::GameObject::Destroy(uploadStatus); - uploadStatus = ::QuestUI::BeatSaberUI::CreateText(parentScreen->get_transform(), "", false); - move(uploadStatus, 150, 60); - resize(uploadStatus, 10, 0); + if(uploadStatus) UnityEngine::GameObject::DestroyImmediate(uploadStatus); + uploadStatus = ::BSML::Lite::CreateText(parentScreen->get_transform(), "", {150, 60}); + resize(uploadStatus, 100, 3); uploadStatus->set_fontSize(3); uploadStatus->set_richText(true); - upPageButton = ::QuestUI::BeatSaberUI::CreateClickableImage(parentScreen->get_transform(), Sprites::get_UpIcon(), UnityEngine::Vector2(100, 17), UnityEngine::Vector2(8, 5.12), [](){ + upPageButton = ::BSML::Lite::CreateClickableImage(parentScreen->get_transform(), Sprites::get_UpIcon(), [](){ PageUp(); - }); - downPageButton = ::QuestUI::BeatSaberUI::CreateClickableImage(parentScreen->get_transform(), Sprites::get_DownIcon(), UnityEngine::Vector2(100, -20), UnityEngine::Vector2(8, 5.12), [](){ + }, UnityEngine::Vector2(100, 17), UnityEngine::Vector2(8, 5.12)); + downPageButton = ::BSML::Lite::CreateClickableImage(parentScreen->get_transform(), Sprites::get_DownIcon(), [](){ PageDown(); - }); + }, UnityEngine::Vector2(100, -20), UnityEngine::Vector2(8, 5.12)); - contextsButton = ::QuestUI::BeatSaberUI::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->modifiersIcon, UnityEngine::Vector2(100, 28), UnityEngine::Vector2(6, 6), [](){ + contextsButton = ::BSML::Lite::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->modifiersIcon, [](){ contextsContainer->Show(true, true, nullptr); - }); + }, UnityEngine::Vector2(100, 28), UnityEngine::Vector2(6, 6)); contextsButton->set_defaultColor(SelectedColor); contextsButton->set_highlightColor(SelectedColor); // We need to add an empty hover hint, so we can set it later to the correct content depending on the selected context - contextsButtonHover = ::QuestUI::BeatSaberUI::AddHoverHint(contextsButton, ""); + contextsButtonHover = ::BSML::Lite::AddHoverHint(contextsButton, ""); initContextsModal(self->get_transform()); updateModifiersButton(); - auto votingButtonImage = ::QuestUI::BeatSaberUI::CreateClickableImage( + auto votingButtonImage = ::BSML::Lite::CreateClickableImage( parentScreen->get_transform(), BundleLoader::bundle->modifiersIcon, - UnityEngine::Vector2(100, 22), - UnityEngine::Vector2(4, 4), []() { - if (votingButton->state != 2) return; - - votingUI->reset(); - votingUI->modal->Show(true, true, nullptr); - }); + if (votingButton->state != 2) return; + + votingUI->reset(); + votingUI->modal->Show(true, true, nullptr); + }, + UnityEngine::Vector2(100, 22), + UnityEngine::Vector2(4, 4) + ); votingButton = websiteLink->get_gameObject()->AddComponent(); votingButton->Init(votingButtonImage); initSettingsModal(self->get_transform()); - auto settingsButton = ::QuestUI::BeatSaberUI::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->settingsIcon, {180, 36}, {4.5, 4.5}, [](){ + auto settingsButton = ::BSML::Lite::CreateClickableImage(parentScreen->get_transform(), BundleLoader::bundle->settingsIcon, [](){ settingsContainer->Show(true, true, nullptr); - }); + }, {180, 36}, {4.5, 4.5}); settingsButton->set_material(BundleLoader::bundle->UIAdditiveGlowMaterial); settingsButton->set_defaultColor(FadedColor); @@ -861,12 +855,12 @@ namespace LeaderboardUI { if (ssInstalled && !sspageUpButton) { ArrayW buttons = UnityEngine::Resources::FindObjectsOfTypeAll(); - for (size_t i = 0; i < buttons.Length(); i++) + for (size_t i = 0; i < buttons.size(); i++) { auto button = buttons[i]; TMPro::TextMeshProUGUI* textMesh = button->GetComponentInChildren(); - if (textMesh && textMesh->get_text() && to_utf8(csstrtostr(textMesh->get_text())) == "") { + if (textMesh && textMesh->get_text() && textMesh->get_text() == "") { auto position = button->GetComponent()->get_anchoredPosition(); if (position.x == -40 && position.y == 20) { sspageDownButton = button; @@ -884,7 +878,6 @@ namespace LeaderboardUI { downPageButton->get_gameObject()->SetActive(false); } - IPreviewBeatmapLevel* levelData = reinterpret_cast(self->difficultyBeatmap->get_level()); refreshFromTheServerCurrent(); } @@ -895,7 +888,7 @@ namespace LeaderboardUI { } void updateSelectedLeaderboard() { - if (preferencesButton && PlayerController::currentPlayer == std::nullopt) { + if (loginPrompt && PlayerController::currentPlayer == std::nullopt) { loginPrompt->get_gameObject()->SetActive(showBeatLeader); preferencesButton->get_gameObject()->SetActive(showBeatLeader); } @@ -935,7 +928,7 @@ namespace LeaderboardUI { parentScreen->get_gameObject()->SetActive(showBeatLeader); retryButton->get_gameObject()->SetActive(showBeatLeader && showRetryButton); - plvc->leaderboardTableView->rowHeight = 6; + plvc->_leaderboardTableView->_rowHeight = 6; } } @@ -947,7 +940,7 @@ namespace LeaderboardUI { if (ssInstalled && showBeatLeaderButton == NULL) { auto headerTransform = self->get_gameObject()->get_transform()->Find("HeaderPanel")->get_transform(); - QuestUI::BeatSaberUI::CreateText(headerTransform, "BeatLeader", {-12.2, 2}); + BSML::Lite::CreateText(headerTransform, "BeatLeader", {-12.2, 2}); showBeatLeaderButton = CreateToggle(headerTransform, showBeatLeader, UnityEngine::Vector2(-84.5, 0), [](bool changed){ showBeatLeader = !showBeatLeader; getModConfig().ShowBeatleader.SetValue(showBeatLeader); @@ -956,7 +949,7 @@ namespace LeaderboardUI { }); if (!showBeatLeader) { - QuestUI::MainThreadScheduler::Schedule([] { + BSML::MainThreadScheduler::Schedule([] { HMUI::ImageView* imageView = plvc->get_gameObject()->get_transform()->Find("HeaderPanel")->get_gameObject()->GetComponentInChildren(); imageView->set_color(UnityEngine::Color(0.5,0.5,0.5,1)); imageView->set_color0(UnityEngine::Color(0.5,0.5,0.5,1)); @@ -978,7 +971,7 @@ namespace LeaderboardUI { if (ssInstalled && showBeatLeader && sspageUpButton == NULL) { std::async(std::launch::async, [] () { std::this_thread::sleep_for(std::chrono::seconds{1}); - QuestUI::MainThreadScheduler::Schedule([] { + BSML::MainThreadScheduler::Schedule([] { refreshLeaderboardCall(); }); }); @@ -990,82 +983,80 @@ namespace LeaderboardUI { } LeaderboardTableCell* CellForIdxReimplement(LeaderboardTableView* self, HMUI::TableView* tableView) { - LeaderboardTableCell* leaderboardTableCell = (LeaderboardTableCell *)tableView->DequeueReusableCellForIdentifier("Cell"); + LeaderboardTableCell* leaderboardTableCell = tableView->DequeueReusableCellForIdentifier("Cell").try_cast().value_or(UnityW()); if (leaderboardTableCell == NULL) { - leaderboardTableCell = (LeaderboardTableCell *)Object::Instantiate(self->cellPrefab); + leaderboardTableCell = (LeaderboardTableCell *)Object::Instantiate(self->_cellPrefab); ((TableCell *)leaderboardTableCell)->set_reuseIdentifier("Cell"); } - auto score = self->scores->get_Item(10); + auto score = self->_scores->get_Item(10); leaderboardTableCell->set_rank(score->rank); leaderboardTableCell->set_playerName(score->playerName); leaderboardTableCell->set_score(score->score); leaderboardTableCell->set_showFullCombo(score->fullCombo); leaderboardTableCell->set_showSeparator(false); - leaderboardTableCell->set_specialScore(self->specialScorePos == 10); + leaderboardTableCell->set_specialScore(self->_specialScorePos == 10); return leaderboardTableCell; } - MAKE_HOOK_MATCH(LeaderboardCellSource, &LeaderboardTableView::CellForIdx, HMUI::TableCell*, LeaderboardTableView* self, HMUI::TableView* tableView, int row) { - LeaderboardTableCell* result = row == 10 ? CellForIdxReimplement(self, tableView) : (LeaderboardTableCell *)LeaderboardCellSource(self, tableView, row); + MAKE_HOOK_MATCH(LeaderboardCellSource, &LeaderboardTableView::CellForIdx, UnityW, LeaderboardTableView* self, HMUI::TableView* tableView, int row) { + LeaderboardTableCell* result = row == 10 ? CellForIdxReimplement(self, tableView) : LeaderboardCellSource(self, tableView, row).cast().ptr(); if (showBeatLeader && !isLocal) { - if (result->playerNameText->get_fontSize() > 3 || result->playerNameText->get_enableAutoSizing()) { - result->playerNameText->set_enableAutoSizing(false); - result->playerNameText->set_richText(true); - - resize(result->playerNameText, 24, 0); - move(result->rankText, -6.2, -0.1); - result->rankText->set_alignment(TMPro::TextAlignmentOptions::Right); - - move(result->playerNameText, -0.5, 0); - move(result->fullComboText, 0.2, 0); - move(result->scoreText, 4, 0); - result->playerNameText->set_fontSize(3); - result->fullComboText->set_fontSize(3); - result->scoreText->set_fontSize(2); - EmojiSupport::AddSupport(result->playerNameText); - - if (!cellBackgrounds.count(result)) { - avatars[result] = ::QuestUI::BeatSaberUI::CreateImage(result->get_transform(), plvc->aroundPlayerLeaderboardIcon, UnityEngine::Vector2(-30, 0), UnityEngine::Vector2(4, 4)); - avatars[result]->get_gameObject()->set_active(getModConfig().AvatarsActive.GetValue()); - - auto scoreSelector = ::QuestUI::BeatSaberUI::CreateClickableImage(result->get_transform(), Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, 0), UnityEngine::Vector2(80, 6), [result]() { - auto openEvent = il2cpp_utils::MakeDelegate( - classof(System::Action*), - static_cast(nullptr), setTheScoreAgain); - detailsTextWorkaround = cellScores[result]; - - scoreDetailsUI->modal->Show(true, true, openEvent); - scoreDetailsUI->setScore(cellScores[result]); - }); + if (result->_playerNameText->get_fontSize() > 3 || result->_playerNameText->get_enableAutoSizing()) { + result->_playerNameText->set_enableAutoSizing(false); + result->_playerNameText->set_richText(true); - scoreSelector->set_material(UnityEngine::Object::Instantiate(BundleLoader::bundle->scoreUnderlineMaterial)); - - cellHighlights[result] = scoreSelector; + resize(result->_playerNameText, 24, 0); + move(result->_rankText, 1, 0); + result->_rankText->set_alignment(TMPro::TextAlignmentOptions::Right); + + move(result->_playerNameText, -0.5, 0); + move(result->_fullComboText, 0.2, 0); + move(result->_scoreText, 4, 0); + result->_playerNameText->set_fontSize(3); + result->_fullComboText->set_fontSize(3); + result->_scoreText->set_fontSize(2); + EmojiSupport::AddSupport(result->_playerNameText); + + if (!cellBackgrounds.count(result)) { + avatars[result] = ::BSML::Lite::CreateImage(result->get_transform(), plvc->_aroundPlayerLeaderboardIcon, UnityEngine::Vector2(-30, 0), UnityEngine::Vector2(4, 4)); + avatars[result]->get_gameObject()->set_active(getModConfig().AvatarsActive.GetValue()); + + auto scoreSelector = ::BSML::Lite::CreateClickableImage(result->get_transform(), Sprites::get_TransparentPixel(), [result]() { + auto openEvent = custom_types::MakeDelegate((std::function)setTheScoreAgain); + detailsTextWorkaround = cellScores[result]; + + scoreDetailsUI->modal->Show(true, true, openEvent); + scoreDetailsUI->setScore(cellScores[result]); + }, UnityEngine::Vector2(0, 0), UnityEngine::Vector2(80, 6)); + + scoreSelector->set_material(UnityEngine::Object::Instantiate(BundleLoader::bundle->scoreUnderlineMaterial)); + + cellHighlights[result] = scoreSelector; - auto backgroundImage = ::QuestUI::BeatSaberUI::CreateImage(result->get_transform(), Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, 0), UnityEngine::Vector2(80, 6)); - backgroundImage->set_material(BundleLoader::bundle->scoreBackgroundMaterial); - backgroundImage->get_transform()->SetAsFirstSibling(); - cellBackgrounds[result] = backgroundImage; + auto backgroundImage = ::BSML::Lite::CreateImage(result->get_transform(), Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, 0), UnityEngine::Vector2(80, 6)); + backgroundImage->set_material(BundleLoader::bundle->scoreBackgroundMaterial); + backgroundImage->get_transform()->SetAsFirstSibling(); + cellBackgrounds[result] = backgroundImage; - // auto tagsList = QuestUI::BeatSaberUI::CreateHorizontalLayoutGroup(result->get_transform()); - // clanGroups[result] = tagsList; + // auto tagsList = BSML::Lite::CreateHorizontalLayoutGroup(result->get_transform()); + // clanGroups[result] = tagsList; + } } - } } else { - if (result->scoreText->get_fontSize() == 2) { - EmojiSupport::RemoveSupport(result->playerNameText); - result->playerNameText->set_enableAutoSizing(true); - resize(result->playerNameText, -24, 0); - move(result->rankText, 6.2, 0.1); - result->rankText->set_alignment(TMPro::TextAlignmentOptions::Left); - move(result->playerNameText, 0.5, 0); - move(result->fullComboText, -0.2, 0); - move(result->scoreText, -4, 0); - result->playerNameText->set_fontSize(4); - result->fullComboText->set_fontSize(4); - result->scoreText->set_fontSize(4); + if (result->_scoreText->get_fontSize() == 2) { + EmojiSupport::RemoveSupport(result->_playerNameText); + result->_playerNameText->set_enableAutoSizing(true); + resize(result->_playerNameText, -24, 0); + move(result->_rankText, 6.2, 0.1); + result->_rankText->set_alignment(TMPro::TextAlignmentOptions::Left); + move(result->_playerNameText, 0.5, 0); + move(result->_fullComboText, -0.2, 0); + move(result->_scoreText, -4, 0); + result->_playerNameText->set_fontSize(4); + result->_fullComboText->set_fontSize(4); + result->_scoreText->set_fontSize(4); } } @@ -1076,12 +1067,12 @@ namespace LeaderboardUI { auto player = scoreVector[row].player; cellBackgrounds[result]->get_gameObject()->set_active(true); - result->playerNameText->GetComponent()->set_anchoredPosition({ + result->_playerNameText->GetComponent()->set_anchoredPosition({ getModConfig().AvatarsActive.GetValue() ? 10.5f : 6.5f, - result->playerNameText->GetComponent()->get_anchoredPosition().y + result->_playerNameText->GetComponent()->get_anchoredPosition().y }); avatars[result]->get_gameObject()->set_active(getModConfig().AvatarsActive.GetValue()); - result->scoreText->get_gameObject()->set_active(getModConfig().ScoresActive.GetValue()); + result->_scoreText->get_gameObject()->set_active(getModConfig().ScoresActive.GetValue()); if (row == selectedScore) { cellBackgrounds[result]->set_color(ownScoreColor); @@ -1091,11 +1082,11 @@ namespace LeaderboardUI { cellScores[result] = scoreVector[row]; if (getModConfig().AvatarsActive.GetValue()){ - avatars[result]->set_sprite(plvc->aroundPlayerLeaderboardIcon); + avatars[result]->set_sprite(plvc->_aroundPlayerLeaderboardIcon); if (!PlayerController::IsIncognito(player)) { Sprites::get_Icon(player.avatar, [result](UnityEngine::Sprite* sprite) { - if (sprite != NULL && avatars[result] != NULL && sprite->get_texture() != NULL) { + if (sprite != NULL && avatars[result] != NULL && sprite->get_texture()) { avatars[result]->set_sprite(sprite); } }); @@ -1114,12 +1105,12 @@ namespace LeaderboardUI { auto clan = clanScoreVector[row].clan; cellBackgrounds[result]->get_gameObject()->set_active(true); - result->playerNameText->GetComponent()->set_anchoredPosition({ + result->_playerNameText->GetComponent()->set_anchoredPosition({ getModConfig().AvatarsActive.GetValue() ? 10.5f : 6.5f, - result->playerNameText->GetComponent()->get_anchoredPosition().y + result->_playerNameText->GetComponent()->get_anchoredPosition().y }); avatars[result]->get_gameObject()->set_active(getModConfig().AvatarsActive.GetValue()); - result->scoreText->get_gameObject()->set_active(getModConfig().ScoresActive.GetValue()); + result->_scoreText->get_gameObject()->set_active(getModConfig().ScoresActive.GetValue()); if (PlayerController::InClan(clan.tag)) { cellBackgrounds[result]->set_color(ownScoreColor); @@ -1128,11 +1119,11 @@ namespace LeaderboardUI { } if (getModConfig().AvatarsActive.GetValue()){ - avatars[result]->set_sprite(plvc->aroundPlayerLeaderboardIcon); + avatars[result]->set_sprite(plvc->_aroundPlayerLeaderboardIcon); Sprites::get_Icon(clan.icon, [result](UnityEngine::Sprite* sprite) { - if (sprite != NULL && avatars[result] != NULL && sprite->get_texture() != NULL) { + if (sprite != NULL && avatars[result] != NULL && sprite->get_texture()) { avatars[result]->set_sprite(sprite); } }); @@ -1168,7 +1159,7 @@ namespace LeaderboardUI { if (visible && showBeatLeader) { statusWasCached = false; - uploadStatus->SetText(description); + uploadStatus->SetText(description, true); switch (status) { case ReplayUploadStatus::finished: @@ -1185,7 +1176,7 @@ namespace LeaderboardUI { case ReplayUploadStatus::inProgress: logoAnimation->SetAnimating(true); if (progress >= 100) - uploadStatus->SetText("Posting replay: Finishing up..."); + uploadStatus->SetText("Posting replay: Finishing up...", true); break; } } else { @@ -1198,13 +1189,13 @@ namespace LeaderboardUI { } void initContextsModal(UnityEngine::Transform* parent){ - auto container = QuestUI::BeatSaberUI::CreateModal(parent, {40, static_cast((static_cast(Context::SCPM) + 2) * 10 + 5)}, nullptr, true); + auto container = BSML::Lite::CreateModal(parent, {40, static_cast((static_cast(Context::SCPM) + 2) * 10 + 5)}, nullptr, true); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Scores Context", {20, 19}); + BSML::Lite::CreateText(container->get_transform(), "Scores Context", {-6, 25}); for(int i = 0; i <= static_cast(Context::SCPM); i++) { - QuestUI::BeatSaberUI::CreateUIButton(container->get_transform(), contextToDisplayString[static_cast(i)], {0.0f, static_cast(21 - (i + 1) * 10)}, [i](){ + BSML::Lite::CreateUIButton(container->get_transform(), contextToDisplayString[static_cast(i)], {20.0f, static_cast(-6 - (i + 1) * 10)}, [i](){ // Set the new value getModConfig().Context.SetValue(i); // Hide the modal @@ -1217,7 +1208,7 @@ namespace LeaderboardUI { refreshFromTheServerCurrent(); // Refresh the player rank PlayerController::Refresh(0, [](auto player, auto str){ - QuestUI::MainThreadScheduler::Schedule([]{ + BSML::MainThreadScheduler::Schedule([]{ LeaderboardUI::updatePlayerRank(); }); }); @@ -1228,39 +1219,39 @@ namespace LeaderboardUI { } void initSettingsModal(UnityEngine::Transform* parent){ - auto container = QuestUI::BeatSaberUI::CreateModal(parent, {40,60}, nullptr, true); + auto container = BSML::Lite::CreateModal(parent, {40,60}, nullptr, true); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Leaderboard Settings", {16, 24}); + BSML::Lite::CreateText(container->get_transform(), "Leaderboard Settings", {-14, 24}); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Avatar", {12, 14}); + BSML::Lite::CreateText(container->get_transform(), "Avatar", {-11, 18}); CreateToggle(container->get_transform(), getModConfig().AvatarsActive.GetValue(), {-3, 16}, [](bool value){ getModConfig().AvatarsActive.SetValue(value); plvc->Refresh(true, true); }); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Clans", {12, 4}); + BSML::Lite::CreateText(container->get_transform(), "Clans", {-11, 5}); CreateToggle(container->get_transform(), getModConfig().ClansActive.GetValue(), {-3, 6}, [](bool value){ getModConfig().ClansActive.SetValue(value); plvc->Refresh(true, true); }); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Score", {12, -6}); + BSML::Lite::CreateText(container->get_transform(), "Score", {-11, -5}); CreateToggle(container->get_transform(), getModConfig().ScoresActive.GetValue(), {-3, -4}, [](bool value){ getModConfig().ScoresActive.SetValue(value); plvc->Refresh(true, true); }); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Time", {12, -16}); + BSML::Lite::CreateText(container->get_transform(), "Time", {-11, -15}); CreateToggle(container->get_transform(), getModConfig().TimesetActive.GetValue(), {-3, -14}, [](bool value){ getModConfig().TimesetActive.SetValue(value); plvc->Refresh(true, true); }); - QuestUI::BeatSaberUI::CreateText(container->get_transform(), "Capture", {12, -26}); + BSML::Lite::CreateText(container->get_transform(), "Capture", {-11, -25}); CreateToggle(container->get_transform(), getModConfig().CaptureActive.GetValue(), {-3, -24}, [](bool value){ getModConfig().CaptureActive.SetValue(value); @@ -1273,22 +1264,38 @@ namespace LeaderboardUI { UnityEngine::UI::Toggle* CreateToggle(UnityEngine::Transform* parent, bool currentValue, UnityEngine::Vector2 anchoredPosition, std::function onValueChange) { - // Code adapted from: https://github.com/darknight1050/QuestUI/blob/master/src/BeatSaberUI.cpp#L826 - static SafePtrUnity toggleCopy; - if(!toggleCopy){ - toggleCopy = Resources::FindObjectsOfTypeAll().FirstOrDefault([](auto x) {return x->get_transform()->get_parent()->get_gameObject()->get_name() == "Fullscreen"; }); + // Code adapted from: https://github.com/darknight1050/bsml/blob/master/src/BeatSaberUI.cpp#L826 + static SafePtrUnity toggleCopy; + if (!toggleCopy) { + auto foundToggle = Resources::FindObjectsOfTypeAll()->FirstOrDefault([](auto x) { return x->get_transform()->get_parent()->get_gameObject()->get_name() == "Fullscreen"; }); + toggleCopy = foundToggle ? foundToggle->get_transform()->get_parent()->get_gameObject() : nullptr; } - UnityEngine::UI::Toggle* newToggle = Object::Instantiate(toggleCopy.ptr(), parent, false); - newToggle->set_interactable(true); - newToggle->set_isOn(currentValue); - newToggle->onValueChanged = UnityEngine::UI::Toggle::ToggleEvent::New_ctor(); + + GameObject* gameObject = Object::Instantiate(toggleCopy.ptr(), parent, false); + static ConstString nameTextName("NameText"); + GameObject* nameText = gameObject->get_transform()->Find(nameTextName)->get_gameObject(); + Object::Destroy(gameObject->GetComponent()); + + static ConstString name("QuestUICheckboxSetting"); + gameObject->set_name(name); + + gameObject->SetActive(false); + + Object::Destroy(nameText->GetComponent()); + nameText->SetActive(false); + + UnityEngine::UI::Toggle* toggle = gameObject->GetComponentInChildren(); + toggle->set_interactable(true); + toggle->set_isOn(currentValue); + toggle->onValueChanged = UnityEngine::UI::Toggle::ToggleEvent::New_ctor(); if(onValueChange) - newToggle->onValueChanged->AddListener(custom_types::MakeDelegate*>(onValueChange)); - RectTransform* rectTransform = newToggle->GetComponent(); - rectTransform->set_anchoredPosition(anchoredPosition); - newToggle->get_gameObject()->set_active(true); - return newToggle; + toggle->onValueChanged->AddListener(custom_types::MakeDelegate*>(onValueChange)); + RectTransform* rectTransform = gameObject->GetComponent(); + rectTransform->set_anchoredPosition({anchoredPosition.x, anchoredPosition.y - 27}); + + gameObject->SetActive(true); + return toggle; } MAKE_HOOK_MATCH(LocalLeaderboardDidActivate, &LocalLeaderboardViewController::DidActivate, void, LocalLeaderboardViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { @@ -1300,17 +1307,15 @@ namespace LeaderboardUI { void setup() { if (hooksInstalled) return; - LoggerContextObject logger = getLogger().WithContext("load"); - - INSTALL_HOOK(logger, LeaderboardActivate); - INSTALL_HOOK(logger, LeaderboardDeactivate); - INSTALL_HOOK(logger, LocalLeaderboardDidActivate); - INSTALL_HOOK(logger, RefreshLeaderboard); - INSTALL_HOOK(logger, LeaderboardCellSource); - INSTALL_HOOK(logger, SegmentedControlHandleCellSelection); + INSTALL_HOOK(BeatLeaderLogger, LeaderboardActivate); + INSTALL_HOOK(BeatLeaderLogger, LeaderboardDeactivate); + INSTALL_HOOK(BeatLeaderLogger, LocalLeaderboardDidActivate); + INSTALL_HOOK(BeatLeaderLogger, RefreshLeaderboard); + INSTALL_HOOK(BeatLeaderLogger, LeaderboardCellSource); + INSTALL_HOOK(BeatLeaderLogger, SegmentedControlHandleCellSelection); PlayerController::playerChanged.emplace_back([](std::optional const& updated) { - QuestUI::MainThreadScheduler::Schedule([] { + BSML::MainThreadScheduler::Schedule([] { if (playerName != NULL) { updatePlayerInfoLabel(); } @@ -1320,11 +1325,15 @@ namespace LeaderboardUI { ssInstalled = false; showBeatLeader = true; - for(auto& [key, value] : Modloader::getMods()){ - if (key == "ScoreSaber") { - ssInstalled = true; - showBeatLeader = getModConfig().ShowBeatleader.GetValue(); - break; + for(auto& modInfo : modloader::get_all()) + { + if(auto loadedMod = std::get_if(&modInfo)) + { + if(loadedMod->info.id == "ScoreSaber"){ + ssInstalled = true; + showBeatLeader = getModConfig().ShowBeatleader.GetValue(); + break; + } } } diff --git a/src/UI/LevelInfoUI.cpp b/src/UI/LevelInfoUI.cpp index 086797d..d869114 100644 --- a/src/UI/LevelInfoUI.cpp +++ b/src/UI/LevelInfoUI.cpp @@ -3,12 +3,10 @@ #include "GlobalNamespace/LevelSelectionNavigationController.hpp" #include "GlobalNamespace/StandardLevelDetailViewController.hpp" #include "GlobalNamespace/StandardLevelDetailView.hpp" -#include "GlobalNamespace/IPreviewBeatmapLevel.hpp" -#include "GlobalNamespace/IDifficultyBeatmap.hpp" +#include "GlobalNamespace/BeatmapLevel.hpp" +#include "GlobalNamespace/BeatmapKey.hpp" #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" -#include "GlobalNamespace/IDifficultyBeatmapSet.hpp" #include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" #include "GlobalNamespace/BeatmapCharacteristicSegmentedControlController.hpp" #include "GlobalNamespace/LevelParamsPanel.hpp" @@ -34,10 +32,11 @@ #include "TMPro/TMP_Text.hpp" #include "TMPro/TextMeshProUGUI.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/ArrayUtil.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" + +#include "scotland2/shared/modloader.h" #include #include @@ -46,8 +45,8 @@ using namespace GlobalNamespace; using namespace std; -using namespace QuestUI; -using namespace BeatSaberUI; +using namespace BSML; +using namespace Lite; namespace LevelInfoUI { TMPro::TextMeshProUGUI* starsLabel = NULL; @@ -101,10 +100,9 @@ namespace LevelInfoUI { MAKE_HOOK_MATCH(LevelRefreshContent, &StandardLevelDetailView::RefreshContent, void, StandardLevelDetailView* self) { LevelRefreshContent(self); - if (self->level == NULL || - self->level->get_beatmapLevelData() == NULL || - self->beatmapCharacteristicSegmentedControlController == NULL || - self->beatmapCharacteristicSegmentedControlController->selectedBeatmapCharacteristic == NULL) return; + if (!self->_beatmapLevel || + !self->_beatmapCharacteristicSegmentedControlController || + !self->_beatmapCharacteristicSegmentedControlController->_selectedBeatmapCharacteristic) return; if (starsLabel == NULL && !bslInstalled) { /////////////////////////// @@ -112,25 +110,25 @@ namespace LevelInfoUI { /////////////////////////// // Create Modal - skillTriangleContainer = QuestUI::BeatSaberUI::CreateModal(self->levelParamsPanel->get_transform(), {40,40}, nullptr, true); + skillTriangleContainer = BSML::Lite::CreateModal(self->_levelParamsPanel->get_transform(), {40,40}, nullptr, true); // Create Actual Triangle Image - auto skillTriangleImage = QuestUI::BeatSaberUI::CreateImage(skillTriangleContainer->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, {0, 0}, {35, 35}); + auto skillTriangleImage = BSML::Lite::CreateImage(skillTriangleContainer->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, {0, 0}, {35, 35}); skillTriangleMat = UnityEngine::Material::Instantiate(BundleLoader::bundle->skillTriangleMaterial); skillTriangleImage->set_material(skillTriangleMat.ptr()); int normalizedValuesPropertyId = UnityEngine::Shader::PropertyToID("_Normalized"); // Create Star Value Labels for Triangle - auto techLabel = QuestUI::BeatSaberUI::CreateText(skillTriangleContainer->get_transform(), "Tech - ", {12, 12}); - auto accLabel = QuestUI::BeatSaberUI::CreateText(skillTriangleContainer->get_transform(), "Acc - ", {34, 12}); - auto passLabel = QuestUI::BeatSaberUI::CreateText(skillTriangleContainer->get_transform(), "Pass - ", {23, -17}); + auto techLabel = BSML::Lite::CreateText(skillTriangleContainer->get_transform(), "Tech - ", {-18, 16}); + auto accLabel = BSML::Lite::CreateText(skillTriangleContainer->get_transform(), "Acc - ", {6, 16}); + auto passLabel = BSML::Lite::CreateText(skillTriangleContainer->get_transform(), "Pass - ", {-7, -12}); // OnClick Function to open the SkillTriangle auto openSkillTriangle = [techLabel, accLabel, passLabel, normalizedValuesPropertyId](){ if(currentlySelectedRating.stars > 0) { - techLabel->SetText("Tech - " + to_string_wprecision(currentlySelectedRating.techRating, 2)); - accLabel->SetText("Acc - " + to_string_wprecision(currentlySelectedRating.accRating, 2)); - passLabel->SetText("Pass - " + to_string_wprecision(currentlySelectedRating.passRating, 2)); + techLabel->SetText("Tech - " + to_string_wprecision(currentlySelectedRating.techRating, 2), true); + accLabel->SetText("Acc - " + to_string_wprecision(currentlySelectedRating.accRating, 2), true); + passLabel->SetText("Pass - " + to_string_wprecision(currentlySelectedRating.passRating, 2), true); skillTriangleMat->SetVector(normalizedValuesPropertyId, { clamp(currentlySelectedRating.techRating / 15.0f, 0.0f, 1.0f), clamp(currentlySelectedRating.accRating / 15.0f, 0.0f, 1.0f), @@ -145,37 +143,33 @@ namespace LevelInfoUI { // Init Stars, PP, Type, Status and NoSubmission Label /////////////////////////// - starsLabel = CreateText(self->levelParamsPanel->get_transform(), "0.00", true, UnityEngine::Vector2(-27, 6), UnityEngine::Vector2(8, 4)); + starsLabel = CreateText(self->_levelParamsPanel->get_transform(), "0.00", UnityEngine::Vector2(-27, 6), UnityEngine::Vector2(8, 4)); starsLabel->set_color(UnityEngine::Color(0.651,0.651,0.651, 1)); - starsLabel->set_fontStyle(TMPro::FontStyles::Italic); - starsImage = CreateClickableImage(self->levelParamsPanel->get_transform(), Sprites::get_StarIcon(), UnityEngine::Vector2(-33, 5.6), UnityEngine::Vector2(3, 3), openSkillTriangle); + starsImage = CreateClickableImage(self->_levelParamsPanel->get_transform(), Sprites::get_StarIcon(), openSkillTriangle, UnityEngine::Vector2(-33, 5.6), UnityEngine::Vector2(3, 3)); AddHoverHint(starsLabel, "Song not ranked"); - ppLabel = CreateText(self->levelParamsPanel->get_transform(), "0", true, UnityEngine::Vector2(-9, 6), UnityEngine::Vector2(8, 4)); + ppLabel = CreateText(self->_levelParamsPanel->get_transform(), "0", UnityEngine::Vector2(-9, 6), UnityEngine::Vector2(8, 4)); ppLabel->set_color(UnityEngine::Color(0.651,0.651,0.651, 1)); - ppLabel->set_fontStyle(TMPro::FontStyles::Italic); AddHoverHint(ppLabel, "BeatLeader approximate pp"); - ppImage = CreateImage(self->levelParamsPanel->get_transform(), Sprites::get_GraphIcon(), UnityEngine::Vector2(-15.5, 5.6), UnityEngine::Vector2(3, 3)); + ppImage = CreateImage(self->_levelParamsPanel->get_transform(), Sprites::get_GraphIcon(), UnityEngine::Vector2(-15.5, 5.6), UnityEngine::Vector2(3, 3)); - typeLabel = CreateText(self->levelParamsPanel->get_transform(), "-", {9, 6}, {8,4}); + typeLabel = CreateText(self->_levelParamsPanel->get_transform(), "-", {9, 6}, {8,4}); typeLabel->set_color(UnityEngine::Color(0.651,0.651,0.651, 1)); - typeLabel->set_fontStyle(TMPro::FontStyles::Italic); AddHoverHint(typeLabel, "Map type\n\nunknown"); - typeImage = CreateImage(self->levelParamsPanel->get_transform(), Sprites::get_ArrowIcon(), {2.5, 5.6}, {3,3}); + typeImage = CreateImage(self->_levelParamsPanel->get_transform(), Sprites::get_ArrowIcon(), {2.5, 5.6}, {3,3}); - statusLabel = CreateText(self->levelParamsPanel->get_transform(), "unr.", {27, 6}, {8,4}); + statusLabel = CreateText(self->_levelParamsPanel->get_transform(), "unr.", {27, 6}, {8,4}); statusLabel->set_color(UnityEngine::Color(0.651,0.651,0.651, 1)); - statusLabel->set_fontStyle(TMPro::FontStyles::Italic); AddHoverHint(statusLabel, "Ranking status - unranked \nTo vote for a song to be ranked, click the message box on the leaderboard"); - statusImage = CreateImage(self->levelParamsPanel->get_transform(), Sprites::get_ClipboardIcon(), {20.5, 5.6}, {3,3}); + statusImage = CreateImage(self->_levelParamsPanel->get_transform(), Sprites::get_ClipboardIcon(), {20.5, 5.6}, {3,3}); } if (!submissionLabel) { submissionLabel = true; - noSubmissionLabel = CreateText(self->levelParamsPanel->get_transform(), "", true, UnityEngine::Vector2(-5, bslInstalled ? -24 : -20)); + noSubmissionLabel = CreateText(self->_levelParamsPanel->get_transform(), "", true, UnityEngine::Vector2(-5, bslInstalled ? -24 : -20)); noSubmissionLabel->set_color(UnityEngine::Color(1.0, 0.0, 0.0, 1)); noSubmissionLabel->set_fontSize(3); AddHoverHint(noSubmissionLabel, "Check their settings for 'force' or 'hitbox'"); @@ -190,9 +184,9 @@ namespace LevelInfoUI { // Why not just substr str.substr("custom_level_".size())? // Because not every level is a custom level. - string hash = regex_replace((string)reinterpret_cast(self->level)->get_levelID(), basic_regex("custom_level_"), ""); - string difficulty = MapEnhancer::DiffName(self->selectedDifficultyBeatmap->get_difficulty().value); - string mode = (string)self->beatmapCharacteristicSegmentedControlController->selectedBeatmapCharacteristic->serializedName; + string hash = regex_replace((string)self->_beatmapLevel->levelID, basic_regex("custom_level_"), ""); + string difficulty = MapEnhancer::DiffName(self->beatmapKey.difficulty.value__); + string mode = (string)self->_beatmapCharacteristicSegmentedControlController->_selectedBeatmapCharacteristic->serializedName; pair key = {hash, difficulty + mode}; @@ -213,7 +207,7 @@ namespace LevelInfoUI { // If the map was already switched again, the response is irrelevant if(lastKey != key) return; - QuestUI::MainThreadScheduler::Schedule([status, key, stringResult] () { + BSML::MainThreadScheduler::Schedule([status, key, stringResult] () { if (status != 200) { setLabels(defaultDiff); return; @@ -249,15 +243,17 @@ namespace LevelInfoUI { } void setup() { - LoggerContextObject logger = getLogger().WithContext("load"); - - INSTALL_HOOK(logger, LevelRefreshContent); - INSTALL_HOOK(logger, DidDeactivate); + INSTALL_HOOK(BeatLeaderLogger, LevelRefreshContent); + INSTALL_HOOK(BeatLeaderLogger, DidDeactivate); - for(auto& [key, value] : Modloader::getMods()){ - if (key == "BetterSongList") { - bslInstalled = true; - break; + for(auto& modInfo : modloader::get_all()) + { + if(auto loadedMod = std::get_if(&modInfo)) + { + if(loadedMod->info.id == "BetterSongList"){ + bslInstalled = true; + break; + } } } } @@ -362,7 +358,7 @@ namespace LevelInfoUI { typeHoverHint.pop_back(); } // Actually set the labels with the prepared strings - typeLabel->SetText(typeToSet); + typeLabel->SetText(typeToSet, true); AddHoverHint(typeLabel, typeHoverHint); string rankingStatus = mapStatuses[selectedDifficulty.status]; @@ -385,7 +381,7 @@ namespace LevelInfoUI { } // Set Color according to calculated VoteRatio (0% = red, 100% = green) - statusLabel->SetText(rankingStatus.substr(0, shortWritingChars) + "."); + statusLabel->SetText(rankingStatus.substr(0, shortWritingChars) + ".", true); if (rating == 0) { statusLabel->set_color(UnityEngine::Color(0.5, 0.5, 0.5, 1)); } else { @@ -415,8 +411,8 @@ namespace LevelInfoUI { currentlySelectedRating = modifierRating; // Set the stars and pp - starsLabel->SetText(to_string_wprecision(UIUtils::getStarsToShow(currentlySelectedRating), 2)); - ppLabel->SetText(to_string_wprecision(currentlySelectedRating.stars * 51.0f, 2)); + starsLabel->SetText(to_string_wprecision(UIUtils::getStarsToShow(currentlySelectedRating), 2), true); + ppLabel->SetText(to_string_wprecision(currentlySelectedRating.stars * 51.0f, 2), true); // Add Hoverhint with all star ratings string starsHoverHint; diff --git a/src/UI/LinksContainer.cpp b/src/UI/LinksContainer.cpp index aea5db4..1e67199 100644 --- a/src/UI/LinksContainer.cpp +++ b/src/UI/LinksContainer.cpp @@ -9,13 +9,14 @@ #include "UnityEngine/Application.hpp" #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" +#include "UnityEngine/UI/LayoutElement.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "main.hpp" -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -29,62 +30,82 @@ void BeatLeader::initLinksContainerPopup(BeatLeader::LinksContainerPopup** modal } if (modalUI == nullptr) modalUI = (BeatLeader::LinksContainerPopup*) malloc(sizeof(BeatLeader::LinksContainerPopup)); - auto container = CreateModal(parent, UnityEngine::Vector2(75, 50), [](HMUI::ModalView *modal) {}, true); + auto container = CreateModal(parent, {10, 0}, {75, 50}, []() {}, true); modalUI->modal = container; auto modalTransform = container->get_transform(); - modalUI->versionText = CreateText(modalTransform, "Loading...", UnityEngine::Vector2(-4.0, 14.0)); - CreateText(modalTransform, "These buttons will open the browser!", UnityEngine::Vector2(-4.0, 4.0)); + modalUI->versionText = CreateText(modalTransform, "Loading...", UnityEngine::Vector2(-8, 14.0)); + CreateText(modalTransform, "These buttons will open the browser!", UnityEngine::Vector2(-22.0, 10.0)); - modalUI->profile = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->websiteLinkIcon, UnityEngine::Vector2(-24, -1), UnityEngine::Vector2(22, 6), [](){ + static auto UnityEngine_Application_OpenURL = il2cpp_utils::resolve_icall("UnityEngine.Application::OpenURL"); + + modalUI->profile = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->websiteLinkIcon, [](){ string url = WebUtils::WEB_URL; if (PlayerController::currentPlayer != std::nullopt) { url += "u/" + PlayerController::currentPlayer->id; } - UnityEngine::Application::OpenURL(url); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->profile, "Your web profile"); + UnityEngine_Application_OpenURL(url); + }, UnityEngine::Vector2(-24, -1), UnityEngine::Vector2(22, 6)); + ::BSML::Lite::AddHoverHint(modalUI->profile, "Your web profile"); - modalUI->discord = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->discordLinkIcon, UnityEngine::Vector2(0, -1), UnityEngine::Vector2(22, 6), [](){ - UnityEngine::Application::OpenURL("https://discord.gg/2RG5YVqtG6"); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->discord, "Our discord server"); - modalUI->patreon = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->patreonLinkIcon, UnityEngine::Vector2(24, -1), UnityEngine::Vector2(22, 6), [](){ - UnityEngine::Application::OpenURL("https://patreon.com/BeatLeader"); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->patreon, "Patreon page"); + modalUI->discord = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->discordLinkIcon, [](){ + UnityEngine_Application_OpenURL("https://discord.gg/2RG5YVqtG6"); + }, UnityEngine::Vector2(0, -1), UnityEngine::Vector2(22, 6)); + ::BSML::Lite::AddHoverHint(modalUI->discord, "Our discord server"); + + modalUI->patreon = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->patreonLinkIcon, [](){ + UnityEngine_Application_OpenURL("https://patreon.com/BeatLeader"); + }, UnityEngine::Vector2(24, -1), UnityEngine::Vector2(22, 6)); + ::BSML::Lite::AddHoverHint(modalUI->patreon, "Patreon page"); WebUtils::GetJSONAsync(WebUtils::API_URL + "mod/lastVersions", [modalUI](long status, bool error, rapidjson::Document const& result){ if (status == 200 && !error && result.HasMember("quest")) { string version = result["quest"].GetObject()["version"].GetString(); - QuestUI::MainThreadScheduler::Schedule([modalUI, version] { + BSML::MainThreadScheduler::Schedule([modalUI, version] { if (modInfo.version == version) { - modalUI->versionText->SetText("Mod is up to date!"); + modalUI->versionText->SetText("Mod is up to date!", true); } else { - modalUI->versionText->SetText("Mod is outdated!"); + modalUI->versionText->SetText("Mod is outdated!", true); } }); } }); - CreateText(modalTransform, "Install playlists. You need to sync them yourself!", UnityEngine::Vector2(-4.0, -11.0)); - modalUI->nominated = ::QuestUI::BeatSaberUI::CreateUIButton(modalTransform, "Nominated", UnityEngine::Vector2(-24.0, -19.0), [modalUI]() { + CreateText(modalTransform, "Install playlists. You need to sync them yourself!", UnityEngine::Vector2(-31.0, -7.0)); + modalUI->nominated = ::BSML::Lite::CreateUIButton(modalTransform, "Nominated", UnityEngine::Vector2(14, -42.0), [modalUI]() { PlaylistSynchronizer::InstallPlaylist(WebUtils::API_URL + "playlist/nominated", "BL Nominated"); }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->nominated, "Playlist of nominated maps"); + SetButtonSize(modalUI->nominated, {22, 8}); + ::BSML::Lite::AddHoverHint(modalUI->nominated, "Playlist of nominated maps"); - modalUI->qualified = ::QuestUI::BeatSaberUI::CreateUIButton(modalTransform, "Qualified", UnityEngine::Vector2(0, -19.0), [modalUI]() { + modalUI->qualified = ::BSML::Lite::CreateUIButton(modalTransform, "Qualified", UnityEngine::Vector2(38, -42.0), [modalUI]() { PlaylistSynchronizer::InstallPlaylist(WebUtils::API_URL + "playlist/qualified", "BL Qualified"); }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->nominated, "Playlist of qualified maps"); + SetButtonSize(modalUI->qualified, {22, 8}); + ::BSML::Lite::AddHoverHint(modalUI->nominated, "Playlist of qualified maps"); - modalUI->ranked = ::QuestUI::BeatSaberUI::CreateUIButton(modalTransform, "Ranked", UnityEngine::Vector2(24.0, -19.0), [modalUI]() { + modalUI->ranked = ::BSML::Lite::CreateUIButton(modalTransform, "Ranked", UnityEngine::Vector2(62, -42.0), [modalUI]() { PlaylistSynchronizer::InstallPlaylist(WebUtils::API_URL + "playlist/ranked", "BL Ranked"); }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->nominated, "Playlist of ranked maps"); + SetButtonSize(modalUI->ranked, {22, 8}); + ::BSML::Lite::AddHoverHint(modalUI->nominated, "Playlist of ranked maps"); modalUI->modal->set_name("BeatLeaderLinksModal"); *modalUIPointer = modalUI; +} + +void BeatLeader::SetButtonSize(UnityEngine::UI::Button* button, UnityEngine::Vector2 sizeDelta) +{ + UnityEngine::Object::DestroyImmediate(button->get_gameObject()->GetComponent()); + UnityEngine::UI::LayoutElement* layoutElement = button->get_gameObject()->GetComponent(); + if(!layoutElement) + layoutElement = button->get_gameObject()->AddComponent(); + layoutElement->set_minWidth(sizeDelta.x); + layoutElement->set_minHeight(sizeDelta.y); + layoutElement->set_preferredWidth(sizeDelta.x); + layoutElement->set_preferredHeight(sizeDelta.y); + layoutElement->set_flexibleWidth(sizeDelta.x); + layoutElement->set_flexibleHeight(sizeDelta.y); } \ No newline at end of file diff --git a/src/UI/LogoAnimation.cpp b/src/UI/LogoAnimation.cpp index ac732a3..6f21208 100644 --- a/src/UI/LogoAnimation.cpp +++ b/src/UI/LogoAnimation.cpp @@ -1,8 +1,8 @@ #include "HMUI/Touchable.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "UnityEngine/Application.hpp" #include "UnityEngine/GUIUtility.hpp" @@ -21,7 +21,7 @@ #include #include -using namespace QuestUI; +using namespace BSML; using namespace std; DEFINE_TYPE(BeatLeader, LogoAnimation); diff --git a/src/UI/ModifiersUI.cpp b/src/UI/ModifiersUI.cpp index 897a9c4..76f6215 100644 --- a/src/UI/ModifiersUI.cpp +++ b/src/UI/ModifiersUI.cpp @@ -103,7 +103,7 @@ namespace ModifiersUI { ModifierStart(self); string key = modifierKeyFromName[self->get_gameplayModifier()->get_modifierNameLocalizationKey()]; - getLogger().info("%s", key.c_str()); + BeatLeaderLogger.info("%s", key.c_str()); if(!multiActive) allModifierToggles[key] = self; } @@ -125,7 +125,7 @@ namespace ModifiersUI { } // Set the text underneath the modifier - value->multiplierText->SetText(modifierSubText); + value->_multiplierText->SetText(modifierSubText, true); } return refreshMultiplierAndMaxRank(); } @@ -137,7 +137,7 @@ namespace ModifiersUI { if (modifiersPanel) { // Now we iterate all modifiers to set the totalMultiplier (% value on top) and the max achievable rank - auto modifierParams = modifiersPanel->gameplayModifiersModel->CreateModifierParamsList(modifiersPanel->gameplayModifiers); + auto modifierParams = modifiersPanel->_gameplayModifiersModel->CreateModifierParamsList(modifiersPanel->gameplayModifiers); float totalMultiplier = 1; @@ -165,23 +165,21 @@ namespace ModifiersUI { if (totalMultiplier < 0) totalMultiplier = 0; // thanks Beat Games for Zen mode -1000% // Correct texts & color of total multiplier & rank with our values - modifiersPanel->totalMultiplierValueText->SetText((totalMultiplier > 1 ? "+" : "") + to_string_wprecision(totalMultiplier * 100.0f, 1) + "%"); - modifiersPanel->maxRankValueText->SetText(getRankForMultiplier(totalMultiplier)); + modifiersPanel->_totalMultiplierValueText->SetText((totalMultiplier > 1 ? "+" : "") + to_string_wprecision(totalMultiplier * 100.0f, 1) + "%", true); + modifiersPanel->_maxRankValueText->SetText(getRankForMultiplier(totalMultiplier), true); - auto color = totalMultiplier >= 1 ? modifiersPanel->positiveColor : modifiersPanel->negativeColor; - modifiersPanel->totalMultiplierValueText->set_color(color); - modifiersPanel->maxRankValueText->set_color(color); + auto color = totalMultiplier >= 1 ? modifiersPanel->_positiveColor : modifiersPanel->_negativeColor; + modifiersPanel->_totalMultiplierValueText->set_color(color); + modifiersPanel->_maxRankValueText->set_color(color); } return ratingSelected; } void setup() { - LoggerContextObject logger = getLogger().WithContext("load"); - - INSTALL_HOOK(logger, ModifierStart); - INSTALL_HOOK(logger, RefreshMultipliers); - INSTALL_HOOK(logger, ActivateMultiplayer); - INSTALL_HOOK(logger, DeActivateMultiplayer); + INSTALL_HOOK(BeatLeaderLogger, ModifierStart); + INSTALL_HOOK(BeatLeaderLogger, RefreshMultipliers); + INSTALL_HOOK(BeatLeaderLogger, ActivateMultiplayer); + INSTALL_HOOK(BeatLeaderLogger, DeActivateMultiplayer); } void SetModifiersActive(bool active) { @@ -191,7 +189,7 @@ namespace ModifiersUI { for (auto& [key, value] : allModifierToggles) { if(songModifiers.contains(key)){ float modifierValue = value->gameplayModifier->multiplier; - value->multiplierText->SetText((modifierValue > 0 ? "+" : "") + to_string_wprecision(modifierValue * 100.0f, 1) + "%"); + value->_multiplierText->SetText((modifierValue > 0 ? "+" : "") + to_string_wprecision(modifierValue * 100.0f, 1) + "%", true); } } } diff --git a/src/UI/PlayerAvatar.cpp b/src/UI/PlayerAvatar.cpp index dee18f1..0bf2038 100644 --- a/src/UI/PlayerAvatar.cpp +++ b/src/UI/PlayerAvatar.cpp @@ -1,8 +1,8 @@ #include "HMUI/Touchable.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "UnityEngine/Application.hpp" #include "UnityEngine/GUIUtility.hpp" @@ -21,7 +21,7 @@ #include "main.hpp" -using namespace QuestUI; +using namespace BSML; static int AvatarTexturePropertyId; static int FadeValuePropertyId; @@ -113,7 +113,7 @@ void BeatLeader::PlayerAvatar::HideImage() { // Stolen from Nya: https://github.com/FrozenAlex/Nya-utils :lovege: void BeatLeader::PlayerAvatar::Update() { if (play) { - int length = animationFrames.Length(); + int length = animationFrames.size(); if (length > 0) { float deltaTime = UnityEngine::Time::get_deltaTime(); @@ -137,7 +137,7 @@ void BeatLeader::PlayerAvatar::Update() { } if (isFrameNeeded) { - if (animationFrames.Length() > currentFrame) { + if (animationFrames.size() > currentFrame) { auto frame = animationFrames.get(currentFrame); if (frame != nullptr) { diff --git a/src/UI/PreferencesViewController.cpp b/src/UI/PreferencesViewController.cpp index d4aecab..e7e1817 100644 --- a/src/UI/PreferencesViewController.cpp +++ b/src/UI/PreferencesViewController.cpp @@ -1,8 +1,8 @@ #include "HMUI/Touchable.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "UnityEngine/Application.hpp" #include "UnityEngine/GUIUtility.hpp" @@ -28,24 +28,24 @@ #include -using namespace QuestUI; +using namespace BSML; using namespace std; -DEFINE_TYPE(BeatLeader, PreferencesViewController); +UnityEngine::Transform* containerTransform; UnityEngine::UI::Button* logoutButton; HMUI::InputFieldView* loginField; HMUI::InputFieldView* passwordField; UnityEngine::UI::Button* loginButton; UnityEngine::UI::Button* signupButton; -TMPro::TextMeshProUGUI* name; +TMPro::TextMeshProUGUI* nameField; TMPro::TextMeshProUGUI* label3; TMPro::TextMeshProUGUI* errorDescriptionLabel; -HMUI::SimpleTextDropdown* serverDropdown; -HMUI::SimpleTextDropdown* starsDropdown; -UnityEngine::UI::Toggle* saveToggle; -UnityEngine::UI::Toggle* showReplaySettingsToggle; +BSML::DropdownListSetting* serverDropdown; +BSML::DropdownListSetting* starsDropdown; +BSML::ToggleSetting* saveToggle; +BSML::ToggleSetting* showReplaySettingsToggle; BeatLeader::LogoAnimation* spinner = NULL; @@ -59,8 +59,8 @@ void UpdateUI(optional player) { spinner->SetAnimating(false); if (player != nullopt) { - name->SetText(player->name + ", hi!"); - name->get_gameObject()->SetActive(true); + nameField->SetText(player->name + ", hi!", true); + nameField->get_gameObject()->SetActive(true); label3->get_gameObject()->SetActive(false); logoutButton->get_gameObject()->SetActive(true); @@ -69,12 +69,12 @@ void UpdateUI(optional player) { loginButton->get_gameObject()->SetActive(false); signupButton->get_gameObject()->SetActive(false); - saveToggle->get_transform()->get_parent()->get_gameObject()->SetActive(true); + saveToggle->get_gameObject()->SetActive(true); starsDropdown->get_transform()->get_parent()->get_gameObject()->SetActive(true); if(showReplaySettingsToggle) - showReplaySettingsToggle->get_transform()->get_parent()->get_gameObject()->SetActive(true); + showReplaySettingsToggle->get_gameObject()->SetActive(true); } else { - name->get_gameObject()->SetActive(false); + nameField->get_gameObject()->SetActive(false); label3->get_gameObject()->SetActive(true); logoutButton->get_gameObject()->SetActive(false); @@ -88,13 +88,13 @@ void UpdateUI(optional player) { loginButton->set_interactable(true); signupButton->set_interactable(true); - saveToggle->get_transform()->get_parent()->get_gameObject()->SetActive(false); + saveToggle->get_gameObject()->SetActive(false); starsDropdown->get_transform()->get_parent()->get_gameObject()->SetActive(false); if(showReplaySettingsToggle) - showReplaySettingsToggle->get_transform()->get_parent()->get_gameObject()->SetActive(false); + showReplaySettingsToggle->get_gameObject()->SetActive(false); } - errorDescriptionLabel->SetText(errorDescription); + errorDescriptionLabel->SetText(errorDescription, true); if (errorDescription.length() > 0) { errorDescriptionLabel->get_gameObject()->SetActive(true); } else { @@ -112,38 +112,45 @@ void showLoading() { signupButton->set_interactable(false); } -void BeatLeader::PreferencesViewController::DidDeactivate(bool removedFromHierarchy, bool screenSystemDisabling) { - errorDescription = ""; -} +std::vector starValueOptions = { + "Overall", + "Tech", + "Acc", + "Pass" +}; -void BeatLeader::PreferencesViewController::DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { +void BeatLeader::PreferencesViewController::DidActivate(HMUI::ViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { if (firstActivation) { - this->get_gameObject()->AddComponent(); - UnityEngine::GameObject* container = BeatSaberUI::CreateScrollableSettingsContainer(this->get_transform()); + // Make Touchable + self->get_gameObject()->AddComponent(); + + // Create Container + auto* container = BSML::Lite::CreateScrollableSettingsContainer(self->get_transform()); - auto containerTransform = container->get_transform(); + containerTransform = container->get_transform(); - auto spinnerImage = ::QuestUI::BeatSaberUI::CreateImage(this->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, {0, 20}, {20, 20}); - spinner = this->get_gameObject()->AddComponent(); + auto spinnerImage = ::BSML::Lite::CreateImage(self->get_transform(), BundleLoader::bundle->beatLeaderLogoGradient, {0, 20}, {20, 20}); + spinner = self->get_gameObject()->AddComponent(); spinner->Init(spinnerImage); spinnerImage->get_gameObject()->SetActive(false); - name = ::QuestUI::BeatSaberUI::CreateText(containerTransform, "", false); - EmojiSupport::AddSupport(name); + nameField = ::BSML::Lite::CreateText(containerTransform, ""); + EmojiSupport::AddSupport(nameField); - logoutButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "Logout", [](){ + logoutButton = ::BSML::Lite::CreateUIButton(containerTransform, "Logout", [](){ PlayerController::LogOut(); + UpdateUI(nullopt); }); - loginField = ::QuestUI::BeatSaberUI::CreateStringSetting(containerTransform, "Login", "", [](StringW value) { + loginField = ::BSML::Lite::CreateStringSetting(containerTransform, "Login", "", [](StringW value) { login = (string) value; }); - passwordField = ::QuestUI::BeatSaberUI::CreateStringSetting(containerTransform, "Password", "", [](StringW value) { + passwordField = ::BSML::Lite::CreateStringSetting(containerTransform, "Password", "", [](StringW value) { password = (string) value; }); - loginButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "Log in", [spinnerImage]() { + loginButton = ::BSML::Lite::CreateUIButton(containerTransform, "Log in", []() { if (login.empty() || password.empty()) { errorDescription = "Enter a username and/or password!"; UpdateUI(nullopt); @@ -151,7 +158,7 @@ void BeatLeader::PreferencesViewController::DidActivate(bool firstActivation, bo } showLoading(); PlayerController::LogIn(login, password, [](std::optional const& player, string error) { - QuestUI::MainThreadScheduler::Schedule([player, error] { + BSML::MainThreadScheduler::Schedule([player, error] { if (player == nullopt) { errorDescription = error; } else { @@ -165,7 +172,7 @@ void BeatLeader::PreferencesViewController::DidActivate(bool firstActivation, bo }); }); }); - signupButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "Sign up", []() { + signupButton = ::BSML::Lite::CreateUIButton(containerTransform, "Sign up", []() { if (login.empty() || password.empty()) { errorDescription = "Enter a username and/or password!"; UpdateUI(nullopt); @@ -173,7 +180,7 @@ void BeatLeader::PreferencesViewController::DidActivate(bool firstActivation, bo } showLoading(); PlayerController::SignUp((string)login, (string)password, [](std::optional const& player, string error) { - QuestUI::MainThreadScheduler::Schedule([player, error] { + BSML::MainThreadScheduler::Schedule([player, error] { if (player == nullopt) { errorDescription = error; } else { @@ -188,32 +195,30 @@ void BeatLeader::PreferencesViewController::DidActivate(bool firstActivation, bo }); }); - auto captureSelf = this; + auto captureSelf = self; PlayerController::playerChanged.emplace_back([captureSelf](std::optional const& updated) { if (!captureSelf->isActivated) return; - QuestUI::MainThreadScheduler::Schedule([updated] { - UpdateUI(updated); + BSML::MainThreadScheduler::Schedule([updated] { + if (updated) { + UpdateUI(updated); + } }); }); saveToggle = AddConfigValueToggle(containerTransform, getModConfig().SaveLocalReplays); - starsDropdown = AddConfigValueDropdownEnum(containerTransform, getModConfig().StarValueToShow, { - "Overall", - "Tech", - "Acc", - "Pass" - }); - // After switching the setting we need to manually call refresh, because StandardLevelDetailView::RefreshContent is not called again, - // if the same map, that was selected before changing the setting, is selected again before selecting any other map. - // This results in setLabels not being called again and the stars of the old setting are displayed, which is why we call it manually here after selecting an option - starsDropdown->add_didSelectCellWithIdxEvent(custom_types::MakeDelegate*>((function)[](auto throwaway1, auto throwaway2){ + starsDropdown = AddConfigValueDropdownEnum(containerTransform, getModConfig().StarValueToShow, starValueOptions); + // // After switching the setting we need to manually call refresh, because StandardLevelDetailView::RefreshContent is not called again, + // // if the same map, that was selected before changing the setting, is selected again before selecting any other map. + // // This results in setLabels not being called again and the stars of the old setting are displayed, which is why we call it manually here after selecting an option + starsDropdown->dropdown->add_didSelectCellWithIdxEvent(custom_types::MakeDelegate, int>*>((function, int)>)[](auto throwaway1, auto throwaway2){ LevelInfoUI::refreshRatingLabels(); })); if (ReplayInstalled()) { showReplaySettingsToggle = AddConfigValueToggle(containerTransform, getModConfig().ShowReplaySettings); } - errorDescriptionLabel = ::QuestUI::BeatSaberUI::CreateText(containerTransform, "", false); - label3 = ::QuestUI::BeatSaberUI::CreateText(containerTransform, "Never used BeatLeader? Sign up with any new login/password.\nTo log in, enter your existing account's login information.\nYour account is temporary until at least one score has been posted!\nYou can change your profile details on the website.", false); + errorDescriptionLabel = ::BSML::Lite::CreateText(containerTransform, ""); + label3 = ::BSML::Lite::CreateText(containerTransform, "Never used BeatLeader? Sign up with any new login/password.\nTo log in, enter your existing account's login information.\nYour account is temporary until at least one score has been posted!\nYou can change your profile details on the website."); + label3->set_fontSize(3.0f); } UpdateUI(PlayerController::currentPlayer); diff --git a/src/UI/QuestUI.cpp b/src/UI/QuestUI.cpp new file mode 100644 index 0000000..68c4ee5 --- /dev/null +++ b/src/UI/QuestUI.cpp @@ -0,0 +1,360 @@ +#include "UI/QuestUI.hpp" + +#include "GlobalNamespace/UIKeyboardManager.hpp" +#include "GlobalNamespace/BoolSettingsController.hpp" +#include "GlobalNamespace/FormattedFloatListSettingsValueController.hpp" +#include "GlobalNamespace/ColorPickerButtonController.hpp" +#include "GlobalNamespace/HSVPanelController.hpp" +#include "GlobalNamespace/MenuShockwave.hpp" +#include "GlobalNamespace/LevelCollectionTableView.hpp" + +#include "UnityEngine/Canvas.hpp" +#include "UnityEngine/CanvasGroup.hpp" +#include "UnityEngine/AdditionalCanvasShaderChannels.hpp" +#include "UnityEngine/RenderMode.hpp" +#include "UnityEngine/Resources.hpp" +#include "UnityEngine/Rect.hpp" +#include "UnityEngine/RectOffset.hpp" +#include "UnityEngine/SpriteMeshType.hpp" +#include "UnityEngine/Texture2D.hpp" +#include "UnityEngine/TextureFormat.hpp" +#include "UnityEngine/TextureWrapMode.hpp" +#include "UnityEngine/ImageConversion.hpp" +#include "UnityEngine/Material.hpp" +#include "UnityEngine/UI/RectMask2D.hpp" +#include "UnityEngine/UI/ScrollRect.hpp" +#include "UnityEngine/UI/CanvasScaler.hpp" +#include "UnityEngine/Events/UnityAction_1.hpp" +#include "UnityEngine/Events/UnityAction.hpp" + +#include "HMUI/Touchable.hpp" +#include "HMUI/HoverHintController.hpp" +#include "HMUI/TableView.hpp" +#include "HMUI/ImageView.hpp" +#include "HMUI/TextPageScrollView.hpp" +#include "HMUI/CurvedTextMeshPro.hpp" +#include "HMUI/TextSegmentedControl.hpp" +#include "HMUI/UIKeyboard.hpp" +#include "HMUI/CurvedCanvasSettings.hpp" +#include "HMUI/EventSystemListener.hpp" +#include "HMUI/DropdownWithTableView.hpp" +#include "HMUI/ButtonSpriteSwap.hpp" +#include "HMUI/TimeSlider.hpp" +#include "HMUI/ColorGradientSlider.hpp" +#include "HMUI/TextSegmentedControl.hpp" +#include "HMUI/HoverTextSetter.hpp" + +#include "VRUIControls/VRGraphicRaycaster.hpp" +#include "BGLib/Polyglot/LocalizedTextMeshProUGUI.hpp" + +#include "System/Convert.hpp" +#include "System/Action_2.hpp" +#include "System/Action.hpp" +#include "System/Collections/Generic/HashSet_1.hpp" + +#include "Libraries/HM/HMLib/VR/HapticPresetSO.hpp" +#include "UI/Components/ExternalComponents.hpp" + +#include "Zenject/DiContainer.hpp" + +#define DEFAULT_BUTTONTEMPLATE "PracticeButton" + +#include "custom-types/shared/delegate.hpp" + +using namespace GlobalNamespace; +using namespace UnityEngine; +using namespace UnityEngine::UI; +using namespace UnityEngine::Events; +using namespace TMPro; +using namespace HMUI; +using namespace VRUIControls; +using namespace Zenject; +using namespace BGLib::Polyglot; + +namespace QuestUI { + GameObject* beatSaberUIObject = nullptr; + GameObject* dropdownListPrefab = nullptr; + GameObject* modalPrefab = nullptr; + + void SetupPersistentObjects() { + if(!beatSaberUIObject) { + static ConstString name("BeatSaberUIObject"); + beatSaberUIObject = GameObject::New_ctor(name); + GameObject::DontDestroyOnLoad(beatSaberUIObject); + // beatSaberUIObject->AddComponent(); + } + if(!dropdownListPrefab) { + GameObject* search = Resources::FindObjectsOfTypeAll()->First([](SimpleTextDropdown* x) { + return x->get_transform()->get_parent()->get_name() == "NormalLevels"; + } + )->get_transform()->get_parent()->get_gameObject(); + dropdownListPrefab = Object::Instantiate(search, beatSaberUIObject->get_transform(), false); + static ConstString name("QuestUIDropdownListPrefab"); + dropdownListPrefab->set_name(name); + dropdownListPrefab->SetActive(false); + } + if (!modalPrefab) { + GameObject* search = Resources::FindObjectsOfTypeAll()->First([](ModalView* x) { + return x->get_transform()->get_name() == "DropdownTableView"; + } + )->get_gameObject(); + modalPrefab = Object::Instantiate(search, beatSaberUIObject->get_transform(), false); + + modalPrefab->GetComponent()->_presentPanelAnimations = search->GetComponent()->_presentPanelAnimations; + modalPrefab->GetComponent()->_dismissPanelAnimation = search->GetComponent()->_dismissPanelAnimation; + + static ConstString name("QuestUIModalPrefab"); + modalPrefab->set_name(name); + modalPrefab->SetActive(false); + } + } + + static PhysicsRaycasterWithCache* physicsRaycaster = nullptr; + PhysicsRaycasterWithCache* GetPhysicsRaycasterWithCache() + { + if(!physicsRaycaster) physicsRaycaster = Resources::FindObjectsOfTypeAll()->First()->GetComponent()->_physicsRaycaster; + if(!physicsRaycaster) { + return nullptr; + } + return physicsRaycaster; + } + + static DiContainer* diContainer = nullptr; + DiContainer* GetDiContainer() + { + if(!diContainer) diContainer = Resources::FindObjectsOfTypeAll()->FirstOrDefault([](TextSegmentedControl* x) { return x->get_transform()->get_parent()->get_name() == "PlayerStatisticsViewController" && x->_container; })->_container; + if(!diContainer) { + return nullptr; + } + return diContainer; + } + + static SafePtrUnity mat_UINoGlow; + Material* NoGlowMaterial() { + if(!mat_UINoGlow) mat_UINoGlow.emplace(Resources::FindObjectsOfTypeAll()->First([](Material* x) { return x->get_name() == "UINoGlow"; })); + if(!mat_UINoGlow) { + return nullptr; + } + + return mat_UINoGlow.ptr(); + } + + static SafePtrUnity mainTextFont; + TMP_FontAsset* GetMainTextFont() { + if(!mainTextFont) mainTextFont.emplace(Resources::FindObjectsOfTypeAll()->FirstOrDefault([](TMP_FontAsset* x) { return x->get_name() == "Teko-Medium SDF"; })); + if(!mainTextFont) { + return nullptr; + } + return mainTextFont.ptr(); + } + + static SafePtrUnity mainUIFontMaterial; + Material* GetMainUIFontMaterial() { + if(!mainUIFontMaterial) mainUIFontMaterial.emplace(Resources::FindObjectsOfTypeAll()->FirstOrDefault([](Material* x) { return x->get_name() == "Teko-Medium SDF Curved Softer"; })); + if(!mainUIFontMaterial) { + return nullptr; + } + return mainUIFontMaterial.ptr(); + } + + void ClearCache() { + diContainer = nullptr; + physicsRaycaster = nullptr; + } + + ModalView* CreateModal(Transform* parent, UnityEngine::Vector2 sizeDelta, UnityEngine::Vector2 anchoredPosition, std::function onBlockerClicked, bool dismissOnBlockerClicked) { + static ConstString name("QuestUIModalPrefab"); + + // declare var + ModalView* orig = modalPrefab->GetComponent(); + + // instantiate + GameObject* modalObj = Object::Instantiate(modalPrefab, parent, false); + + modalObj->set_name(name); + modalObj->SetActive(false); + + // get the modal + ModalView* modal = modalObj->GetComponent(); + + // copy fields + modal->_presentPanelAnimations = orig->_presentPanelAnimations; + modal->_dismissPanelAnimation = orig->_dismissPanelAnimation; + modal->_container = GetDiContainer(); + modalObj->GetComponent()->_physicsRaycaster = GetPhysicsRaycasterWithCache(); + + // destroy unneeded objects + Object::DestroyImmediate(modalObj->GetComponent()); + Object::DestroyImmediate(modalObj->GetComponent()); + Object::DestroyImmediate(modalObj->GetComponent()); + Object::DestroyImmediate(modalObj->GetComponent()); + + // destroy all children except background + int childCount = modal->get_transform()->get_childCount(); + for (int i = 0; i < childCount; i++) { + auto* child = modal->get_transform()->GetChild(i)->GetComponent(); + + if (child->get_gameObject()->get_name() == "BG") { + child->set_anchoredPosition(Vector2::get_zero()); + child->set_sizeDelta(Vector2::get_zero()); + child->GetComponent()->set_raycastTarget(true); + } + else { + // yeet the child + Object::Destroy(child->get_gameObject()); + } + } + + // set recttransform data + auto rect = modalObj->GetComponent(); + rect->set_anchorMin({0.5f, 0.5f}); + rect->set_anchorMax({0.5f, 0.5f}); + rect->set_sizeDelta(sizeDelta); + rect->set_anchoredPosition(anchoredPosition); + + // add callback + modal->add_blockerClickedEvent( + custom_types::MakeDelegate(classof(System::Action *), (std::function) [onBlockerClicked, modal, dismissOnBlockerClicked] () { + if (onBlockerClicked) + onBlockerClicked(modal); + if (dismissOnBlockerClicked) + modal->Hide(true, nullptr); + }) + ); + return modal; + } + + TextMeshProUGUI* CreateText(Transform* parent, StringW text, UnityEngine::Vector2 anchoredPosition) { + return CreateText(parent, text, true, anchoredPosition); + } + + TextMeshProUGUI* CreateText(Transform* parent, StringW text, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta) { + return CreateText(parent, text, true, anchoredPosition, sizeDelta); + } + + TextMeshProUGUI* CreateText(Transform* parent, StringW text, bool italic) { + return CreateText(parent, text, italic, UnityEngine::Vector2(0.0f, 0.0f), UnityEngine::Vector2(60.0f, 10.0f)); + } + + TextMeshProUGUI* CreateText(Transform* parent, StringW text, bool italic, UnityEngine::Vector2 anchoredPosition) { + return CreateText(parent, text, italic, anchoredPosition, UnityEngine::Vector2(60.0f, 10.0f)); + } + + TextMeshProUGUI* CreateText(Transform* parent, StringW text, bool italic, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta) { + static ConstString name("QuestUIText"); + GameObject* gameObj = GameObject::New_ctor(name); + gameObj->SetActive(false); + + CurvedTextMeshPro* textMesh = gameObj->AddComponent(); + RectTransform* rectTransform = textMesh->get_rectTransform(); + rectTransform->SetParent(parent, false); + textMesh->set_font(GetMainTextFont()); + textMesh->set_fontSharedMaterial(GetMainUIFontMaterial()); + if (italic) textMesh->set_fontStyle(TMPro::FontStyles::Italic); + textMesh->set_text(text); + textMesh->set_fontSize(4.0f); + textMesh->set_color(UnityEngine::Color::get_white()); + textMesh->set_richText(true); + rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_anchoredPosition(anchoredPosition); + rectTransform->set_sizeDelta(sizeDelta); + + gameObj->AddComponent(); + + gameObj->SetActive(true); + return textMesh; + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, std::string_view buttonTemplate, std::function onClick) { + static std::unordered_map> buttonCopyMap; + auto& buttonCopy = buttonCopyMap[std::string(buttonTemplate)]; + if (!buttonCopy) { + buttonCopy = Resources::FindObjectsOfTypeAll()->LastOrDefault([&buttonTemplate](auto* x) { return x->get_name() == buttonTemplate; }); + } + + Button* button = Object::Instantiate(buttonCopy.ptr(), parent, false); + button->set_onClick(Button::ButtonClickedEvent::New_ctor()); + static ConstString name("QuestUIButton"); + button->set_name(name); + if(onClick) + button->get_onClick()->AddListener(custom_types::MakeDelegate(onClick)); + + LocalizedTextMeshProUGUI* localizer = button->GetComponentInChildren(); + if (localizer != nullptr) + GameObject::Destroy(localizer); + ExternalComponents* externalComponents = button->get_gameObject()->AddComponent(); + + TextMeshProUGUI* textMesh = button->GetComponentInChildren(); + if (textMesh) + { + textMesh->set_richText(true); + textMesh->set_alignment(TextAlignmentOptions::Center); + textMesh->set_text(buttonText); + externalComponents->Add(textMesh); + } + RectTransform* rectTransform = button->get_transform().cast(); + rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_pivot(UnityEngine::Vector2(0.5f, 0.5f)); + + HorizontalLayoutGroup* horiztonalLayoutGroup = button->GetComponentInChildren(); + if (horiztonalLayoutGroup != nullptr) + externalComponents->Add(horiztonalLayoutGroup); + + // if the original button was for some reason not interactable, now it will be + button->set_interactable(true); + button->get_gameObject()->SetActive(true); + return button; + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, std::string_view buttonTemplate, UnityEngine::Vector2 anchoredPosition, std::function onClick) { + Button* button = CreateUIButton(parent, buttonText, buttonTemplate, onClick); + button->GetComponent()->set_anchoredPosition(anchoredPosition); + return button; + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, std::string_view buttonTemplate, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta, std::function onClick) { + Button* button = CreateUIButton(parent, buttonText, buttonTemplate, anchoredPosition, onClick); + button->GetComponent()->set_sizeDelta(sizeDelta); + LayoutElement* layoutElement = button->GetComponent(); + if(!layoutElement) + layoutElement = button->get_gameObject()->AddComponent(); + layoutElement->set_minWidth(sizeDelta.x); + layoutElement->set_minHeight(sizeDelta.y); + layoutElement->set_preferredWidth(sizeDelta.x); + layoutElement->set_preferredHeight(sizeDelta.y); + layoutElement->set_flexibleWidth(sizeDelta.x); + layoutElement->set_flexibleHeight(sizeDelta.y); + return button; + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, std::function onClick) { + return CreateUIButton(parent, buttonText, DEFAULT_BUTTONTEMPLATE, onClick); + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, UnityEngine::Vector2 anchoredPosition, std::function onClick) { + return CreateUIButton(parent, buttonText, DEFAULT_BUTTONTEMPLATE, anchoredPosition, onClick); + } + + Button* CreateUIButton(Transform* parent, StringW buttonText, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta, std::function onClick) { + return CreateUIButton(parent, buttonText, DEFAULT_BUTTONTEMPLATE, anchoredPosition, sizeDelta, onClick); + } + + ImageView* CreateImage(Transform* parent, Sprite* sprite, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta) { + static ConstString name("QuestUIImage"); + GameObject* gameObj = GameObject::New_ctor(name); + ImageView* image = gameObj->AddComponent(); + image->set_material(NoGlowMaterial()); + image->get_transform()->SetParent(parent, false); + image->set_sprite(sprite); + RectTransform* rectTransform = image->get_transform().cast(); + rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); + rectTransform->set_anchoredPosition(anchoredPosition); + rectTransform->set_sizeDelta(sizeDelta); + + gameObj->AddComponent(); + return image; + } +} \ No newline at end of file diff --git a/src/UI/ResultsViewController.cpp b/src/UI/ResultsViewController.cpp index 79a2517..061e29c 100644 --- a/src/UI/ResultsViewController.cpp +++ b/src/UI/ResultsViewController.cpp @@ -8,11 +8,10 @@ #include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp" #include "GlobalNamespace/MenuTransitionsHelper.hpp" #include "HMUI/ViewController.hpp" -#include "HMUI/ViewController_AnimationDirection.hpp" #include "HMUI/FlowCoordinator.hpp" #include "UnityEngine/GameObject.hpp" #include "UI/LeaderboardUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" +#include "bsml/shared/BSML-Lite.hpp" #include "UnityEngine/RectTransform.hpp" #include "UnityEngine/UI/Button.hpp" #include "System/Action.hpp" @@ -23,6 +22,7 @@ #include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" #include "custom-types/shared/delegate.hpp" #include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" +#include "bsml/shared/Helpers/getters.hpp" #include @@ -50,7 +50,7 @@ namespace ResultsView { if(firstActivation){ auto transform = self->get_transform(); // Create voting button - auto votingButtonImage = ::QuestUI::BeatSaberUI::CreateClickableImage(transform, BundleLoader::bundle->modifiersIcon, {-67, 9}, {8, 8}, [transform]() { + auto votingButtonImage = ::BSML::Lite::CreateClickableImage(transform, BundleLoader::bundle->modifiersIcon, [transform]() { if (resultsVotingButton->state != 2) return; if (votingUI == NULL) { @@ -60,19 +60,19 @@ namespace ResultsView { votingUI->reset(); votingUI->modal->Show(true, true, nullptr); - }); + }, {-67, 9}, {8, 8}); resultsVotingButton = self->get_gameObject()->AddComponent(); resultsVotingButton->Init(votingButtonImage); // If we have replay, also show the replay button if(ReplayInstalled()) { - replayButton = QuestUI::BeatSaberUI::CreateUIButton(transform, "", "PracticeButton", {-46, -19}, {12, 10}, []() { + replayButton = BSML::Lite::CreateUIButton(transform, "", "PracticeButton", {-46, -19}, {12, 10}, []() { // Dont crash if file doesnt exist yet if(std::filesystem::exists(ReplayManager::lastReplayFilename)) { - auto flow = QuestUI::BeatSaberUI::GetMainFlowCoordinator()->YoungestChildFlowCoordinatorOrSelf(); + auto flow = BSML::Helpers::GetMainFlowCoordinator()->YoungestChildFlowCoordinatorOrSelf(); flow->DismissViewController(flow->get_topViewController(), HMUI::ViewController::AnimationDirection::Vertical, custom_types::MakeDelegate(classof(System::Action *), (std::function)[flow]() { - if (il2cpp_utils::try_cast(flow)) { - ((GlobalNamespace::SinglePlayerLevelSelectionFlowCoordinator *)flow)->SinglePlayerLevelSelectionFlowCoordinatorDidActivate(false, false); + if (flow.try_cast()) { + flow.cast()->SinglePlayerLevelSelectionFlowCoordinatorDidActivate(false, false); } if(!lastGameWasReplay || !getModConfig().ShowReplaySettings.GetValue()){ if (getModConfig().ShowReplaySettings.GetValue()) { @@ -86,14 +86,14 @@ namespace ResultsView { } }); // Set icon of button - auto *image = QuestUI::BeatSaberUI::CreateImage(replayButton->get_transform(), BundleLoader::bundle->replayIcon); + auto *image = BSML::Lite::CreateImage(replayButton->get_transform(), BundleLoader::bundle->replayIcon); image->get_rectTransform()->set_localScale({0.64f, 0.8f, 1.0f}); } } // Ajust position based on result screen type (position is different between failure and success) if(replayButton) { - ((RectTransform*)replayButton->get_transform())->set_anchoredPosition(self->levelCompletionResults->levelEndStateType == LevelCompletionResults::LevelEndStateType::Cleared ? UnityEngine::Vector2(-46, -30) : UnityEngine::Vector2(-46, -19)); + replayButton->get_transform().cast()->set_anchoredPosition(self->_levelCompletionResults->levelEndStateType == LevelCompletionResults::LevelEndStateType::Cleared ? UnityEngine::Vector2(-46, -30) : UnityEngine::Vector2(-46, -19)); } // Load initial status @@ -101,8 +101,7 @@ namespace ResultsView { } void setup() { - LoggerContextObject logger = getLogger().WithContext("load"); - INSTALL_HOOK(logger, GetLastReplayStateHook); - INSTALL_HOOK(logger, ResultsViewDidActivate); + INSTALL_HOOK(BeatLeaderLogger, GetLastReplayStateHook); + INSTALL_HOOK(BeatLeaderLogger, ResultsViewDidActivate); } } \ No newline at end of file diff --git a/src/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.cpp b/src/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.cpp index 1ec8855..617bbd4 100644 --- a/src/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.cpp +++ b/src/UI/ScoreDetails/AccuracyGraph/AccuracyGraph.cpp @@ -5,6 +5,7 @@ #include "include/UI/ScoreDetails/ScoreStatsGraph.hpp" #include "include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp" +#include "include/UI/QuestUI.hpp" #include "include/Utils/FormatUtils.hpp" #include "include/Utils/Range.hpp" @@ -54,7 +55,7 @@ void BeatLeader::AccuracyGraph::Construct( graphLineObject->set_material(BundleLoader::bundle->accuracyGraphLine); - underlineText = QuestUI::BeatSaberUI::CreateText(backgroundImage->get_transform(), "", UnityEngine::Vector2(2, -18)); + underlineText = QuestUI::CreateText(backgroundImage->get_transform(), "", UnityEngine::Vector2(2, -18)); auto vrpointers = UnityEngine::Resources::FindObjectsOfTypeAll(); if (vrpointers.size() != 0) { @@ -68,7 +69,7 @@ float BeatLeader::AccuracyGraph::GetCanvasRadius() { } auto canvasSettings = curvedCanvasSettingsHelper->GetCurvedCanvasSettings(graphLine->get_canvas()); - return canvasSettings == NULL ? 100 : canvasSettings->radius; + return canvasSettings ? 140 : canvasSettings->radius; } void BeatLeader::AccuracyGraph::Setup(ArrayW points, float songDuration) { @@ -92,17 +93,18 @@ static string FormatCursorText(float songTime, float accuracy) { } void BeatLeader::AccuracyGraph::Update() { - if (!modal->isShown || isnan(abs(targetViewTime))) return; + if (!modal->_isShown || isnan(abs(targetViewTime))) return; currentViewTime = AccuracyGraphUtils::Lerp(currentViewTime, targetViewTime, UnityEngine::Time::get_deltaTime() * 10.0); auto songTime = currentViewTime * songDuration; auto accuracy = GetAccuracy(currentViewTime); + backgroundMaterial->SetFloat(CursorPositionPropertyId, currentViewTime); - underlineText->SetText(FormatCursorText(songTime, accuracy)); + underlineText->SetText(FormatCursorText(songTime, accuracy), true); } Vector2 CalculateCursorPosition(Vector3 worldCursor, BeatLeader::AccuracyGraphLine* graphLine, float canvasRadius) { - UnityEngine::RectTransform* graphContainer = (UnityEngine::RectTransform*)graphLine->get_transform(); + UnityEngine::RectTransform* graphContainer = graphLine->get_transform().cast(); auto nonCurved = AccuracyGraphUtils::TransformPointFrom3DToCanvas(worldCursor, canvasRadius * graphContainer->get_lossyScale().x); @@ -122,7 +124,7 @@ float UpdateCursor(Vector2 normalized) { } void BeatLeader::AccuracyGraph::LateUpdate() { - if (!modal->isShown || vrPointer == NULL) return; + if (!modal->_isShown || vrPointer == NULL) return; auto cursorPosition3D = vrPointer->get_cursorPosition(); if (cursorPosition3D.Equals(lastPosition3D)) return; @@ -137,7 +139,7 @@ void BeatLeader::AccuracyGraph::LateUpdate() { } float BeatLeader::AccuracyGraph::GetAccuracy(float viewTime) { - int pointsLength = points.Length(); + int pointsLength = points.size(); if (pointsLength == 0) return 1.0; auto xStep = 1.0 / (float)pointsLength; diff --git a/src/UI/ScoreDetails/AccuracyGraph/GraphMeshHelper.cpp b/src/UI/ScoreDetails/AccuracyGraph/GraphMeshHelper.cpp index 252d998..6b6351b 100644 --- a/src/UI/ScoreDetails/AccuracyGraph/GraphMeshHelper.cpp +++ b/src/UI/ScoreDetails/AccuracyGraph/GraphMeshHelper.cpp @@ -19,7 +19,7 @@ BeatLeader::GraphMeshHelper::GraphMeshHelper(int horizontalResolution, int verti } void BeatLeader::GraphMeshHelper::SetPoints(ArrayW points) { - int size = points.Length(); + int size = points.size(); if (size <= 1) { spline = nullopt; @@ -52,17 +52,17 @@ void BeatLeader::GraphMeshHelper::PopulateMesh(UnityEngine::UI::VertexHelper* vh float verticalRatio = ((float) rowIndex / verticalResolution - 0.5) * 2.0; int vertexIndex = GetVertexIndex(rowIndex, columnIndex); - auto widthOffset = verticalRatio * lineThickness * screenNodeNormal; - auto screenVertexPosition = screenNodePosition + widthOffset; + auto widthOffset = UnityEngine::Vector2::op_Multiply(UnityEngine::Vector2::op_Multiply(screenNodeNormal, lineThickness), verticalRatio); + auto screenVertexPosition = UnityEngine::Vector2::op_Addition(widthOffset, screenNodePosition); auto screenNormalizedPosition = svt.NormalizeScreenPosition(screenVertexPosition); vh->AddVert( UnityEngine::Vector3(screenVertexPosition.x, screenVertexPosition.y, 0), - UnityEngine::Color32(1, 1, 1, 1), - uv0[vertexIndex], - screenNormalizedPosition, - UnityEngine::Vector2(canvasRadius, 0), - node.position, + UnityEngine::Color32(0, 1, 1, 1, 1), + UnityEngine::Vector4::op_Implicit___UnityEngine__Vector4(uv0[vertexIndex]), + UnityEngine::Vector4::op_Implicit___UnityEngine__Vector4(screenNormalizedPosition), + UnityEngine::Vector4::op_Implicit___UnityEngine__Vector4(UnityEngine::Vector2(canvasRadius, 0)), + UnityEngine::Vector4::op_Implicit___UnityEngine__Vector4(node.position), UnityEngine::Vector3{1,1,1}, UnityEngine::Vector4{1,1,1,1} ); diff --git a/src/UI/ScoreDetails/AccuracyGraph/GraphSplineSegment.cpp b/src/UI/ScoreDetails/AccuracyGraph/GraphSplineSegment.cpp index e43f5a2..c663bbd 100644 --- a/src/UI/ScoreDetails/AccuracyGraph/GraphSplineSegment.cpp +++ b/src/UI/ScoreDetails/AccuracyGraph/GraphSplineSegment.cpp @@ -1,21 +1,21 @@ #include "include/UI/ScoreDetails/AccuracyGraph/GraphSplineSegment.hpp" BeatLeader::GraphSplineSegment::GraphSplineSegment(UnityEngine::Vector2 handleNodeA, UnityEngine::Vector2 handleNodeB, UnityEngine::Vector2 handleNodeC) noexcept { - p00 = (handleNodeA + handleNodeB) / 2.0f; + p00 = UnityEngine::Vector2::op_Division(UnityEngine::Vector2::op_Addition(handleNodeA, handleNodeB), 2.0f); p01 = handleNodeB; - auto p02 = (handleNodeB + handleNodeC) / 2.0f; - v00 = p01 - p00; - v01 = p02 - p01; + auto p02 = UnityEngine::Vector2::op_Division(UnityEngine::Vector2::op_Addition(handleNodeB, handleNodeC), 2.0f); + v00 = UnityEngine::Vector2::op_Subtraction(p01, p00); + v01 = UnityEngine::Vector2::op_Subtraction(p02, p01); } BeatLeader::GraphSplineSegment::GraphSplineSegment() {} BeatLeader::GraphPoint BeatLeader::GraphSplineSegment::Evaluate(float t) { - p10 = p00 + v00 * t; - p11 = p01 + v01 * t; - v10 = p11 - p10; + p10 = UnityEngine::Vector2::op_Addition(p00, UnityEngine::Vector2::op_Multiply(v00, t)); + p11 = UnityEngine::Vector2::op_Addition(p01, UnityEngine::Vector2::op_Multiply(v01, t)); + v10 = UnityEngine::Vector2::op_Subtraction(p11, p10); return BeatLeader::GraphPoint( - p10 + v10 * t, + UnityEngine::Vector2::op_Addition(p10, UnityEngine::Vector2::op_Multiply(v10, t)), v10.get_normalized() ); } \ No newline at end of file diff --git a/src/UI/ScoreDetails/AccuracyGraph/ScreenViewTransfrom.cpp b/src/UI/ScoreDetails/AccuracyGraph/ScreenViewTransfrom.cpp index 145b271..1912bd0 100644 --- a/src/UI/ScoreDetails/AccuracyGraph/ScreenViewTransfrom.cpp +++ b/src/UI/ScoreDetails/AccuracyGraph/ScreenViewTransfrom.cpp @@ -46,7 +46,7 @@ UnityEngine::Vector2 BeatLeader::ScreenViewTransform::InverseTransformVector(Uni UnityEngine::Vector2 BeatLeader::ScreenViewTransform::FromToVector(UnityEngine::Rect const &from, UnityEngine::Rect const &to, UnityEngine::Vector2 const &vector) { auto scaleFn = [](auto&& r) constexpr {return Sombrero::FastVector2(r.m_Width, r.m_Height);}; auto scale = scaleFn(to) / scaleFn(from); - return vector * scale; + return Sombrero::FastVector2(vector) * scale; } UnityEngine::Vector2 BeatLeader::ScreenViewTransform::TransformDirection(UnityEngine::Vector2 const &screenDirection) const { diff --git a/src/UI/ScoreDetails/AdditionalScoreDetails.cpp b/src/UI/ScoreDetails/AdditionalScoreDetails.cpp index 971a6f9..e107a4f 100644 --- a/src/UI/ScoreDetails/AdditionalScoreDetails.cpp +++ b/src/UI/ScoreDetails/AdditionalScoreDetails.cpp @@ -2,6 +2,7 @@ #include "include/Utils/FormatUtils.hpp" #include "include/Utils/StringUtils.hpp" #include "include/UI/ScoreDetails/AdditionalScoreDetails.hpp" +#include "include/UI/QuestUI.hpp" #include "UnityEngine/Resources.hpp" #include "UnityEngine/Component.hpp" @@ -10,7 +11,7 @@ #include -using namespace QuestUI::BeatSaberUI; +using namespace QuestUI; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -54,10 +55,10 @@ string FormatPositions(string platform, ScoreStats scoreStats) { } void BeatLeader::AdditionalScoreDetails::setScore(string platform, ScoreStats score) const { - details->SetText(FormatDetails(platform, score)); + details->SetText(FormatDetails(platform, score), true); details->set_alignment(TMPro::TextAlignmentOptions::Center); - positions->SetText(FormatPositions(platform, score)); + positions->SetText(FormatPositions(platform, score), true); positions->set_alignment(TMPro::TextAlignmentOptions::Center); positionsTitle->set_alignment(TMPro::TextAlignmentOptions::Center); diff --git a/src/UI/ScoreDetails/GeneralScoreDetails.cpp b/src/UI/ScoreDetails/GeneralScoreDetails.cpp index 322ef7c..cdfd7ab 100644 --- a/src/UI/ScoreDetails/GeneralScoreDetails.cpp +++ b/src/UI/ScoreDetails/GeneralScoreDetails.cpp @@ -3,18 +3,19 @@ #include "include/Assets/Sprites.hpp" #include "include/UI/EmojiSupport.hpp" #include "include/UI/ScoreDetails/GeneralScoreDetails.hpp" +#include "include/UI/QuestUI.hpp" #include "UnityEngine/Resources.hpp" #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace QuestUI; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -106,14 +107,14 @@ inline string FormatPP(const Score& score) { } void BeatLeader::GeneralScoreDetails::setScore(const Score& score) const { - datePlayed->SetText(GetTimeSetString(score)); + datePlayed->SetText(GetTimeSetString(score), true); datePlayed->set_alignment(TMPro::TextAlignmentOptions::Center); - modifiedScore->SetText(GetStringWithLabel(FormatScore(score), "score")); - accuracy->SetText(GetStringWithLabel(FormatAcc(score), "accuracy")); - scorePp->SetText(GetStringWithLabel(FormatPP(score), "pp")); + modifiedScore->SetText(GetStringWithLabel(FormatScore(score), "score"), true); + accuracy->SetText(GetStringWithLabel(FormatAcc(score), "accuracy"), true); + scorePp->SetText(GetStringWithLabel(FormatPP(score), "pp"), true); - scoreDetails->SetText(GetDetailsString(score)); + scoreDetails->SetText(GetDetailsString(score), true); } void BeatLeader::GeneralScoreDetails::setSelected(bool selected) const { diff --git a/src/UI/ScoreDetails/MiniProfileButton.cpp b/src/UI/ScoreDetails/MiniProfileButton.cpp index 685fe07..2955fd4 100644 --- a/src/UI/ScoreDetails/MiniProfileButton.cpp +++ b/src/UI/ScoreDetails/MiniProfileButton.cpp @@ -20,7 +20,7 @@ #include -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -32,11 +32,11 @@ BeatLeader::MiniProfileButton::MiniProfileButton( string hint, UnityEngine::Color glowColor, bool labelOnLeft, - QuestUI::ClickableImage* button) noexcept { + BSML::ClickableImage* button) noexcept { this->button = button; this->glowColor = glowColor; this->labelOnLeft = labelOnLeft; - this->hint = ::QuestUI::BeatSaberUI::AddHoverHint(button, hint); + this->hint = ::BSML::Lite::AddHoverHint(button, hint); // this->hint = CreateText(button->get_transform(), hint, UnityEngine::Vector2(0.0, 0.0)); // UpdateLabelOffset(); } diff --git a/src/UI/ScoreDetails/PlayerButtons.cpp b/src/UI/ScoreDetails/PlayerButtons.cpp index 11e4f1e..aedcf1f 100644 --- a/src/UI/ScoreDetails/PlayerButtons.cpp +++ b/src/UI/ScoreDetails/PlayerButtons.cpp @@ -20,13 +20,14 @@ #include "HMUI/CurvedCanvasSettingsHelper.hpp" #include "HMUI/CurvedCanvasSettings.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -62,58 +63,70 @@ void BeatLeader::PlayerButtons::Setup(HMUI::ModalView *modal, functionget_transform(); auto rightTransform = rightBackground->get_transform(); - friendsButton = MiniProfileButton("Friends management", SelectedColor, true, ::QuestUI::BeatSaberUI::CreateClickableImage( + friendsButton = MiniProfileButton("Friends management", SelectedColor, true, ::BSML::Lite::CreateClickableImage( leftTransform, BundleLoader::bundle->friendsIcon, + [captureSelf](){ + captureSelf->toggleFriend(); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->toggleFriend(); - })); + {4, 4} + )); friendsButton.RegisterCallback(); - incognitoButton = MiniProfileButton("Hide player info", SelectedColor, true, ::QuestUI::BeatSaberUI::CreateClickableImage( + incognitoButton = MiniProfileButton("Hide player info", SelectedColor, true, ::BSML::Lite::CreateClickableImage( leftTransform, BundleLoader::bundle->incognitoIcon, + [captureSelf](){ + captureSelf->toggleBlacklist(); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->toggleBlacklist(); - })); + {4, 4} + )); incognitoButton.RegisterCallback(); - linkButton = MiniProfileButton("Open profile", SelectedColor, true, ::QuestUI::BeatSaberUI::CreateClickableImage( + linkButton = MiniProfileButton("Open profile", SelectedColor, true, ::BSML::Lite::CreateClickableImage( leftTransform, BundleLoader::bundle->profileIcon, + [captureSelf](){ + captureSelf->openProfile(); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->openProfile(); - })); + {4, 4} + )); linkButton.RegisterCallback(); - twitterButton = MiniProfileButton("Twitter", TwitterColor, false, ::QuestUI::BeatSaberUI::CreateClickableImage( + twitterButton = MiniProfileButton("Twitter", TwitterColor, false, ::BSML::Lite::CreateClickableImage( rightTransform, BundleLoader::bundle->twitterIcon, + [captureSelf](){ + captureSelf->openSocial("Twitter"); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->openSocial("Twitter"); - })); + {4, 4} + )); twitterButton.RegisterCallback(); - twitchButton = MiniProfileButton("Twitch", TwitchColor, false, ::QuestUI::BeatSaberUI::CreateClickableImage( + twitchButton = MiniProfileButton("Twitch", TwitchColor, false, ::BSML::Lite::CreateClickableImage( rightTransform, BundleLoader::bundle->twitchIcon, + [captureSelf](){ + captureSelf->openSocial("Twitch"); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->openSocial("Twitch"); - })); + {4, 4} + )); twitchButton.RegisterCallback(); - youtubeButton = MiniProfileButton("YouTube", YoutubeColor, false, ::QuestUI::BeatSaberUI::CreateClickableImage( + youtubeButton = MiniProfileButton("YouTube", YoutubeColor, false, ::BSML::Lite::CreateClickableImage( rightTransform, BundleLoader::bundle->youtubeIcon, + [captureSelf](){ + captureSelf->openSocial("YouTube"); + }, {0, 0}, - {4, 4}, [captureSelf](){ - captureSelf->openSocial("YouTube"); - })); + {4, 4} + )); youtubeButton.RegisterCallback(); UpdateLayout(); @@ -135,22 +148,26 @@ void BeatLeader::PlayerButtons::toggleFriend() const { auto captureSelf = this; if (!PlayerController::IsFriend(player)) { WebUtils::RequestAsync(WebUtils::API_URL + "user/friend?playerId=" + player.id, "POST", 60, [captureSelf](long status, string response) { - captureSelf->friendsButton.setHint("Remove friend"); - captureSelf->friendsButton.setState(MiniProfileButtonState::InteractableGlowing); - PlayerController::currentPlayer->friends.push_back(player.id); + BSML::MainThreadScheduler::Schedule([captureSelf] { + captureSelf->friendsButton.setHint("Remove friend"); + captureSelf->friendsButton.setState(MiniProfileButtonState::InteractableGlowing); + PlayerController::currentPlayer->friends.push_back(player.id); + }); }); } else { WebUtils::RequestAsync(WebUtils::API_URL + "user/friend?playerId=" + player.id, "DELETE", 60, [captureSelf](long status, string response) { - captureSelf->friendsButton.setHint("Add friend"); - captureSelf->friendsButton.setState(MiniProfileButtonState::InteractableFaded); - auto iterator = PlayerController::currentPlayer->friends.begin(); - - for (auto it = PlayerController::currentPlayer->friends.begin(); it != PlayerController::currentPlayer->friends.end(); it++) { - if (*it == player.id) { - PlayerController::currentPlayer->friends.erase(it); - break; + BSML::MainThreadScheduler::Schedule([captureSelf] { + captureSelf->friendsButton.setHint("Add friend"); + captureSelf->friendsButton.setState(MiniProfileButtonState::InteractableFaded); + auto iterator = PlayerController::currentPlayer->friends.begin(); + + for (auto it = PlayerController::currentPlayer->friends.begin(); it != PlayerController::currentPlayer->friends.end(); it++) { + if (*it == player.id) { + PlayerController::currentPlayer->friends.erase(it); + break; + } } - } + }); }); } } @@ -189,30 +206,20 @@ void BeatLeader::PlayerButtons::updateFriendButton() const { } void BeatLeader::PlayerButtons::updateSocialButtons() const { - if (!PlayerController::IsPatron(player)) { - twitterButton.setState(MiniProfileButtonState::Hidden); - twitchButton.setState(MiniProfileButtonState::Hidden); - youtubeButton.setState(MiniProfileButtonState::Hidden); + twitterButton.setState(MiniProfileButtonState::NonInteractable); + twitchButton.setState(MiniProfileButtonState::NonInteractable); + youtubeButton.setState(MiniProfileButtonState::NonInteractable); - rightBackground->get_gameObject()->SetActive(false); - } else { - twitterButton.setState(MiniProfileButtonState::NonInteractable); - twitchButton.setState(MiniProfileButtonState::NonInteractable); - youtubeButton.setState(MiniProfileButtonState::NonInteractable); - - rightBackground->get_gameObject()->SetActive(false); - - for (size_t i = 0; i < player.socials.size(); i++) - { - if (player.socials[i].service == "Twitter") { - twitterButton.setState(MiniProfileButtonState::InteractableGlowing); - } - if (player.socials[i].service == "Twitch") { - twitchButton.setState(MiniProfileButtonState::InteractableGlowing); - } - if (player.socials[i].service == "YouTube") { - youtubeButton.setState(MiniProfileButtonState::InteractableGlowing); - } + for (size_t i = 0; i < player.socials.size(); i++) + { + if (player.socials[i].service == "Twitter") { + twitterButton.setState(MiniProfileButtonState::InteractableGlowing); + } + if (player.socials[i].service == "Twitch") { + twitchButton.setState(MiniProfileButtonState::InteractableGlowing); + } + if (player.socials[i].service == "YouTube") { + youtubeButton.setState(MiniProfileButtonState::InteractableGlowing); } } } @@ -237,12 +244,14 @@ void BeatLeader::PlayerButtons::openSocial(string name) const { } } - UnityEngine::Application::OpenURL(social.link); + static auto UnityEngine_Application_OpenURL = il2cpp_utils::resolve_icall("UnityEngine.Application::OpenURL"); + UnityEngine_Application_OpenURL(social.link); } void BeatLeader::PlayerButtons::openProfile() const { string url = WebUtils::WEB_URL + "u/" + player.id; - UnityEngine::Application::OpenURL(url); + static auto UnityEngine_Application_OpenURL = il2cpp_utils::resolve_icall("UnityEngine.Application::OpenURL"); + UnityEngine_Application_OpenURL(url); } const float Deg2Rad = 0.017f; diff --git a/src/UI/ScoreDetails/ScoreDetailsUI.cpp b/src/UI/ScoreDetails/ScoreDetailsUI.cpp index edc0c6c..bc637b7 100644 --- a/src/UI/ScoreDetails/ScoreDetailsUI.cpp +++ b/src/UI/ScoreDetails/ScoreDetailsUI.cpp @@ -9,6 +9,7 @@ #include "include/UI/EmojiSupport.hpp" #include "include/UI/UIUtils.hpp" #include "include/UI/Themes/ThemeUtils.hpp" +#include "include/UI/QuestUI.hpp" #include "include/Core/ReplayPlayer.hpp" @@ -16,15 +17,15 @@ #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" #include "main.hpp" #include #include -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace GlobalNamespace; static UnityEngine::Color SelectedColor = UnityEngine::Color(0.0f, 0.4f, 1.0f, 1.0f); @@ -57,12 +58,12 @@ void BeatLeader::initScoreDetailsPopup( UnityEngine::GameObject::Destroy(modalUI->modal->get_gameObject()); } if (modalUI == nullptr) modalUI = (BeatLeader::ScoreDetailsPopup*) malloc(sizeof(BeatLeader::ScoreDetailsPopup)); - modalUI->modal = CreateModal(parent, UnityEngine::Vector2(60, 90), [](HMUI::ModalView *modal) {}, true); + modalUI->modal = QuestUI::CreateModal(parent, UnityEngine::Vector2(60, 90), {}, nullptr, true); MakeModalTransparent(modalUI->modal); auto modalTransform = modalUI->modal->get_transform(); - auto playerAvatarImage = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, NULL, UnityEngine::Vector2(0, 30), UnityEngine::Vector2(24, 24)); + auto playerAvatarImage = ::BSML::Lite::CreateImage(modalTransform, NULL, UnityEngine::Vector2(0, 30), UnityEngine::Vector2(24, 24)); modalUI->playerAvatar = playerAvatarImage->get_gameObject()->AddComponent(); modalUI->playerAvatar->Init(playerAvatarImage); @@ -73,13 +74,14 @@ void BeatLeader::initScoreDetailsPopup( UIUtils::CreateRoundRectImage(modalTransform, UnityEngine::Vector2(-24.5, -24), UnityEngine::Vector2(7, 7)); } - modalUI->rank = CreateText(modalTransform, "", UnityEngine::Vector2(6.0, 16.0)); - modalUI->name = CreateText(modalTransform, "", UnityEngine::Vector2(0.0, 18.0)); - modalUI->sponsorMessage = CreateText(modalTransform, "", UnityEngine::Vector2(0, -32)); + modalUI->rank = QuestUI::CreateText(modalTransform, "", UnityEngine::Vector2(6.0, 16.0)); + modalUI->name = QuestUI::CreateText(modalTransform, "", UnityEngine::Vector2(0.0, 18.0)); + modalUI->sponsorMessage = QuestUI::CreateText(modalTransform, "", UnityEngine::Vector2(0, -32)); EmojiSupport::AddSupport(modalUI->name); + EmojiSupport::AddSupport(modalUI->sponsorMessage); - modalUI->pp = CreateText(modalTransform, "", UnityEngine::Vector2(45.0, 16.0)); + modalUI->pp = QuestUI::CreateText(modalTransform, "", UnityEngine::Vector2(45.0, 16.0)); modalUI->playerButtons.Setup(modalUI->modal, [modalUI, incognitoCallback](Player player) { modalUI->updatePlayerDetails(player); @@ -91,41 +93,41 @@ void BeatLeader::initScoreDetailsPopup( modalUI->grid = ScoreStatsGrid(modalUI->modal); modalUI->graph = ScoreStatsGraph(modalUI->modal); - modalUI->generalButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->overview1Icon, UnityEngine::Vector2(-14, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->generalButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->overview1Icon, [modalUI](){ modalUI->selectTab(0); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->generalButton, "General score info"); + }, UnityEngine::Vector2(-14, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->generalButton, "General score info"); - modalUI->additionalButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->overview2Icon, UnityEngine::Vector2(-7, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->additionalButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->overview2Icon, [modalUI](){ modalUI->selectTab(1); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->generalButton, "General score info"); + }, UnityEngine::Vector2(-7, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->generalButton, "General score info"); - modalUI->overviewButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->detailsIcon, UnityEngine::Vector2(0, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->overviewButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->detailsIcon, [modalUI](){ modalUI->selectTab(2); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->overviewButton, "Detailed score info"); + }, UnityEngine::Vector2(0, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->overviewButton, "Detailed score info"); - modalUI->gridButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->gridIcon, UnityEngine::Vector2(7, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->gridButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->gridIcon, [modalUI](){ modalUI->selectTab(3); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->gridButton, "Note accuracy distribution"); + }, UnityEngine::Vector2(7, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->gridButton, "Note accuracy distribution"); - modalUI->graphButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->graphIcon, UnityEngine::Vector2(14, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->graphButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->graphIcon, [modalUI](){ modalUI->selectTab(4); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->graphButton, "Accuracy timeline graph"); + }, UnityEngine::Vector2(14, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->graphButton, "Accuracy timeline graph"); if (ReplayInstalled()) { - modalUI->replayButton = ::QuestUI::BeatSaberUI::CreateClickableImage(modalTransform, BundleLoader::bundle->replayIcon, UnityEngine::Vector2(-24.5, -24), UnityEngine::Vector2(5, 5), [modalUI](){ + modalUI->replayButton = ::BSML::Lite::CreateClickableImage(modalTransform, BundleLoader::bundle->replayIcon, [modalUI](){ modalUI->playReplay(); - }); - ::QuestUI::BeatSaberUI::AddHoverHint(modalUI->replayButton, "Watch the replay"); + }, UnityEngine::Vector2(-24.5, -24), UnityEngine::Vector2(5, 5)); + ::BSML::Lite::AddHoverHint(modalUI->replayButton, "Watch the replay"); } modalUI->setButtonsMaterial(); - modalUI->loadingText = CreateText(modalTransform, "Loading...", UnityEngine::Vector2(0.0, 0.0)); + modalUI->loadingText = QuestUI::CreateText(modalTransform, "Loading...", UnityEngine::Vector2(0.0, 0.0)); modalUI->loadingText->set_alignment(TMPro::TextAlignmentOptions::Center); modalUI->loadingText->get_gameObject()->SetActive(false); @@ -135,11 +137,11 @@ void BeatLeader::initScoreDetailsPopup( void BeatLeader::ScoreDetailsPopup::updatePlayerDetails(Player player) { if (!PlayerController::IsIncognito(player)) { - name->SetText(FormatUtils::FormatNameWithClans(player, 20, true)); + name->SetText(FormatUtils::FormatNameWithClans(player, 20, true), true); auto params = GetAvatarParams(player, false); - playerAvatar->SetPlayer(player.avatar, params.baseMaterial, params.hueShift, params.saturation); + playerAvatar->SetPlayer(player.avatar, BundleLoader::bundle->defaultAvatarMaterial, params.hueShift, params.saturation); } else { - name->SetText("[REDACTED]"); + name->SetText("[REDACTED]", true); playerAvatar->SetHiddenPlayer(); } } @@ -152,13 +154,13 @@ void BeatLeader::ScoreDetailsPopup::setScore(const Score& score) { updatePlayerDetails(score.player); name->set_alignment(TMPro::TextAlignmentOptions::Center); - rank->SetText(FormatUtils::FormatRank(score.player.rank, true)); - pp->SetText(FormatUtils::FormatPP(score.player.pp)); + rank->SetText(FormatUtils::FormatRank(score.player.rank, true), true); + pp->SetText(FormatUtils::FormatPP(score.player.pp), true); if (score.player.profileSettings != nullopt) { - sponsorMessage->SetText(score.player.profileSettings->message); + sponsorMessage->SetText(score.player.profileSettings->message, true); } else { - sponsorMessage->SetText(""); + sponsorMessage->SetText("", true); } sponsorMessage->set_alignment(TMPro::TextAlignmentOptions::Center); @@ -171,7 +173,7 @@ void BeatLeader::ScoreDetailsPopup::setScore(const Score& score) { selectTab(0); } -void selectButton(QuestUI::ClickableImage* button, bool selected) { +void selectButton(BSML::ClickableImage* button, bool selected) { button->set_defaultColor(selected ? SelectedColor : FadedColor); button->set_highlightColor(selected ? SelectedColor : FadedHoverColor); } @@ -228,20 +230,20 @@ void BeatLeader::ScoreDetailsPopup::selectTab(int index) { if (index > 0 && !scoreStatsFetched) { auto self = this; - self->loadingText->SetText("Loading..."); + self->loadingText->SetText("Loading...", true); loadingText->get_gameObject()->SetActive(true); string url = WebUtils::API_URL + "score/statistic/" + to_string(scoreId); WebUtils::GetJSONAsync(url, [self, index](long status, bool error, rapidjson::Document const& result) { if (status == 200 && !error && result.HasMember("scoreGraphTracker")) { scoreStats = ScoreStats(result); - QuestUI::MainThreadScheduler::Schedule([self, index] { + BSML::MainThreadScheduler::Schedule([self, index] { self->scoreStatsFetched = true; self->loadingText->get_gameObject()->SetActive(false); self->selectTab(index); }); } else { - QuestUI::MainThreadScheduler::Schedule([self] { - self->loadingText->SetText("Failed to fetch stats"); + BSML::MainThreadScheduler::Schedule([self] { + self->loadingText->SetText("Failed to fetch stats", true); }); } @@ -279,22 +281,22 @@ void BeatLeader::ScoreDetailsPopup::playReplay() { WebUtils::GetAsyncFile(replayLink, file, 64, [file, self](long httpCode) { if (httpCode == 200) { - QuestUI::MainThreadScheduler::Schedule([file, self] { + BSML::MainThreadScheduler::Schedule([file, self] { if ((getModConfig().ShowReplaySettings.GetValue() && PlayReplayFromFile(file)) || (!getModConfig().ShowReplaySettings.GetValue() && PlayReplayFromFileWithoutSettings(file))) { self->modal->Hide(true, nullptr); ReplayManager::lastReplayFilename = file; } else { - self->loadingText->SetText("Failed to parse the replay"); + self->loadingText->SetText("Failed to parse the replay", true); } }); } else { - QuestUI::MainThreadScheduler::Schedule([file, self] { - self->loadingText->SetText("Failed to download the replay"); + BSML::MainThreadScheduler::Schedule([file, self] { + self->loadingText->SetText("Failed to download the replay", true); }); } }, [self](float progress) { - QuestUI::MainThreadScheduler::Schedule([progress, self] { - self->loadingText->SetText("Downloading: " + to_string_wprecision(progress, 2) + "%"); + BSML::MainThreadScheduler::Schedule([progress, self] { + self->loadingText->SetText("Downloading: " + to_string_wprecision(progress, 2) + "%", true); }); }); WebUtils::GetAsync(WebUtils::API_URL + "/watched/" + to_string(scoreId), [](long code, string result) {}); diff --git a/src/UI/ScoreDetails/ScoreStatsGraph.cpp b/src/UI/ScoreDetails/ScoreStatsGraph.cpp index 5adce5e..79803c0 100644 --- a/src/UI/ScoreDetails/ScoreStatsGraph.cpp +++ b/src/UI/ScoreDetails/ScoreStatsGraph.cpp @@ -3,6 +3,7 @@ #include "include/Assets/Sprites.hpp" #include "include/Assets/BundleLoader.hpp" #include "include/UI/EmojiSupport.hpp" +#include "include/UI/QuestUI.hpp" #include "include/UI/ScoreDetails/ScoreStatsGraph.hpp" #include "include/UI/ScoreDetails/AccuracyGraph/AccuracyGraphUtils.hpp" @@ -17,13 +18,13 @@ #include "HMUI/CurvedCanvasSettingsHelper.hpp" #include "HMUI/CurvedCanvasSettings.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace QuestUI; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -36,7 +37,7 @@ BeatLeader::ScoreStatsGraph::ScoreStatsGraph(HMUI::ModalView *modal) noexcept { auto graphLine = gameObj->AddComponent(); graphLine->get_transform()->SetParent(graphBackground->get_transform(), false); - RectTransform* rectTransform = (RectTransform*)graphLine->get_transform(); + RectTransform* rectTransform = graphLine->get_transform().cast(); rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchoredPosition(UnityEngine::Vector2(0, 0)); diff --git a/src/UI/ScoreDetails/ScoreStatsGrid.cpp b/src/UI/ScoreDetails/ScoreStatsGrid.cpp index 5b207f5..35611c9 100644 --- a/src/UI/ScoreDetails/ScoreStatsGrid.cpp +++ b/src/UI/ScoreDetails/ScoreStatsGrid.cpp @@ -8,13 +8,13 @@ #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; diff --git a/src/UI/ScoreDetails/ScoreStatsGridCell.cpp b/src/UI/ScoreDetails/ScoreStatsGridCell.cpp index 3b84629..2763eab 100644 --- a/src/UI/ScoreDetails/ScoreStatsGridCell.cpp +++ b/src/UI/ScoreDetails/ScoreStatsGridCell.cpp @@ -4,18 +4,19 @@ #include "include/Assets/BundleLoader.hpp" #include "include/UI/EmojiSupport.hpp" #include "include/UI/ScoreDetails/ScoreStatsGrid.hpp" +#include "include/UI/QuestUI.hpp" #include "UnityEngine/Resources.hpp" #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace QuestUI; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -30,16 +31,17 @@ BeatLeader::ScoreStatsGridCell::ScoreStatsGridCell(HMUI::ModalView *modal, int i float row = (float)(index / 4); float column = (float)(index % 4); - imageView = ::QuestUI::BeatSaberUI::CreateImage(modal->get_transform(), Sprites::get_TransparentPixel(), UnityEngine::Vector2((column - 2.0f) * 11.0 + 5, (row - 1.0f) * 11.0 - 3), UnityEngine::Vector2(10, 10)); + imageView = QuestUI::CreateImage(modal->get_transform(), Sprites::get_TransparentPixel(), UnityEngine::Vector2((column - 2.0f) * 11.0 + 5, (row - 1.0f) * 11.0 - 3), UnityEngine::Vector2(10, 10)); imageView->set_color(EmptyColor); - imageView->set_material(UnityEngine::Object::Instantiate(BundleLoader::bundle->accGridBackgroundMaterial)); + auto backgroundMaterial = UnityEngine::Object::Instantiate(BundleLoader::bundle->accGridBackgroundMaterial); + imageView->set_material(backgroundMaterial); scoreText = CreateText(modal->get_transform(), "", UnityEngine::Vector2((column - 2.0f) * 11.0 + 32, (row - 1.0f) * 11.0 - 6)); scoreText->set_fontSize(3); } void BeatLeader::ScoreStatsGridCell::setScore(float score, float ratio) const { - scoreText->SetText(to_string_wprecision(score, 2)); + scoreText->SetText(to_string_wprecision(score, 2), true); imageView->set_color(UnityEngine::Color::Lerp(BadColor, GoodColor, ratio * ratio)); } diff --git a/src/UI/ScoreDetails/ScoreStatsOverview.cpp b/src/UI/ScoreDetails/ScoreStatsOverview.cpp index bf55851..7311773 100644 --- a/src/UI/ScoreDetails/ScoreStatsOverview.cpp +++ b/src/UI/ScoreDetails/ScoreStatsOverview.cpp @@ -4,18 +4,19 @@ #include "include/Assets/BundleLoader.hpp" #include "include/UI/EmojiSupport.hpp" #include "include/UI/ScoreDetails/ScoreStatsOverview.hpp" +#include "include/UI/QuestUI.hpp" #include "UnityEngine/Resources.hpp" #include "HMUI/ImageView.hpp" #include "UnityEngine/Component.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" #include -using namespace QuestUI::BeatSaberUI; +using namespace QuestUI; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -33,12 +34,12 @@ BeatLeader::ScoreStatsOverview::ScoreStatsOverview(HMUI::ModalView *modal) noexc leftPostScore = CreateText(modalTransform, "", UnityEngine::Vector2(3.0, -1.0)); leftScore = CreateText(modalTransform, "", UnityEngine::Vector2(-9.0, 5.8)); - leftPieImage = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(-9, 6), UnityEngine::Vector2(14, 14)); + leftPieImage = ::BSML::Lite::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(-9, 6), UnityEngine::Vector2(14, 14)); leftPieImage->set_color(UnityEngine::Color(0.8f, 0.2f, 0.2f, 0.1f)); leftPieImage->set_material(UnityEngine::Object::Instantiate(BundleLoader::bundle->handAccIndicatorMaterial)); rightScore = CreateText(modalTransform, "", UnityEngine::Vector2(9.0, 5.8)); - rightPieImage = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(9, 6), UnityEngine::Vector2(14, 14)); + rightPieImage = ::BSML::Lite::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(9, 6), UnityEngine::Vector2(14, 14)); rightPieImage->set_color(UnityEngine::Color(0.2f, 0.2f, 0.8f, 0.1f)); rightPieImage->set_material(UnityEngine::Object::Instantiate(BundleLoader::bundle->handAccIndicatorMaterial)); @@ -50,15 +51,15 @@ BeatLeader::ScoreStatsOverview::ScoreStatsOverview(HMUI::ModalView *modal) noexc tdTitle = CreateText(modalTransform, "TD", UnityEngine::Vector2(0.0, -5.0)); tdTitle->set_alignment(TMPro::TextAlignmentOptions::Center); - tdBackground = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -9), UnityEngine::Vector2(50, 5)); + tdBackground = ::BSML::Lite::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -9), UnityEngine::Vector2(50, 5)); tdBackground->set_material(BundleLoader::bundle->accDetailsRowMaterial); preTitle = CreateText(modalTransform, "Pre", UnityEngine::Vector2(0.0, -10.0)); preTitle->set_alignment(TMPro::TextAlignmentOptions::Center); - preBackground = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -14), UnityEngine::Vector2(50, 5)); + preBackground = ::BSML::Lite::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -14), UnityEngine::Vector2(50, 5)); preBackground->set_material(BundleLoader::bundle->accDetailsRowMaterial); postTitle = CreateText(modalTransform, "Post", UnityEngine::Vector2(0.0, -15.0)); postTitle->set_alignment(TMPro::TextAlignmentOptions::Center); - postBackground = ::QuestUI::BeatSaberUI::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -19), UnityEngine::Vector2(50, 5)); + postBackground = ::BSML::Lite::CreateImage(modalTransform, Sprites::get_TransparentPixel(), UnityEngine::Vector2(0, -19), UnityEngine::Vector2(50, 5)); postBackground->set_material(BundleLoader::bundle->accDetailsRowMaterial); leftTd = CreateText(modalTransform, "", UnityEngine::Vector2(5.0, -7.0)); @@ -77,34 +78,34 @@ static float CalculateFillValue(float score) { } void BeatLeader::ScoreStatsOverview::setScore(ScoreStats score) const { - leftPreScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[0], 2)); - leftAccScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[1], 2)); - leftPostScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[2], 2)); + leftPreScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[0], 2), true); + leftAccScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[1], 2), true); + leftPostScore->SetText(to_string_wprecision(score.accuracyTracker.leftAverageCut[2], 2), true); - leftScore->SetText(to_string_wprecision(score.accuracyTracker.accLeft, 2)); + leftScore->SetText(to_string_wprecision(score.accuracyTracker.accLeft, 2), true); leftScore->set_alignment(TMPro::TextAlignmentOptions::Center); leftPieImage->get_material()->SetFloat(FillPropertyId, CalculateFillValue(score.accuracyTracker.accLeft)); - rightScore->SetText(to_string_wprecision(score.accuracyTracker.accRight, 2)); + rightScore->SetText(to_string_wprecision(score.accuracyTracker.accRight, 2), true); rightScore->set_alignment(TMPro::TextAlignmentOptions::Center); rightPieImage->get_material()->SetFloat(FillPropertyId, CalculateFillValue(score.accuracyTracker.accRight)); - rightPreScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[0], 2)); + rightPreScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[0], 2), true); rightPreScore->set_alignment(TMPro::TextAlignmentOptions::Right); - rightAccScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[1], 2)); + rightAccScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[1], 2), true); rightAccScore->set_alignment(TMPro::TextAlignmentOptions::Right); - rightPostScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[2], 2)); + rightPostScore->SetText(to_string_wprecision(score.accuracyTracker.rightAverageCut[2], 2), true); rightPostScore->set_alignment(TMPro::TextAlignmentOptions::Right); - leftTd->SetText(to_string_wprecision(score.accuracyTracker.leftTimeDependence, 3)); - leftPre->SetText(to_string_wprecision(score.accuracyTracker.leftPreswing * 100.0, 2) + "%"); - leftPost->SetText(to_string_wprecision(score.accuracyTracker.leftPostswing * 100.0, 2) + "%"); + leftTd->SetText(to_string_wprecision(score.accuracyTracker.leftTimeDependence, 3), true); + leftPre->SetText(to_string_wprecision(score.accuracyTracker.leftPreswing * 100.0, 2) + "%", true); + leftPost->SetText(to_string_wprecision(score.accuracyTracker.leftPostswing * 100.0, 2) + "%", true); - rightTd->SetText(to_string_wprecision(score.accuracyTracker.rightTimeDependence, 3)); + rightTd->SetText(to_string_wprecision(score.accuracyTracker.rightTimeDependence, 3), true); rightTd->set_alignment(TMPro::TextAlignmentOptions::Right); - rightPre->SetText(to_string_wprecision(score.accuracyTracker.rightPreswing * 100.0, 2) + "%"); + rightPre->SetText(to_string_wprecision(score.accuracyTracker.rightPreswing * 100.0, 2) + "%", true); rightPre->set_alignment(TMPro::TextAlignmentOptions::Right); - rightPost->SetText(to_string_wprecision(score.accuracyTracker.rightPostswing * 100.0, 2) + "%"); + rightPost->SetText(to_string_wprecision(score.accuracyTracker.rightPostswing * 100.0, 2) + "%", true); rightPost->set_alignment(TMPro::TextAlignmentOptions::Right); } diff --git a/src/UI/UIUtils.cpp b/src/UI/UIUtils.cpp index 3a6a25e..654db1c 100644 --- a/src/UI/UIUtils.cpp +++ b/src/UI/UIUtils.cpp @@ -11,27 +11,15 @@ #include "main.hpp" -#include "UnityEngine/HideFlags.hpp" +#include "bsml/shared/BSML/Settings/BSMLSettings.hpp" +#include "bsml/shared/BSML/Settings/SettingsMenu.hpp" +#include "bsml/shared/BSML/Settings/UI/ModSettingsFlowCoordinator.hpp" +#include "bsml/shared/BSML/Settings/UI/SettingsMenuListViewController.hpp" +#include "bsml/shared/Helpers/delegates.hpp" +#include "bsml/shared/Helpers/getters.hpp" -// Access into internal QuestUI structures -namespace QuestUI::ModSettingsInfos { - struct ModSettingsInfo { - ModInfo modInfo; - bool showModInfo; - std::string title; - Register::Type type; - System::Type* il2cpp_type; - union { - HMUI::ViewController* viewController; - HMUI::FlowCoordinator* flowCoordinator; - }; - Register::DidActivateEvent didActivateEvent; - Register::MenuLocation location; - void Present(); - }; - - std::vector& get(); -} +#include "UnityEngine/HideFlags.hpp" +#include "UnityEngine/Resources.hpp" namespace UIUtils { @@ -39,7 +27,7 @@ namespace UIUtils { HMUI::ImageView* getRoundRectSprite() { if (!roundRectSprite) { - roundRectSprite = QuestUI::ArrayUtil::First(UnityEngine::Resources::FindObjectsOfTypeAll(), [](HMUI::ImageView* image){ + roundRectSprite = UnityEngine::Resources::FindObjectsOfTypeAll()->First([](HMUI::ImageView* image){ auto sprite = image->get_sprite(); if (!sprite || sprite->get_name() != "RoundRect10") return false; @@ -88,12 +76,24 @@ namespace UIUtils { } void OpenSettings() { - // Get all of the mod settings infos, and get the one that is for beatleader - for (auto& s : QuestUI::ModSettingsInfos::get()) { - if (s.modInfo.id == MOD_ID) { - s.Present(); + auto modFC = BSML::BSMLSettings::get_instance()->get_modSettingsFlowCoordinator(); + modFC->isAnimating = true; + + auto fc = BSML::Helpers::GetMainFlowCoordinator()->YoungestChildFlowCoordinatorOrSelf(); + fc->PresentFlowCoordinator(modFC, BSML::MakeSystemAction([modFC]{ + int index = 0; + BSML::SettingsMenu* menu = NULL; + for (auto& s : BSML::BSMLSettings::get_instance()->get_settingsMenus()) { + if (s->text == "BeatLeader") { + menu = reinterpret_cast(s); + break; + } + index++; } - } + modFC->settingsMenuListViewController->list->tableView->SelectCellWithIdx(index, false); + modFC->OpenMenu(menu); + modFC->isAnimating = false; + }), HMUI::ViewController::AnimationDirection::Horizontal, false, false); } void AddRoundRect(HMUI::ImageView* background) { @@ -103,9 +103,9 @@ namespace UIUtils { background->set_color0(bgTemplate->get_color0()); background->set_color1(bgTemplate->get_color1()); background->set_gradient(bgTemplate->get_gradient()); - background->gradientDirection = bgTemplate->gradientDirection; - background->flipGradientColors = bgTemplate->flipGradientColors; - background->skew = bgTemplate->skew; + background->_gradientDirection = bgTemplate->_gradientDirection; + background->_flipGradientColors = bgTemplate->_flipGradientColors; + background->_skew = bgTemplate->_skew; background->set_eventAlphaThreshold(bgTemplate->get_eventAlphaThreshold()); background->set_fillAmount(bgTemplate->get_fillAmount()); background->set_fillCenter(bgTemplate->get_fillCenter()); @@ -130,7 +130,7 @@ namespace UIUtils { // Copied from BSML HMUI::ImageView* CreateRoundRectImage(UnityEngine::Transform* parent, UnityEngine::Vector2 anchoredPosition, UnityEngine::Vector2 sizeDelta) { - static ConstString name("QuestUIImage"); + static ConstString name("bsmlImage"); UnityEngine::GameObject* gameObj = UnityEngine::GameObject::New_ctor(name); HMUI::ImageView* background = gameObj->AddComponent();// GetCopyOf(, getRoundRectSprite()); AddRoundRect(background); @@ -138,7 +138,7 @@ namespace UIUtils { background->get_transform()->SetParent(parent, false); background->set_enabled(true); - UnityEngine::RectTransform* rectTransform = (UnityEngine::RectTransform*)background->get_transform(); + UnityEngine::RectTransform* rectTransform = background->get_transform().cast(); rectTransform->set_anchorMin(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchorMax(UnityEngine::Vector2(0.5f, 0.5f)); rectTransform->set_anchoredPosition(anchoredPosition); diff --git a/src/UI/VotingButton.cpp b/src/UI/VotingButton.cpp index e31dccb..3a2d668 100644 --- a/src/UI/VotingButton.cpp +++ b/src/UI/VotingButton.cpp @@ -1,7 +1,7 @@ #include "HMUI/Touchable.hpp" -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" +#include "bsml/shared/BSML.hpp" +#include "bsml/shared/BSML-Lite.hpp" #include "UnityEngine/Application.hpp" #include "UnityEngine/GUIUtility.hpp" @@ -15,7 +15,7 @@ #include #include -using namespace QuestUI; +using namespace BSML; using namespace std; DEFINE_TYPE(BeatLeader, VotingButton); @@ -25,7 +25,7 @@ static int GradientValuePropertyId; static int StatePropertyId; static int TintPropertyId; -void BeatLeader::VotingButton::Init(QuestUI::ClickableImage* imageView) { +void BeatLeader::VotingButton::Init(BSML::ClickableImage* imageView) { this->imageView = imageView; this->materialInstance = UnityEngine::Object::Instantiate(BundleLoader::bundle->VotingButtonMaterial); @@ -34,7 +34,7 @@ void BeatLeader::VotingButton::Init(QuestUI::ClickableImage* imageView) { imageView->set_defaultColor(UnityEngine::Color(0.0, 0.0, 0.0, 1.0)); imageView->set_highlightColor(UnityEngine::Color(1.0, 0.0, 0.0, 1.0)); - this->hoverHint = QuestUI::BeatSaberUI::AddHoverHint(imageView, "Rank voting"); + this->hoverHint = BSML::Lite::AddHoverHint(imageView, "Rank voting"); SpinnerValuePropertyId = UnityEngine::Shader::PropertyToID("_SpinnerValue"); GradientValuePropertyId = UnityEngine::Shader::PropertyToID("_GradientValue"); diff --git a/src/UI/VotingUI.cpp b/src/UI/VotingUI.cpp index 6008d85..f435990 100644 --- a/src/UI/VotingUI.cpp +++ b/src/UI/VotingUI.cpp @@ -1,4 +1,7 @@ #include "include/UI/VotingUI.hpp" +#include "UnityEngine/zzzz__Vector2_def.hpp" +#include "bsml/shared/BSML-Lite/Creation/Buttons.hpp" +#include "bsml/shared/BSML-Lite/Creation/Settings.hpp" #include "include/Utils/FormatUtils.hpp" #include "include/UI/UIUtils.hpp" #include "include/Assets/Sprites.hpp" @@ -12,15 +15,16 @@ #include "UnityEngine/Component.hpp" #include "UnityEngine/Color.hpp" #include "UnityEngine/UI/ColorBlock.hpp" -#include "UnityEngine/UI/Selectable_SelectionState.hpp" -#include "questui/shared/CustomTypes/Components/Backgroundable.hpp" +#include "bsml/shared/BSML/Components/Backgroundable.hpp" #include "main.hpp" +#include "UI/LinksContainer.hpp" + #include -using namespace QuestUI::BeatSaberUI; +using namespace BSML::Lite; using namespace UnityEngine; using namespace UnityEngine::UI; using namespace GlobalNamespace; @@ -28,16 +32,16 @@ using namespace GlobalNamespace; void setupButtonTitle(UnityEngine::UI::Button* button, float offset, int fontSize = 0) { UnityEngine::Object::Destroy(button->get_transform()->Find("Content")->GetComponent()); - auto title = button->GetComponentsInChildren()->get(0); + auto title = button->GetComponentsInChildren()[0]; if (fontSize > 0) { title->set_fontSize(fontSize); } - title->set_margin(offset); + title->set_margin(UnityEngine::Vector4(offset,offset,offset,offset)); } void setButtonTitleColor(UnityEngine::UI::Button* button, UnityEngine::Color32 const& color) { - auto title = button->GetComponentsInChildren()->get(0); + auto title = button->GetComponentsInChildren()[0]; title->SetFaceColor(color); } @@ -51,85 +55,103 @@ void BeatLeader::initVotingPopup( } if (modalUI == nullptr) modalUI = (BeatLeader::RankVotingPopup*) malloc(sizeof(BeatLeader::RankVotingPopup)); - auto container = CreateModal(parent, UnityEngine::Vector2(60, 30), [](HMUI::ModalView *modal) {}, true); + auto container = CreateModal(parent, UnityEngine::Vector2(60, 30), nullptr, true); modalUI->modal = container; auto containerTransform = container->get_transform(); // Page 1 - modalUI->header1 = CreateText(containerTransform, "Is this map suitable for rank?", UnityEngine::Vector2(4.0, 8.0)); - modalUI->yesButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "YES", UnityEngine::Vector2(-12.0, 3.0), [modalUI]() { - modalUI->rankable = true; - modalUI->rightButton->get_gameObject()->SetActive(true); + modalUI->header1 = CreateText(containerTransform, "Is this map suitable for rank?", UnityEngine::Vector2(-16.0, 10)); + modalUI->noButton = ::BSML::Lite::CreateUIButton(containerTransform, "NO", UnityEngine::Vector2(18.0, -13.0), [modalUI]() { + modalUI->rankable = false; + modalUI->rightButton->get_gameObject()->SetActive(false); modalUI->voteButton->set_interactable(true); - setButtonTitleColor(modalUI->noButton, UnityEngine::Color32(255, 255, 255, 255)); - setButtonTitleColor(modalUI->yesButton, UnityEngine::Color32(102, 255, 102, 255)); + setButtonTitleColor(modalUI->noButton, UnityEngine::Color32(0, 255, 102, 102, 255)); + setButtonTitleColor(modalUI->yesButton, UnityEngine::Color32(0, 255, 255, 255, 255)); }); - modalUI->noButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "NO", UnityEngine::Vector2(14.0, 3.0), [modalUI]() { - modalUI->rankable = false; - modalUI->rightButton->get_gameObject()->SetActive(false); + BeatLeader::SetButtonSize(modalUI->noButton, UnityEngine::Vector2(20.0, 10.0)); + SetButtonTextSize(modalUI->noButton, 6.0f); + + modalUI->yesButton = ::BSML::Lite::CreateUIButton(containerTransform, "YES", UnityEngine::Vector2(42.0, -13.0), [modalUI]() { + modalUI->rankable = true; + modalUI->rightButton->get_gameObject()->SetActive(true); modalUI->voteButton->set_interactable(true); - setButtonTitleColor(modalUI->noButton, UnityEngine::Color32(255, 102, 102, 255)); - setButtonTitleColor(modalUI->yesButton, UnityEngine::Color32(255, 255, 255, 255)); + setButtonTitleColor(modalUI->noButton, UnityEngine::Color32(0, 255, 255, 255, 255)); + setButtonTitleColor(modalUI->yesButton, UnityEngine::Color32(0, 102, 255, 102, 255)); }); + BeatLeader::SetButtonSize(modalUI->yesButton, UnityEngine::Vector2(20.0, 10.0)); + SetButtonTextSize(modalUI->yesButton, 6.0f); + // Page 2 - modalUI->header2 = CreateText(containerTransform, "Difficulty and category (optional)", UnityEngine::Vector2(4.0, 8.0)); + modalUI->header2 = CreateText(containerTransform, "Difficulty and category (optional)", UnityEngine::Vector2(-19.0, 14.0)); + modalUI->header2->color = UnityEngine::Color(0.667f, 0.667f, 0.667f, 1.0f); + modalUI->starSlider = CreateSliderSetting( modalUI->modal->get_transform(), - "Stars", - 0.1, + "", + 0.1f, + 0.0f, 0, - 1, 15, - UnityEngine::Vector2(4, -7), + 0.0f, + true, + UnityEngine::Vector2(4, -10), [modalUI](float stars) { modalUI->stars = stars; }); - move(modalUI->starSlider->slider, -7, 0); + modalUI->starSlider->formatter = [](float value) { + std::stringstream ss; + ss << std::fixed << std::setprecision(1) << value << "*"; + return value > 0.0f ? ss.str() : "Skip"; + }; + resize(modalUI->starSlider->slider, 4.5, 0); + move(modalUI->starSlider->slider, -4.5, 0); - modalUI->accButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "acc", UnityEngine::Vector2(-20, -3), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { + float typeButtonY = -17.0; + + modalUI->accButton = ::BSML::Lite::CreateUIButton(containerTransform, "acc", UnityEngine::Vector2(9, typeButtonY), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { modalUI->updateType(MapType::acc, modalUI->accButton); }); + BeatLeader::SetButtonSize(modalUI->accButton, UnityEngine::Vector2(12.0, 7.0)); setupButtonTitle(modalUI->accButton, -0.5, 3); - modalUI->techButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "tech", UnityEngine::Vector2(-6, -3), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { + modalUI->techButton = ::BSML::Lite::CreateUIButton(containerTransform, "tech", UnityEngine::Vector2(23, typeButtonY), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { modalUI->updateType(MapType::tech, modalUI->techButton); }); + BeatLeader::SetButtonSize(modalUI->techButton, UnityEngine::Vector2(12.0, 7.0)); setupButtonTitle(modalUI->techButton, -0.5, 3); - modalUI->midspeedButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "midspeed", UnityEngine::Vector2(8, -3), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { + modalUI->midspeedButton = ::BSML::Lite::CreateUIButton(containerTransform, "midspeed", UnityEngine::Vector2(37, typeButtonY), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { modalUI->updateType(MapType::midspeed, modalUI->midspeedButton); }); + BeatLeader::SetButtonSize(modalUI->midspeedButton, UnityEngine::Vector2(12.0, 7.0)); setupButtonTitle(modalUI->midspeedButton, -0.5, 3); - modalUI->speedButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "speed", UnityEngine::Vector2(22, -3), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { + modalUI->speedButton = ::BSML::Lite::CreateUIButton(containerTransform, "speed", UnityEngine::Vector2(51, typeButtonY), UnityEngine::Vector2(12.0, 6.0), [modalUI]() { modalUI->updateType(MapType::speed, modalUI->speedButton); }); + BeatLeader::SetButtonSize(modalUI->speedButton, UnityEngine::Vector2(12.0, 7.0)); setupButtonTitle(modalUI->speedButton, -0.5, 3); - modalUI->voteButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "Vote", UnityEngine::Vector2(-12.0, -10.0), UnityEngine::Vector2(15.0, 8.0), [callback, modalUI]() { + modalUI->voteButton = ::BSML::Lite::CreateUIButton(containerTransform, "Submit", UnityEngine::Vector2(30, -25.0), UnityEngine::Vector2(16.0, 3.0), [callback, modalUI]() { callback(true, modalUI->rankable, modalUI->stars, modalUI->type); }); + BeatLeader::SetButtonSize(modalUI->voteButton, UnityEngine::Vector2(16.0, 8.0)); setupButtonTitle(modalUI->voteButton, -0.5); - modalUI->cancelButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "Cancel", UnityEngine::Vector2(12.0, -10.0), UnityEngine::Vector2(15.0, 8.0), [callback]() { - callback(false, false, 0, 0); - }); - setupButtonTitle(modalUI->cancelButton, -0.5); - - modalUI->leftButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, "<", UnityEngine::Vector2(-27.0, -10.0), UnityEngine::Vector2(5.0, 6.0), [modalUI]() { + modalUI->leftButton = ::BSML::Lite::CreateUIButton(containerTransform, "◄", UnityEngine::Vector2(10, -25.0), UnityEngine::Vector2(5.0, 6.0), [modalUI]() { modalUI->left(); }); - setupButtonTitle(modalUI->leftButton, -1.5, 3); + BeatLeader::SetButtonSize(modalUI->leftButton, UnityEngine::Vector2(11.0, 8.0)); - modalUI->rightButton = ::QuestUI::BeatSaberUI::CreateUIButton(containerTransform, ">", UnityEngine::Vector2(26.0, -10.0), UnityEngine::Vector2(5.0, 6.0), [modalUI]() { + modalUI->rightButton = ::BSML::Lite::CreateUIButton(containerTransform, "►", UnityEngine::Vector2(10, -25.0), UnityEngine::Vector2(5.0, 6.0), [modalUI]() { modalUI->right(); }); - setupButtonTitle(modalUI->rightButton, -1.5, 3); + BeatLeader::SetButtonSize(modalUI->rightButton, UnityEngine::Vector2(11.0, 8.0)); modalUI->modal->set_name("BeatLeaderVotingModal"); *modalUIPointer = modalUI; @@ -144,13 +166,13 @@ void BeatLeader::RankVotingPopup::reset() { midspeedButton->get_gameObject()->SetActive(false); speedButton->get_gameObject()->SetActive(false); - setButtonTitleColor(accButton, UnityEngine::Color32(255, 255, 255, 255)); - setButtonTitleColor(techButton, UnityEngine::Color32(255, 255, 255, 255)); - setButtonTitleColor(midspeedButton, UnityEngine::Color32(255, 255, 255, 255)); - setButtonTitleColor(speedButton, UnityEngine::Color32(255, 255, 255, 255)); + setButtonTitleColor(accButton, UnityEngine::Color32(0, 255, 255, 255, 255)); + setButtonTitleColor(techButton, UnityEngine::Color32(0, 255, 255, 255, 255)); + setButtonTitleColor(midspeedButton, UnityEngine::Color32(0, 255, 255, 255, 255)); + setButtonTitleColor(speedButton, UnityEngine::Color32(0, 255, 255, 255, 255)); - setButtonTitleColor(noButton, UnityEngine::Color32(255, 255, 255, 255)); - setButtonTitleColor(yesButton, UnityEngine::Color32(255, 255, 255, 255)); + setButtonTitleColor(noButton, UnityEngine::Color32(0, 255, 255, 255, 255)); + setButtonTitleColor(yesButton, UnityEngine::Color32(0, 255, 255, 255, 255)); leftButton->get_gameObject()->SetActive(false); rightButton->get_gameObject()->SetActive(false); @@ -159,7 +181,7 @@ void BeatLeader::RankVotingPopup::reset() { noButton->get_gameObject()->SetActive(true); starSlider->get_gameObject()->SetActive(false); - starSlider->set_value(0); + starSlider->set_Value(0); voteButton->set_interactable(false); @@ -207,9 +229,9 @@ void BeatLeader::RankVotingPopup::updateType(MapType mapType, UnityEngine::UI::B if ((type & mapType) != 0) { type &= ~mapType; - setButtonTitleColor(button, UnityEngine::Color32(255, 255, 255, 255)); + setButtonTitleColor(button, UnityEngine::Color32(0, 255, 255, 255, 255)); } else { type |= mapType; - setButtonTitleColor(button, UnityEngine::Color32(153, 255, 255, 255)); + setButtonTitleColor(button, UnityEngine::Color32(0, 153, 255, 255, 255)); } } \ No newline at end of file diff --git a/src/Utils/FileManager.cpp b/src/Utils/FileManager.cpp index 34584de..f3c3558 100644 --- a/src/Utils/FileManager.cpp +++ b/src/Utils/FileManager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "include/Utils/FileManager.hpp" #include "beatsaber-hook/shared/config/config-utils.hpp" diff --git a/src/Utils/PlaylistSynchronizer.cpp b/src/Utils/PlaylistSynchronizer.cpp index 9c0ea63..20fd0ff 100644 --- a/src/Utils/PlaylistSynchronizer.cpp +++ b/src/Utils/PlaylistSynchronizer.cpp @@ -12,9 +12,9 @@ #include "beatsaber-hook/shared/rapidjson/include/rapidjson/filereadstream.h" #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" -#include "songloader/shared/API.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" +#include "songcore/shared/SongCore.hpp" #include "zip.h" @@ -33,20 +33,20 @@ void done() { void DownloadBeatmap(string path, string hash, int index) { WebUtils::GetAsync(path, 64, [hash, index](long httpCode, std::string data) { - auto targetFolder = RuntimeSongLoader::API::GetCustomLevelsPath() + hash; + auto targetFolder = string(SongCore::API::Loading::GetPreferredCustomLevelPath()) + hash; int args = 2; int statusCode = zip_stream_extract(data.data(), data.length(), targetFolder.c_str(), +[](const char* name, void* arg) -> int { return 0; }, &args); - getLogger().info("%s", "Map downloaded"); + BeatLeaderLogger.info("%s", "Map downloaded"); if (index + 1 < mapsToDownload.size()) { PlaylistSynchronizer::GetBeatmap(index + 1); } else { done(); - QuestUI::MainThreadScheduler::Schedule([] { - getLogger().info("%s", "Refreshing songs"); - RuntimeSongLoader::API::RefreshSongs(false); - RuntimeSongLoader::API::RefreshPacks(true); + BSML::MainThreadScheduler::Schedule([] { + BeatLeaderLogger.info("%s", "Refreshing songs"); + SongCore::API::Loading::RefreshSongs(false); + SongCore::API::Loading::RefreshLevelPacks(); }); } }); @@ -54,7 +54,7 @@ void DownloadBeatmap(string path, string hash, int index) { void PlaylistSynchronizer::GetBeatmap(int index) { string hash = mapsToDownload[index]; - getLogger().info("%s", ("Will download " + hash).c_str()); + BeatLeaderLogger.info("%s", ("Will download " + hash).c_str()); WebUtils::GetJSONAsync("https://api.beatsaver.com/maps/hash/" + hash, [hash, index] (long status, bool error, rapidjson::Document const& result){ if (status == 200 && !error && result.HasMember("versions")) { DownloadBeatmap(result["versions"].GetArray()[0]["downloadURL"].GetString(), hash, index); @@ -102,7 +102,7 @@ void ActuallySyncPlaylist() { { auto const& song = songs[index]; string hash = toLower(song["hash"].GetString()); - if (RuntimeSongLoader::API::GetLevelByHash(hash) == nullopt) { + if (!SongCore::API::Loading::GetLevelByHash(hash)) { mapsToDownload.push_back(hash); } } @@ -116,19 +116,18 @@ void ActuallySyncPlaylist() { } void PlaylistSynchronizer::SyncPlaylist() { - RuntimeSongLoader::API::AddSongsLoadedEvent( + SongCore::API::Loading::GetSongsLoadedEvent() += [] (auto songs) { if (mapsSynchronized) return; ActuallySyncPlaylist(); - } - ); + }; } void PlaylistSynchronizer::InstallPlaylist(string url, string filename) { DownloadPlaylist(url, filename, true, [](auto songs) { - QuestUI::MainThreadScheduler::Schedule([] { - RuntimeSongLoader::API::RefreshSongs(false); - RuntimeSongLoader::API::RefreshPacks(true); + BSML::MainThreadScheduler::Schedule([] { + SongCore::API::Loading::RefreshSongs(false); + SongCore::API::Loading::RefreshLevelPacks(); }); }); } diff --git a/src/Utils/RecorderUtils.cpp b/src/Utils/RecorderUtils.cpp deleted file mode 100644 index 79984d7..0000000 --- a/src/Utils/RecorderUtils.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -#include "include/Utils/RecorderUtils.hpp" - -#include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp" -#include "GlobalNamespace/PracticeViewController.hpp" -#include "GlobalNamespace/PauseMenuManager.hpp" -#include "GlobalNamespace/ResultsViewController.hpp" -#include "GlobalNamespace/StandardLevelFailedController.hpp" -#include "GlobalNamespace/StandardLevelFailedController_InitData.hpp" -#include "GlobalNamespace/GameScenesManager.hpp" -#include "GlobalNamespace/GameScenesManager_ScenePresentType.hpp" -#include "GlobalNamespace/GameScenesManager_SceneDismissType.hpp" - -#include "beatsaber-hook/shared/utils/hooking.hpp" -#include "beatsaber-hook/shared/utils/logging.hpp" - -#include "main.hpp" - -using namespace GlobalNamespace; -using namespace std; - -bool RecorderUtils::shouldRecord; -bool buffer = false; - -static void OnRestartPauseButtonWasPressed() { - RecorderUtils::shouldRecord = buffer; - buffer = false; -} - -static void OnActionButtonWasPressed() { - RecorderUtils::shouldRecord = true; -} - -static void OnSceneTransitionStarted(bool menuToGame, bool gameToMenu) { - if (menuToGame || gameToMenu) return; // only transition related to Standard gameplay - RecorderUtils::shouldRecord = false; -} - -/* -* A set of patches that check if the user initiated a transition to the standard game scene through interaction with the default UI elements. -* All transitions beyond these are forbidden and will not trigger the replay recorder. -* Allowed flows for now are: -* - Press 'Play' button on a level select screen -* - Press 'Play' button on a practice setup screen -* - Press 'Restart' button on a game pause sceen -* - Press 'Restart' button on a level result details screen -* - Failing a map with the enabled 'AutoRestart' option is considered as a restart button action in a game pause screen -* -* This should prevent activation of the replay recorder for any unknown ways to run GameCore scene. -*/ - -// Play button from a level selection screen -MAKE_HOOK_MATCH(ActionButtonWasPressed, &SinglePlayerLevelSelectionFlowCoordinator::ActionButtonWasPressed, void, SinglePlayerLevelSelectionFlowCoordinator* self) { - ActionButtonWasPressed(self); - OnActionButtonWasPressed(); -} - -// Play button from a practice mode setting screen -MAKE_HOOK_MATCH(PlayButtonPressed, &PracticeViewController::PlayButtonPressed, void, PracticeViewController* self) { - PlayButtonPressed(self); - OnActionButtonWasPressed(); -} - -// Restart button from a GameCore paused screen -MAKE_HOOK_MATCH(RestartButtonPressed, &PauseMenuManager::RestartButtonPressed, void, PauseMenuManager* self) { - RestartButtonPressed(self); - OnRestartPauseButtonWasPressed(); -} - -// Restart button from a level result screen -MAKE_HOOK_MATCH(RestartButtonPressed2, &ResultsViewController::RestartButtonPressed, void, ResultsViewController* self) { - RestartButtonPressed2(self); - OnActionButtonWasPressed(); -} - -MAKE_HOOK_MATCH(LevelFailedCoroutine, &StandardLevelFailedController::LevelFailedCoroutine, System::Collections::IEnumerator*, StandardLevelFailedController* self) { - - if (self->initData->autoRestart) { - OnRestartPauseButtonWasPressed(); - } - - return LevelFailedCoroutine(self); -} - -MAKE_HOOK_MATCH(ScenesTransitionCoroutine, &GameScenesManager::ScenesTransitionCoroutine, System::Collections::IEnumerator*, GameScenesManager* self, ::GlobalNamespace::ScenesTransitionSetupDataSO* newScenesTransitionSetupData, ::System::Collections::Generic::List_1<::StringW>* scenesToPresent, ::GlobalNamespace::GameScenesManager::ScenePresentType presentType, ::System::Collections::Generic::List_1<::StringW>* scenesToDismiss, ::GlobalNamespace::GameScenesManager::SceneDismissType dismissType, float minDuration, ::System::Action* afterMinDurationCallback, ::System::Action_1<::Zenject::DiContainer*>* extraBindingsCallback, ::System::Action_1<::Zenject::DiContainer*>* finishCallback) { - - bool menuToGame = scenesToDismiss->Contains("MainMenu") && scenesToPresent->Contains("StandardGameplay"); - bool gameToMenu = scenesToDismiss->Contains("StandardGameplay") && scenesToPresent->Contains("MainMenu"); - - OnSceneTransitionStarted(menuToGame, gameToMenu); - - return ScenesTransitionCoroutine(self, newScenesTransitionSetupData, scenesToPresent, presentType, scenesToDismiss, dismissType, minDuration, afterMinDurationCallback, extraBindingsCallback, finishCallback); -} - -void RecorderUtils::StartRecorderUtils() { - LoggerContextObject logger = getLogger().WithContext("load"); - - // INSTALL_HOOK(logger, ActionButtonWasPressed); - // INSTALL_HOOK(logger, PlayButtonPressed); - // INSTALL_HOOK(logger, RestartButtonPressed); - // INSTALL_HOOK(logger, RestartButtonPressed2); - // INSTALL_HOOK(logger, LevelFailedCoroutine); - // INSTALL_HOOK(logger, ScenesTransitionCoroutine); -} \ No newline at end of file diff --git a/src/Utils/ReplayManager.cpp b/src/Utils/ReplayManager.cpp index e10c0be..4ba925d 100644 --- a/src/Utils/ReplayManager.cpp +++ b/src/Utils/ReplayManager.cpp @@ -23,7 +23,7 @@ void ReplayManager::ProcessReplay(Replay const &replay, PlayEndData status, bool lastReplayFilename = filename; FileManager::WriteReplay(replay); - getLogger().info("%s",("Replay saved " + filename).c_str()); + BeatLeaderLogger.info("%s",("Replay saved " + filename).c_str()); if(!UploadEnabled()) { finished(ReplayUploadStatus::finished, "Upload disabled. But replay was saved.", 0, -1); @@ -46,7 +46,7 @@ void ReplayManager::TryPostReplay(string name, PlayEndData status, int tryIndex, bool runCallback = status.GetEndType() == LevelEndType::Clear; if (tryIndex == 0) { - getLogger().info("%s",("Started posting " + to_string(file_info.st_size)).c_str()); + BeatLeaderLogger.info("%s",("Started posting " + to_string(file_info.st_size)).c_str()); if (runCallback) { finished(ReplayUploadStatus::inProgress, "Posting replay...", 0, 0); } @@ -57,7 +57,7 @@ void ReplayManager::TryPostReplay(string name, PlayEndData status, int tryIndex, WebUtils::PostFileAsync(WebUtils::API_URL + "replayoculus" + status.ToQueryString(), replayFile, (long)file_info.st_size, [name, tryIndex, finished, replayFile, replayPostStart, runCallback, status](long statusCode, string result, string headers) { fclose(replayFile); if (statusCode >= 450 && tryIndex < 2) { - getLogger().info("%s", ("Retrying posting replay after " + to_string(statusCode) + " #" + to_string(tryIndex) + " " + std::string(result)).c_str()); + BeatLeaderLogger.info("%s", ("Retrying posting replay after " + to_string(statusCode) + " #" + to_string(tryIndex) + " " + std::string(result)).c_str()); if (statusCode == 100) { result = "Timed out"; } @@ -67,7 +67,7 @@ void ReplayManager::TryPostReplay(string name, PlayEndData status, int tryIndex, TryPostReplay(name, status, tryIndex + 1, finished); } else if (statusCode == 200) { auto duration = chrono::duration_cast(chrono::steady_clock::now() - replayPostStart).count(); - getLogger().info("%s", ("Replay was posted! It took: " + to_string((int)duration) + "msec. \n Headers:\n" + headers).c_str()); + BeatLeaderLogger.info("%s", ("Replay was posted! It took: " + to_string((int)duration) + "msec. \n Headers:\n" + headers).c_str()); if (runCallback) { finished(ReplayUploadStatus::finished, "Replay was posted!", 100, statusCode); } @@ -79,7 +79,7 @@ void ReplayManager::TryPostReplay(string name, PlayEndData status, int tryIndex, statusCode = 100; result = "Timed out"; } - getLogger().error("%s", ("Replay was not posted! " + to_string(statusCode) + result).c_str()); + BeatLeaderLogger.error("%s", ("Replay was not posted! " + to_string(statusCode) + result).c_str()); if (runCallback) { finished(ReplayUploadStatus::error, std::string("Replay was not posted. " + result), 0, statusCode); } diff --git a/src/Utils/WebUtils.cpp b/src/Utils/WebUtils.cpp index 97681ed..edf981c 100644 --- a/src/Utils/WebUtils.cpp +++ b/src/Utils/WebUtils.cpp @@ -9,7 +9,7 @@ #include #define TIMEOUT 60 -#define USER_AGENT string(ID "/" VERSION " (BeatSaber/" + GameVersion + ") (Oculus)").c_str() +#define USER_AGENT string(MOD_ID "/" VERSION " (BeatSaber/" + GameVersion + ") (Oculus)").c_str() #define X_BSSB "X-BSSB: ✔" namespace WebUtils { @@ -103,7 +103,7 @@ namespace WebUtils { s.append((char*)contents, newLength); } catch(std::bad_alloc &e) { //handle memory problem - getLogger().critical("Failed to allocate string of size: %lu", newLength); + BeatLeaderLogger.critical("Failed to allocate string of size: %lu", newLength); return 0; } return newLength; @@ -170,7 +170,7 @@ namespace WebUtils { auto res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); @@ -245,7 +245,7 @@ namespace WebUtils { auto res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); @@ -311,7 +311,7 @@ namespace WebUtils { auto res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); if (val) { @@ -378,7 +378,7 @@ namespace WebUtils { CURLcode res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); @@ -449,7 +449,7 @@ namespace WebUtils { CURLcode res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); @@ -556,7 +556,7 @@ namespace WebUtils { CURLcode res = curl_easy_perform(curl); /* Check for errors */ if (res != CURLE_OK) { - getLogger().critical("curl_easy_perform() failed: %u: %s", res, curl_easy_strerror(res)); + BeatLeaderLogger.critical("curl_easy_perform() failed: %i: %s", static_cast(res), curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); diff --git a/src/main.cpp b/src/main.cpp index a2ca686..ab15f0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "include/UI/PreferencesViewController.hpp" #include "include/UI/EmojiSupport.hpp" #include "include/UI/ResultsViewController.hpp" +#include "include/UI/QuestUI.hpp" #include "include/API/PlayerController.hpp" #include "include/Core/ReplayRecorder.hpp" @@ -16,7 +17,6 @@ #include "include/Utils/ModConfig.hpp" #include "include/Utils/PlaylistSynchronizer.hpp" #include "include/Utils/WebUtils.hpp" -#include "include/Utils/RecorderUtils.hpp" #include "include/Utils/FileManager.hpp" #include "config-utils/shared/config-utils.hpp" @@ -24,29 +24,26 @@ #include "GlobalNamespace/MenuTransitionsHelper.hpp" #include "GlobalNamespace/AppInit.hpp" +#include "GlobalNamespace/RichPresenceManager.hpp" +#include "BeatSaber/Init/BSAppInit.hpp" +#include "UnityEngine/SceneManagement/SceneManager.hpp" +#include "UnityEngine/SceneManagement/Scene.hpp" -#include "questui/shared/CustomTypes/Components/MainThreadScheduler.hpp" -#include "questui/shared/QuestUI.hpp" +#include "bsml/shared/BSML/MainThreadScheduler.hpp" +#include "bsml/shared/BSML.hpp" using namespace GlobalNamespace; -using namespace QuestUI; +using namespace BSML; -ModInfo modInfo; // Stores the ID and version of our mod, and is sent to the modloader upon startup +// Called at the early stages of game loading +MOD_EXPORT void setup(CModInfo *info) noexcept { + *info = modInfo.to_c(); -// Returns a logger, useful for printing debug messages -Logger& getLogger() { - static Logger* logger = new Logger(modInfo, LoggerOptions(false, true)); - return *logger; -} + getModConfig().Init(modInfo); -// Called at the early stages of game loading -extern "C" void setup(ModInfo& info) { - info.id = ID; - info.version = VERSION; - modInfo = info; - - getModConfig().Init(modInfo); // Load the config file - getLogger().info("Completed setup!"); + Paper::Logger::RegisterFileContextId(BeatLeaderLogger.tag); + + BeatLeaderLogger.info("Completed setup!"); } MAKE_HOOK_MATCH(Restart, &MenuTransitionsHelper::RestartGame, void, MenuTransitionsHelper* self, ::System::Action_1<::Zenject::DiContainer*>* finishCallback) { @@ -60,13 +57,13 @@ MAKE_HOOK_MATCH(Restart, &MenuTransitionsHelper::RestartGame, void, MenuTransiti void replayPostCallback(ReplayUploadStatus status, const string& description, float progress, int code) { if (!ReplayRecorder::recording) { - QuestUI::MainThreadScheduler::Schedule([status, description, progress, code] { + BSML::MainThreadScheduler::Schedule([status, description, progress, code] { LeaderboardUI::updateStatus(status, description, progress, code > 450 || code < 200); if (status == ReplayUploadStatus::finished) { std::thread t ([] { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); PlayerController::Refresh(0, [](auto player, auto str){ - QuestUI::MainThreadScheduler::Schedule([]{ + BSML::MainThreadScheduler::Schedule([]{ LeaderboardUI::updatePlayerRank(); }); }); @@ -78,13 +75,48 @@ void replayPostCallback(ReplayUploadStatus status, const string& description, fl } } -MAKE_HOOK_MATCH(AppInitStart, &AppInit::Start, void, - AppInit *self) { +MAKE_HOOK_MATCH(AppInitStart, &BeatSaber::Init::BSAppInit::InstallBindings, void, + BeatSaber::Init::BSAppInit *self) { self->::UnityEngine::MonoBehaviour::StartCoroutine(custom_types::Helpers::CoroutineHelper::New(BundleLoader::LoadBundle(self->get_gameObject()))); AppInitStart(self); LeaderboardUI::setup(); } +static bool hasInited = false; +static bool shouldClear = false; + +// do things with the scene transition stuff +MAKE_HOOK_MATCH(RichPresenceManager_HandleGameScenesManagerTransitionDidFinish, &GlobalNamespace::RichPresenceManager::HandleGameScenesManagerTransitionDidFinish, void, GlobalNamespace::RichPresenceManager* self, GlobalNamespace::ScenesTransitionSetupDataSO* setupData, Zenject::DiContainer* container) { + RichPresenceManager_HandleGameScenesManagerTransitionDidFinish(self, setupData, container); + + if (shouldClear) { + shouldClear = false; + QuestUI::ClearCache(); + if (hasInited) { + hasInited = false; + QuestUI::SetupPersistentObjects(); + } + } +} + +// Here we just check if we should be doing things after all the scene transitions are done: +MAKE_HOOK_MATCH(SceneManager_Internal_ActiveSceneChanged, &UnityEngine::SceneManagement::SceneManager::Internal_ActiveSceneChanged, void, UnityEngine::SceneManagement::Scene prevScene, UnityEngine::SceneManagement::Scene nextScene) { + SceneManager_Internal_ActiveSceneChanged(prevScene, nextScene); + bool prevValid = prevScene.IsValid(), nextValid = nextScene.IsValid(); + + if (prevValid && nextValid) { + std::string prevSceneName(prevScene.get_name()); + std::string nextSceneName(nextScene.get_name()); + + if (prevSceneName == "QuestInit") hasInited = true; + + // if we just inited, and aren't already going to clear, check the next scene name for the menu + if (hasInited && !shouldClear && nextSceneName.find("Menu") != std::u16string::npos) { + shouldClear = true; + } + } +} + #include "HMUI/ModalView.hpp" #include "HMUI/Screen.hpp" #include "UnityEngine/Transform.hpp" @@ -96,7 +128,7 @@ MAKE_HOOK_MATCH(ModalView_Show, &HMUI::ModalView::Show, void, HMUI::ModalView* s ModalView_Show(self, animated, moveToCenter, finishedCallback); if (((string)self->get_name()).find("BeatLeader") != string::npos) { - auto cb = self->blockerGO->get_gameObject()->GetComponent(); + auto cb = self->_blockerGO->get_gameObject()->GetComponent(); cb->set_overrideSorting(false); auto cm = self->get_gameObject()->GetComponent(); @@ -105,16 +137,14 @@ MAKE_HOOK_MATCH(ModalView_Show, &HMUI::ModalView::Show, void, HMUI::ModalView* s } // Called later on in the game loading - a good time to install function hooks -extern "C" void load() { +MOD_EXPORT "C" void late_load() { il2cpp_functions::Init(); custom_types::Register::AutoRegister(); WebUtils::refresh_urls(); FileManager::EnsureReplaysFolderExists(); - LoggerContextObject logger = getLogger().WithContext("load"); - - QuestUI::Init(); - QuestUI::Register::RegisterModSettingsViewController(modInfo, "BeatLeader"); + BSML::Init(); + BSML::Register::RegisterSettingsMenu("BeatLeader", BeatLeader::PreferencesViewController::DidActivate, false); LeaderboardUI::retryCallback = []() { ReplayManager::RetryPosting(replayPostCallback); }; @@ -122,17 +152,15 @@ extern "C" void load() { LevelInfoUI::setup(); ModifiersUI::setup(); ResultsView::setup(); - RecorderUtils::StartRecorderUtils(); PlayerController::playerChanged.emplace_back([](optional const& updated) { // if (synchronizer == nullopt) { // synchronizer.emplace(); // } }); - QuestUI::MainThreadScheduler::Schedule([] { + BSML::MainThreadScheduler::Schedule([] { PlayerController::Refresh(); - LoggerContextObject logger = getLogger().WithContext("load"); - INSTALL_HOOK(logger, ModalView_Show); + INSTALL_HOOK(BeatLeaderLogger, ModalView_Show); }); PlaylistSynchronizer::SyncPlaylist(); @@ -145,10 +173,12 @@ extern "C" void load() { ReplayManager::ProcessReplay(replay, status, skipUpload, replayPostCallback); }); - getLogger().info("Installing main hooks..."); + BeatLeaderLogger.info("Installing main hooks..."); - INSTALL_HOOK(logger, Restart); - INSTALL_HOOK(logger, AppInitStart); + INSTALL_HOOK(BeatLeaderLogger, Restart); + INSTALL_HOOK(BeatLeaderLogger, AppInitStart); + INSTALL_HOOK(BeatLeaderLogger, SceneManager_Internal_ActiveSceneChanged); + INSTALL_HOOK(BeatLeaderLogger, RichPresenceManager_HandleGameScenesManagerTransitionDidFinish); - getLogger().info("Installed main hooks!"); + BeatLeaderLogger.info("Installed main hooks!"); } \ No newline at end of file diff --git a/start-logging.ps1 b/start-logging.ps1 deleted file mode 100644 index ee96219..0000000 --- a/start-logging.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -Param( - [Parameter(Mandatory=$false)] - [Switch] $self, - - [Parameter(Mandatory=$false)] - [Switch] $all, - - [Parameter(Mandatory=$false)] - [String] $custom="", - - [Parameter(Mandatory=$false)] - [Switch] $file, - - [Parameter(Mandatory=$false)] - [Switch] $help, - - [Parameter(Mandatory=$false)] - [Switch] $excludeHeader -) - -if ($help -eq $true) { - if ($excludeHeader -eq $false) { - echo "`"Start-Logging`" - Logs Beat Saber using `"adb logcat`"" - echo "`n-- Arguments --`n" - } - - echo "-Self `t`t Only Logs your mod and Crashes" - echo "-All `t`t Logs everything, including logs made by the Quest itself" - echo "-Custom `t Specify a specific logging pattern, e.g `"custom-types|questui`"" - echo "`t`t NOTE: The paterent `"AndriodRuntime|CRASH`" is always appended to a custom pattern" - echo "-File `t`t Saves the output of the log to `"logcat.log`"" - - exit -} - -$timestamp = Get-Date -Format "MM-dd HH:mm:ss.fff" -$bspid = adb shell pidof com.beatgames.beatsaber -$command = "adb logcat -T `"$timestamp`"" - -if ($all -eq $false) { - while ([string]::IsNullOrEmpty($bspid)) { - Start-Sleep -Milliseconds 100 - $bspid = adb shell pidof com.beatgames.beatsaber - } - - $command += "--pid $bspid" -} - -if ($self -eq $true) { - $command += " | Select-String -pattern `"(bl|AndroidRuntime|CRASH)`"" -} elseif ($custom -ne "") { - $pattern = "(" + $custom + "|AndriodRuntime|CRASH)" - $command += " | Select-String -pattern `"$pattern`"" -} -elseif ($all -eq $false) { - $command += " | Select-String -pattern `"(QuestHook|modloader|AndroidRuntime|CRASH)`"" -} - -if ($file -eq $true) { - $command += " | Out-File -FilePath $PSScriptRoot\logcat.log" -} - -echo "Logging using Command `"$command`"" -Invoke-Expression $command \ No newline at end of file