Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions AquaMai.Mods/Fancy/TitleScreenVideo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
using AquaMai.Config.Attributes;
using AquaMai.Core.Attributes;
using AquaMai.Core.Helpers;
using HarmonyLib;
using MAI2.Util;
using Manager;
using MelonLoader;
using Monitor;
using Process;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Video;

namespace AquaMai.Mods.Fancy;

[ConfigSection(
"标题画面视频",
en: "Plays custom video on title screen, just like in the good ol' days",
zh: "复刻 bud 代之前的标题界面视频动画")]
[EnableGameVersion(24000)]
public class TitleScreenVideo
{
[ConfigEntry(
en: "Title Video / Audio File Path (without file extensions, mp4 video and acb/awb audio are supported)",
zh: "标题视音频文件路径,不包括文件后缀名(视频为 mp4 格式,音频为 acb/awb 格式)")]
public static readonly string VideoPath = "LocalAssets/DX_title";

[ConfigEntry(
en: "Skip the SEGA / All.Net logo when custom title video file is loaded",
zh: "自定标题视频成功加载后,跳过 SEGA / All.Net 标志动画")]
public static readonly bool SkipLogo = false;

[ConfigEntry(
en: "Hide copyright information on the bottom of the title screen",
zh: "隐藏标题画面底部的版权信息")]
public static readonly bool HideCopyright = false;

private static GameObject[] _movieObjects = new GameObject[2];
private static VideoPlayer[] _videoPlayers = new VideoPlayer[2];
private static Material[] _videoMaterials = new Material[2];

private static List<string>[] _disabledCompoments = [[], []];
Comment thread
WUGqnwvMQPzl marked this conversation as resolved.

private static int _videoPreparedCount = 0;
private static bool IsVideoPrepared => _videoPreparedCount >= 2;

private static bool _isAudioPrepared = false;

[HarmonyPostfix]
[HarmonyPatch(typeof(AdvertiseProcess), "OnStart")]
public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors)
{
var moviePref = Resources.Load<GameObject>("Process/AdvertiseCommercial/AdvertiseCommercialProcess").transform.Find("Canvas/Main/MovieMask").gameObject;

for (int i = 0; i < ____monitors.Length; ++i)
{
var monitor = ____monitors[i];

// Disable fade out cover on cir (and maybe future version?)
if (GameInfo.GameVersion >= 26000)
monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/out_cover")?.gameObject.SetActive(false);

// Hide copyright information
if (HideCopyright)
monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/Licence").gameObject.SetActive(false);

var titleLoop = monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/TitleLoop");

// Disable all elements on the original title screen
for (int j = 0; j < titleLoop.childCount; ++j)
{
var obj = titleLoop.GetChild(j).gameObject;
if (obj.activeSelf)
{
obj.SetActive(false);
_disabledCompoments[i].Add(obj.name);
}
}

_movieObjects[i] = UnityEngine.Object.Instantiate(moviePref, titleLoop);
_movieObjects[i].GetComponent<SpriteRenderer>().color = new Color(0, 0, 0, 0);

_videoPlayers[i] = _movieObjects[i].AddComponent<VideoPlayer>();
_videoPlayers[i].url = FileSystem.ResolvePath(VideoPath + ".mp4");
_videoPlayers[i].playOnAwake = false;
_videoPlayers[i].isLooping = false;
_videoPlayers[i].renderMode = VideoRenderMode.MaterialOverride;
_videoPlayers[i].audioOutputMode = VideoAudioOutputMode.None;

var movieSprite = _movieObjects[i].transform.Find("Movie").gameObject.GetComponent<SpriteRenderer>();

_videoPlayers[i].prepareCompleted += (source) =>
{
// Prevent autoplay
source.Pause();
source.time = 0;

// Setting the video player size
var vWidth = source.width;
var vHeight = source.height;

var calWidth = vHeight > vWidth ? (1080 * vWidth / vHeight) : 1080;
var calHeight = vHeight > vWidth ? 1080 : (1080 * vHeight / vWidth);

movieSprite.size = new Vector2(calWidth, calHeight);

Interlocked.Increment(ref _videoPreparedCount);
};

_videoPlayers[i].errorReceived += (source, err) =>
{
MelonLogger.Error($"[TitleScreenVideo] Failed to load video file: {err}");
};

_videoPlayers[i].Prepare();

_videoMaterials[i] = new Material(Shader.Find("Sprites/Default"));
movieSprite.material = _videoMaterials[i];
_videoPlayers[i].targetMaterialRenderer = movieSprite;
}

_isAudioPrepared = SoundManager.MusicPrepareForFileName(VideoPath);
if (!_isAudioPrepared)
MelonLogger.Warning("[TitleScreenVideo] Failed to load audio file, game's default title jingle will be played instead");
}

[HarmonyPostfix]
[HarmonyPatch(typeof(AdvertiseProcess), "OnUpdate")]
public static void OnUpdate_Postfix(AdvertiseProcess.AdvertiseSequence ____state, AdvertiseMonitor[] ____monitors)
{
if (____state == AdvertiseProcess.AdvertiseSequence.TransitionOut && IsVideoPrepared)
{
for (int i = 0; i < ____monitors.Length; ++i)
{
// Stop yelling "maimai deluxe" I'm tired to hearing it
SoundManager.StopVoice(i);

_videoPlayers[i].Play();

if (_isAudioPrepared)
{
// Stop game's original title music and plays our own
SoundManager.StopJingle(i);
SoundManager.StartMusic();
}
}
}
}

[HarmonyPostfix]
[HarmonyPatch(typeof(AdvertiseProcess), "LeaveAdvertise")]
public static void LeaveAdvertise_Postfix()
{
if (_isAudioPrepared)
{
// Stop and unloads title music
SoundManager.StopMusic();
Singleton<SoundCtrl>.Instance.UnloadCueSheet(1);
Comment thread
WUGqnwvMQPzl marked this conversation as resolved.
}
}

[HarmonyPrefix]
[HarmonyPatch(typeof(AdvertiseProcess), "OnRelease")]
public static void OnRelease_Prefix(AdvertiseMonitor[] ____monitors)
{
for (int i = 0; i < ____monitors.Length; ++i)
{
if (_videoMaterials[i] != null)
{
UnityEngine.Object.Destroy(_videoMaterials[i]);
_videoMaterials[i] = null;
}

if (_videoPlayers[i] != null)
{
UnityEngine.Object.Destroy(_videoPlayers[i]);
_videoPlayers[i] = null;
}

if (_movieObjects[i] != null)
{
UnityEngine.Object.Destroy(_movieObjects[i]);
_movieObjects[i] = null;
}
}

// Resets status
Interlocked.Exchange(ref _videoPreparedCount, 0);
_isAudioPrepared = false;
_disabledCompoments = [[], []];
}

[HarmonyPrefix]
[HarmonyPatch(typeof(AdvertiseMonitor), "AllStop")]
public static bool Monitor_AllStop_Prefix()
{
// So uhh... When I was testing the feature, this method makes title screen suddently go black before transition
// I don't like the sudden cutout so I disabled it, not sure about the side effect or compatibility though
Comment thread
WUGqnwvMQPzl marked this conversation as resolved.
return false;
}

[HarmonyPrefix]
[HarmonyPatch(typeof(AdvertiseMonitor), "IsTitleAnimationEnd")]
public static bool Monitor_IsTitleAnimationEnd_Prefix(ref bool __result, int ___monitorIndex)
{
if (!IsVideoPrepared)
return true;

__result = !_videoPlayers[___monitorIndex].isPlaying && _videoPlayers[___monitorIndex].frame >= (long) _videoPlayers[___monitorIndex].frameCount - 1;
return false;
}

[HarmonyPrefix]
[HarmonyPatch(typeof(AdvertiseMonitor), "PlayLogo")]
public static bool Monitor_PlayLogo_Prefix(int ___monitorIndex, GameObject ____eventModeObject, CanvasGroup ___Main)
{
if (!IsVideoPrepared)
{
// Re-enable original title screen elements if the video is unavailable
// doing this early so the transition will be less noticable
_movieObjects[___monitorIndex].SetActive(false);

var titleLoop = ___Main.transform.Find("UI_ADV_Title/Null_all/TitleLoop");
foreach (string name in _disabledCompoments[___monitorIndex])
titleLoop.Find(name).gameObject.SetActive(true);

_disabledCompoments[___monitorIndex] = [];

return true;
}

if (!SkipLogo)
return true;

____eventModeObject.SetActive(GameManager.IsEventMode);
___Main.transform.Find("UI_ADV_SegaAllNet").gameObject.SetActive(false);
return false;
}

[HarmonyPrefix]
[HarmonyPatch(typeof(AdvertiseMonitor), "IsLogoAnimationEnd")]
public static bool Monitor_IsLogoAnimationEnd_Prefix(ref bool __result)
{
if (!IsVideoPrepared || !SkipLogo)
return true;

__result = true;
return false;
}
}