mirror of
https://github.com/hykilpikonna/AquaDX.git
synced 2026-08-28 20:24:47 -05:00
[+] mp4 movie
[+] mp4 movie
This commit is contained in:
@@ -6,6 +6,7 @@ public interface IConfigView
|
||||
public T GetValueOrDefault<T>(string path, T defaultValue = default);
|
||||
public bool TryGetValue<T>(string path, out T resultValue);
|
||||
public bool Remove(string path);
|
||||
public bool IsSectionEnabled(string path);
|
||||
public string ToToml();
|
||||
public IConfigView Clone();
|
||||
}
|
||||
|
||||
@@ -139,4 +139,24 @@ public class ConfigView : IConfigView
|
||||
{
|
||||
return new ConfigView(ToToml());
|
||||
}
|
||||
|
||||
public bool IsSectionEnabled(string path)
|
||||
{
|
||||
if (TryGetValue(path, out object section))
|
||||
{
|
||||
if (section is bool enabled)
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
else if (section is TomlTable table)
|
||||
{
|
||||
if (Utility.TomlTryGetValueCaseInsensitive(table, "Disabled", out var disabled))
|
||||
{
|
||||
return !Utility.IsTrutyOrDefault(disabled);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ public class ConfigMigrationManager : IConfigMigrationManager
|
||||
new List<IConfigMigration>
|
||||
{
|
||||
new ConfigMigration_V1_0_V2_0(),
|
||||
new ConfigMigration_V2_0_V2_1()
|
||||
new ConfigMigration_V2_0_V2_1(),
|
||||
new ConfigMigration_V2_1_V2_2(),
|
||||
}.ToDictionary(m => m.FromVersion);
|
||||
|
||||
public string LatestVersion { get; }
|
||||
|
||||
@@ -13,7 +13,7 @@ public class ConfigMigration_V2_0_V2_1 : IConfigMigration
|
||||
var dst = src.Clone();
|
||||
dst.SetValue("Version", ToVersion);
|
||||
|
||||
if (IsSectionEnabled(src, "Tweaks.ResetTouchAfterTrack"))
|
||||
if (src.IsSectionEnabled("Tweaks.ResetTouchAfterTrack"))
|
||||
{
|
||||
dst.Remove("Tweaks.ResetTouchAfterTrack");
|
||||
dst.SetValue("Tweaks.ResetTouch.AfterTrack", true);
|
||||
@@ -21,24 +21,4 @@ public class ConfigMigration_V2_0_V2_1 : IConfigMigration
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
public bool IsSectionEnabled(IConfigView src, string path)
|
||||
{
|
||||
if (src.TryGetValue(path, out object section))
|
||||
{
|
||||
if (section is bool enabled)
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
else if (section is TomlTable table)
|
||||
{
|
||||
if (Utility.TomlTryGetValueCaseInsensitive(table, "Disabled", out var disabled))
|
||||
{
|
||||
return !Utility.IsTrutyOrDefault(disabled);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
30
AquaMai.Config/Migration/ConfigMigration_V2_1_V2_2.cs
Normal file
30
AquaMai.Config/Migration/ConfigMigration_V2_1_V2_2.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using AquaMai.Config.Interfaces;
|
||||
using Tomlet.Models;
|
||||
|
||||
namespace AquaMai.Config.Migration;
|
||||
|
||||
public class ConfigMigration_V2_1_V2_2 : IConfigMigration
|
||||
{
|
||||
public string FromVersion => "2.1";
|
||||
public string ToVersion => "2.2";
|
||||
|
||||
public IConfigView Migrate(IConfigView src)
|
||||
{
|
||||
var dst = src.Clone();
|
||||
dst.SetValue("Version", ToVersion);
|
||||
|
||||
if (src.IsSectionEnabled("GameSystem.Assets.UseJacketAsDummyMovie"))
|
||||
{
|
||||
dst.Remove("GameSystem.Assets.UseJacketAsDummyMovie");
|
||||
dst.SetValue("GameSystem.Assets.MovieLoader.JacketAsMovie", true);
|
||||
}
|
||||
|
||||
if (src.TryGetValue<string>("GameSystem.Assets.LoadLocalImages.LocalAssetsDir", out var localAssetsDir))
|
||||
{
|
||||
dst.SetValue("GameSystem.Assets.LoadLocalImages.ImageAssetsDir", localAssetsDir);
|
||||
dst.Remove("GameSystem.Assets.LoadLocalImages.LocalAssetsDir");
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace AquaMai.Mods.GameSystem.Assets;
|
||||
public class LoadLocalImages
|
||||
{
|
||||
[ConfigEntry]
|
||||
private static readonly string localAssetsDir = "LocalAssets";
|
||||
private static readonly string imageAssetsDir = "LocalAssets";
|
||||
|
||||
private static readonly string[] imageExts = [".jpg", ".png", ".jpeg"];
|
||||
private static readonly Dictionary<string, string> jacketPaths = [];
|
||||
@@ -122,7 +122,7 @@ public class LoadLocalImages
|
||||
|
||||
MelonLogger.Msg($"[LoadLocalImages] Loaded {jacketPaths.Count} Jacket, {platePaths.Count} NamePlate, {framePaths.Count} Frame, {framemaskPaths.Count} FrameMask, {framepatternPaths.Count} FramePattern, {iconPaths.Count} Icon, {charaPaths.Count} Chara, {partnerPaths.Count} PartnerLogo, {tabTitlePaths.Count} Tab Titles from AssetBundleImages.");
|
||||
|
||||
var resolvedDir = FileSystem.ResolvePath(localAssetsDir);
|
||||
var resolvedDir = FileSystem.ResolvePath(imageAssetsDir);
|
||||
if (Directory.Exists(resolvedDir))
|
||||
foreach (var laFile in Directory.EnumerateFiles(resolvedDir))
|
||||
{
|
||||
|
||||
167
AquaMai.Mods/GameSystem/Assets/MovieLoader.cs
Normal file
167
AquaMai.Mods/GameSystem/Assets/MovieLoader.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using CriMana;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Monitor.Game;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Use mp4 files or the png jacket above as PV if no .dat found in the movie folder.",
|
||||
zh: "使用封面作为 PV 以及直读 MP4 格式 PV 的选项")]
|
||||
public class MovieLoader
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: """
|
||||
Use jacket as movie
|
||||
Use together with `LoadLocalImages`.
|
||||
""",
|
||||
zh: """
|
||||
用封面作为背景 PV
|
||||
请和 `LoadLocalImages` 一起用
|
||||
""")]
|
||||
private static bool jacketAsMovie = true;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Load MP4 files from LocalAssets",
|
||||
zh: "从 LocalAssets 中加载 MP4 文件作为 PV")]
|
||||
private static bool loadMp4Movie = true;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "MP4 files directory",
|
||||
zh: "加载 MP4 文件的路径")]
|
||||
private static readonly string movieAssetsDir = "LocalAssets";
|
||||
|
||||
private static readonly Dictionary<string, string> optionFileMap = [];
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(DataManager), "LoadMusicBase")]
|
||||
public static void LoadMusicPostfix(List<string> ____targetDirs)
|
||||
{
|
||||
foreach (var aDir in ____targetDirs)
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(aDir, "MovieData"))) continue;
|
||||
var files = Directory.GetFiles(Path.Combine(aDir, "MovieData"), "*.mp4");
|
||||
foreach (var file in files)
|
||||
{
|
||||
optionFileMap[Path.GetFileName(file)] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static VideoPlayer[] _videoPlayers = new VideoPlayer[2];
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameCtrl), "Initialize")]
|
||||
public static void LoadLocalBgaAwake(GameObject ____movieMaskObj, int ___monitorIndex)
|
||||
{
|
||||
var music = Singleton<DataManager>.Instance.GetMusic(GameManager.SelectMusicID[0]);
|
||||
if (music is null) return;
|
||||
|
||||
var moviePath = Singleton<OptionDataManager>.Instance.GetMovieDataPath($"{music.movieName.id:000000}") + ".dat";
|
||||
if (!moviePath.Contains("dummy")) return;
|
||||
|
||||
var resolvedDir = FileSystem.ResolvePath(movieAssetsDir);
|
||||
if (!optionFileMap.TryGetValue($"{music.movieName.id:000000}.mp4", out var mp4Path))
|
||||
{
|
||||
mp4Path = Path.Combine(resolvedDir, $"{music.movieName.id:000000}.mp4");
|
||||
}
|
||||
|
||||
var mp4Exists = File.Exists(mp4Path);
|
||||
var jacket = LoadLocalImages.GetJacketTexture2D(music.movieName.id);
|
||||
if (!mp4Exists && jacket is null)
|
||||
{
|
||||
MelonLogger.Msg("No jacket found for music " + music);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mp4Exists)
|
||||
{
|
||||
if (_videoPlayers[___monitorIndex] is null)
|
||||
{
|
||||
# if DEBUG
|
||||
MelonLogger.Msg("Init _videoPlayer");
|
||||
# endif
|
||||
_videoPlayers[___monitorIndex] = ____movieMaskObj.AddComponent<VideoPlayer>();
|
||||
}
|
||||
# if DEBUG
|
||||
else
|
||||
{
|
||||
MelonLogger.Msg("_videoPlayer already exists");
|
||||
}
|
||||
# endif
|
||||
_videoPlayers[___monitorIndex].url = mp4Path;
|
||||
_videoPlayers[___monitorIndex].playOnAwake = false;
|
||||
_videoPlayers[___monitorIndex].renderMode = VideoRenderMode.MaterialOverride;
|
||||
_videoPlayers[___monitorIndex].audioOutputMode = VideoAudioOutputMode.None;
|
||||
// 似乎 MaterialOverride 没法保持视频的长宽比,用 RenderTexture 的话放在 SpriteRenderer 里面会比较麻烦。
|
||||
// 所以就不保持了,在塞 pv 的时候自己转吧,反正原本也要根据 first 加 padding
|
||||
}
|
||||
|
||||
var movie = ____movieMaskObj.transform.Find("Movie");
|
||||
|
||||
// If I create a new RawImage component, the jacket will be not be displayed
|
||||
// I think it will be difficult to make it work with RawImage
|
||||
// So I change the material that plays video to default sprite material
|
||||
// The original player is actually a sprite renderer and plays video with a custom material
|
||||
var sprite = movie.GetComponent<SpriteRenderer>();
|
||||
if (mp4Exists)
|
||||
{
|
||||
sprite.material = new Material(Shader.Find("Sprites/Default"));
|
||||
_videoPlayers[___monitorIndex].targetMaterialRenderer = sprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.sprite = Sprite.Create(jacket, new Rect(0, 0, jacket.width, jacket.height), new Vector2(0.5f, 0.5f));
|
||||
sprite.material = new Material(Shader.Find("Sprites/Default"));
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(MovieController), "Play")]
|
||||
public static void Play(int frame)
|
||||
{
|
||||
foreach (var player in _videoPlayers)
|
||||
{
|
||||
if (player == null) continue;
|
||||
player.frame = frame;
|
||||
player.Play();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(MovieController), "Pause")]
|
||||
public static void Pause(bool pauseFlag)
|
||||
{
|
||||
foreach (var player in _videoPlayers)
|
||||
{
|
||||
if (player == null) continue;
|
||||
if (pauseFlag)
|
||||
{
|
||||
player.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
player.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(Player), "SetSpeed")]
|
||||
public static void SetSpeed(float speed)
|
||||
{
|
||||
foreach (var player in _videoPlayers)
|
||||
{
|
||||
if (player == null) continue;
|
||||
player.playbackSpeed = speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Linq;
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Monitor.Game;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
Use the png jacket above as MV if no .dat found in the movie folder.
|
||||
Use together with `LoadLocalImages`.
|
||||
""",
|
||||
zh: """
|
||||
如果 movie 文件夹中没有 dat 格式的 MV 的话,就用歌曲的封面做背景,而不是显示迪拉熊的笑脸
|
||||
请和 `LoadLocalImages` 一起用
|
||||
""")]
|
||||
public class UseJacketAsDummyMovie
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameCtrl), "IsReady")]
|
||||
public static void LoadLocalBgaAwake(GameObject ____movieMaskObj)
|
||||
{
|
||||
var music = Singleton<DataManager>.Instance.GetMusic(GameManager.SelectMusicID[0]);
|
||||
if (music is null) return;
|
||||
|
||||
var moviePath = Singleton<OptionDataManager>.Instance.GetMovieDataPath($"{music.movieName.id:000000}") + ".dat";
|
||||
if (!moviePath.Contains("dummy")) return;
|
||||
|
||||
var jacket = LoadLocalImages.GetJacketTexture2D(music.movieName.id);
|
||||
if (jacket is null)
|
||||
{
|
||||
MelonLogger.Msg("No jacket found for music " + music);
|
||||
return;
|
||||
}
|
||||
|
||||
var components = ____movieMaskObj.GetComponentsInChildren<Component>(false);
|
||||
var movies = components.Where(it => it.name == "Movie");
|
||||
|
||||
foreach (var movie in movies)
|
||||
{
|
||||
// If I create a new RawImage component, the jacket will be not be displayed
|
||||
// I think it will be difficult to make it work with RawImage
|
||||
// So I change the material that plays video to default sprite material
|
||||
// The original player is actually a sprite renderer and plays video with a custom material
|
||||
var sprite = movie.GetComponent<SpriteRenderer>();
|
||||
sprite.sprite = Sprite.Create(jacket, new Rect(0, 0, jacket.width, jacket.height), new Vector2(0.5f, 0.5f));
|
||||
sprite.material = new Material(Shader.Find("Sprites/Default"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user