Skip to content

Commit

Permalink
feat: pp counter UI (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine authored Mar 28, 2024
1 parent da5a600 commit 66bece8
Show file tree
Hide file tree
Showing 25 changed files with 783 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using JetBrains.Annotations;
using Osu.Stubs;

namespace Osu.Patcher.Hook.Patches.LivePerformance;

/// <summary>
/// Hooks the constructor of <c>ScoreDisplay</c> to add our own <c>pTextSprite</c> for displaying
/// the performance counter to the ScoreDisplay's sprite manager.
/// This needs [email protected] or score-p.png in your skin's score font assets!
/// </summary>
[HarmonyPatch]
[UsedImplicitly]
public class PatchAddPerformanceToScoreDisplay
{
[UsedImplicitly]
[HarmonyTargetMethod]
private static MethodBase Target() => ScoreDisplay.Constructor.Reference;

[UsedImplicitly]
[HarmonyPostfix]
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static void After(
object __instance, // ScoreDisplay
[HarmonyArgument(0)] object spriteManager, // SpriteManager
[HarmonyArgument(1)] object position, // Vector2
[HarmonyArgument(2)] bool alignRight,
[HarmonyArgument(3)] float scale
)
{
var currentSkin = SkinManager.Current.Get();
var scoreFont = SkinOsu.FontScore.Get(currentSkin);
var scoreFontOverlap = SkinOsu.FontScoreOverlap.Get(currentSkin);

var performanceSprite = ((ConstructorInfo)pSpriteText.Constructor.Reference).Invoke(
[
/* text: */ "00.0pp",
/* fontName: */ scoreFont,
/* spacingOverlap: */ (float)scoreFontOverlap,
/* fieldType: */ alignRight ? Fields.TopRight : Fields.TopLeft,
/* origin: */ alignRight ? Origins.TopRight : Origins.TopLeft,
/* clock: */ Clocks.Game,
/* startPosition: */ ((ConstructorInfo)Vector2.Constructor.Reference).Invoke([0f, 0f]),
/* drawDepth: */ 0.95f,
/* alwaysDraw: */ true,
/* color: */ Color.White,
/* precache: */ true,
/* source: */ SkinSource.ExceptBeatmap,
]);

// Cannot be startPosition directly
// TODO: don't add 9f offset if [email protected]/score-p.png texture exists
var positionX = Vector2.X.Get(position) + 9f;
var positionY = GetYOffset(Vector2.Y.Get(position), scale, __instance);
var newPosition = ((ConstructorInfo)Vector2.Constructor.Reference).Invoke([positionX, positionY]);
pDrawable.Position.Set(performanceSprite, newPosition);

pDrawable.Scale.Set(performanceSprite, 0.50f);
pSpriteText.TextConstantSpacing.Set(performanceSprite, true);
pSpriteText.MeasureText.Invoke(performanceSprite);

SpriteManager.Add.Invoke(spriteManager, [performanceSprite]);
PerformanceDisplay.SetPerformanceCounter(performanceSprite);
}

[UsedImplicitly]
[HarmonyFinalizer]
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static void Finalizer(Exception? __exception)
{
if (__exception != null)
{
Console.WriteLine($"Exception due to {nameof(PatchAddPerformanceToScoreDisplay)}: {__exception}");
}
}

private static float GetYOffset(float baseYPosition, float scale, object scoreDisplay)
{
// Read the heights of both pSpriteTexts: s_Score, s_Accuracy
var sprites = ScoreDisplay.RuntimeType
.GetDeclaredFields()
.Where(f => f.FieldType == pSpriteText.RuntimeType)
.Select(f => f.GetValue(scoreDisplay));
var spriteSizes = sprites
.Where(s => s != null)
.Select(s => pSpriteText.MeasureText.Invoke(s));
var totalSpriteHeight = spriteSizes.Sum(v => Vector2.Y.Get(v)) * 0.58f * scale;

// Preserve additional spacing between s_Score and s_Accuracy
var additionalOffset = SkinManager.GetUseNewLayout.Invoke() ? 3f : 0f;

return baseYPosition + totalSpriteHeight + additionalOffset;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks;
using HarmonyLib;
using JetBrains.Annotations;
using Osu.Performance;
Expand Down Expand Up @@ -28,7 +29,6 @@ private static void After(
[HarmonyArgument(0)] int increaseScoreType,
[HarmonyArgument(2)] bool increaseCombo)
{
Console.WriteLine(increaseScoreType);
if (!PerformanceCalculator.IsInitialized)
{
Console.WriteLine("OnIncreaseScoreHit called before performance calculator initialized!");
Expand All @@ -53,7 +53,7 @@ private static void After(
var CurrentScore = Ruleset.CurrentScore.Get(__instance);
var MaxCombo = Score.MaxCombo.Get(CurrentScore);

PerformanceCalculator.Calculator?.AddJudgement(judgement, (uint)MaxCombo);
Task.Run(() => PerformanceCalculator.Calculator?.AddJudgement(judgement, (uint)MaxCombo));
}

[UsedImplicitly]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Osu.Patcher.Hook.Patches.LivePerformance;

/// <summary>
/// Hooks <c>Ruleset::ResetScore()</c> to also reset our performance calculator.
/// Hooks <c>Ruleset::ResetScore()</c> to also reset our performance calculator and caches.
/// </summary>
[HarmonyPatch]
[UsedImplicitly]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static void ResetCalculatorSync()
if (beatmapPath == null) return;

Calculator = new OsuPerformance(beatmapPath, (uint)mods);
Calculator.OnNewCalculation += Console.WriteLine;
Calculator.OnNewCalculation += PerformanceDisplay.UpdatePerformanceCounter;

Debug.WriteLine("Initialized performance calculator!");
}
Expand Down
38 changes: 38 additions & 0 deletions Osu.Patcher.Hook/Patches/LivePerformance/PerformanceDisplay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Osu.Stubs;

namespace Osu.Patcher.Hook.Patches.LivePerformance;

internal static class PerformanceDisplay
{
/// <summary>
/// The last known patched instance of our <c>pSpriteText</c> performance counter sprite.
/// </summary>
private static readonly WeakReference<object?> PerformanceCounter = new(null);

/// <summary>
/// Set a new active performance counter to update.
/// </summary>
/// <param name="sprite">The <c>pSpriteText</c> performance counter sprite.</param>
public static void SetPerformanceCounter(object sprite) =>
PerformanceCounter.SetTarget(sprite);

/// <summary>
/// Change the pp value for the currently active performance counter.
/// </summary>
public static void UpdatePerformanceCounter(double pp)
{
try
{
if (!PerformanceCounter.TryGetTarget(out var sprite) || sprite == null)
return;

// Technically this should be run with "GameBase.Scheduler.AddOnce(() => ...)" but it works anyways, so...
pText.SetText.Invoke(sprite, [$"{pp:00.0}pp"]);
}
catch (Exception e)
{
Console.WriteLine($"Failed to set performance counter sprite text: {e}");
}
}
}
16 changes: 16 additions & 0 deletions Osu.Stubs/Clocks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;

namespace Osu.Stubs;

/// <summary>
/// Original: <c>osu.Graphics.Sprites.Clocks</c>
/// </summary>
[UsedImplicitly]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class Clocks
{
public const int Game = 0;
public const int Audio = 0;
public const int AudioOnce = 0;
}
1 change: 1 addition & 0 deletions Osu.Stubs/Color.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class Color
{
public static readonly XnaColor Red = GetColor("Red");
public static readonly XnaColor Orange = GetColor("Orange");
public static readonly XnaColor White = GetColor("White");
public static readonly XnaColor GhostWhite = GetColor("GhostWhite");

private static XnaColor GetColor(string name)
Expand Down
32 changes: 32 additions & 0 deletions Osu.Stubs/Fields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;

namespace Osu.Stubs;

/// <summary>
/// Original: <c>osu.Graphics.Sprites.Fields</c>
/// </summary>
[UsedImplicitly]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class Fields
{
public const int GameField = 1;
public const int GameFieldWide = 2;
public const int Storyboard = 3;
public const int StoryboardCentre = 4;
public const int Native = 5;
public const int TopLeft = 6;
public const int TopCentre = 7;
public const int TopRight = 8;
public const int CentreLeft = 9;
public const int Centre = 10;
public const int CentreRight = 11;
public const int BottomLeft = 12;
public const int BottomCentre = 13;
public const int BottomRight = 14;
public const int StandardGameFieldScale = 15;
public const int NativeStandardScale = 16;
public const int NativeRight = 17;
public const int NativeBottomRight = 18;
public const int NativeBottomCentre = 19;
}
1 change: 1 addition & 0 deletions Osu.Stubs/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class Logger
Leave_S,
Ret,
},
false,
true
);
}
2 changes: 1 addition & 1 deletion Osu.Stubs/Obfuscated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class Obfuscated
Stsfld,
Ldc_I4,
Ldc_I4_0,
Callvirt, // This is a reference to Scheduler::AddDelayed, TODO: use this?
Callvirt,
Pop,
Leave_S,
Ldarg_0,
Expand Down
15 changes: 15 additions & 0 deletions Osu.Stubs/Opcode/LazyField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ public FieldInfo Reference
/// <returns>The current field value casted to the type defined by this LazyField.</returns>
public T Get(object? instance = null) =>
(T)Reference.GetValue(instance);

/// <summary>
/// Set a instance field to a specific value.
/// </summary>
/// <param name="instance">An instance of the field's enclosing class.</param>
/// <param name="value">The new value.</param>
public void Set(object instance, T value) =>
Reference.SetValue(instance, value);

/// <summary>
/// Set a static field to a specific value.
/// </summary>
/// <param name="value">The new value.</param>
public void Set(T value) =>
Reference.SetValue(null, value);
}
15 changes: 11 additions & 4 deletions Osu.Stubs/Opcode/LazyMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ public class LazyMethod
/// </summary>
/// <param name="name"><c>Class#Method</c> name of what this signature is matching.</param>
/// <param name="signature">Sequential opcodes to search the target method with.</param>
/// <param name="isConstructor">Whether this method is a constructor.</param>
/// <param name="entireMethod">Whether the signature is the entire method.</param>
internal LazyMethod(string name, IReadOnlyList<OpCode> signature, bool entireMethod = false)
internal LazyMethod(string name, IReadOnlyList<OpCode> signature,
bool isConstructor = false,
bool entireMethod = false)
{
_name = name;
_lazy = new Lazy<MethodBase?>(() => OpCodeMatcher.FindMethodBySignature(signature, entireMethod));
_lazy = new Lazy<MethodBase?>(() => isConstructor
? OpCodeMatcher.FindConstructorBySignature(signature, entireMethod)
: OpCodeMatcher.FindMethodBySignature(signature, entireMethod));
}

/// <summary>
Expand Down Expand Up @@ -66,8 +71,10 @@ public void Invoke(object? instance = null, object?[]? parameters = null) =>
public class LazyMethod<R> : LazyMethod
{
/// <inheritdoc />
internal LazyMethod(string name, IReadOnlyList<OpCode> signature)
: base(name, signature)
internal LazyMethod(string name, IReadOnlyList<OpCode> signature,
bool isConstructor = false,
bool entireMethod = false)
: base(name, signature, isConstructor, entireMethod)
{
}

Expand Down
6 changes: 4 additions & 2 deletions Osu.Stubs/Opcode/OpCodeMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ public static class OpCodeMatcher
/// Search for a constructor inside the osu! assembly by an IL OpCode signature.
/// </summary>
/// <param name="signature">A set of sequential OpCodes to match.</param>
/// <param name="entireMethod">Whether the signature is the entire method to search for.</param>
/// <returns>The found constructor (method) or null if none found.</returns>
public static ConstructorInfo? FindConstructorBySignature(IReadOnlyList<OpCode> signature)
public static ConstructorInfo? FindConstructorBySignature(IReadOnlyList<OpCode> signature,
bool entireMethod = false)
{
if (signature.Count <= 0) return null;

Expand All @@ -50,7 +52,7 @@ public static class OpCodeMatcher
var instructions = method.GetMethodBody()?.GetILAsByteArray();
if (instructions == null) continue;

if (InstructionsMatchesSignature(instructions, signature, false))
if (InstructionsMatchesSignature(instructions, signature, entireMethod))
return method;
}

Expand Down
23 changes: 23 additions & 0 deletions Osu.Stubs/Origins.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;

namespace Osu.Stubs;

/// <summary>
/// Original: <c>osu.Graphics.Sprites.Origins</c>
/// </summary>
[UsedImplicitly]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class Origins
{
public const int TopLeft = 0;
public const int Centre = 1;
public const int CentreLeft = 2;
public const int TopRight = 3;
public const int BottomCentre = 4;
public const int TopCentre = 5;
public const int Custom = 6;
public const int CentreRight = 7;
public const int BottomLeft = 8;
public const int BottomRight = 9;
}
Loading

0 comments on commit 66bece8

Please sign in to comment.