Skip to content

Commit 478e9a6

Browse files
committed
feat: StartupDirectToGame
1 parent 62cbfb5 commit 478e9a6

3 files changed

Lines changed: 357 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,4 +375,5 @@ tools
375375
AquaMai/BuildInfo.g.cs
376376
.idea
377377
.sisyphus
378+
.omo
378379
*.lscache
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Globalization;
3+
using DB;
4+
5+
namespace AquaMai.Mods.Types;
6+
7+
public static class OptionSpeedParser
8+
{
9+
public static bool TryParse(string input, out OptionNotespeedID noteSpeed)
10+
{
11+
noteSpeed = OptionNotespeedID.Invalid;
12+
if (!TryParseCommonSpeed(input, out var speed)) return false;
13+
14+
noteSpeed = speed == SonicSpeed ? OptionNotespeedID.Speed_Sonic : (OptionNotespeedID)speed;
15+
return true;
16+
}
17+
18+
public static OptionNotespeedID ParseOrDefault(string input, OptionNotespeedID fallback)
19+
{
20+
return TryParse(input, out OptionNotespeedID noteSpeed) ? noteSpeed : fallback;
21+
}
22+
23+
public static bool TryParse(string input, out OptionTouchspeedID touchSpeed)
24+
{
25+
touchSpeed = OptionTouchspeedID.Invalid;
26+
if (!TryParseCommonSpeed(input, out var speed)) return false;
27+
28+
touchSpeed = speed == SonicSpeed ? OptionTouchspeedID.Speed_Sonic : (OptionTouchspeedID)speed;
29+
return true;
30+
}
31+
32+
public static OptionTouchspeedID ParseOrDefault(string input, OptionTouchspeedID fallback)
33+
{
34+
return TryParse(input, out OptionTouchspeedID touchSpeed) ? touchSpeed : fallback;
35+
}
36+
37+
public static bool TryParse(string input, out OptionSlidespeedID slideSpeed)
38+
{
39+
slideSpeed = OptionSlidespeedID.Invalid;
40+
var value = input?.Trim();
41+
if (string.IsNullOrEmpty(value)) return false;
42+
43+
if (string.Equals(value, "normal", StringComparison.OrdinalIgnoreCase))
44+
{
45+
slideSpeed = OptionSlidespeedID.Normal;
46+
return true;
47+
}
48+
49+
if (TryParseRelativeValue(value, "fast", out var fastValue)) return TrySetSlideSpeed(-fastValue, out slideSpeed);
50+
if (TryParseRelativeValue(value, "late", out var lateValue)) return TrySetSlideSpeed(lateValue, out slideSpeed);
51+
52+
return decimal.TryParse(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var numericValue)
53+
&& TrySetSlideSpeed(numericValue, out slideSpeed);
54+
}
55+
56+
public static OptionSlidespeedID ParseOrDefault(string input, OptionSlidespeedID fallback)
57+
{
58+
return TryParse(input, out OptionSlidespeedID slideSpeed) ? slideSpeed : fallback;
59+
}
60+
61+
private const int SonicSpeed = 37;
62+
63+
private static bool TryParseCommonSpeed(string input, out int speed)
64+
{
65+
speed = (int)OptionNotespeedID.Invalid;
66+
var value = input?.Trim();
67+
if (string.IsNullOrEmpty(value)) return false;
68+
69+
if (string.Equals(value, "sonic", StringComparison.OrdinalIgnoreCase))
70+
{
71+
speed = SonicSpeed;
72+
return true;
73+
}
74+
75+
if (!decimal.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var numericValue)) return false;
76+
if (numericValue < 1m || numericValue > 10m || numericValue * 4m != decimal.Truncate(numericValue * 4m)) return false;
77+
78+
speed = (int)((numericValue - 1m) * 4m);
79+
return true;
80+
}
81+
82+
private static bool TryParseRelativeValue(string input, string prefix, out decimal value)
83+
{
84+
value = 0m;
85+
if (!input.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return false;
86+
87+
var suffix = input.Substring(prefix.Length).TrimStart(' ', '_');
88+
return decimal.TryParse(suffix, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value);
89+
}
90+
91+
private static bool TrySetSlideSpeed(decimal numericValue, out OptionSlidespeedID slideSpeed)
92+
{
93+
slideSpeed = OptionSlidespeedID.Invalid;
94+
if (numericValue < -1m || numericValue > 1m || numericValue * 10m != decimal.Truncate(numericValue * 10m)) return false;
95+
96+
slideSpeed = (OptionSlidespeedID)((int)((numericValue + 1m) * 10m));
97+
return true;
98+
}
99+
}

0 commit comments

Comments
 (0)