forked from Cookies_Github_mirror/AquaDX
[RF] AquaMai configuration refactor (#82)
更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用) 旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们 Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default) Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
This commit is contained in:
26
AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs
Normal file
26
AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using HarmonyLib;
|
||||
using Process;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(exampleHidden: true, defaultOn: true)]
|
||||
[EnableIf(nameof(isCiBuild))]
|
||||
public class CiBuildAlert
|
||||
{
|
||||
# if CI
|
||||
private static readonly bool isCiBuild = true;
|
||||
# else
|
||||
private static readonly bool isCiBuild = false;
|
||||
# endif
|
||||
|
||||
[HarmonyPatch(typeof(AdvertiseProcess), "OnStart")]
|
||||
[HarmonyPostfix]
|
||||
public static void OnStart(AdvertiseProcess __instance)
|
||||
{
|
||||
MessageHelper.ShowMessage(Locale.CiBuildAlertContent, title: Locale.CiBuildAlertTitle);
|
||||
}
|
||||
}
|
||||
136
AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs
Normal file
136
AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
using AquaMai.Core.Helpers;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Process;
|
||||
using Util;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
en: "One key to hide all self-made charts in the music select process. Or hide for some users.",
|
||||
zh: "在选曲界面一键隐藏所有自制谱,或对一部分用户进行隐藏")]
|
||||
public class HideSelfMadeCharts
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Key to toggle self-made charts.",
|
||||
zh: "切换自制谱显示的按键")]
|
||||
public static readonly KeyCodeOrName key = KeyCodeOrName.Test;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly bool longPress = false;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "One user ID per line in the file. Hide self-made charts when these users login.",
|
||||
zh: "该文件中每行一个用户 ID,当这些用户登录时隐藏自制谱")]
|
||||
private static readonly string selfMadeChartsDenyUsersFile = "LocalAssets/SelfMadeChartsDenyUsers.txt";
|
||||
|
||||
[ConfigEntry(
|
||||
en: "One user ID per line in the file. Only show self-made charts when these users login.",
|
||||
zh: "该文件中每行一个用户 ID,只有这些用户登录时才显示自制谱")]
|
||||
private static readonly string selfMadeChartsWhiteListUsersFile = "LocalAssets/SelfMadeChartsWhiteListUsers.txt";
|
||||
|
||||
private static Safe.ReadonlySortedDictionary<int, Manager.MaiStudio.MusicData> _musics;
|
||||
private static Safe.ReadonlySortedDictionary<int, Manager.MaiStudio.MusicData> _musicsNoneSelfMade;
|
||||
|
||||
private static bool isShowSelfMadeCharts = true;
|
||||
private static bool isForceDisable;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(DataManager), "GetMusics")]
|
||||
public static void GetMusics(ref Safe.ReadonlySortedDictionary<int, Manager.MaiStudio.MusicData> __result, List<string> ____targetDirs)
|
||||
{
|
||||
if (_musics is null)
|
||||
{
|
||||
// init musics for the first time
|
||||
if (__result.Count == 0) return;
|
||||
_musics = __result;
|
||||
var nonSelfMadeList = new SortedDictionary<int, Manager.MaiStudio.MusicData>();
|
||||
var officialDirs = ____targetDirs.Where(it => File.Exists(Path.Combine(it, "DataConfig.xml")) || File.Exists(Path.Combine(it, "OfficialChartsMark.txt")));
|
||||
foreach (var music in __result)
|
||||
{
|
||||
if (officialDirs.Any(it => MusicDirHelper.LookupPath(music.Value).StartsWith(it)))
|
||||
{
|
||||
nonSelfMadeList.Add(music.Key, music.Value);
|
||||
}
|
||||
}
|
||||
|
||||
_musicsNoneSelfMade = new Safe.ReadonlySortedDictionary<int, Manager.MaiStudio.MusicData>(nonSelfMadeList);
|
||||
MelonLogger.Msg($"[HideSelfMadeCharts] All music count: {__result.Count}, Official music count: {_musicsNoneSelfMade.Count}");
|
||||
}
|
||||
|
||||
var stackTrace = new StackTrace(); // get call stack
|
||||
var stackFrames = stackTrace.GetFrames(); // get method calls (frames)
|
||||
if (stackFrames.All(it => it.GetMethod().DeclaringType.Name != "MusicSelectProcess")) return;
|
||||
if (isShowSelfMadeCharts && !isForceDisable) return;
|
||||
__result = _musicsNoneSelfMade;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(MusicSelectProcess), "OnUpdate")]
|
||||
public static void MusicSelectProcessOnUpdate(ref MusicSelectProcess __instance)
|
||||
{
|
||||
if (isForceDisable) return;
|
||||
if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return;
|
||||
isShowSelfMadeCharts = !isShowSelfMadeCharts;
|
||||
MelonLogger.Msg($"[HideSelfMadeCharts] isShowSelfMadeCharts: {isShowSelfMadeCharts}");
|
||||
SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, __instance, new MusicSelectProcess(SharedInstances.ProcessDataContainer)));
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
MessageHelper.ShowMessage($"{(isShowSelfMadeCharts ? "Show" : "Hide")} Self-Made Charts");
|
||||
});
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(MusicSelectProcess), "OnStart")]
|
||||
public static void MusicSelectProcessOnStart(ref MusicSelectProcess __instance)
|
||||
{
|
||||
var denyPath = FileSystem.ResolvePath(selfMadeChartsDenyUsersFile);
|
||||
if (File.Exists(denyPath))
|
||||
{
|
||||
var userIds = File.ReadAllLines(denyPath);
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
var user = Singleton<UserDataManager>.Instance.GetUserData(i);
|
||||
if (!user.IsEntry) continue;
|
||||
if (!userIds.Contains(user.Detail.UserID.ToString())) continue;
|
||||
isForceDisable = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var whiteListPath = FileSystem.ResolvePath(selfMadeChartsWhiteListUsersFile);
|
||||
if (File.Exists(whiteListPath))
|
||||
{
|
||||
var userIds = File.ReadAllLines(whiteListPath);
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
var user = Singleton<UserDataManager>.Instance.GetUserData(i);
|
||||
if (!user.IsEntry) continue;
|
||||
if (userIds.Contains(user.Detail.UserID.ToString())) continue;
|
||||
isForceDisable = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isForceDisable = false;
|
||||
}
|
||||
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(EntryProcess), "OnStart")]
|
||||
public static void EntryProcessOnStart(ref EntryProcess __instance)
|
||||
{
|
||||
// reset status on login
|
||||
isShowSelfMadeCharts = true;
|
||||
}
|
||||
}
|
||||
240
AquaMai/AquaMai.Mods/UX/ImmediateSave.cs
Normal file
240
AquaMai/AquaMai.Mods/UX/ImmediateSave.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using DB;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using MAI2System;
|
||||
using Manager;
|
||||
using Manager.MaiStudio;
|
||||
using Manager.UserDatas;
|
||||
using MelonLoader;
|
||||
using Monitor.Entry.Parts.Screens;
|
||||
using Net.Packet;
|
||||
using Net.Packet.Helper;
|
||||
using Process;
|
||||
using Process.UserDataNet.State.UserDataULState;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Save immediate after playing a song.",
|
||||
zh: "打完一首歌的时候立即向服务器保存成绩")]
|
||||
public class ImmediateSave
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StateULUserAime), "RequestUploadUserPlayLogData")]
|
||||
public static bool PreRequestUploadUserPlayLogData(StateULUserAime __instance)
|
||||
{
|
||||
Traverse.Create(__instance).Method("RequestUploadUserPortraitData").GetValue();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SavingUi ui;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ResultProcess), "OnStart")]
|
||||
public static void ResultProcessOnStart()
|
||||
{
|
||||
var doneCount = 0;
|
||||
|
||||
void CheckSaveDone()
|
||||
{
|
||||
doneCount++;
|
||||
if (doneCount != 4) return;
|
||||
if (ui == null) return;
|
||||
UnityEngine.Object.Destroy(ui);
|
||||
ui = null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
var userData = Singleton<UserDataManager>.Instance.GetUserData(i);
|
||||
if (!userData.IsEntry || userData.IsGuest())
|
||||
{
|
||||
doneCount += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ui == null)
|
||||
{
|
||||
ui = SharedInstances.GameMainObject.gameObject.AddComponent<SavingUi>();
|
||||
}
|
||||
|
||||
SaveDataFix(userData);
|
||||
PacketHelper.StartPacket(Shim.CreatePacketUploadUserPlaylog(i, userData, (int)GameManager.MusicTrackNumber - 1,
|
||||
delegate
|
||||
{
|
||||
MelonLogger.Msg("[ImmediateSave] Playlog saved");
|
||||
CheckSaveDone();
|
||||
},
|
||||
delegate(PacketStatus err)
|
||||
{
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, i);
|
||||
MelonLogger.Error("[ImmediateSave] Playlog save error");
|
||||
MelonLogger.Error(err);
|
||||
MessageHelper.ShowMessage("Playlog save error");
|
||||
CheckSaveDone();
|
||||
}));
|
||||
PacketHelper.StartPacket(Shim.CreatePacketUpsertUserAll(i, userData, delegate(int code)
|
||||
{
|
||||
if (code == 1)
|
||||
{
|
||||
MelonLogger.Msg("[ImmediateSave] UserAll saved");
|
||||
CheckSaveDone();
|
||||
}
|
||||
else
|
||||
{
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, i);
|
||||
MelonLogger.Error("[ImmediateSave] UserAll upsert error");
|
||||
MelonLogger.Error(code);
|
||||
MessageHelper.ShowMessage("UserAll upsert error");
|
||||
CheckSaveDone();
|
||||
}
|
||||
}, delegate(PacketStatus err)
|
||||
{
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, i);
|
||||
MelonLogger.Error("[ImmediateSave] UserAll upsert error");
|
||||
MelonLogger.Error(err);
|
||||
MessageHelper.ShowMessage("UserAll upsert error");
|
||||
CheckSaveDone();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void SaveDataFix(UserData userData)
|
||||
{
|
||||
UserDetail detail = userData.Detail;
|
||||
detail.EventWatchedDate = TimeManager.GetDateString(TimeManager.PlayBaseTime);
|
||||
userData.CalcTotalValue();
|
||||
float num = 0f;
|
||||
try
|
||||
{
|
||||
if (userData.RatingList.RatingList.Any())
|
||||
{
|
||||
num = userData.RatingList.RatingList.Last().SingleRate;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
num = (float)Math.Ceiling((double)((num + 1f) / GameManager.TheoryRateBorderNum) * 10.0);
|
||||
float num2 = 0f;
|
||||
try
|
||||
{
|
||||
if (userData.RatingList.NextRatingList.Any())
|
||||
{
|
||||
num2 = userData.RatingList.NewRatingList.Last().SingleRate;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
num2 = (float)Math.Ceiling((double)((num2 + 1f) / GameManager.TheoryRateBorderNum) * 10.0);
|
||||
string logDateString = TimeManager.GetLogDateString(TimeManager.PlayBaseTime);
|
||||
string timeJp = (string.IsNullOrEmpty(userData.Detail.DailyBonusDate) ? TimeManager.GetLogDateString(0L) : userData.Detail.DailyBonusDate);
|
||||
if (userData.IsEntry && userData.Detail.IsNetMember >= 2 && !GameManager.IsEventMode && TimeManager.GetUnixTime(logDateString) > TimeManager.GetUnixTime(timeJp) && Singleton<UserDataManager>.Instance.IsSingleUser() && !GameManager.IsFreedomMode && !GameManager.IsCourseMode && !DoneEntry.IsWeekdayBonus(userData))
|
||||
{
|
||||
userData.Detail.DailyBonusDate = logDateString;
|
||||
}
|
||||
|
||||
List<UserRate> list = new List<UserRate>();
|
||||
List<UserRate> list2 = new List<UserRate>();
|
||||
IEnumerable<UserScore>[] scoreList = Shim.GetUserScoreList(userData);
|
||||
List<UserRate> ratingList = userData.RatingList.RatingList;
|
||||
List<UserRate> newRatingList = userData.RatingList.NewRatingList;
|
||||
int achive = RatingTableID.Rate_22.GetAchive();
|
||||
for (int j = 0; j < scoreList.Length; j++)
|
||||
{
|
||||
if (scoreList[j] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (UserScore item2 in scoreList[j])
|
||||
{
|
||||
if (achive <= item2.achivement)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MusicData music = Singleton<DataManager>.Instance.GetMusic(item2.id);
|
||||
if (music == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UserRate item = new UserRate(item2.id, j, item2.achivement, (uint)music.version);
|
||||
if (item.OldFlag)
|
||||
{
|
||||
if (num <= (float)item.Level && !ratingList.Contains(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
else if (num2 <= (float)item.Level && !newRatingList.Contains(item))
|
||||
{
|
||||
list2.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list.Sort();
|
||||
list.Reverse();
|
||||
if (list.Count > 10)
|
||||
{
|
||||
list.RemoveRange(10, list.Count - 10);
|
||||
}
|
||||
|
||||
userData.RatingList.NextRatingList = list;
|
||||
list2.Sort();
|
||||
list2.Reverse();
|
||||
if (list2.Count > 10)
|
||||
{
|
||||
list2.RemoveRange(10, list2.Count - 10);
|
||||
}
|
||||
|
||||
userData.RatingList.NextNewRatingList = list2;
|
||||
|
||||
userData.Detail.LastPlayCredit = 0;
|
||||
userData.Detail.LastPlayMode = 0;
|
||||
if (GameManager.IsFreedomMode)
|
||||
{
|
||||
userData.Detail.LastPlayMode = 1;
|
||||
}
|
||||
|
||||
if (GameManager.IsCourseMode)
|
||||
{
|
||||
userData.Detail.LastPlayMode = 2;
|
||||
}
|
||||
|
||||
userData.Detail.LastGameId = ConstParameter.GameIDStr;
|
||||
userData.Detail.LastRomVersion = Singleton<SystemConfig>.Instance.config.romVersionInfo.versionNo.versionString;
|
||||
userData.Detail.LastDataVersion = Singleton<SystemConfig>.Instance.config.dataVersionInfo.versionNo.versionString;
|
||||
}
|
||||
|
||||
private class SavingUi : MonoBehaviour
|
||||
{
|
||||
public void OnGUI()
|
||||
{
|
||||
var y = Screen.height * .075f;
|
||||
var width = GuiSizes.FontSize * 20f;
|
||||
var x = GuiSizes.PlayerCenter + GuiSizes.PlayerWidth / 2f - width;
|
||||
var rect = new Rect(x, y, width, GuiSizes.LabelHeight * 2.5f);
|
||||
|
||||
var labelStyle = GUI.skin.GetStyle("label");
|
||||
labelStyle.fontSize = (int)(GuiSizes.FontSize * 1.2);
|
||||
labelStyle.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUI.Box(rect, "");
|
||||
GUI.Label(rect, Locale.SavingDontExit);
|
||||
}
|
||||
}
|
||||
}
|
||||
247
AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs
Normal file
247
AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
using Monitor;
|
||||
using Monitor.Result;
|
||||
using Process;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
zh: "在游戏总结的计分板中显示击打误差的详细信息(以帧为单位)",
|
||||
en: "Show detailed accuracy info in the score board.")]
|
||||
public class JudgeAccuracyInfo
|
||||
{
|
||||
public class AccuracyEntryList
|
||||
{
|
||||
public List<float>[] DiffList = new List<float>[TableRowNames.Length];
|
||||
public List<float>[] RawDiffList = new List<float>[TableRowNames.Length];
|
||||
public HashSet<int> NoteIndices = new();
|
||||
|
||||
public AccuracyEntryList()
|
||||
{
|
||||
for (int i = 0; i < TableRowNames.Length; i++)
|
||||
{
|
||||
DiffList[i] = new List<float>();
|
||||
RawDiffList[i] = new List<float>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AccuracyEntryList[] EntryList = new AccuracyEntryList[2];
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameProcess), "OnStart")]
|
||||
private static void OnGameProcessStartFinish()
|
||||
{
|
||||
for (int i = 0; i < EntryList.Length; i++)
|
||||
{
|
||||
EntryList[i] = new AccuracyEntryList();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ResultProcess), "OnRelease")]
|
||||
private static void OnResultProcessReleaseFinish()
|
||||
{
|
||||
for (int i = 0; i < EntryList.Length; i++)
|
||||
{
|
||||
EntryList[i] = null;
|
||||
Controllers[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class NoteBaseJudgePatch
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
return
|
||||
[
|
||||
AccessTools.Method(typeof(NoteBase), "Judge"),
|
||||
AccessTools.Method(typeof(HoldNote), "JudgeHoldHead"),
|
||||
AccessTools.Method(typeof(BreakHoldNote), "JudgeHoldHead"),
|
||||
AccessTools.Method(typeof(TouchNoteB), "Judge"),
|
||||
AccessTools.Method(typeof(TouchHoldC), "JudgeHoldHead"),
|
||||
];
|
||||
}
|
||||
|
||||
public static void Postfix(
|
||||
NoteBase __instance, bool __result,
|
||||
float ___JudgeTimingDiffMsec, float ___AppearMsec, NoteJudge.EJudgeType ___JudgeType, int ___NoteIndex
|
||||
)
|
||||
{
|
||||
var monitor = __instance.MonitorId;
|
||||
if (!__result || EntryList[monitor].NoteIndices.Contains(___NoteIndex)) return;
|
||||
|
||||
EntryList[monitor].NoteIndices.Add(___NoteIndex);
|
||||
|
||||
var raw = (NotesManager.GetCurrentMsec() - ___AppearMsec) - NoteJudge.JudgeAdjustMs;
|
||||
switch (___JudgeType)
|
||||
{
|
||||
case NoteJudge.EJudgeType.Tap:
|
||||
case NoteJudge.EJudgeType.Break:
|
||||
{
|
||||
EntryList[monitor].DiffList[0].Add(___JudgeTimingDiffMsec);
|
||||
EntryList[monitor].RawDiffList[0].Add(raw);
|
||||
break;
|
||||
}
|
||||
case NoteJudge.EJudgeType.Touch:
|
||||
{
|
||||
EntryList[monitor].DiffList[2].Add(___JudgeTimingDiffMsec);
|
||||
EntryList[monitor].RawDiffList[2].Add(raw);
|
||||
break;
|
||||
}
|
||||
case NoteJudge.EJudgeType.ExTap:
|
||||
{
|
||||
EntryList[monitor].DiffList[3].Add(___JudgeTimingDiffMsec);
|
||||
EntryList[monitor].RawDiffList[3].Add(raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MelonLogger.Msg($"{___JudgeType}: {___JudgeTimingDiffMsec}, {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(SlideRoot), "Judge")]
|
||||
private static void SlideRootJudgePatch(
|
||||
SlideRoot __instance, bool __result,
|
||||
float ___JudgeTimingDiffMsec, float ___TailMsec, float ___lastWaitTimeForJudge,
|
||||
NoteJudge.EJudgeType ___JudgeType, int ___NoteIndex
|
||||
)
|
||||
{
|
||||
var monitor = __instance.MonitorId;
|
||||
if (!__result || EntryList[monitor].NoteIndices.Contains(___NoteIndex)) return;
|
||||
|
||||
EntryList[monitor].NoteIndices.Add(___NoteIndex);
|
||||
|
||||
var raw = (NotesManager.GetCurrentMsec() - ___TailMsec + ___lastWaitTimeForJudge) - NoteJudge.JudgeAdjustMs;
|
||||
EntryList[monitor].DiffList[1].Add(___JudgeTimingDiffMsec - NoteJudge.JudgeAdjustMs);
|
||||
EntryList[monitor].RawDiffList[1].Add(raw);
|
||||
|
||||
// MelonLogger.Msg($"{___JudgeType}: {___JudgeTimingDiffMsec}, {raw}");
|
||||
}
|
||||
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ResultProcess), "OnStart")]
|
||||
private static void OnResultProcessStartFinish(
|
||||
ResultMonitor[] ____monitors, ResultProcess.ResultScoreViewType[] ____resultScoreViewType, UserData[] ____userData
|
||||
)
|
||||
{
|
||||
foreach (var monitor in ____monitors)
|
||||
{
|
||||
var idx = monitor.MonitorIndex;
|
||||
if (!____userData[idx].IsEntry) continue;
|
||||
|
||||
var fileName = $"Acc_Track_{GameManager.MusicTrackNumber}_Player_{idx}.txt";
|
||||
var filePath = Path.Combine(Environment.CurrentDirectory, fileName);
|
||||
|
||||
using (var writer = new StreamWriter(filePath))
|
||||
{
|
||||
for (int i = 0; i < TableRowNames.Length; i++)
|
||||
{
|
||||
writer.WriteLine($"Row: {TableRowNames[i]}");
|
||||
writer.WriteLine(" DiffList:");
|
||||
writer.WriteLine($" {string.Join(", ", EntryList[idx].DiffList[i])}");
|
||||
writer.WriteLine(" RawDiffList:");
|
||||
writer.WriteLine($" {string.Join(", ", EntryList[idx].RawDiffList[i])}");
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
var controller = Traverse.Create(monitor).Field<ScoreBoardController>("_scoreBoardController").Value;
|
||||
var newController = Object.Instantiate(controller, controller.transform);
|
||||
newController.gameObject.GetComponent<Animator>().enabled = false;
|
||||
newController.transform.localPosition = Vector3.zero;
|
||||
var table = ExtractTextObjs(newController);
|
||||
for (var i = 0; i < TableHead.Length; i++)
|
||||
{
|
||||
table[0, i].text = TableHead[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < TableRowNames.Length; i++)
|
||||
{
|
||||
table[i + 1, 0].text = TableRowNames[i];
|
||||
var num = EntryList[idx].DiffList[i].Count;
|
||||
table[i + 1, 1].text = num.ToString();
|
||||
if (num <= 0)
|
||||
{
|
||||
table[i + 1, 2].text = "——";
|
||||
table[i + 1, 3].text = "——";
|
||||
table[i + 1, 4].text = "——";
|
||||
continue;
|
||||
}
|
||||
|
||||
var average = EntryList[idx].DiffList[i].Average();
|
||||
var averageFrame = average * 0.06f;
|
||||
table[i + 1, 2].text = averageFrame.ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture);
|
||||
var averageRawFrame = EntryList[idx].RawDiffList[i].Average() * 0.06f;
|
||||
table[i + 1, 3].text = averageRawFrame.ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture);
|
||||
|
||||
if (num <= 1)
|
||||
{
|
||||
table[i + 1, 4].text = "——";
|
||||
}
|
||||
else
|
||||
{
|
||||
var deviSqr = EntryList[idx].DiffList[i].Sum(x => (x - average) * (x - average)) / (num - 1);
|
||||
var devi = Mathf.Sqrt(deviSqr) * 0.06f;
|
||||
table[i + 1, 4].text = devi.ToString("0.00", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
newController.gameObject.SetActive(____resultScoreViewType[idx] == ResultProcess.ResultScoreViewType.VSResult);
|
||||
Controllers[idx] = newController;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ScoreBoardController[] Controllers = new ScoreBoardController[2];
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ResultMonitor), "ChangeScoreBoard")]
|
||||
private static void OnChangeScoreBoard(
|
||||
ResultMonitor __instance, ResultProcess.ResultScoreViewType resultScoreType
|
||||
)
|
||||
{
|
||||
Controllers[__instance.MonitorIndex].gameObject.SetActive(resultScoreType == ResultProcess.ResultScoreViewType.VSResult);
|
||||
}
|
||||
|
||||
|
||||
private static readonly string[] RowNames = ["_tap", "_hold", "_slide", "_touch", "_break"];
|
||||
private static readonly string[] ColumnNames = ["_critical", "_perfect", "_great", "_good", "_miss"];
|
||||
private static readonly string[] TableHead = ["", "NUM", "AVG", "RAW", "S.D."];
|
||||
private static readonly string[] TableRowNames = ["TAP", "SLD", "TCH", "EX"];
|
||||
|
||||
private static TextMeshProUGUI[,] ExtractTextObjs(ScoreBoardController controller)
|
||||
{
|
||||
var result = new TextMeshProUGUI[RowNames.Length, ColumnNames.Length];
|
||||
for (var i = 0; i < RowNames.Length; i++)
|
||||
{
|
||||
for (int j = 0; j < ColumnNames.Length; j++)
|
||||
{
|
||||
var trav = Traverse.Create(controller)
|
||||
.Field(RowNames[i])
|
||||
.Field(ColumnNames[j]);
|
||||
var text = trav.Field<TextMeshProUGUI>("_numberText").Value;
|
||||
text.color = Color.black;
|
||||
result[i, j] = text;
|
||||
trav.GetValue<ScoreBoardColumnObject>().SetVisibleCloseBox(false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
65
AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs
Normal file
65
AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
using AquaMai.Core.Helpers;
|
||||
using HarmonyLib;
|
||||
using Mai2.Mai2Cue;
|
||||
using Main;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Process;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
en: "One key to proceed to music select (during entry) or end current PC (during music select).",
|
||||
zh: "一键跳过登录过程直接进入选歌界面,或在选歌界面直接结束本局游戏")]
|
||||
public class OneKeyEntryEnd
|
||||
{
|
||||
[ConfigEntry]
|
||||
public static readonly KeyCodeOrName key = KeyCodeOrName.Service;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly bool longPress = true;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(GameMainObject), "Update")]
|
||||
public static void OnGameMainObjectUpdate()
|
||||
{
|
||||
if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return;
|
||||
MelonLogger.Msg("[QuickSkip] Activated");
|
||||
|
||||
var traverse = Traverse.Create(SharedInstances.ProcessDataContainer.processManager);
|
||||
var processList = traverse.Field("_processList").GetValue<LinkedList<ProcessManager.ProcessControle>>();
|
||||
|
||||
ProcessBase processToRelease = null;
|
||||
|
||||
foreach (ProcessManager.ProcessControle process in processList)
|
||||
{
|
||||
switch (process.Process.ToString())
|
||||
{
|
||||
// After login
|
||||
case "Process.ModeSelect.ModeSelectProcess":
|
||||
case "Process.LoginBonus.LoginBonusProcess":
|
||||
case "Process.RegionalSelectProcess":
|
||||
case "Process.CharacterSelectProcess":
|
||||
case "Process.TicketSelect.TicketSelectProcess":
|
||||
processToRelease = process.Process;
|
||||
break;
|
||||
|
||||
case "Process.MusicSelectProcess":
|
||||
// Skip to save
|
||||
SoundManager.PreviewEnd();
|
||||
SoundManager.PlayBGM(Cue.BGM_COLLECTION, 2);
|
||||
SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, process.Process, new UnlockMusicProcess(SharedInstances.ProcessDataContainer)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (processToRelease != null)
|
||||
{
|
||||
GameManager.SetMaxTrack();
|
||||
SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, processToRelease, new MusicSelectProcess(SharedInstances.ProcessDataContainer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
46
AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs
Normal file
46
AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
using AquaMai.Core.Helpers;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using Process;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
en: "One key to retry (1.30+) or skip current chart in gameplay.",
|
||||
zh: "在游戏中途一键重试(1.30+)或跳过当前谱面")]
|
||||
public class OneKeyRetrySkip
|
||||
{
|
||||
[ConfigEntry]
|
||||
public static readonly KeyCodeOrName retryKey = KeyCodeOrName.Service;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly bool retryLongPress = false;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly KeyCodeOrName skipKey = KeyCodeOrName.Service;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly bool skipLongPress = true;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameProcess), "OnUpdate")]
|
||||
public static void PostGameProcessUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container)
|
||||
{
|
||||
if (KeyListener.GetKeyDownOrLongPress(skipKey, skipLongPress))
|
||||
{
|
||||
var traverse = Traverse.Create(__instance);
|
||||
___container.processManager.SendMessage(____message[0]);
|
||||
Singleton<GamePlayManager>.Instance.SetSyncResult(0);
|
||||
traverse.Method("SetRelease").GetValue();
|
||||
}
|
||||
|
||||
if (KeyListener.GetKeyDownOrLongPress(retryKey, retryLongPress) && GameInfo.GameVersion >= 23000)
|
||||
{
|
||||
// This is original typo in Assembly-CSharp
|
||||
Singleton<GamePlayManager>.Instance.SetQuickRetryFrag(flag: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs
Normal file
151
AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using AquaMai.Mods.Fix;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using Manager;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.UX.PracticeMode.Libs;
|
||||
|
||||
public class PracticeModeUI : MonoBehaviour
|
||||
{
|
||||
private static float windowTop => Screen.height - GuiSizes.PlayerWidth + GuiSizes.PlayerWidth * .22f;
|
||||
private static float controlHeight => GuiSizes.PlayerWidth * .13f;
|
||||
private static float sideButtonWidth => GuiSizes.PlayerWidth * .1f;
|
||||
private static float centerButtonWidth => GuiSizes.PlayerWidth * .28f;
|
||||
private static int fontSize => (int)(GuiSizes.PlayerWidth * .02f);
|
||||
|
||||
private static Rect GetButtonRect(int pos, int row)
|
||||
{
|
||||
float x;
|
||||
float width;
|
||||
switch (pos)
|
||||
{
|
||||
case 0:
|
||||
x = GuiSizes.PlayerCenter - centerButtonWidth / 2 - sideButtonWidth - GuiSizes.Margin;
|
||||
width = sideButtonWidth;
|
||||
break;
|
||||
case 1:
|
||||
x = GuiSizes.PlayerCenter - centerButtonWidth / 2;
|
||||
width = centerButtonWidth;
|
||||
break;
|
||||
case 2:
|
||||
x = GuiSizes.PlayerCenter + centerButtonWidth / 2 + GuiSizes.Margin;
|
||||
width = sideButtonWidth;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(pos), pos, null);
|
||||
}
|
||||
|
||||
return new Rect(x, windowTop + (GuiSizes.Margin + controlHeight) * row + GuiSizes.Margin, width, controlHeight);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
var labelStyle = GUI.skin.GetStyle("label");
|
||||
labelStyle.fontSize = fontSize;
|
||||
labelStyle.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var buttonStyle = GUI.skin.GetStyle("button");
|
||||
buttonStyle.fontSize = fontSize;
|
||||
|
||||
GUI.Box(new Rect(
|
||||
GuiSizes.PlayerCenter - centerButtonWidth / 2 - sideButtonWidth - GuiSizes.Margin * 2,
|
||||
windowTop,
|
||||
centerButtonWidth + sideButtonWidth * 2 + GuiSizes.Margin * 4,
|
||||
controlHeight * 4 + GuiSizes.Margin * 5
|
||||
), "");
|
||||
|
||||
GUI.Button(GetButtonRect(0, 0), Locale.SeekBackward);
|
||||
GUI.Button(GetButtonRect(1, 0), Locale.Pause);
|
||||
GUI.Button(GetButtonRect(2, 0), Locale.SeekForward);
|
||||
|
||||
if (PracticeMode.repeatStart == -1)
|
||||
{
|
||||
GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatStart);
|
||||
GUI.Label(GetButtonRect(1, 1), Locale.RepeatNotSet);
|
||||
}
|
||||
else if (PracticeMode.repeatEnd == -1)
|
||||
{
|
||||
GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatEnd);
|
||||
GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartSet);
|
||||
GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartEndSet);
|
||||
GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset);
|
||||
}
|
||||
|
||||
GUI.Button(GetButtonRect(0, 2), Locale.SpeedDown);
|
||||
GUI.Label(GetButtonRect(1, 2), $"{Locale.Speed} {PracticeMode.speed * 100:000}%");
|
||||
GUI.Button(GetButtonRect(2, 2), Locale.SpeedUp);
|
||||
GUI.Button(GetButtonRect(1, 3), Locale.SpeedReset);
|
||||
|
||||
GUI.Label(GetButtonRect(0, 3), $"{TimeSpan.FromMilliseconds(PracticeMode.CurrentPlayMsec):mm\\:ss\\.fff}\n{TimeSpan.FromMilliseconds(NotesManager.Instance().getPlayFinalMsec()):mm\\:ss\\.fff}");
|
||||
GUI.Button(GetButtonRect(2, 3), $"保持流速\n{(PracticeMode.keepNoteSpeed ? "ON" : "OFF")}");
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E8))
|
||||
{
|
||||
PracticeMode.Seek(-1000);
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E2))
|
||||
{
|
||||
PracticeMode.Seek(1000);
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B8) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B1))
|
||||
{
|
||||
DebugFeature.Pause = !DebugFeature.Pause;
|
||||
if (!DebugFeature.Pause)
|
||||
{
|
||||
PracticeMode.Seek(0);
|
||||
}
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B7) && PracticeMode.repeatStart == -1)
|
||||
{
|
||||
PracticeMode.repeatStart = PracticeMode.CurrentPlayMsec;
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B7) && PracticeMode.repeatEnd == -1)
|
||||
{
|
||||
PracticeMode.SetRepeatEnd(PracticeMode.CurrentPlayMsec);
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B2))
|
||||
{
|
||||
PracticeMode.ClearRepeat();
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B6))
|
||||
{
|
||||
PracticeMode.SpeedDown();
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B3))
|
||||
{
|
||||
PracticeMode.SpeedUp();
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B5) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B4))
|
||||
{
|
||||
PracticeMode.SpeedReset();
|
||||
}
|
||||
else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E4))
|
||||
{
|
||||
PracticeMode.keepNoteSpeed = !PracticeMode.keepNoteSpeed;
|
||||
PracticeMode.gameCtrl?.ResetOptionSpeed();
|
||||
}
|
||||
else if (
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A1) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A2) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A3) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A4) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A5) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A6) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A7) ||
|
||||
InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A8)
|
||||
)
|
||||
{
|
||||
PracticeMode.ui = null;
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
282
AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs
Normal file
282
AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AquaMai.Mods.Fix;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using AquaMai.Mods.UX.PracticeMode.Libs;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
using Monitor;
|
||||
using Monitor.Game;
|
||||
using Process;
|
||||
using UnityEngine;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
|
||||
namespace AquaMai.Mods.UX.PracticeMode;
|
||||
|
||||
[ConfigCollapseNamespace]
|
||||
[ConfigSection(
|
||||
en: "Practice Mode.",
|
||||
zh: "练习模式")]
|
||||
public class PracticeMode
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Key to show Practice Mode UI.",
|
||||
zh: "显示练习模式 UI 的按键")]
|
||||
public static readonly KeyCodeOrName key = KeyCodeOrName.Test;
|
||||
|
||||
[ConfigEntry]
|
||||
public static readonly bool longPress = false;
|
||||
|
||||
public static double repeatStart = -1;
|
||||
public static double repeatEnd = -1;
|
||||
public static float speed = 1;
|
||||
private static CriAtomExPlayer player;
|
||||
private static MovieMaterialMai2 movie;
|
||||
public static GameCtrl gameCtrl;
|
||||
public static bool keepNoteSpeed = false;
|
||||
|
||||
public static void SetRepeatEnd(double time)
|
||||
{
|
||||
if (repeatStart == -1)
|
||||
{
|
||||
MessageHelper.ShowMessage(Locale.RepeatStartTimeNotSet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (time < repeatStart)
|
||||
{
|
||||
MessageHelper.ShowMessage(Locale.RepeatEndTimeLessThenStartTime);
|
||||
return;
|
||||
}
|
||||
|
||||
repeatEnd = time;
|
||||
}
|
||||
|
||||
public static void ClearRepeat()
|
||||
{
|
||||
repeatStart = -1;
|
||||
repeatEnd = -1;
|
||||
}
|
||||
|
||||
public static void SetSpeed()
|
||||
{
|
||||
player.SetPitch((float)(1200 * Math.Log(speed, 2)));
|
||||
// player.SetDspTimeStretchRatio(1 / speed);
|
||||
player.UpdateAll();
|
||||
|
||||
movie.player.SetSpeed(speed);
|
||||
gameCtrl?.ResetOptionSpeed();
|
||||
}
|
||||
|
||||
private static IEnumerator SetSpeedCoroutineInner()
|
||||
{
|
||||
yield return null;
|
||||
SetSpeed();
|
||||
}
|
||||
|
||||
public static void SetSpeedCoroutine()
|
||||
{
|
||||
SharedInstances.GameMainObject.StartCoroutine(SetSpeedCoroutineInner());
|
||||
}
|
||||
|
||||
public static void SpeedUp()
|
||||
{
|
||||
speed += .05f;
|
||||
if (speed > 2)
|
||||
{
|
||||
speed = 2;
|
||||
}
|
||||
|
||||
SetSpeed();
|
||||
}
|
||||
|
||||
public static void SpeedDown()
|
||||
{
|
||||
speed -= .05f;
|
||||
if (speed < 0.05)
|
||||
{
|
||||
speed = 0.05f;
|
||||
}
|
||||
|
||||
SetSpeed();
|
||||
}
|
||||
|
||||
public static void SpeedReset()
|
||||
{
|
||||
speed = 1;
|
||||
SetSpeed();
|
||||
}
|
||||
|
||||
public static void Seek(int addMsec)
|
||||
{
|
||||
// Debug feature 里面那个 timer 不能感知变速
|
||||
// 为了和魔改版本统一,polyfill 里面不修这个
|
||||
// 这里重新实现一个能感知变速的 Seek
|
||||
var msec = CurrentPlayMsec + addMsec;
|
||||
if (msec < 0)
|
||||
{
|
||||
msec = 0;
|
||||
}
|
||||
|
||||
CurrentPlayMsec = msec;
|
||||
}
|
||||
|
||||
public static double CurrentPlayMsec
|
||||
{
|
||||
get => NotesManager.GetCurrentMsec() - 91;
|
||||
set
|
||||
{
|
||||
DebugFeature.CurrentPlayMsec = value;
|
||||
SetSpeedCoroutine();
|
||||
}
|
||||
}
|
||||
|
||||
public static PracticeModeUI ui;
|
||||
|
||||
[HarmonyPatch]
|
||||
public class PatchNoteSpeed
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
yield return AccessTools.Method(typeof(GameManager), "GetNoteSpeed");
|
||||
yield return AccessTools.Method(typeof(GameManager), "GetTouchSpeed");
|
||||
}
|
||||
|
||||
public static void Postfix(ref float __result)
|
||||
{
|
||||
if (!keepNoteSpeed) return;
|
||||
__result /= speed;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameProcess), "OnStart")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameProcessPostStart()
|
||||
{
|
||||
repeatStart = -1;
|
||||
repeatEnd = -1;
|
||||
speed = 1;
|
||||
ui = null;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameProcess), "OnRelease")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameProcessPostRelease()
|
||||
{
|
||||
repeatStart = -1;
|
||||
repeatEnd = -1;
|
||||
speed = 1;
|
||||
ui = null;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameCtrl), "Initialize")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameCtrlPostInitialize(GameCtrl __instance)
|
||||
{
|
||||
gameCtrl = __instance;
|
||||
}
|
||||
|
||||
# if DEBUG
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(GenericProcess), "OnUpdate")]
|
||||
public static void OnGenericProcessUpdate(GenericMonitor[] ____monitors)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F11))
|
||||
{
|
||||
____monitors[0].gameObject.AddComponent<PracticeModeUI>();
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
[HarmonyPatch(typeof(GameProcess), "OnUpdate")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameProcessPostUpdate(GameProcess __instance, GameMonitor[] ____monitors)
|
||||
{
|
||||
if (KeyListener.GetKeyDownOrLongPress(key, longPress) && ui is null)
|
||||
{
|
||||
ui = ____monitors[0].gameObject.AddComponent<PracticeModeUI>();
|
||||
}
|
||||
|
||||
if (repeatStart >= 0 && repeatEnd >= 0)
|
||||
{
|
||||
if (CurrentPlayMsec >= repeatEnd)
|
||||
{
|
||||
CurrentPlayMsec = repeatStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float startGap = -1f;
|
||||
|
||||
[HarmonyPatch(typeof(NotesManager), "StartPlay")]
|
||||
[HarmonyPostfix]
|
||||
public static void NotesManagerPostUpdateTimer(float msecStartGap)
|
||||
{
|
||||
startGap = msecStartGap;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(NotesManager), "UpdateTimer")]
|
||||
[HarmonyPrefix]
|
||||
public static bool NotesManagerPostUpdateTimer(bool ____isPlaying, Stopwatch ____stopwatch, ref float ____curMSec, ref float ____curMSecPre, float ____msecStartGap)
|
||||
{
|
||||
var stackTrace = new StackTrace(); // get call stack
|
||||
var stackFrames = stackTrace.GetFrames(); // get method calls (frames)
|
||||
if(stackFrames.Select(it => it.GetMethod().DeclaringType.Name).Contains("AdvDemoProcess"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (startGap != -1f)
|
||||
{
|
||||
____curMSec = startGap;
|
||||
____curMSecPre = startGap;
|
||||
____stopwatch?.Reset();
|
||||
startGap = -1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
____curMSecPre = ____curMSec;
|
||||
if (____isPlaying && ____stopwatch != null && !DebugFeature.Pause)
|
||||
{
|
||||
var num = (double)____stopwatch.ElapsedTicks / Stopwatch.Frequency * 1000.0 * speed;
|
||||
____curMSec += (float)num;
|
||||
____stopwatch.Reset();
|
||||
____stopwatch.Start();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SoundCtrl), "Initialize")]
|
||||
[HarmonyPostfix]
|
||||
public static void SoundCtrlPostInitialize(SoundCtrl.InitParam param, Dictionary<int, object> ____players)
|
||||
{
|
||||
var wrapper = ____players[2];
|
||||
player = (CriAtomExPlayer)wrapper.GetType().GetField("Player").GetValue(wrapper);
|
||||
// var pool = new CriAtomExStandardVoicePool(1, 8, 96000, true, 2);
|
||||
// pool.AttachDspTimeStretch();
|
||||
// player.SetVoicePoolIdentifier(pool.identifier);
|
||||
|
||||
// debug
|
||||
// var wrapper1 = ____players[7];
|
||||
// var player1 = (CriAtomExPlayer)wrapper1.GetType().GetField("Player").GetValue(wrapper1);
|
||||
// var pool = new CriAtomExStandardVoicePool(1, 8, 96000, true, 2);
|
||||
// pool.AttachDspTimeStretch();
|
||||
// player1.SetVoicePoolIdentifier(pool.identifier);
|
||||
// player1.SetDspTimeStretchRatio(2);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MovieController), "Awake")]
|
||||
[HarmonyPostfix]
|
||||
public static void MovieControllerPostAwake(MovieMaterialMai2 ____moviePlayers)
|
||||
{
|
||||
movie = ____moviePlayers;
|
||||
}
|
||||
}
|
||||
70
AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs
Normal file
70
AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using Monitor;
|
||||
using Process;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Show a \"skip\" button like AstroDX after the notes end.",
|
||||
zh: "音符结束之后显示像 AstroDX 一样的「跳过」按钮")]
|
||||
public class QuickEndPlay
|
||||
{
|
||||
private static int _timer;
|
||||
|
||||
[HarmonyPatch(typeof(GameProcess), "OnStart")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameProcessPostStart(GameMonitor[] ____monitors)
|
||||
{
|
||||
_timer = 0;
|
||||
____monitors[0].gameObject.AddComponent<Ui>();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameProcess), "OnUpdate")]
|
||||
[HarmonyPostfix]
|
||||
public static void GameProcessPostUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container, byte ____sequence)
|
||||
{
|
||||
switch (____sequence)
|
||||
{
|
||||
case 9:
|
||||
_timer = 0;
|
||||
break;
|
||||
case > 4:
|
||||
_timer++;
|
||||
break;
|
||||
default:
|
||||
_timer = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_timer > 60 && (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B4) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E4)))
|
||||
{
|
||||
var traverse = Traverse.Create(__instance);
|
||||
___container.processManager.SendMessage(____message[0]);
|
||||
Singleton<GamePlayManager>.Instance.SetSyncResult(0);
|
||||
traverse.Method("SetRelease").GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
private class Ui : MonoBehaviour
|
||||
{
|
||||
public void OnGUI()
|
||||
{
|
||||
if (_timer < 60) return;
|
||||
|
||||
// 这里重新 setup 一下 style 也可以
|
||||
var x = GuiSizes.PlayerCenter;
|
||||
var y = Screen.height - GuiSizes.PlayerWidth * .37f;
|
||||
var width = GuiSizes.PlayerWidth * .25f;
|
||||
var height = GuiSizes.PlayerWidth * .13f;
|
||||
|
||||
GUI.Box(new Rect(x, y, width, height), "");
|
||||
GUI.Button(new Rect(x, y, width, height), Locale.Skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
AquaMai/AquaMai.Mods/UX/README.md
Normal file
3
AquaMai/AquaMai.Mods/UX/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# UX
|
||||
|
||||
Features aiming at improving the user experience and can't fit in other categories go here. Generally, UX features are triggered by some user action or let the user perceive.
|
||||
143
AquaMai/AquaMai.Mods/UX/SelectionDetail.cs
Normal file
143
AquaMai/AquaMai.Mods/UX/SelectionDetail.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using Manager.MaiStudio;
|
||||
using Manager.UserDatas;
|
||||
using Monitor;
|
||||
using Process;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.UX;
|
||||
|
||||
[EnableGameVersion(23500)]
|
||||
[ConfigSection(
|
||||
en: "Show detail of selected song in music selection screen.",
|
||||
zh: "选歌界面显示选择的歌曲的详情")]
|
||||
public class SelectionDetail
|
||||
{
|
||||
private static readonly Window[] window = new Window[2];
|
||||
private static MusicSelectProcess.MusicSelectData SelectData { get; set; }
|
||||
private static readonly int[] difficulty = new int[2];
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(MusicSelectMonitor), "UpdateRivalScore")]
|
||||
public static void ScrollUpdate(MusicSelectProcess ____musicSelect, MusicSelectMonitor __instance)
|
||||
{
|
||||
int player;
|
||||
if (__instance == ____musicSelect.MonitorArray[0])
|
||||
{
|
||||
player = 0;
|
||||
}
|
||||
else if (__instance == ____musicSelect.MonitorArray[1])
|
||||
{
|
||||
player = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (window[player] != null)
|
||||
{
|
||||
window[player].Close();
|
||||
}
|
||||
|
||||
var userData = Singleton<UserDataManager>.Instance.GetUserData(player);
|
||||
if (!userData.IsEntry) return;
|
||||
|
||||
if (____musicSelect.IsRandomIndex()) return;
|
||||
|
||||
SelectData = ____musicSelect.GetMusic(0);
|
||||
if (SelectData == null) return;
|
||||
difficulty[player] = ____musicSelect.GetDifficulty(player);
|
||||
|
||||
window[player] = player == 0 ? __instance.gameObject.AddComponent<P1Window>() : __instance.gameObject.AddComponent<P2Window>();
|
||||
}
|
||||
|
||||
private class P1Window : Window
|
||||
{
|
||||
protected override int player => 0;
|
||||
}
|
||||
|
||||
private class P2Window : Window
|
||||
{
|
||||
protected override int player => 1;
|
||||
}
|
||||
|
||||
private abstract class Window : MonoBehaviour
|
||||
{
|
||||
protected abstract int player { get; }
|
||||
private UserData userData => Singleton<UserDataManager>.Instance.GetUserData(player);
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
var dataToShow = new List<string>();
|
||||
dataToShow.Add($"ID: {SelectData.MusicData.name.id}");
|
||||
dataToShow.Add(MusicDirHelper.LookupPath(SelectData.MusicData.name.id).Split('/').Reverse().ToArray()[3]);
|
||||
if (SelectData.MusicData.genreName is not null) // SelectData.MusicData.genreName.str may not correct
|
||||
dataToShow.Add(Singleton<DataManager>.Instance.GetMusicGenre(SelectData.MusicData.genreName.id)?.genreName);
|
||||
if (SelectData.MusicData.AddVersion is not null)
|
||||
dataToShow.Add(Singleton<DataManager>.Instance.GetMusicVersion(SelectData.MusicData.AddVersion.id)?.genreName);
|
||||
var notesData = SelectData.MusicData.notesData[difficulty[player]];
|
||||
dataToShow.Add($"{notesData?.level}.{notesData?.levelDecimal}");
|
||||
|
||||
var rate = CalcB50(SelectData.MusicData, difficulty[player]);
|
||||
if (rate > 0)
|
||||
{
|
||||
dataToShow.Add(string.Format(Locale.RatingUpWhenSSSp, rate));
|
||||
}
|
||||
|
||||
var playCount = Shim.GetUserScoreList(userData)[difficulty[player]].FirstOrDefault(it => it.id == SelectData.MusicData.name.id)?.playcount ?? 0;
|
||||
if (playCount > 0)
|
||||
{
|
||||
dataToShow.Add(string.Format(Locale.PlayCount, playCount));
|
||||
}
|
||||
|
||||
|
||||
var width = GuiSizes.FontSize * 15f;
|
||||
var x = GuiSizes.PlayerCenter - width / 2f + GuiSizes.PlayerWidth * player;
|
||||
var y = Screen.height * 0.87f;
|
||||
|
||||
var labelStyle = GUI.skin.GetStyle("label");
|
||||
labelStyle.fontSize = GuiSizes.FontSize;
|
||||
labelStyle.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUI.Box(new Rect(x, y, width, dataToShow.Count * GuiSizes.LabelHeight + 2 * GuiSizes.Margin), "");
|
||||
for (var i = 0; i < dataToShow.Count; i++)
|
||||
{
|
||||
GUI.Label(new Rect(x, y + GuiSizes.Margin + i * GuiSizes.LabelHeight, width, GuiSizes.LabelHeight), dataToShow[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private uint CalcB50(MusicData musicData, int difficulty)
|
||||
{
|
||||
var theory = new UserRate(musicData.name.id, difficulty, 1010000, (uint)musicData.version);
|
||||
var list = theory.OldFlag ? userData.RatingList.RatingList : userData.RatingList.NewRatingList;
|
||||
var userLowRate = list.Last();
|
||||
var userSongRate = list.FirstOrDefault(it => it.MusicId == musicData.name.id && it.Level == difficulty);
|
||||
|
||||
if (!userSongRate.Equals(default(UserRate)))
|
||||
{
|
||||
return theory.SingleRate - userSongRate.SingleRate;
|
||||
}
|
||||
|
||||
if (theory.SingleRate > userLowRate.SingleRate)
|
||||
{
|
||||
return theory.SingleRate - userLowRate.SingleRate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user