Example #1
0
        public IEnumerator DoOnLevelStart()
        {
            yield return(new WaitUntil(() => standardLevelGameplayManager.GetField <StandardLevelGameplayManager.GameState>("_gameState") == StandardLevelGameplayManager.GameState.Playing));

            yield return(new WaitUntil(() => Resources.FindObjectsOfTypeAll <GameEnergyCounter>().Any()));

            gameSongController = standardLevelGameplayManager.GetField <GameSongController>("_gameSongController");
            gameEnergyCounter  = Resources.FindObjectsOfTypeAll <GameEnergyCounter>().First();

            //Prevent the gameEnergyCounter from invoking death by obstacle
            _oldObstacleEnergyDrainPerSecond = gameEnergyCounter.GetField <float>("_obstacleEnergyDrainPerSecond");
            gameEnergyCounter.SetField("_obstacleEnergyDrainPerSecond", 0f);

            //Unhook the functions in the energy counter that watch note events, so we can peek inside the process
            beatmapObjectManager = gameEnergyCounter.GetField <BeatmapObjectManager>("_beatmapObjectManager");

            beatmapObjectManager.noteWasMissedEvent -= gameEnergyCounter.HandleNoteWasMissedEvent;
            beatmapObjectManager.noteWasMissedEvent += beatmapObjectManager_noteWasMissedEvent;

            beatmapObjectManager.noteWasCutEvent -= gameEnergyCounter.HandleNoteWasCutEvent;
            beatmapObjectManager.noteWasCutEvent += beatmapObjectManager_noteWasCutEvent;

            //Unhook the level end event so we can reset everything before the level ends
            gameSongController.songDidFinishEvent -= standardLevelGameplayManager.HandleSongDidFinish;
            gameSongController.songDidFinishEvent += gameSongController_songDidFinishEvent;
        }
Example #2
0
        private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
        {
            if (scene.buildIndex == 1)
            {
                if (SettingsObject == null)
                {
                    var volumeSettings = Resources.FindObjectsOfTypeAll <VolumeSettingsController>().FirstOrDefault();
                    volumeSettings.gameObject.SetActive(false);
                    SettingsObject = Object.Instantiate(volumeSettings.gameObject);
                    SettingsObject.SetActive(false);
                    volumeSettings.gameObject.SetActive(true);
                    var volume = SettingsObject.GetComponent <VolumeSettingsController>();
                    ReflectionUtil.CopyComponent(volume, typeof(SimpleSettingsController), typeof(SpeedSettingsController), SettingsObject);
                    Object.DestroyImmediate(volume);
                    SettingsObject.GetComponentInChildren <TMP_Text>().text = "SPEED";
                    Object.DontDestroyOnLoad(SettingsObject);
                }
            }
            else
            {
                if (_mainGameSceneSetupData == null)
                {
                    _mainGameSceneSetupData = Resources.FindObjectsOfTypeAll <MainGameSceneSetupData>().FirstOrDefault();
                }

                if (_mainGameSceneSetupData == null || scene.buildIndex != 4)
                {
                    return;
                }

                if (_lastLevelId != _mainGameSceneSetupData.levelId && !string.IsNullOrEmpty(_lastLevelId))
                {
                    TimeScale    = 1;
                    _lastLevelId = _mainGameSceneSetupData.levelId;
                }

                _lastLevelId = _mainGameSceneSetupData.levelId;

                _audioTimeSync = Resources.FindObjectsOfTypeAll <AudioTimeSyncController>().FirstOrDefault();
                _audioTimeSync.forcedAudioSync = true;
                _songAudio = ReflectionUtil.GetPrivateField <AudioSource>(_audioTimeSync, "_audioSource");
                Enabled    = _mainGameSceneSetupData.gameplayOptions.noEnergy;
                var noteCutSoundEffectManager = Resources.FindObjectsOfTypeAll <NoteCutSoundEffectManager>().FirstOrDefault();
                var noteCutSoundEffect        =
                    ReflectionUtil.GetPrivateField <NoteCutSoundEffect>(noteCutSoundEffectManager, "_noteCutSoundEffectPrefab");
                _noteCutAudioSource =
                    ReflectionUtil.GetPrivateField <AudioSource>(noteCutSoundEffect, "_audioSource");

                var canvas = Resources.FindObjectsOfTypeAll <HorizontalLayoutGroup>().FirstOrDefault(x => x.name == "Buttons")
                             .transform.parent;
                canvas.gameObject.AddComponent <SpeedSettingsCreator>();
                TimeScale = TimeScale;

                gameSongController        = Resources.FindObjectsOfTypeAll <GameSongController>().FirstOrDefault();
                songObjectSpawnController = Resources.FindObjectsOfTypeAll <SongObjectSpawnController>().FirstOrDefault();
                scoreController           = Resources.FindObjectsOfTypeAll <ScoreController>().FirstOrDefault <ScoreController>();

                scoresDict = new Dictionary <int, ScoreListItem>();
            }
        }
Example #3
0
 public void OnActiveSceneChanged(Scene arg0, Scene scene)
 {
     _energyCounter   = UnityEngine.Object.FindObjectOfType <GameEnergyCounter>();
     _songController  = UnityEngine.Object.FindObjectOfType <GameSongController>();
     _gameplayManager = UnityEngine.Object.FindObjectOfType <GameplayManager>();
     if (_energyCounter != null)
     {
         _oldEnergy = _energyCounter.energy;
     }
 }
Example #4
0
        public void Start()
        {
            // The goal now is to find the clip this scene will be playing.
            // For this, we find the single root gameObject (which is the gameObject
            // to which we are attached),
            // then we get its GameSongController to find the audio clip,
            // and its FlyingTextSpawner to display the lyrics.

            textSpawner    = gameObject.GetComponentInChildren <FlyingTextSpawner>();
            songController = gameObject.GetComponentInChildren <GameSongController>();

            if (textSpawner == null || songController == null)
            {
                return;
            }


            audioClip = (AudioClip)SongFileField.GetValue(songController);

            // Clip found, now select the first song that has the same clip (using reference comparison).
            levelData = (from world in PersistentSingleton <GameDataModel> .instance.gameStaticData.worldsData
                         from levelStaticData in world.levelsData
                         from difficultyLevel in levelStaticData.difficultyLevels
                         where ReferenceEquals(difficultyLevel.audioClip, audioClip)
                         select levelStaticData).FirstOrDefault();

            if (levelData == null)
            {
                Debug.Log("Corresponding song not found.");

                return;
            }

            // We found the matching song, we can get started.
            Debug.Log($"Corresponding song data found: {levelData.songName} by {levelData.authorName}.");

            // When this coroutine ends, it will call the given callback with a list
            // of all the subtitles we found, and allow us to react.
            // If no subs are found, the callback is not called.
            StartCoroutine(LyricsFetcher.GetLyrics(levelData.songName, levelData.authorName, subs =>
            {
                SpawnText("Lyrics found", 3f);
                StartCoroutine(DisplayLyrics(subs));
            }));
        }
Example #5
0
        public void Construct(PluginConfig config,
                              Submission submission,
                              AudioTimeSyncController audioTimeSyncController,
                              BeatmapLevelsModel beatmapLevelsModel,
                              GameplayCoreSceneSetupData gameplayCoreSceneSetupData,
                              GameSongController gameSongController)
        {
            _config     = config;
            _submission = submission;
            _audioTimeSyncController    = audioTimeSyncController;
            _beatmapLevelsModel         = beatmapLevelsModel;
            _gameplayCoreSceneSetupData = gameplayCoreSceneSetupData;
            _gameSongController         = gameSongController;
            _random = new System.Random();
            IBeatmapLevelPack beatmapLevelPack = _beatmapLevelsModel.GetLevelPackForLevelId(_gameplayCoreSceneSetupData.difficultyBeatmap.level.levelID);

            _previewBeatmapLevels = beatmapLevelPack.beatmapLevelCollection.beatmapLevels;
        }
Example #6
0
        private IEnumerator WaitForLoad()
        {
            bool loaded = false;

            while (!loaded)
            {
                songController = Resources.FindObjectsOfTypeAll <GameSongController>().FirstOrDefault();

                if (songController == null)
                {
                    Plugin.Log("Music Volume songController is null!", Plugin.LogLevel.DebugOnly);
                    yield return(new WaitForSeconds(0.01f));
                }
                else
                {
                    Plugin.Log("Music Volume found songController!", Plugin.LogLevel.DebugOnly);
                    loaded = true;
                }
            }
            LoadingDidFinishEvent();
        }
Example #7
0
        public IEnumerator Start()
        {
            // The goal now is to find the clip this scene will be playing.
            // For this, we find the single root gameObject (which is the gameObject
            // to which we are attached),
            // then we get its GameSongController to find the audio clip,
            // and its FlyingTextSpawner to display the lyrics.

            if (Settings.VerboseLogging)
            {
                Debug.Log("[Beat Singer] Attached to scene.");
                Debug.Log($"[Beat Singer] Lyrics are enabled: {Settings.DisplayLyrics}.");
            }

            textSpawner    = FindObjectOfType <FlyingTextSpawner>();
            songController = FindObjectOfType <GameSongController>();

            var sceneSetup = FindObjectOfType <GameplayCoreSceneSetup>();

            if (songController == null || sceneSetup == null)
            {
                yield break;
            }

            if (textSpawner == null)
            {
                var installer = FindObjectOfType <EffectPoolsInstaller>();
                var container = (Zenject.DiContainer)ContainerField.GetValue(installer);

                textSpawner = container.InstantiateComponentOnNewGameObject <FlyingTextSpawner>();
            }

            var sceneSetupData = (GameplayCoreSceneSetupData)SceneSetupDataField.GetValue(sceneSetup);

            if (sceneSetupData == null)
            {
                yield break;
            }

            audio = (AudioTimeSyncController)AudioTimeSyncField.GetValue(songController);

            IBeatmapLevel   level     = sceneSetupData.difficultyBeatmap.level;
            List <Subtitle> subtitles = new List <Subtitle>();

            Debug.Log($"[Beat Singer] Corresponding song data found: {level.songName} by {level.songAuthorName} ({(level.songSubName != null ? level.songSubName : "No sub-name")}).");

            if (LyricsFetcher.GetLocalLyrics(sceneSetupData.difficultyBeatmap.level.levelID, subtitles))
            {
                Debug.Log("[Beat Singer] Found local lyrics.");
                Debug.Log($"[Beat Singer] These lyrics can be uploaded online using the ID: \"{level.GetLyricsHash()}\".");

                // Lyrics found locally, continue with them.
                SpawnText("Lyrics found locally", 3f);
            }
            else
            {
                Debug.Log("[Beat Singer] Did not find local lyrics, trying online lyrics...");

                // When this coroutine ends, it will call the given callback with a list
                // of all the subtitles we found, and allow us to react.
                // If no subs are found, the callback is not called.
                yield return(StartCoroutine(LyricsFetcher.GetOnlineLyrics(level, subtitles)));

                if (subtitles.Count != 0)
                {
                    goto FoundOnlineLyrics;
                }

                yield return(StartCoroutine(LyricsFetcher.GetMusixmatchLyrics(level.songName, level.songAuthorName, subtitles)));

                if (subtitles.Count != 0)
                {
                    goto FoundOnlineLyrics;
                }

                yield return(StartCoroutine(LyricsFetcher.GetMusixmatchLyrics(level.songName, level.songSubName, subtitles)));

                if (subtitles.Count != 0)
                {
                    goto FoundOnlineLyrics;
                }

                yield break;

FoundOnlineLyrics:
                SpawnText("Lyrics found online", 3f);
            }

            StartCoroutine(DisplayLyrics(subtitles));
        }
Example #8
0
        public IEnumerator Start()
        {
            // The goal now is to find the clip this scene will be playing.
            // For this, we find the single root gameObject (which is the gameObject
            // to which we are attached),
            // then we get its GameSongController to find the audio clip,
            // and its FlyingTextSpawner to display the lyrics.


            textSpawner    = FindObjectOfType <FlyingTextSpawner>();
            songController = FindObjectOfType <GameSongController>();

            if (textSpawner == null || songController == null)
            {
                yield break;
            }

            MainGameSceneSetup sceneSetup = FindObjectOfType <MainGameSceneSetup>();

            if (sceneSetup == null)
            {
                yield break;
            }

            MainGameSceneSetupData sceneSetupData = SetupDataField.GetValue(sceneSetup) as MainGameSceneSetupData;

            if (sceneSetupData == null)
            {
                yield break;
            }

            List <Subtitle> subtitles = new List <Subtitle>();

            if (LyricsFetcher.GetLocalLyrics(sceneSetupData.difficultyLevel.level.levelID, subtitles))
            {
                // Lyrics found locally, continue with them.
                SpawnText("Lyrics found locally", 3f);
            }
            else
            {
                // Clip found, now select the first song that has the same clip (using reference comparison).
                IStandardLevel level = sceneSetupData.difficultyLevel.level;

                // We found the matching song, we can get started.
                Debug.Log($"Corresponding song data found: {level.songName} by {level.songAuthorName}.");

                // When this coroutine ends, it will call the given callback with a list
                // of all the subtitles we found, and allow us to react.
                // If no subs are found, the callback is not called.
                yield return(StartCoroutine(LyricsFetcher.GetOnlineLyrics(level.songName, level.songAuthorName, subtitles)));

                if (subtitles.Count == 0)
                {
                    yield break;
                }

                SpawnText("Lyrics found online", 3f);
            }

            StartCoroutine(DisplayLyrics(subtitles));
        }
Example #9
0
        private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
        {
            try
            {
                this._playerController = FindObjectOfType <PlayerController>();

                if (scene.buildIndex == 1)
                {
                    IsLeftSaberOn = true;
                    if (_beatmapObjectSpawnController != null)
                    {
                        _beatmapObjectSpawnController.noteWasCutEvent -= this.HandleNoteWasCutEvent;
                    }

                    if (_mainSettingsModel != null && stored)
                    {
                        StartCoroutine(RestoreNoEnergy());
                    }
                }

                if (scene.buildIndex == 5)
                {
                    var _mainGameSceneSetup = FindObjectOfType <MainGameSceneSetup>();
                    this._gameSongController           = FindObjectOfType <GameSongController>();
                    this._noteCutEffectSpawner         = FindObjectOfType <NoteCutEffectSpawner>();
                    this._beatmapObjectSpawnController = FindObjectOfType <BeatmapObjectSpawnController>();
                    _noteCutHapticEffect      = ReflectionUtil.GetPrivateField <NoteCutHapticEffect>(_noteCutEffectSpawner, "_noteCutHapticEffect");
                    _hapticFeedbackController = ReflectionUtil.GetPrivateField <HapticFeedbackController>(_noteCutHapticEffect, "_hapticFeedbackController");
                    _mainSettingsModel        = ReflectionUtil.GetPrivateField <MainSettingsModel>(_hapticFeedbackController, "_mainSettingsModel");
                    _mainSettingsModel.controllersRumbleEnabled = true;

                    this._mainGameSceneSetupData = ReflectionUtil.GetPrivateField <MainGameSceneSetupData>(_mainGameSceneSetup, "_mainGameSceneSetupData");

                    if (!stored)
                    {
                        storedNoEnergy = _mainGameSceneSetupData.gameplayOptions.noEnergy;
                    }
                    stored = true;

                    if (_mainGameSceneSetupData.gameplayMode == GameplayMode.SoloNoArrows)
                    {
                        BeatmapDataModel _beatmapDataModel = ReflectionUtil.GetPrivateField <BeatmapDataModel>(_mainGameSceneSetup, "_beatmapDataModel");
                        BeatmapData      beatmapData       = CreateTransformedBeatmapData(_mainGameSceneSetupData.difficultyLevel.beatmapData, _mainGameSceneSetupData.gameplayOptions, _mainGameSceneSetupData.gameplayMode);
                        if (beatmapData != null)
                        {
                            _beatmapDataModel.beatmapData = beatmapData;
                            ReflectionUtil.SetPrivateField(_mainGameSceneSetup, "_beatmapDataModel", _beatmapDataModel);
                        }

                        if (Plugin.IsOneColorModeOn)
                        {
                            _mainGameSceneSetupData.gameplayOptions.noEnergy = true;
                            _sabers = FindObjectsOfType <Saber>();

                            Saber targetSaber = (Plugin.IsColorRed) ? _playerController.leftSaber : _playerController.rightSaber;
                            Saber otherSaber  = (Plugin.IsColorRed) ? _playerController.rightSaber : _playerController.leftSaber;

                            if (targetSaber == null || otherSaber == null)
                            {
                                return;
                            }

                            var   targetCopy = Instantiate(targetSaber.gameObject);
                            Saber newSaber   = targetCopy.GetComponent <Saber>();
                            targetCopy.transform.parent         = targetSaber.transform.parent;
                            targetCopy.transform.localPosition  = Vector3.zero;
                            targetCopy.transform.localRotation  = Quaternion.identity;
                            targetSaber.transform.parent        = otherSaber.transform.parent;
                            targetSaber.transform.localPosition = Vector3.zero;
                            targetSaber.transform.localRotation = Quaternion.identity;
                            otherSaber.gameObject.SetActive(false);

                            if (Plugin.IsColorRed)
                            {
                                ReflectionUtil.SetPrivateField(_playerController, "_rightSaber", targetSaber);
                                ReflectionUtil.SetPrivateField(_playerController, "_leftSaber", newSaber);
                            }
                            else
                            {
                                ReflectionUtil.SetPrivateField(_playerController, "_leftSaber", targetSaber);
                                ReflectionUtil.SetPrivateField(_playerController, "_rightSaber", newSaber);
                            }

                            _playerController.leftSaber.gameObject.SetActive(IsLeftSaberOn);

                            if (_beatmapObjectSpawnController != null)
                            {
                                _beatmapObjectSpawnController.noteWasCutEvent += this.HandleNoteWasCutEvent;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
            }
        }
Example #10
0
        public async void HandleSongStart()
        {
            // Check if level data is actually available in BS_Utils before proceeding further. It isn't available in the tutorial
            if (!BS_Utils.Plugin.LevelData.IsSet)
            {
                Plugin.log.Debug("BS_Utils level data is not present. Probably due to the tutorial being active.");
                return;
            }

            GameStatus gameStatus = statusManager.gameStatus;

            // Check for multiplayer early to abort if needed: gameplay controllers don't exist in multiplayer until later
            multiplayerSessionManager = FindFirstOrDefaultOptional <MultiplayerSessionManager>();
            multiplayerController     = FindFirstOrDefaultOptional <MultiplayerController>();

            if (multiplayerSessionManager && multiplayerController)
            {
                Plugin.log.Debug("Multiplayer Level loaded");

                // public event Action<DisconnectedReason> MultiplayerSessionManager#disconnectedEvent;
                multiplayerSessionManager.disconnectedEvent += OnMultiplayerDisconnected;

                // public event Action<State> MultiplayerController#stateChangedEvent;
                multiplayerController.stateChangedEvent += OnMultiplayerStateChanged;

                // Do nothing until the next state change to Gameplay.
                if (multiplayerController.state != MultiplayerController.State.Gameplay)
                {
                    return;
                }

                multiplayerLocalActivePlayerFacade = FindFirstOrDefaultOptional <MultiplayerLocalActivePlayerFacade>();

                if (multiplayerLocalActivePlayerFacade != null)
                {
                    multiplayerLocalActivePlayerFacade.playerDidFinishEvent += OnMultiplayerLevelFinished;
                }
            }
            else if (!doDelayedSongStart)
            {
                doDelayedSongStart = true;

                return;
            }

            // `wants_to_play_next_level` is set for players who don't want to play the song aka want to spectate aka are not "active". `isSpectating` is apparently not spectating.
            gameStatus.scene       = multiplayerSessionManager.isSpectating || !multiplayerSessionManager.LocalPlayerHasState(NetworkConstants.wantsToPlayNextLevel) ? "Spectator" : "Song";
            gameStatus.multiplayer = multiplayerSessionManager.isConnectingOrConnected;

            pauseController                  = FindFirstOrDefaultOptional <PauseController>();
            scoreController                  = FindWithMultiplayerFix <ScoreController>();
            beatmapObjectManager             = (BeatmapObjectManager)scoreControllerBeatmapObjectManagerField.GetValue(scoreController);
            gameplayManager                  = FindFirstOrDefaultOptional <StandardLevelGameplayManager>() as MonoBehaviour ?? FindFirstOrDefaultOptional <MissionLevelGameplayManager>();
            beatmapObjectCallbackController  = FindWithMultiplayerFix <BeatmapObjectCallbackController>();
            gameplayModifiersSO              = FindFirstOrDefault <GameplayModifiersModelSO>();
            audioTimeSyncController          = FindWithMultiplayerFix <AudioTimeSyncController>();
            playerHeadAndObstacleInteraction = (PlayerHeadAndObstacleInteraction)scoreControllerHeadAndObstacleInteractionField.GetValue(scoreController);
            gameSongController               = FindWithMultiplayerFix <GameSongController>();
            gameEnergyCounter                = FindWithMultiplayerFix <GameEnergyCounter>();

            if (multiplayerController)
            {
                // NOOP
            }
            else if (gameplayManager is StandardLevelGameplayManager)
            {
                Plugin.log.Debug("Standard Level loaded");
            }
            else if (gameplayManager is MissionLevelGameplayManager)
            {
                Plugin.log.Debug("Mission Level loaded");
            }

            gameplayCoreSceneSetupData = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData;

            // Register event listeners
            // PauseController doesn't exist in multiplayer
            if (pauseController != null)
            {
                // public event Action PauseController#didPauseEvent;
                pauseController.didPauseEvent += OnGamePause;
                // public event Action PauseController#didResumeEvent;
                pauseController.didResumeEvent += OnGameResume;
            }
            // public ScoreController#noteWasCutEvent<NoteData, NoteCutInfo, int multiplier> // called after CutScoreBuffer is created
            scoreController.noteWasCutEvent += OnNoteWasCut;
            // public ScoreController#noteWasMissedEvent<NoteData, int multiplier>
            scoreController.noteWasMissedEvent += OnNoteWasMissed;
            // public ScoreController#scoreDidChangeEvent<int, int> // score
            scoreController.scoreDidChangeEvent += OnScoreDidChange;
            // public ScoreController#comboDidChangeEvent<int> // combo
            scoreController.comboDidChangeEvent += OnComboDidChange;
            // public ScoreController#multiplierDidChangeEvent<int, float> // multiplier, progress [0..1]
            scoreController.multiplierDidChangeEvent += OnMultiplierDidChange;

            beatmapObjectManager.noteWasSpawnedEvent += OnNoteWasSpawned;
            // public event Action<BeatmapEventData> BeatmapObjectCallbackController#beatmapEventDidTriggerEvent
            beatmapObjectCallbackController.beatmapEventDidTriggerEvent += OnBeatmapEventDidTrigger;
            // public event Action GameSongController#songDidFinishEvent;
            gameSongController.songDidFinishEvent += OnLevelFinished;
            // public event Action GameEnergyCounter#gameEnergyDidReach0Event;
            gameEnergyCounter.gameEnergyDidReach0Event += OnEnergyDidReach0Event;
            if (gameplayManager is ILevelEndActions levelEndActions)
            {
                // event Action levelFailedEvent;
                levelEndActions.levelFailedEvent += OnLevelFailed;
            }

            IDifficultyBeatmap diff  = gameplayCoreSceneSetupData.difficultyBeatmap;
            IBeatmapLevel      level = diff.level;

            gameStatus.partyMode = Gamemode.IsPartyActive;

            gameplayModifiers         = gameplayCoreSceneSetupData.gameplayModifiers;
            gameplayModiferParamsList = gameplayModifiersSO.CreateModifierParamsList(gameplayModifiers);

            PlayerSpecificSettings playerSettings   = gameplayCoreSceneSetupData.playerSpecificSettings;
            PracticeSettings       practiceSettings = gameplayCoreSceneSetupData.practiceSettings;

            float songSpeedMul = gameplayModifiers.songSpeedMul;

            if (practiceSettings != null)
            {
                songSpeedMul = practiceSettings.songSpeedMul;
            }

            int beatmapObjectId    = 0;
            var beatmapObjectsData = diff.beatmapData.beatmapObjectsData;

            // Generate NoteData to id mappings for backwards compatiblity with <1.12.1
            noteToIdMapping = new NoteData[beatmapObjectsData.Count(obj => obj is NoteData)];
            lastNoteId      = 0;

            foreach (BeatmapObjectData beatmapObjectData in beatmapObjectsData)
            {
                if (beatmapObjectData is NoteData noteData)
                {
                    noteToIdMapping[beatmapObjectId++] = noteData;
                }
            }

            gameStatus.songName                = level.songName;
            gameStatus.songSubName             = level.songSubName;
            gameStatus.songAuthorName          = level.songAuthorName;
            gameStatus.levelAuthorName         = level.levelAuthorName;
            gameStatus.songBPM                 = level.beatsPerMinute;
            gameStatus.noteJumpSpeed           = diff.noteJumpMovementSpeed;
            gameStatus.noteJumpStartBeatOffset = diff.noteJumpStartBeatOffset;
            // 13 is "custom_level_" and 40 is the magic number for the length of the SHA-1 hash
            gameStatus.songHash       = Regex.IsMatch(level.levelID, "^custom_level_[0-9A-F]{40}", RegexOptions.IgnoreCase) && !level.levelID.EndsWith(" WIP") ? level.levelID.Substring(13, 40) : null;
            gameStatus.levelId        = level.levelID;
            gameStatus.songTimeOffset = (long)(level.songTimeOffset * 1000f / songSpeedMul);
            gameStatus.length         = (long)(level.beatmapLevelData.audioClip.length * 1000f / songSpeedMul);
            gameStatus.start          = GetCurrentTime() - (long)(audioTimeSyncController.songTime * 1000f / songSpeedMul);
            if (practiceSettings != null)
            {
                gameStatus.start -= (long)(practiceSettings.startSongTime * 1000f / songSpeedMul);
            }
            gameStatus.paused          = 0;
            gameStatus.difficulty      = diff.difficulty.Name();
            gameStatus.difficultyEnum  = Enum.GetName(typeof(BeatmapDifficulty), diff.difficulty);
            gameStatus.characteristic  = diff.parentDifficultyBeatmapSet.beatmapCharacteristic.serializedName;
            gameStatus.notesCount      = diff.beatmapData.cuttableNotesCount;
            gameStatus.bombsCount      = diff.beatmapData.bombsCount;
            gameStatus.obstaclesCount  = diff.beatmapData.obstaclesCount;
            gameStatus.environmentName = level.environmentInfo.sceneInfo.sceneName;

            ColorScheme colorScheme = gameplayCoreSceneSetupData.colorScheme ?? new ColorScheme(gameplayCoreSceneSetupData.environmentInfo.colorScheme);

            gameStatus.colorSaberA       = colorScheme.saberAColor;
            gameStatus.colorSaberB       = colorScheme.saberBColor;
            gameStatus.colorEnvironment0 = colorScheme.environmentColor0;
            gameStatus.colorEnvironment1 = colorScheme.environmentColor1;
            if (colorScheme.supportsEnvironmentColorBoost)
            {
                gameStatus.colorEnvironmentBoost0 = colorScheme.environmentColor0Boost;
                gameStatus.colorEnvironmentBoost1 = colorScheme.environmentColor1Boost;
            }
            gameStatus.colorObstacle = colorScheme.obstaclesColor;

            try {
                // From https://support.unity3d.com/hc/en-us/articles/206486626-How-can-I-get-pixels-from-unreadable-textures-
                // Modified to correctly handle texture atlases. Fixes #82.
                var active = RenderTexture.active;

                var sprite = await level.GetCoverImageAsync(CancellationToken.None);

                var texture   = sprite.texture;
                var temporary = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);

                Graphics.Blit(texture, temporary);
                RenderTexture.active = temporary;

                var spriteRect = sprite.rect;
                var uv         = sprite.uv[0];

                var cover = new Texture2D((int)spriteRect.width, (int)spriteRect.height);
                // Unity sucks. The coordinates of the sprite on its texture atlas are only accessible through the Sprite.uv property since rect always returns `x=0,y=0`, so we need to convert them back into texture space.
                cover.ReadPixels(new Rect(
                                     uv.x * texture.width,
                                     texture.height - uv.y * texture.height,
                                     spriteRect.width,
                                     spriteRect.height
                                     ), 0, 0);
                cover.Apply();

                RenderTexture.active = active;
                RenderTexture.ReleaseTemporary(temporary);

                gameStatus.songCover = System.Convert.ToBase64String(ImageConversion.EncodeToPNG(cover));
            } catch {
                gameStatus.songCover = null;
            }

            gameStatus.ResetPerformance();

            UpdateModMultiplier();

            gameStatus.songSpeedMultiplier = songSpeedMul;
            gameStatus.batteryLives        = gameEnergyCounter.batteryLives;

            gameStatus.modObstacles          = gameplayModifiers.enabledObstacleType.ToString();
            gameStatus.modInstaFail          = gameplayModifiers.instaFail;
            gameStatus.modNoFail             = gameplayModifiers.noFailOn0Energy;
            gameStatus.modBatteryEnergy      = gameplayModifiers.energyType == GameplayModifiers.EnergyType.Battery;
            gameStatus.modDisappearingArrows = gameplayModifiers.disappearingArrows;
            gameStatus.modNoBombs            = gameplayModifiers.noBombs;
            gameStatus.modSongSpeed          = gameplayModifiers.songSpeed.ToString();
            gameStatus.modNoArrows           = gameplayModifiers.noArrows;
            gameStatus.modGhostNotes         = gameplayModifiers.ghostNotes;
            gameStatus.modFailOnSaberClash   = gameplayModifiers.failOnSaberClash;
            gameStatus.modStrictAngles       = gameplayModifiers.strictAngles;
            gameStatus.modFastNotes          = gameplayModifiers.fastNotes;
            gameStatus.modSmallNotes         = gameplayModifiers.smallCubes;
            gameStatus.modProMode            = gameplayModifiers.proMode;
            gameStatus.modZenMode            = gameplayModifiers.zenMode;

            var environmentEffectsFilterPreset = diff.difficulty == BeatmapDifficulty.ExpertPlus ? playerSettings.environmentEffectsFilterExpertPlusPreset : playerSettings.environmentEffectsFilterDefaultPreset;

            // Backwards compatibility for <1.13.4
            gameStatus.staticLights           = environmentEffectsFilterPreset != EnvironmentEffectsFilterPreset.AllEffects;
            gameStatus.leftHanded             = playerSettings.leftHanded;
            gameStatus.playerHeight           = playerSettings.playerHeight;
            gameStatus.sfxVolume              = playerSettings.sfxVolume;
            gameStatus.reduceDebris           = playerSettings.reduceDebris;
            gameStatus.noHUD                  = playerSettings.noTextsAndHuds;
            gameStatus.advancedHUD            = playerSettings.advancedHud;
            gameStatus.autoRestart            = playerSettings.autoRestart;
            gameStatus.saberTrailIntensity    = playerSettings.saberTrailIntensity;
            gameStatus.environmentEffects     = environmentEffectsFilterPreset.ToString();
            gameStatus.hideNoteSpawningEffect = playerSettings.hideNoteSpawnEffect;

            statusManager.EmitStatusUpdate(ChangedProperties.AllButNoteCut, "songStart");
        }
Example #11
0
        private void CleanUpSong()
        {
            statusManager.gameStatus.ResetMapInfo();
            statusManager.gameStatus.ResetPerformance();

            noteControllerMapping.Clear();

            // Release references for AfterCutScoreBuffers that don't resolve due to player leaving the map before finishing.
            foreach (var noteCutItem in noteCutMapping)
            {
                // CutScoreBuffers are pooled. Remove the event listener just in case it never fires the event.
                noteCutItem.Key.didFinishEvent.Remove(this);
            }
            noteCutMapping.Clear();

            // Clear note id mappings.
            noteToIdMapping = null;

            if (pauseController != null)
            {
                pauseController.didPauseEvent  -= OnGamePause;
                pauseController.didResumeEvent -= OnGameResume;
                pauseController = null;
            }

            if (scoreController != null)
            {
                scoreController.noteWasCutEvent          -= OnNoteWasCut;
                scoreController.noteWasMissedEvent       -= OnNoteWasMissed;
                scoreController.scoreDidChangeEvent      -= OnScoreDidChange;
                scoreController.comboDidChangeEvent      -= OnComboDidChange;
                scoreController.multiplierDidChangeEvent -= OnMultiplierDidChange;
                scoreController = null;
            }

            if (beatmapObjectManager != null)
            {
                beatmapObjectManager.noteWasSpawnedEvent -= OnNoteWasSpawned;
                beatmapObjectManager = null;
            }

            if (gameplayManager != null)
            {
                if (gameplayManager is ILevelEndActions levelEndActions)
                {
                    // event Action levelFailedEvent;
                    levelEndActions.levelFailedEvent -= OnLevelFailed;
                }
                gameplayManager = null;
            }

            if (beatmapObjectCallbackController != null)
            {
                beatmapObjectCallbackController.beatmapEventDidTriggerEvent -= OnBeatmapEventDidTrigger;
                beatmapObjectCallbackController = null;
            }

            if (gameplayModifiersSO != null)
            {
                gameplayModifiersSO = null;
            }

            if (audioTimeSyncController != null)
            {
                audioTimeSyncController = null;
            }

            if (playerHeadAndObstacleInteraction != null)
            {
                playerHeadAndObstacleInteraction = null;
            }

            if (gameSongController != null)
            {
                gameSongController.songDidFinishEvent -= OnLevelFinished;
                gameSongController = null;
            }

            if (gameEnergyCounter != null)
            {
                gameEnergyCounter.gameEnergyDidReach0Event -= OnEnergyDidReach0Event;
                gameEnergyCounter = null;
            }

            if (gameplayCoreSceneSetupData != null)
            {
                gameplayCoreSceneSetupData = null;
            }

            if (gameplayModifiers != null)
            {
                gameplayModifiers = null;
            }

            if (gameplayModiferParamsList != null)
            {
                gameplayModiferParamsList = null;
            }
        }
Example #12
0
        private void OnSceneLoaded(Scene newScene, LoadSceneMode mode)
        {
            var gameStatus = statusManager.gameStatus;

            gameStatus.scene = newScene.name;

            if (newScene.name == "Menu")
            {
                // Menu
                headInObstacle = false;

                // TODO: get the current song, mode and mods while in menu
                gameStatus.ResetMapInfo();

                gameStatus.ResetPerformance();

                statusManager.EmitStatusUpdate(ChangedProperties.AllButNoteCut, "menu");
            }
            else if (newScene.name == "StandardLevel")
            {
                // In game
                mainSetupData = Resources.FindObjectsOfTypeAll <MainGameSceneSetupData>().FirstOrDefault();
                if (mainSetupData == null)
                {
                    Console.WriteLine("[HTTP Status] Couldn't find MainGameSceneSetupData");
                    return;
                }

                gamePauseManager = Resources.FindObjectsOfTypeAll <GamePauseManager>().FirstOrDefault();
                if (gamePauseManager == null)
                {
                    Console.WriteLine("[HTTP Status] Couldn't find GamePauseManager");
                    return;
                }

                scoreController = Resources.FindObjectsOfTypeAll <ScoreController>().FirstOrDefault();
                if (scoreController == null)
                {
                    Console.WriteLine("[HTTP Status] Couldn't find ScoreController");
                    return;
                }

                gameplayManager = Resources.FindObjectsOfTypeAll <GameplayManager>().FirstOrDefault();
                if (gameplayManager == null)
                {
                    Console.WriteLine("[HTTP Status] Couldn't find GameplayManager");
                    return;
                }

                beatmapObjectCallbackController = Resources.FindObjectsOfTypeAll <BeatmapObjectCallbackController>().FirstOrDefault();
                if (beatmapObjectCallbackController == null)
                {
                    Console.WriteLine("[HTTP Status] Couldn't find BeatmapObjectCallbackController");
                    return;
                }

                GameSongController gameSongController = (GameSongController)gameSongControllerField.GetValue(gameplayManager);
                audioTimeSyncController          = (AudioTimeSyncController)audioTimeSyncControllerField.GetValue(gameSongController);
                playerHeadAndObstacleInteraction = (PlayerHeadAndObstacleInteraction)playerHeadAndObstacleInteractionField.GetValue(scoreController);

                // Register event listeners
                // private GameEvent GamePauseManager#_gameDidPauseSignal
                AddSubscriber(gamePauseManager, "_gameDidPauseSignal", OnGamePause);
                // private GameEvent GamePauseManager#_gameDidResumeSignal
                AddSubscriber(gamePauseManager, "_gameDidResumeSignal", OnGameResume);
                // public ScoreController#noteWasCutEvent<NoteData, NoteCutInfo, int multiplier> // called after AfterCutScoreBuffer is created
                scoreController.noteWasCutEvent += OnNoteWasCut;
                // public ScoreController#noteWasMissedEvent<NoteData, int multiplier>
                scoreController.noteWasMissedEvent += OnNoteWasMissed;
                // public ScoreController#scoreDidChangeEvent<int> // score
                scoreController.scoreDidChangeEvent += OnScoreDidChange;
                // public ScoreController#comboDidChangeEvent<int> // combo
                scoreController.comboDidChangeEvent += OnComboDidChange;
                // public ScoreController#multiplierDidChangeEvent<int, float> // multiplier, progress [0..1]
                scoreController.multiplierDidChangeEvent += OnMultiplierDidChange;
                // private GameEvent GameplayManager#_levelFinishedSignal
                AddSubscriber(gameplayManager, "_levelFinishedSignal", OnLevelFinished);
                // private GameEvent GameplayManager#_levelFailedSignal
                AddSubscriber(gameplayManager, "_levelFailedSignal", OnLevelFailed);
                // public event Action<BeatmapEventData> BeatmapObjectCallbackController#beatmapEventDidTriggerEvent
                beatmapObjectCallbackController.beatmapEventDidTriggerEvent += OnBeatmapEventDidTrigger;

                var diff  = mainSetupData.difficultyLevel;
                var level = diff.level;

                gameStatus.mode = mainSetupData.gameplayMode.ToString();

                gameStatus.songName       = level.songName;
                gameStatus.songSubName    = level.songSubName;
                gameStatus.songAuthorName = level.songAuthorName;
                gameStatus.songBPM        = level.beatsPerMinute;
                gameStatus.songTimeOffset = (long)(level.songTimeOffset * 1000f);
                gameStatus.length         = (long)(level.audioClip.length * 1000f);
                gameStatus.start          = GetCurrentTime();
                gameStatus.paused         = 0;
                gameStatus.difficulty     = diff.difficulty.Name();
                gameStatus.notesCount     = diff.beatmapData.notesCount;
                gameStatus.obstaclesCount = diff.beatmapData.obstaclesCount;
                gameStatus.maxScore       = ScoreController.MaxScoreForNumberOfNotes(diff.beatmapData.notesCount);

                try {
                    // From https://support.unity3d.com/hc/en-us/articles/206486626-How-can-I-get-pixels-from-unreadable-textures-
                    var texture   = level.coverImage.texture;
                    var active    = RenderTexture.active;
                    var temporary = RenderTexture.GetTemporary(
                        texture.width,
                        texture.height,
                        0,
                        RenderTextureFormat.Default,
                        RenderTextureReadWrite.Linear
                        );

                    Graphics.Blit(texture, temporary);
                    RenderTexture.active = temporary;

                    var cover = new Texture2D(texture.width, texture.height);
                    cover.ReadPixels(new Rect(0, 0, temporary.width, temporary.height), 0, 0);
                    cover.Apply();

                    RenderTexture.active = active;
                    RenderTexture.ReleaseTemporary(temporary);

                    gameStatus.songCover = System.Convert.ToBase64String(
                        ImageConversion.EncodeToPNG(cover)
                        );
                } catch {
                    gameStatus.songCover = null;
                }

                gameStatus.ResetPerformance();

                // TODO: obstaclesOption can be All, FullHeightOnly or None. Reflect that?
                gameStatus.modObstacles = mainSetupData.gameplayOptions.obstaclesOption.ToString();
                gameStatus.modNoEnergy  = mainSetupData.gameplayOptions.noEnergy;
                gameStatus.modMirror    = mainSetupData.gameplayOptions.mirror;

                statusManager.EmitStatusUpdate(ChangedProperties.AllButNoteCut, "songStart");
            }
            else
            {
                statusManager.EmitStatusUpdate(ChangedProperties.AllButNoteCut, "scene");
            }
        }
Example #13
0
        private void ReceivedFromServer(string[] _data)
        {
            lastCommands.Clear();
            foreach (string data in _data)
            {
                try
                {
                    ServerCommand command = JsonUtility.FromJson <ServerCommand>(data);
                    lastCommands.Add(command);

                    if (command.commandType == ServerCommandType.SetPlayerInfos)
                    {
                        if (command.scoreboardScoreFormat != null)
                        {
                            scoreboardScoreFormat = command.scoreboardScoreFormat;
                        }

                        _playerInfos.Clear();
                        foreach (string playerStr in command.playerInfos)
                        {
                            PlayerInfo player = JsonUtility.FromJson <PlayerInfo>(playerStr);
                            if (!String.IsNullOrEmpty(player.playerAvatar) && Config.Instance.ShowAvatarsInGame)
                            {
                                byte[] avatar = Convert.FromBase64String(player.playerAvatar);

                                player.rightHandPos = Serialization.ToVector3(avatar.Take(12).ToArray());
                                player.leftHandPos  = Serialization.ToVector3(avatar.Skip(12).Take(12).ToArray());
                                player.headPos      = Serialization.ToVector3(avatar.Skip(24).Take(12).ToArray());

                                player.rightHandRot = Serialization.ToQuaternion(avatar.Skip(36).Take(16).ToArray());
                                player.leftHandRot  = Serialization.ToQuaternion(avatar.Skip(52).Take(16).ToArray());
                                player.headRot      = Serialization.ToQuaternion(avatar.Skip(68).Take(16).ToArray());
                            }
                            _playerInfos.Add(player);
                        }

                        localPlayerIndex = FindIndexInList(_playerInfos, localPlayerInfo);

                        if (Config.Instance.ShowAvatarsInGame)
                        {
                            try
                            {
                                if (_avatars.Count > _playerInfos.Count)
                                {
                                    List <AvatarController> avatarsToRemove = new List <AvatarController>();
                                    for (int i = _playerInfos.Count; i < _avatars.Count; i++)
                                    {
                                        avatarsToRemove.Add(_avatars[i]);
                                    }
                                    foreach (AvatarController avatar in avatarsToRemove)
                                    {
                                        _avatars.Remove(avatar);
                                        Destroy(avatar.gameObject);
                                    }
                                }
                                else if (_avatars.Count < _playerInfos.Count)
                                {
                                    for (int i = 0; i < (_playerInfos.Count - _avatars.Count); i++)
                                    {
                                        _avatars.Add(new GameObject("Avatar").AddComponent <AvatarController>());
                                    }
                                }

                                List <PlayerInfo> _playerInfosByID = _playerInfos.OrderBy(x => x.playerId).ToList();
                                for (int i = 0; i < _playerInfos.Count; i++)
                                {
                                    _avatars[i].SetPlayerInfo(_playerInfosByID[i], (i - FindIndexInList(_playerInfosByID, localPlayerInfo)) * 3f, localPlayerInfo.Equals(_playerInfosByID[i]));
                                }
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine($"AVATARS EXCEPTION: {e}");
                            }
                        }


                        if (_playerInfos.Count <= 5)
                        {
                            for (int i = 0; i < _playerInfos.Count; i++)
                            {
                                scoreDisplays[i].UpdatePlayerInfo(_playerInfos[i], FindIndexInList(_playerInfos, _playerInfos[i]));
                            }
                            for (int i = _playerInfos.Count; i < scoreDisplays.Count; i++)
                            {
                                scoreDisplays[i].UpdatePlayerInfo(null, 0);
                            }
                        }
                        else
                        {
                            if (localPlayerIndex < 3)
                            {
                                for (int i = 0; i < 5; i++)
                                {
                                    scoreDisplays[i].UpdatePlayerInfo(_playerInfos[i], FindIndexInList(_playerInfos, _playerInfos[i]));
                                }
                            }
                            else if (localPlayerIndex > _playerInfos.Count - 3)
                            {
                                for (int i = _playerInfos.Count - 5; i < _playerInfos.Count; i++)
                                {
                                    scoreDisplays[i - (_playerInfos.Count - 5)].UpdatePlayerInfo(_playerInfos[i], FindIndexInList(_playerInfos, _playerInfos[i]));
                                }
                            }
                            else
                            {
                                for (int i = localPlayerIndex - 2; i < localPlayerIndex + 3; i++)
                                {
                                    scoreDisplays[i - (localPlayerIndex - 2)].UpdatePlayerInfo(_playerInfos[i], FindIndexInList(_playerInfos, _playerInfos[i]));
                                }
                            }
                        }

                        if (PlayerInfosReceived != null)
                        {
                            PlayerInfosReceived.Invoke(_playerInfos);
                        }
                    }
                    else if (command.commandType == ServerCommandType.Kicked)
                    {
                        Console.WriteLine($"You were kicked! Reason: {command.kickReason}");
                        GameSongController controller = Resources.FindObjectsOfTypeAll <GameSongController>().FirstOrDefault();
                        ReflectionUtil.SetPrivateField(controller, "_songDidFinish", true);
                        controller.SendSongDidFinishEvent();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("EXCEPTION ON RECEIVED: " + e);
                }
            }

            StartCoroutine(ReceiveFromServerCoroutine());
        }
Example #14
0
        public async void HandleSongStart()
        {
            GameStatus gameStatus = statusManager.gameStatus;

            // Check for multiplayer early to abort if needed: gameplay controllers don't exist in multiplayer until later
            multiplayerSessionManager = FindFirstOrDefaultOptional <MultiplayerSessionManager>();
            multiplayerController     = FindFirstOrDefaultOptional <MultiplayerController>();

            if (multiplayerSessionManager && multiplayerController)
            {
                Plugin.log.Debug("Multiplayer Level loaded");

                // public event Action<DisconnectedReason> MultiplayerSessionManager#disconnectedEvent;
                multiplayerSessionManager.disconnectedEvent += OnMultiplayerDisconnected;

                // public event Action<State> MultiplayerController#stateChangedEvent;
                multiplayerController.stateChangedEvent += OnMultiplayerStateChanged;

                // Do nothing until the next state change to Gameplay.
                if (multiplayerController.state != MultiplayerController.State.Gameplay)
                {
                    return;
                }

                multiplayerLocalActivePlayerFacade = FindFirstOrDefaultOptional <MultiplayerLocalActivePlayerFacade>();

                if (multiplayerLocalActivePlayerFacade != null)
                {
                    multiplayerLocalActivePlayerFacade.playerDidFinishEvent += OnMultiplayerLevelFinished;
                }
            }
            else if (!doDelayedSongStart)
            {
                doDelayedSongStart = true;

                return;
            }

            // `wants_to_play_next_level` is set for players who don't want to play the song aka want to spectate aka are not "active". `isSpectating` is apparently not spectating.
            gameStatus.scene       = multiplayerSessionManager.isSpectating || !multiplayerSessionManager.LocalPlayerHasState(NetworkConstants.wantsToPlayNextLevel) ? "Spectator" : "Song";
            gameStatus.multiplayer = multiplayerSessionManager.isConnectingOrConnected;

            pauseController = FindFirstOrDefaultOptional <PauseController>();
            scoreController = FindFirstOrDefault <ScoreController>();
            gameplayManager = FindFirstOrDefaultOptional <StandardLevelGameplayManager>() as MonoBehaviour ?? FindFirstOrDefaultOptional <MissionLevelGameplayManager>();
            beatmapObjectCallbackController  = FindFirstOrDefault <BeatmapObjectCallbackController>();
            gameplayModifiersSO              = FindFirstOrDefault <GameplayModifiersModelSO>();
            audioTimeSyncController          = FindFirstOrDefault <AudioTimeSyncController>();
            playerHeadAndObstacleInteraction = (PlayerHeadAndObstacleInteraction)scoreControllerHeadAndObstacleInteractionField.GetValue(scoreController);
            gameSongController = FindFirstOrDefault <GameSongController>();
            gameEnergyCounter  = FindFirstOrDefault <GameEnergyCounter>();

            if (multiplayerController)
            {
                // NOOP
            }
            else if (gameplayManager is StandardLevelGameplayManager)
            {
                Plugin.log.Debug("Standard Level loaded");
            }
            else if (gameplayManager is MissionLevelGameplayManager)
            {
                Plugin.log.Debug("Mission Level loaded");
            }

            gameplayCoreSceneSetupData = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData;

            // Register event listeners
            // PauseController doesn't exist in multiplayer
            if (pauseController != null)
            {
                // public event Action PauseController#didPauseEvent;
                pauseController.didPauseEvent += OnGamePause;
                // public event Action PauseController#didResumeEvent;
                pauseController.didResumeEvent += OnGameResume;
            }
            // public ScoreController#noteWasCutEvent<NoteData, NoteCutInfo, int multiplier> // called after AfterCutScoreBuffer is created
            scoreController.noteWasCutEvent += OnNoteWasCut;
            // public ScoreController#noteWasMissedEvent<NoteData, int multiplier>
            scoreController.noteWasMissedEvent += OnNoteWasMissed;
            // public ScoreController#scoreDidChangeEvent<int, int> // score
            scoreController.scoreDidChangeEvent += OnScoreDidChange;
            // public ScoreController#comboDidChangeEvent<int> // combo
            scoreController.comboDidChangeEvent += OnComboDidChange;
            // public ScoreController#multiplierDidChangeEvent<int, float> // multiplier, progress [0..1]
            scoreController.multiplierDidChangeEvent += OnMultiplierDidChange;
            // public event Action<BeatmapEventData> BeatmapObjectCallbackController#beatmapEventDidTriggerEvent
            beatmapObjectCallbackController.beatmapEventDidTriggerEvent += OnBeatmapEventDidTrigger;
            // public event Action GameSongController#songDidFinishEvent;
            gameSongController.songDidFinishEvent += OnLevelFinished;
            // public event Action GameEnergyCounter#gameEnergyDidReach0Event;
            gameEnergyCounter.gameEnergyDidReach0Event += OnEnergyDidReach0Event;
            if (gameplayManager is ILevelEndActions levelEndActions)
            {
                // event Action levelFailedEvent;
                levelEndActions.levelFailedEvent += OnLevelFailed;
            }

            IDifficultyBeatmap diff  = gameplayCoreSceneSetupData.difficultyBeatmap;
            IBeatmapLevel      level = diff.level;

            gameStatus.partyMode = Gamemode.IsPartyActive;
            gameStatus.mode      = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData.difficultyBeatmap.parentDifficultyBeatmapSet.beatmapCharacteristic.serializedName;

            gameplayModifiers = gameplayCoreSceneSetupData.gameplayModifiers;
            PlayerSpecificSettings playerSettings   = gameplayCoreSceneSetupData.playerSpecificSettings;
            PracticeSettings       practiceSettings = gameplayCoreSceneSetupData.practiceSettings;

            float songSpeedMul = gameplayModifiers.songSpeedMul;

            if (practiceSettings != null)
            {
                songSpeedMul = practiceSettings.songSpeedMul;
            }

            // Generate NoteData to id mappings for backwards compatiblity with <1.12.1
            noteToIdMapping = new NoteData[diff.beatmapData.cuttableNotesType + diff.beatmapData.bombsCount];
            lastNoteId      = 0;

            int beatmapObjectId    = 0;
            var beatmapObjectsData = diff.beatmapData.beatmapObjectsData;

            foreach (BeatmapObjectData beatmapObjectData in beatmapObjectsData)
            {
                if (beatmapObjectData is NoteData noteData)
                {
                    noteToIdMapping[beatmapObjectId++] = noteData;
                }
            }

            gameStatus.songName        = level.songName;
            gameStatus.songSubName     = level.songSubName;
            gameStatus.songAuthorName  = level.songAuthorName;
            gameStatus.levelAuthorName = level.levelAuthorName;
            gameStatus.songBPM         = level.beatsPerMinute;
            gameStatus.noteJumpSpeed   = diff.noteJumpMovementSpeed;
            // 13 is "custom_level_" and 40 is the magic number for the length of the SHA-1 hash
            gameStatus.songHash       = level.levelID.StartsWith("custom_level_") && !level.levelID.EndsWith(" WIP") ? level.levelID.Substring(13, 40) : null;
            gameStatus.levelId        = level.levelID;
            gameStatus.songTimeOffset = (long)(level.songTimeOffset * 1000f / songSpeedMul);
            gameStatus.length         = (long)(level.beatmapLevelData.audioClip.length * 1000f / songSpeedMul);
            gameStatus.start          = GetCurrentTime() - (long)(audioTimeSyncController.songTime * 1000f / songSpeedMul);
            if (practiceSettings != null)
            {
                gameStatus.start -= (long)(practiceSettings.startSongTime * 1000f / songSpeedMul);
            }
            gameStatus.paused          = 0;
            gameStatus.difficulty      = diff.difficulty.Name();
            gameStatus.notesCount      = diff.beatmapData.cuttableNotesType;
            gameStatus.bombsCount      = diff.beatmapData.bombsCount;
            gameStatus.obstaclesCount  = diff.beatmapData.obstaclesCount;
            gameStatus.environmentName = level.environmentInfo.sceneInfo.sceneName;

            try {
                // From https://support.unity3d.com/hc/en-us/articles/206486626-How-can-I-get-pixels-from-unreadable-textures-
                var texture   = (await level.GetCoverImageAsync(CancellationToken.None)).texture;
                var active    = RenderTexture.active;
                var temporary = RenderTexture.GetTemporary(
                    texture.width,
                    texture.height,
                    0,
                    RenderTextureFormat.Default,
                    RenderTextureReadWrite.Linear
                    );

                Graphics.Blit(texture, temporary);
                RenderTexture.active = temporary;

                var cover = new Texture2D(texture.width, texture.height);
                cover.ReadPixels(new Rect(0, 0, temporary.width, temporary.height), 0, 0);
                cover.Apply();

                RenderTexture.active = active;
                RenderTexture.ReleaseTemporary(temporary);

                gameStatus.songCover = System.Convert.ToBase64String(
                    ImageConversion.EncodeToPNG(cover)
                    );
            } catch {
                gameStatus.songCover = null;
            }

            gameStatus.ResetPerformance();

            UpdateModMultiplier();

            gameStatus.songSpeedMultiplier = songSpeedMul;
            gameStatus.batteryLives        = gameEnergyCounter.batteryLives;

            gameStatus.modObstacles          = gameplayModifiers.enabledObstacleType.ToString();
            gameStatus.modInstaFail          = gameplayModifiers.instaFail;
            gameStatus.modNoFail             = gameplayModifiers.noFailOn0Energy;
            gameStatus.modBatteryEnergy      = gameplayModifiers.energyType == GameplayModifiers.EnergyType.Battery;
            gameStatus.modDisappearingArrows = gameplayModifiers.disappearingArrows;
            gameStatus.modNoBombs            = gameplayModifiers.noBombs;
            gameStatus.modSongSpeed          = gameplayModifiers.songSpeed.ToString();
            gameStatus.modNoArrows           = gameplayModifiers.noArrows;
            gameStatus.modGhostNotes         = gameplayModifiers.ghostNotes;
            gameStatus.modFailOnSaberClash   = gameplayModifiers.failOnSaberClash;
            gameStatus.modStrictAngles       = gameplayModifiers.strictAngles;
            gameStatus.modFastNotes          = gameplayModifiers.fastNotes;

            gameStatus.staticLights = playerSettings.staticLights;
            gameStatus.leftHanded   = playerSettings.leftHanded;
            gameStatus.playerHeight = playerSettings.playerHeight;
            gameStatus.sfxVolume    = playerSettings.sfxVolume;
            gameStatus.reduceDebris = playerSettings.reduceDebris;
            gameStatus.noHUD        = playerSettings.noTextsAndHuds;
            gameStatus.advancedHUD  = playerSettings.advancedHud;
            gameStatus.autoRestart  = playerSettings.autoRestart;

            statusManager.EmitStatusUpdate(ChangedProperties.AllButNoteCut, "songStart");
        }
 public EndlessGameInstaller(GameSongController gameSongController)
 {
     _gameSongController = gameSongController;
 }