Skip to content
Closed
Show file tree
Hide file tree
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
7 changes: 7 additions & 0 deletions HitScoreVisualizer/Config-Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Number of decimal places to show time dependence to
### "timeDependencyDecimalOffset"
Which power of 10 to multiply the time dependence by

### "doIntermediateUpdates"
Disabling this only updates the score number once the note is cut and once the post swing score is done calculating; this slightly improves performance

### "assumeMaxPostSwing"
When the note is first cut, should the post swing score show the max rating before it finishes calculating

### "judgments"
Order from highest threshold to lowest; the first matching judgment will be applied

Expand Down Expand Up @@ -73,6 +79,7 @@ Judgments for time dependence (score is from 0-1)
"timeDependencyDecimalPrecision": 1,
"timeDependencyDecimalOffset": 2,
"doIntermediateUpdates": true,
"assumeMaxPostSwing": false,
"judgments": [
{ "threshold": 115, "text": "<size=250%><b>•", "color": [1, 1, 1, 1] },
{ "threshold": 108, "text": "<size=120%>%B%c%A", "color": [1, 1, 1, 1] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
using HitScoreVisualizer.Settings;
using SiraUtil.Affinity;
using TMPro;
// ReSharper disable InconsistentNaming

namespace HitScoreVisualizer.HarmonyPatches
{
internal class EffectPoolsManualInstallerPatch(BloomFontProvider bloomFontProvider, HSVConfig hsvConfig) : IAffinity
internal class EffectPoolsManualInstallerPatch : IAffinity
{
private readonly BloomFontProvider bloomFontProvider = bloomFontProvider;
private readonly HSVConfig hsvConfig = hsvConfig;
private readonly BloomFontProvider bloomFontProvider;
private readonly HSVConfig hsvConfig;

private EffectPoolsManualInstallerPatch(BloomFontProvider bloomFontProvider, HSVConfig hsvConfig)
{
this.bloomFontProvider = bloomFontProvider;
this.hsvConfig = hsvConfig;
}

[AffinityPrefix]
[AffinityPatch(typeof(EffectPoolsManualInstaller), nameof(EffectPoolsManualInstaller.ManualInstallBindings))]
Expand Down
85 changes: 40 additions & 45 deletions HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,59 @@

namespace HitScoreVisualizer.HarmonyPatches
{
internal class FlyingScoreEffectPatch(JudgmentService judgmentService, ConfigProvider configProvider) : IAffinity
internal class FlyingScoreEffectPatch : IAffinity
{
private readonly JudgmentService judgmentService = judgmentService;
private readonly ConfigProvider configProvider = configProvider;
private readonly JudgmentService judgmentService;
private readonly ConfigProvider configProvider;

private FlyingScoreEffectPatch(JudgmentService judgmentService, ConfigProvider configProvider)
{
this.judgmentService = judgmentService;
this.configProvider = configProvider;
}

// When the flying score effect spawns, InitAndPresent is called
// When the post swing score changes - as the saber moves - HandleCutScoreBufferDidChange is called
// When the post swing score stops changing - HandleCutScoreBufferDidFinish is called

[AffinityPrefix]
[AffinityPatch(typeof(FlyingScoreEffect), nameof(FlyingScoreEffect.InitAndPresent))]
internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScoreBuffer cutScoreBuffer, float duration, Vector3 targetPos, Color color)
internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScoreBuffer cutScoreBuffer, float duration, Vector3 targetPos)
{
var configuration = configProvider.CurrentConfig;
var noteCutInfo = cutScoreBuffer.noteCutInfo;

if (configuration != null)
if (configuration == null)
{
if (configuration.FixedPosition != null)
{
// Set current and target position to the desired fixed position
targetPos = configuration.FixedPosition.Value;
__instance.transform.position = targetPos;
}
else if (configuration.TargetPositionOffset != null)
{
targetPos += configuration.TargetPositionOffset.Value;
}
// Run original implementation
return true;
}

var (text, color) = judgmentService.Judge(cutScoreBuffer, configuration.AssumeMaxPostSwing);
__instance._text.text = text;
__instance._color = color;
__instance._cutScoreBuffer = cutScoreBuffer;
__instance._maxCutDistanceScoreIndicator.enabled = false;
__instance._colorAMultiplier = 1f;

if (!cutScoreBuffer.isFinished)
{
cutScoreBuffer.RegisterDidChangeReceiver(__instance);
cutScoreBuffer.RegisterDidFinishReceiver(__instance);
__instance._registeredToCallbacks = true;
}

if (configuration == null)
if (configuration.FixedPosition != null)
{
__instance._text.text = cutScoreBuffer.cutScore.ToString();
__instance._maxCutDistanceScoreIndicator.enabled = cutScoreBuffer.centerDistanceCutScore == cutScoreBuffer.noteScoreDefinition.maxCenterDistanceCutScore;
__instance._colorAMultiplier = (double) cutScoreBuffer.cutScore > (double) cutScoreBuffer.maxPossibleCutScore * 0.9f ? 1f : 0.3f;
// Set current and target position to the desired fixed position
targetPos = configuration.FixedPosition.Value;
__instance.transform.position = targetPos;
}
else
else if (configuration.TargetPositionOffset != null)
{
__instance._maxCutDistanceScoreIndicator.enabled = false;

// Apply judgments a total of twice - once when the effect is created, once when it finishes.
Judge(__instance, (CutScoreBuffer)cutScoreBuffer);
targetPos += configuration.TargetPositionOffset.Value;
}

__instance.InitAndPresent(duration, targetPos, noteCutInfo.worldRotation, false);

return false;
}

[AffinityPrefix]
[AffinityPatch(typeof(FlyingScoreEffect), nameof(FlyingScoreEffect.ManualUpdate))]
internal bool ManualUpdate(FlyingScoreEffect __instance, float t)
{
var color = __instance._color.ColorWithAlpha(__instance._fadeAnimationCurve.Evaluate(t));
__instance._text.color = color;
__instance._maxCutDistanceScoreIndicator.color = color;
__instance.InitAndPresent(duration, targetPos, cutScoreBuffer.noteCutInfo.worldRotation, false);

return false;
}
Expand All @@ -80,27 +72,30 @@ internal bool HandleCutScoreBufferDidChange(FlyingScoreEffect __instance, CutSco
return true;
}

if (configuration.DoIntermediateUpdates)
if (!configuration.DoIntermediateUpdates)
{
Judge(__instance, cutScoreBuffer);
return false;
}

var (text, color) = judgmentService.Judge(cutScoreBuffer, false);
__instance._text.text = text;
__instance._color = color;

return false;
}

[AffinityPrefix]
[AffinityPatch(typeof(FlyingScoreEffect), nameof(FlyingScoreEffect.HandleCutScoreBufferDidFinish))]
internal void HandleCutScoreBufferDidFinish(FlyingScoreEffect __instance, CutScoreBuffer cutScoreBuffer)
{
if (configProvider.CurrentConfig != null)
if (configProvider.CurrentConfig == null)
{
Judge(__instance, cutScoreBuffer);
return;
}
}

private void Judge(FlyingScoreEffect flyingScoreEffect, IReadonlyCutScoreBuffer cutScoreBuffer)
{
(flyingScoreEffect._text.text, flyingScoreEffect._color) = judgmentService.Judge(cutScoreBuffer);
var (text, color) = judgmentService.Judge(cutScoreBuffer, false);
__instance._text.text = text;
__instance._color = color;
}
}
}
11 changes: 9 additions & 2 deletions HitScoreVisualizer/Installers/HsvAppInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
using HitScoreVisualizer.HarmonyPatches;
using HitScoreVisualizer.Services;
using HitScoreVisualizer.Settings;
using JetBrains.Annotations;
using Zenject;

namespace HitScoreVisualizer.Installers
{
internal sealed class HsvAppInstaller(HSVConfig hsvConfig) : Installer
[UsedImplicitly]
internal sealed class HsvAppInstaller : Installer
{
private readonly HSVConfig hsvConfig = hsvConfig;
private readonly HSVConfig hsvConfig;

private HsvAppInstaller(HSVConfig hsvConfig)
{
this.hsvConfig = hsvConfig;
}

public override void InstallBindings()
{
Expand Down
44 changes: 27 additions & 17 deletions HitScoreVisualizer/Services/JudgmentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,39 @@
using HitScoreVisualizer.Extensions;
using HitScoreVisualizer.Models;
using HitScoreVisualizer.Settings;
using JetBrains.Annotations;
using SiraUtil.Logging;
using IPA.Utilities;
using UnityEngine;

namespace HitScoreVisualizer.Services
{
internal class JudgmentService(ConfigProvider configProvider)
[UsedImplicitly]
internal class JudgmentService
{
private readonly ConfigProvider configProvider = configProvider;
private readonly ConfigProvider configProvider;

private JudgmentService(ConfigProvider configProvider)
{
this.configProvider = configProvider;
}

private Configuration Config => configProvider.CurrentConfig ?? Configuration.Default;

public (string hitScoreText, Color hitScoreColor) Judge(IReadonlyCutScoreBuffer cutScoreBuffer)
public (string hitScoreText, Color hitScoreColor) Judge(IReadonlyCutScoreBuffer cutScoreBuffer, bool assumeMaxPostSwing)
{
var afterCutScore = assumeMaxPostSwing ? cutScoreBuffer.noteScoreDefinition.maxAfterCutScore : cutScoreBuffer.afterCutScore;

return cutScoreBuffer.noteCutInfo.noteData.gameplayType switch
{
NoteData.GameplayType.Normal => GetNormalDisplay(cutScoreBuffer),
NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer),
NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer),
NoteData.GameplayType.Normal => GetNormalDisplay(cutScoreBuffer, afterCutScore),
NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer, afterCutScore),
NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer, afterCutScore),
_ => (string.Empty, Color.white),
};
}

private (string, Color) GetNormalDisplay(IReadonlyCutScoreBuffer cutScoreBuffer)
private (string, Color) GetNormalDisplay(IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore)
{
var judgment = NormalJudgment.Default;
var fadeJudgment = NormalJudgment.Default;
Expand All @@ -52,12 +62,12 @@ internal class JudgmentService(ConfigProvider configProvider)
Mathf.InverseLerp(judgment.Threshold, fadeJudgment.Threshold, cutScoreBuffer.cutScore))
: judgment.Color.ToColor();

var text = FormatJudgmentTextByMode(judgment.Text, cutScoreBuffer);
var text = FormatJudgmentTextByMode(judgment.Text, cutScoreBuffer, afterCutScore);

return (text, color);
}

private (string, Color) GetChainHeadDisplay(IReadonlyCutScoreBuffer cutScoreBuffer)
private (string, Color) GetChainHeadDisplay(IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore)
{
var judgment = ChainHeadJudgment.Default;
var fadeJudgment = ChainHeadJudgment.Default;
Expand All @@ -83,22 +93,22 @@ internal class JudgmentService(ConfigProvider configProvider)
Mathf.InverseLerp(judgment.Threshold, fadeJudgment.Threshold, cutScoreBuffer.cutScore))
: judgment.Color?.ToColor();

var text = FormatJudgmentTextByMode(judgment.Text, cutScoreBuffer);
var text = FormatJudgmentTextByMode(judgment.Text, cutScoreBuffer, afterCutScore);

return (text, color ?? Color.white);
}

private (string, Color) GetChainSegmentDisplay(IReadonlyCutScoreBuffer cutScoreBuffer)
private (string, Color) GetChainSegmentDisplay(IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore)
{
var chainLinkDisplay = Config.ChainLinkDisplay ?? ChainLinkDisplay.Default;
return (chainLinkDisplay.Text, chainLinkDisplay.Color.ToColor());
return (FormatJudgmentTextByMode(chainLinkDisplay.Text, cutScoreBuffer, afterCutScore), chainLinkDisplay.Color.ToColor());
}

private string FormatJudgmentTextByMode(string unformattedText, IReadonlyCutScoreBuffer cutScoreBuffer)
private string FormatJudgmentTextByMode(string unformattedText, IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore)
{
return Config.DisplayMode switch
{
"format" => FormatJudgmentText(unformattedText, cutScoreBuffer),
"format" => FormatJudgmentText(unformattedText, cutScoreBuffer, afterCutScore),
"textOnly" => unformattedText,
"numeric" => cutScoreBuffer.cutScore.ToString(),
"scoreOnTop" => $"{cutScoreBuffer.cutScore}\n{unformattedText}\n",
Expand All @@ -107,7 +117,7 @@ private string FormatJudgmentTextByMode(string unformattedText, IReadonlyCutScor
};
}

private string FormatJudgmentText(string unformattedText, IReadonlyCutScoreBuffer cutScoreBuffer)
private string FormatJudgmentText(string unformattedText, IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore)
{
var formattedBuilder = new StringBuilder();
var nextPercentIndex = unformattedText.IndexOf('%');
Expand All @@ -133,7 +143,7 @@ private string FormatJudgmentText(string unformattedText, IReadonlyCutScoreBuffe
formattedBuilder.Append(cutScoreBuffer.centerDistanceCutScore);
break;
case 'a':
formattedBuilder.Append(cutScoreBuffer.afterCutScore);
formattedBuilder.Append(afterCutScore);
break;
case 't':
formattedBuilder.Append((timeDependence * Mathf.Pow(10, Config.TimeDependenceDecimalOffset)).ToString($"n{Config.TimeDependenceDecimalPrecision}"));
Expand All @@ -145,7 +155,7 @@ private string FormatJudgmentText(string unformattedText, IReadonlyCutScoreBuffe
formattedBuilder.Append(Config.AccuracyJudgments.JudgeSegment(cutScoreBuffer.centerDistanceCutScore));
break;
case 'A':
formattedBuilder.Append(Config.AfterCutAngleJudgments.JudgeSegment(cutScoreBuffer.afterCutScore));
formattedBuilder.Append(Config.AfterCutAngleJudgments.JudgeSegment(afterCutScore));
break;
case 'T':
formattedBuilder.Append(Config.TimeDependenceJudgments.JudgeTimeDependenceSegment(timeDependence, Config.TimeDependenceDecimalOffset, Config.TimeDependenceDecimalPrecision));
Expand Down
20 changes: 14 additions & 6 deletions HitScoreVisualizer/Settings/ChainHeadJudgment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,40 @@

namespace HitScoreVisualizer.Settings
{
[method: JsonConstructor]
public readonly struct ChainHeadJudgment(int threshold = 0, string? text = null, List<float>? color = null, bool fade = false)
public class ChainHeadJudgment
{
[JsonConstructor]
public ChainHeadJudgment(int threshold = 0, string? text = null, List<float>? color = null, bool fade = false)
{
Threshold = threshold;
Text = text ?? string.Empty;
Color = color ?? [0, 0, 0, 0];
Fade = fade;
}

[JsonIgnore]
internal static ChainHeadJudgment Default { get; } = new(0, "%s", [1, 1, 1, 1], false);

// This judgment will be applied only to chain note heads hit with score >= this number.
// Note that if no judgment can be applied to a note, the text will appear as in the unmodded
// game.
[JsonProperty("threshold")]
public int Threshold { get; } = threshold;
public int Threshold { get; }

// The text to display (if judgment text is enabled).
[JsonProperty("text")]
public string Text { get; } = text ?? string.Empty;
public string Text { get; }

// 4 floats, 0-1; red, green, blue, glow (not transparency!)
// leaving this out should look obviously wrong
[JsonProperty("color")]
public List<float> Color { get; } = color ?? [0, 0, 0, 0];
public List<float> Color { get; }

// If true, the text color will be interpolated between this judgment's color and the previous
// based on how close to the next threshold it is.
// Specifying fade : true for the first judgment in the array is an error, and will crash the
// plugin.
[JsonProperty("fade")]
public bool Fade { get; } = fade;
public bool Fade { get; }
}
}
14 changes: 10 additions & 4 deletions HitScoreVisualizer/Settings/ChainLinkDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@

namespace HitScoreVisualizer.Settings
{
[method: JsonConstructor]
public readonly struct ChainLinkDisplay(string? text = null, List<float>? color = null)
public class ChainLinkDisplay
{
[JsonConstructor]
public ChainLinkDisplay(string? text = null, List<float>? color = null)
{
Text = text ?? string.Empty;
Color = color ?? [0, 0, 0, 0];
}

[JsonIgnore]
internal static ChainLinkDisplay Default { get; } = new("<u>20", [1, 1, 1, 1]);

// The text to display for a chain segment
[JsonProperty("text")]
public string Text { get; } = text ?? string.Empty;
public string Text { get; }

// 4 floats, 0-1; red, green, blue, glow (not transparency!)
// leaving this out should look obviously wrong
[JsonProperty("color")]
public List<float> Color { get; } = color ?? [0, 0, 0, 0];
public List<float> Color { get; }
}
}
Loading