Skip to content

Commit

Permalink
Merge pull request #55
Browse files Browse the repository at this point in the history
Customize note skin (just like MajdataView) &  several patches
  • Loading branch information
clansty authored Oct 4, 2024
2 parents cb4cc4e + ac01469 commit 2cea66c
Show file tree
Hide file tree
Showing 8 changed files with 667 additions and 0 deletions.
5 changes: 5 additions & 0 deletions AquaMai/AquaMai.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,14 @@ DEBUG</DefineConstants>
<Compile Include="Config.cs" />
<Compile Include="CustomKeyMap\Enable.cs" />
<Compile Include="CustomKeyMap\KeyCodeID.cs" />
<Compile Include="CustomSkin\CustomNoteSkin.cs" />
<Compile Include="Fix\BasicFix.cs" />
<Compile Include="Fix\DebugFeature.cs" />
<Compile Include="Fix\DisableReboot.cs" />
<Compile Include="Fix\ExtendNotesPool.cs" />
<Compile Include="Fix\FixCharaCrash.cs" />
<Compile Include="Fix\FixCheckAuth.cs" />
<Compile Include="Fix\FixConnSlide.cs" />
<Compile Include="Fix\FontFix.cs" />
<Compile Include="Fix\ForceAsServer.cs" />
<Compile Include="Fix\ForceFreePlay.cs" />
Expand All @@ -312,6 +314,9 @@ DEBUG</DefineConstants>
<Compile Include="Helpers\SharedInstances.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Main.cs" />
<Compile Include="RenderTweak\SlideAutoPlayTweak.cs" />
<Compile Include="RenderTweak\SlideJudgeTweak.cs" />
<Compile Include="RenderTweak\TrackStartProcessTweak.cs" />
<Compile Include="Resources\Locale.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
Expand Down
314 changes: 314 additions & 0 deletions AquaMai/CustomSkin/CustomNoteSkin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using MelonLoader;
using Monitor;
using Monitor.Game;
using Process;
using UnityEngine;

namespace AquaMai.CustomSkin;

public class CustomNoteSkin
{
private static readonly List<string> ImageExts = [".jpg", ".png", ".jpeg"];
private static readonly List<string> SlideFanFields = ["_normalSlideFan", "_eachSlideFan", "_breakSlideFan", "_breakSlideFanEff"];

private static Sprite customOutline;
private static Sprite[,] customSlideFan = new Sprite[4, 11];

private static bool LoadIntoGameNoteImageContainer(string fieldName, int? idx1, int? idx2, Texture2D texture)
{
// 先确定确实有这个 Field, 如果没有的话可以直接跳过这个文件
var fieldTraverse = Traverse.Create(typeof(GameNoteImageContainer)).Field(fieldName);
if (!fieldTraverse.FieldExists())
{
MelonLogger.Msg($"[CustomNoteSkin] Cannot found field {fieldName}");
return false;
}

var fieldType = fieldTraverse.GetValueType();
if (!idx1.HasValue)
{
// 目标 Field 应当是单个 Sprite
if (fieldType != typeof(Sprite))
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite");
return false;
}
var target = fieldTraverse.GetValue<Sprite>();
var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
var custom = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, target.border
);
fieldTraverse.SetValue(custom);
}
else if (!idx2.HasValue)
{
// 目标 Field 是一维数组
if (fieldType != typeof(Sprite[]))
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[]");
return false;
}
var targetArray = fieldTraverse.GetValue<Sprite[]>();
var target = targetArray[idx1.Value];
var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
var custom = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, target.border
);
targetArray[idx1.Value] = custom;
}
else
{
// 目标 Field 是二维数组
if (fieldType != typeof(Sprite[,]))
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[,]");
return false;
}
var targetArray = fieldTraverse.GetValue<Sprite[,]>();
var target = targetArray[idx1.Value, idx2.Value];
var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
var custom = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, target.border
);
targetArray[idx1.Value, idx2.Value] = custom;
}

return true;
}

[HarmonyPostfix]
[HarmonyPatch(typeof(GameNotePrefabContainer), "Initialize")]
private static void LoadNoteSkin()
{
if (!Directory.Exists(Path.Combine(Environment.CurrentDirectory, "Skins"))) return;

foreach (var laFile in Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "Skins")))
{
if (!ImageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue;
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
texture.LoadImage(File.ReadAllBytes(laFile));

var name = Path.GetFileNameWithoutExtension(laFile);
var args = name.Split('_');
// 文件名的格式是 XXXXXXXX_A_B 表示 GameNoteImageContainer._XXXXXXXX[A, B]
// 视具体情况, A, B 可能不存在
var fieldName = '_' + args[0];
int? idx1 = (args.Length < 2)? null : (int.TryParse(args[1], out var temp) ? temp : null);
int? idx2 = (args.Length < 3)? null : (int.TryParse(args[2], out temp) ? temp : null);

Traverse traverse;

if (fieldName == "_outline")
{
customOutline = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 1f);
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (SlideFanFields.Contains(fieldName))
{
if (!idx1.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
continue;
}
var i = SlideFanFields.IndexOf(fieldName);
customSlideFan[i, idx1.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(1f, 0.5f), 1f);
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (fieldName == "_touchJust")
{
traverse = Traverse.Create(GameNotePrefabContainer.TouchTapB);
var noticeObject = traverse.Field<GameObject>("NoticeObject").Value;
var target = noticeObject.GetComponent<SpriteRenderer>();
var pivot = new Vector2(
target.sprite.pivot.x / target.sprite.rect.width,
target.sprite.pivot.y / target.sprite.rect.height
);
var custom = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, target.sprite.border
);
target.sprite = custom;

traverse = Traverse.Create(GameNotePrefabContainer.TouchTapC);
noticeObject = traverse.Field<GameObject>("NoticeObject").Value;
noticeObject.GetComponent<SpriteRenderer>().sprite = custom;
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (fieldName == "_touchHold")
{
if (!idx1.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
continue;
}
traverse = Traverse.Create(GameNotePrefabContainer.TouchHoldC);
var target = traverse.Field<SpriteRenderer[]>("ColorsObject").Value;
var renderer = target[idx1.Value];
var pivot = new Vector2(
renderer.sprite.pivot.x / renderer.sprite.rect.width,
renderer.sprite.pivot.y / renderer.sprite.rect.height
);
var custom = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, renderer.sprite.border
);
renderer.sprite = custom;
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (fieldName == "_normalTouchBorder")
{
if (!idx1.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
continue;
}
traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve);
var target = traverse.Field<Sprite[]>("_reserveSingleSprite").Value;
var targetSprite = target[idx1.Value - 2];
var pivot = new Vector2(
targetSprite.pivot.x / targetSprite.rect.width,
targetSprite.pivot.y / targetSprite.rect.height
);
target[idx1.Value - 2] = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, targetSprite.border
);
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (fieldName == "_eachTouchBorder")
{
if (!idx1.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
continue;
}
traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve);
var target = traverse.Field<Sprite[]>("_reserveEachSprite").Value;
var targetSprite = target[idx1.Value - 2];
var pivot = new Vector2(
targetSprite.pivot.x / targetSprite.rect.width,
targetSprite.pivot.y / targetSprite.rect.height
);
target[idx1.Value - 2] = Sprite.Create(
texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
0, SpriteMeshType.Tight, targetSprite.border
);
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}

if (LoadIntoGameNoteImageContainer(fieldName, idx1, idx2, texture))
{
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
}
}
}

[HarmonyPostfix]
[HarmonyPatch(typeof(GameCtrl), "Initialize")]
private static void ChangeOutlineTexture(GameObject ____guideEndPointObj)
{
if (____guideEndPointObj != null && customOutline != null)
{
____guideEndPointObj.GetComponent<SpriteRenderer>().sprite = customOutline;
}
}

[HarmonyPostfix]
[HarmonyPatch(typeof(SlideFan), "Initialize")]
private static void ChangeFanTexture(
SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, bool ___BreakFlag, bool ___EachFlag
)
{
Vector3 position;
Sprite sprite;
if (___BreakFlag)
{
for (var i = 0; i < 11; i++)
{
sprite = customSlideFan[2, i];
if (sprite != null)
{
____spriteLines[2 * i].sprite = sprite;
position = ____spriteLines[2 * i].transform.localPosition;
____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i].color = Color.white;

____spriteLines[2 * i + 1].sprite = sprite;
position = ____spriteLines[2 * i + 1].transform.localPosition;
____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i + 1].color = Color.white;
}
sprite = customSlideFan[3, i];
if (sprite != null)
{
____effectSprites[2 * i].sprite = sprite;
position = ____effectSprites[2 * i].transform.localPosition;
____effectSprites[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
____effectSprites[2 * i].color = Color.white;

____effectSprites[2 * i + 1].sprite = sprite;
position = ____effectSprites[2 * i + 1].transform.localPosition;
____effectSprites[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
____effectSprites[2 * i + 1].color = Color.white;
}
}
}
else if (___EachFlag)
{
for (var i = 0; i < 11; i++)
{
sprite = customSlideFan[1, i];
if (sprite != null)
{
____spriteLines[2 * i].sprite = sprite;
position = ____spriteLines[2 * i].transform.localPosition;
____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i].color = Color.white;

____spriteLines[2 * i + 1].sprite = sprite;
position = ____spriteLines[2 * i + 1].transform.localPosition;
____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i + 1].color = Color.white;
}
}
}
else
{
for (var i = 0; i < 11; i++)
{
sprite = customSlideFan[0, i];
if (sprite != null)
{
____spriteLines[2 * i].sprite = sprite;
position = ____spriteLines[2 * i].transform.localPosition;
____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i].color = Color.white;

____spriteLines[2 * i + 1].sprite = sprite;
position = ____spriteLines[2 * i + 1].transform.localPosition;
____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
____spriteLines[2 * i + 1].color = Color.white;
}
}
}
}
}
Loading

0 comments on commit 2cea66c

Please sign in to comment.