|
| 1 | +using System; |
| 2 | +using System.Diagnostics; |
| 3 | +using AquaMai.Config.Attributes; |
| 4 | +using AquaMai.Core.Attributes; |
| 5 | +using AquaMai.Core.Helpers; |
| 6 | +using AquaMai.Mods.Types; |
| 7 | +using DB; |
| 8 | +using HarmonyLib; |
| 9 | +using IO; |
| 10 | +using MAI2.Util; |
| 11 | +using Manager; |
| 12 | +using Net; |
| 13 | +using MelonLoader; |
| 14 | +using Process; |
| 15 | +using System.Collections; |
| 16 | + |
| 17 | +namespace AquaMai.Mods.GameSystem; |
| 18 | + |
| 19 | +[ConfigSection( |
| 20 | + name: "启动直达乐曲", |
| 21 | + en: "Launch directly into the song ID specified by --music after startup.", |
| 22 | + zh: "启动流程结束后直接进入通过 --music 传入的歌曲 ID")] |
| 23 | +[EnableIf(nameof(ShouldEnableImplicitly))] |
| 24 | +[EnableImplicitlyIf(nameof(ShouldEnableImplicitly))] |
| 25 | +public class StartupDirectToGame |
| 26 | +{ |
| 27 | + private const byte ReadyState = 8; |
| 28 | + private const byte ReleasedState = 9; |
| 29 | + private const int StandardScoreType = 0; |
| 30 | + private const int DeluxeScoreType = 1; |
| 31 | + private const int DeluxeMusicIdThreshold = 10000; |
| 32 | + |
| 33 | + [ConfigEntry( |
| 34 | + name: "缺省难度", |
| 35 | + zh: "如果没有在命令行中传入难度(--difficulty)的话,默认使用的难度", |
| 36 | + en: "Default difficulty to use if not provided via command line" |
| 37 | + )] |
| 38 | + private static readonly int DefaultDifficulty = 3; |
| 39 | + |
| 40 | + [ConfigEntry( |
| 41 | + name: "缺省 Note Speed", |
| 42 | + zh: "如果没有在命令行中传入 Note Speed(--noteSpeed)的话,默认使用的 Note Speed", |
| 43 | + en: "Default note speed to use if not provided via command line" |
| 44 | + )] |
| 45 | + private static readonly string DefaultNoteSpeed = "6.5"; |
| 46 | + |
| 47 | + [ConfigEntry( |
| 48 | + name: "缺省 Touch Speed", |
| 49 | + zh: "如果没有在命令行中传入 Touch Speed(--touchSpeed)的话,默认使用的 Touch Speed", |
| 50 | + en: "Default touch speed to use if not provided via command line" |
| 51 | + )] |
| 52 | + private static readonly string DefaultTouchSpeed = "6.5"; |
| 53 | + |
| 54 | + [ConfigEntry( |
| 55 | + name: "缺省 Slide Speed", |
| 56 | + zh: "如果没有在命令行中传入 Slide Speed(--slideSpeed)的话,默认使用的 Slide Speed。例如 normal、-0.5、0.5", |
| 57 | + en: "Default slide speed to use if not provided via command line. Examples: normal, -0.5, 0.5" |
| 58 | + )] |
| 59 | + private static readonly string DefaultSlideSpeed = "normal"; |
| 60 | + |
| 61 | + [ConfigEntry( |
| 62 | + name: "缺省 Autoplay 类型", |
| 63 | + zh: "如果没有在命令行中传入 Autoplay 类型(--autoplay)的话,默认使用的类型。例如 Critical、Perfect、Great、Good、Miss、Random、None", |
| 64 | + en: "Default autoplay type to use if not provided via command line. Examples: Critical, Perfect, Great, Good, Miss, Random, None" |
| 65 | + )] |
| 66 | + private static readonly string DefaultAutoplay = "Critical"; |
| 67 | + |
| 68 | + private static bool _argsParsed; |
| 69 | + private static int? _musicId; |
| 70 | + private static int _difficulty; |
| 71 | + private static OptionNotespeedID _noteSpeed; |
| 72 | + private static OptionTouchspeedID _touchSpeed; |
| 73 | + private static OptionSlidespeedID _slideSpeed; |
| 74 | + private static GameManager.AutoPlayMode _autoplay; |
| 75 | + |
| 76 | + public static bool ShouldEnableImplicitly |
| 77 | + { |
| 78 | + get |
| 79 | + { |
| 80 | + ParseCommandLineArgs(); |
| 81 | + return _musicId.HasValue; |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + public static void OnBeforeEnableCheck() |
| 86 | + { |
| 87 | + ParseCommandLineArgs(); |
| 88 | + } |
| 89 | + |
| 90 | + [HarmonyPrefix] |
| 91 | + [HarmonyPatch(typeof(StartupProcess), "OnUpdate")] |
| 92 | + [HarmonyPriority(Priority.HigherThanNormal)] |
| 93 | + public static bool StartupProcessPreOnUpdate(StartupProcess __instance, ProcessDataContainer ___container, ref byte ____state, Stopwatch ___timer) |
| 94 | + { |
| 95 | + if (____state != ReadyState) return true; |
| 96 | + |
| 97 | + GameManager.Initialize(); |
| 98 | + PrepareDummyGuestUser(); |
| 99 | + PrepareGameStartState(); |
| 100 | + |
| 101 | + ____state = ReleasedState; |
| 102 | + Singleton<OperationManager>.Instance.SetCoinBlockerMode(CoinBlocker.Mode.Game); |
| 103 | + GameManager.IsInitializeEnd = true; |
| 104 | + ___container.processManager.AddProcess(new CommonProcess(___container), 10); |
| 105 | + ___container.processManager.AddProcess(new PleaseWaitProcess(___container), 50); |
| 106 | + ___container.processManager.ReleaseProcess(__instance); |
| 107 | + |
| 108 | + MelonCoroutines.Start(NextFrameInit(___container, __instance)); |
| 109 | + return false; |
| 110 | + } |
| 111 | + |
| 112 | + [HarmonyPostfix] |
| 113 | + [HarmonyPatch(typeof(GameProcess), "OnStart")] |
| 114 | + public static void GameProcessPostOnStart() |
| 115 | + { |
| 116 | + GameManager.AutoPlay = _autoplay; |
| 117 | + } |
| 118 | + |
| 119 | + private static IEnumerator NextFrameInit(ProcessDataContainer container, ProcessBase process) |
| 120 | + { |
| 121 | + yield return null; |
| 122 | + container.processManager.AddProcess(new FadeProcess(container, process, new TrackStartProcess(container)), 50); |
| 123 | + container.processManager.ClearTimeoutAction(); |
| 124 | + container.processManager.SendMessage(new Message(ProcessType.CommonProcess, CommonProcess.MessageID_EntryInfoOut, false)); |
| 125 | + container.processManager.SetVisibleTimers(isVisible: false); |
| 126 | + container.processManager.IsTimeCounting(isTimeCount: false); |
| 127 | + SoundManager.PreviewEnd(); |
| 128 | + SoundManager.StopBGM(2); |
| 129 | + } |
| 130 | + |
| 131 | + private static void PrepareDummyGuestUser() |
| 132 | + { |
| 133 | + var userDataManager = Singleton<UserDataManager>.Instance; |
| 134 | + for (var i = 0; i < 2; i++) |
| 135 | + { |
| 136 | + var userData = userDataManager.GetUserData(i); |
| 137 | + userData.Initialize(); |
| 138 | + if (i != 0) |
| 139 | + { |
| 140 | + userDataManager.SetDefault(i); |
| 141 | + continue; |
| 142 | + } |
| 143 | + |
| 144 | + userData.SetEntry(isEntry: true); |
| 145 | + userData.SetActiveUser(isActiveUser: true); |
| 146 | + userData.UserType = UserData.UserIDType.Guest; |
| 147 | + userData.Detail.UserID = UserID.GuestID(); |
| 148 | + userData.Detail.UserName = CommonMessageID.GuestUserName.GetName(); |
| 149 | + userDataManager.SetGuestContentBit(i); |
| 150 | + userDataManager.SetDefault(i); |
| 151 | + userData.Option.OptionKind = OptionKindID.Custom; |
| 152 | + userData.Option.NoteSpeed = _noteSpeed; |
| 153 | + userData.Option.TouchSpeed = _touchSpeed; |
| 154 | + userData.Option.SlideSpeed = _slideSpeed; |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + private static void PrepareGameStartState() |
| 159 | + { |
| 160 | + var musicId = _musicId.Value; |
| 161 | + var dataManager = Singleton<DataManager>.Instance; |
| 162 | + var music = dataManager.GetMusic(musicId); |
| 163 | + |
| 164 | + Shim.Set_GameManager_IsNormalMode(true); |
| 165 | + GameManager.IsLongMusic = dataManager.IsLong(music.longMusic); |
| 166 | + GameManager.SelectedDeleteGhostID = GhostManager.GhostTarget.End; |
| 167 | + GameManager.SelectScoreType = GetScoreType(musicId); |
| 168 | + GameManager.MusicTrackNumber = 1; |
| 169 | + GameManager.SetMaxTrack(); |
| 170 | + |
| 171 | + GameManager.SelectMusicID[0] = musicId; |
| 172 | + GameManager.SelectDifficultyID[0] = _difficulty; |
| 173 | + GameManager.SelectGhostID[0] = GhostManager.GhostTarget.End; |
| 174 | + } |
| 175 | + |
| 176 | + private static void ParseCommandLineArgs() |
| 177 | + { |
| 178 | + if (_argsParsed) return; |
| 179 | + _argsParsed = true; |
| 180 | + |
| 181 | + _difficulty = DefaultDifficulty; |
| 182 | + _noteSpeed = OptionSpeedParser.ParseOrDefault(DefaultNoteSpeed, OptionNotespeedID.Speed6_5); |
| 183 | + _touchSpeed = OptionSpeedParser.ParseOrDefault(DefaultTouchSpeed, OptionTouchspeedID.Speed6_5); |
| 184 | + _slideSpeed = OptionSpeedParser.ParseOrDefault(DefaultSlideSpeed, OptionSlidespeedID.Normal); |
| 185 | + _autoplay = ParseAutoplay(DefaultAutoplay, GameManager.AutoPlayMode.Critical); |
| 186 | + |
| 187 | + var args = Environment.GetCommandLineArgs(); |
| 188 | + for (var i = 0; i < args.Length; i++) |
| 189 | + { |
| 190 | + if (!TryReadOptionValue(args, ref i, "--music", out var value) || !int.TryParse(value, out var musicId)) continue; |
| 191 | + _musicId = musicId; |
| 192 | + break; |
| 193 | + } |
| 194 | + |
| 195 | + if (!_musicId.HasValue) return; |
| 196 | + |
| 197 | + for (var i = 0; i < args.Length; i++) |
| 198 | + { |
| 199 | + if (TryReadOptionValue(args, ref i, "--difficulty", out var difficultyValue)) |
| 200 | + { |
| 201 | + if (int.TryParse(difficultyValue, out var difficulty)) _difficulty = difficulty; |
| 202 | + continue; |
| 203 | + } |
| 204 | + |
| 205 | + if (TryReadOptionValue(args, ref i, "--noteSpeed", out var noteSpeedValue)) |
| 206 | + { |
| 207 | + _noteSpeed = OptionSpeedParser.ParseOrDefault(noteSpeedValue, _noteSpeed); |
| 208 | + continue; |
| 209 | + } |
| 210 | + |
| 211 | + if (TryReadOptionValue(args, ref i, "--touchSpeed", out var touchSpeedValue)) |
| 212 | + { |
| 213 | + _touchSpeed = OptionSpeedParser.ParseOrDefault(touchSpeedValue, _touchSpeed); |
| 214 | + continue; |
| 215 | + } |
| 216 | + |
| 217 | + if (TryReadOptionValue(args, ref i, "--slideSpeed", out var slideSpeedValue)) |
| 218 | + { |
| 219 | + _slideSpeed = OptionSpeedParser.ParseOrDefault(slideSpeedValue, _slideSpeed); |
| 220 | + continue; |
| 221 | + } |
| 222 | + |
| 223 | + if (TryReadOptionValue(args, ref i, "--autoplay", out var autoplayValue) || TryReadOptionValue(args, ref i, "--autoPlay", out autoplayValue)) |
| 224 | + { |
| 225 | + _autoplay = ParseAutoplay(autoplayValue, _autoplay); |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + private static bool TryReadOptionValue(string[] args, ref int index, string name, out string value) |
| 231 | + { |
| 232 | + value = null; |
| 233 | + var arg = args[index]; |
| 234 | + if (arg == name) |
| 235 | + { |
| 236 | + if (index + 1 >= args.Length) return false; |
| 237 | + value = args[++index]; |
| 238 | + return true; |
| 239 | + } |
| 240 | + |
| 241 | + var prefix = name + "="; |
| 242 | + if (!arg.StartsWith(prefix, StringComparison.Ordinal)) return false; |
| 243 | + |
| 244 | + value = arg.Substring(prefix.Length); |
| 245 | + return true; |
| 246 | + } |
| 247 | + |
| 248 | + private static int GetScoreType(int musicId) |
| 249 | + { |
| 250 | + return musicId >= DeluxeMusicIdThreshold ? DeluxeScoreType : StandardScoreType; |
| 251 | + } |
| 252 | + |
| 253 | + private static GameManager.AutoPlayMode ParseAutoplay(string value, GameManager.AutoPlayMode fallback) |
| 254 | + { |
| 255 | + return Enum.TryParse(value, ignoreCase: true, out GameManager.AutoPlayMode autoplay) ? autoplay : fallback; |
| 256 | + } |
| 257 | +} |
0 commit comments