From f4b34fd202208c5cfb25615a5bc4c44850f345e7 Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:24:37 +0100 Subject: [PATCH 1/9] Change struct back to class --- HitScoreVisualizer/Settings/ChainHeadJudgment.cs | 2 +- HitScoreVisualizer/Settings/ChainLinkDisplay.cs | 2 +- HitScoreVisualizer/Settings/JudgmentSegment.cs | 2 +- HitScoreVisualizer/Settings/NormalJudgment.cs | 2 +- HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HitScoreVisualizer/Settings/ChainHeadJudgment.cs b/HitScoreVisualizer/Settings/ChainHeadJudgment.cs index e7dc087..4b924a8 100644 --- a/HitScoreVisualizer/Settings/ChainHeadJudgment.cs +++ b/HitScoreVisualizer/Settings/ChainHeadJudgment.cs @@ -4,7 +4,7 @@ namespace HitScoreVisualizer.Settings { [method: JsonConstructor] - public readonly struct ChainHeadJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) + public class ChainHeadJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) { [JsonIgnore] internal static ChainHeadJudgment Default { get; } = new(0, "%s", [1, 1, 1, 1], false); diff --git a/HitScoreVisualizer/Settings/ChainLinkDisplay.cs b/HitScoreVisualizer/Settings/ChainLinkDisplay.cs index c27a184..8f0e856 100644 --- a/HitScoreVisualizer/Settings/ChainLinkDisplay.cs +++ b/HitScoreVisualizer/Settings/ChainLinkDisplay.cs @@ -4,7 +4,7 @@ namespace HitScoreVisualizer.Settings { [method: JsonConstructor] - public readonly struct ChainLinkDisplay(string? text = null, List? color = null) + public class ChainLinkDisplay(string? text = null, List? color = null) { [JsonIgnore] internal static ChainLinkDisplay Default { get; } = new("20", [1, 1, 1, 1]); diff --git a/HitScoreVisualizer/Settings/JudgmentSegment.cs b/HitScoreVisualizer/Settings/JudgmentSegment.cs index 653d6b0..43e8ca8 100644 --- a/HitScoreVisualizer/Settings/JudgmentSegment.cs +++ b/HitScoreVisualizer/Settings/JudgmentSegment.cs @@ -3,7 +3,7 @@ namespace HitScoreVisualizer.Settings { [method: JsonConstructor] - public readonly struct JudgmentSegment(int threshold = 0, string? text = null) + public class JudgmentSegment(int threshold = 0, string? text = null) { [JsonIgnore] internal static JudgmentSegment Default { get; } = new(0, string.Empty); diff --git a/HitScoreVisualizer/Settings/NormalJudgment.cs b/HitScoreVisualizer/Settings/NormalJudgment.cs index d2b769b..b9f98c3 100644 --- a/HitScoreVisualizer/Settings/NormalJudgment.cs +++ b/HitScoreVisualizer/Settings/NormalJudgment.cs @@ -4,7 +4,7 @@ namespace HitScoreVisualizer.Settings { [method: JsonConstructor] - public readonly struct NormalJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) + public class NormalJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) { [JsonIgnore] internal static NormalJudgment Default { get; } = new(0, "%s", [1, 1, 1, 1], false); diff --git a/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs b/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs index 82e5c36..7942c3c 100644 --- a/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs +++ b/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs @@ -3,7 +3,7 @@ namespace HitScoreVisualizer.Settings { [method: JsonConstructor] - public readonly struct TimeDependenceJudgmentSegment(float threshold = 0f, string? text = null) + public class TimeDependenceJudgmentSegment(float threshold = 0f, string? text = null) { [JsonIgnore] internal static TimeDependenceJudgmentSegment Default { get; } = new(0, string.Empty); From 926fc5db52a3136fe9d7c1b83a0ace0a89fc45bc Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:25:39 +0100 Subject: [PATCH 2/9] Making the score effect patch much clearer --- .../HarmonyPatches/FlyingScoreEffectPatch.cs | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs index 0272a04..d831978 100644 --- a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs @@ -9,29 +9,29 @@ internal class FlyingScoreEffectPatch(JudgmentService judgmentService, ConfigPro private readonly JudgmentService judgmentService = judgmentService; private readonly ConfigProvider 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); + __instance._text.text = text; __instance._color = color; __instance._cutScoreBuffer = cutScoreBuffer; + __instance._maxCutDistanceScoreIndicator.enabled = false; + __instance._colorAMultiplier = 1f; + if (!cutScoreBuffer.isFinished) { cutScoreBuffer.RegisterDidChangeReceiver(__instance); @@ -39,32 +39,18 @@ internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScore __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; } @@ -82,7 +68,9 @@ internal bool HandleCutScoreBufferDidChange(FlyingScoreEffect __instance, CutSco if (configuration.DoIntermediateUpdates) { - Judge(__instance, cutScoreBuffer); + var (text, color) = judgmentService.Judge(cutScoreBuffer); + __instance._text.text = text; + __instance._color = color; } return false; @@ -94,13 +82,10 @@ internal void HandleCutScoreBufferDidFinish(FlyingScoreEffect __instance, CutSco { if (configProvider.CurrentConfig != null) { - Judge(__instance, cutScoreBuffer); + var (text, color) = judgmentService.Judge(cutScoreBuffer); + __instance._text.text = text; + __instance._color = color; } } - - private void Judge(FlyingScoreEffect flyingScoreEffect, IReadonlyCutScoreBuffer cutScoreBuffer) - { - (flyingScoreEffect._text.text, flyingScoreEffect._color) = judgmentService.Judge(cutScoreBuffer); - } } } From bc0d21d023c5e1f9a6add0802b3eb83a57ee856c Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:30:38 +0100 Subject: [PATCH 3/9] Re-add max post swing on score effect init --- .../HarmonyPatches/FlyingScoreEffectPatch.cs | 2 +- .../Services/JudgmentService.cs | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs index d831978..87f7507 100644 --- a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs @@ -25,7 +25,7 @@ internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScore return true; } - var (text, color) = judgmentService.Judge(cutScoreBuffer); + var (text, color) = judgmentService.Judge(cutScoreBuffer, cutScoreBuffer.noteScoreDefinition.maxAfterCutScore); __instance._text.text = text; __instance._color = color; __instance._cutScoreBuffer = cutScoreBuffer; diff --git a/HitScoreVisualizer/Services/JudgmentService.cs b/HitScoreVisualizer/Services/JudgmentService.cs index d3221fe..409b2ca 100644 --- a/HitScoreVisualizer/Services/JudgmentService.cs +++ b/HitScoreVisualizer/Services/JudgmentService.cs @@ -12,18 +12,19 @@ internal class JudgmentService(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, int? afterCutOverride = null) { + afterCutOverride ??= cutScoreBuffer.afterCutScore; return cutScoreBuffer.noteCutInfo.noteData.gameplayType switch { - NoteData.GameplayType.Normal => GetNormalDispaly(cutScoreBuffer), - NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer), - NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer), + NoteData.GameplayType.Normal => GetNormalDispaly(cutScoreBuffer, afterCutOverride.Value), + NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer, afterCutOverride.Value), + NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer, afterCutOverride.Value), _ => (string.Empty, Color.white), }; } - private (string, Color) GetNormalDispaly(IReadonlyCutScoreBuffer cutScoreBuffer) + private (string, Color) GetNormalDispaly(IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore) { var judgment = NormalJudgment.Default; var fadeJudgment = NormalJudgment.Default; @@ -49,12 +50,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; @@ -80,23 +81,24 @@ 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) => - ( - Config.ChainLinkDisplay != null ? FormatJudgmentTextByMode(Config.ChainLinkDisplay.Value.Text, cutScoreBuffer) - : ChainLinkDisplay.Default.Text, - (Config.ChainLinkDisplay?.Color ?? ChainLinkDisplay.Default.Color).ToColor() - ); + private (string, Color) GetChainSegmentDisplay(IReadonlyCutScoreBuffer cutScoreBuffer, int afterCutScore) + { + var text = Config.ChainLinkDisplay == null ? ChainLinkDisplay.Default.Text + : FormatJudgmentTextByMode(Config.ChainLinkDisplay.Text, cutScoreBuffer, afterCutScore); + var color = (Config.ChainLinkDisplay?.Color ?? ChainLinkDisplay.Default.Color).ToColor(); + return (text, color); + } - 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", @@ -104,7 +106,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('%'); @@ -130,7 +132,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}")); @@ -142,7 +144,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)); From 985efa2de553eaef22c8b1383fe0def19594ca58 Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:09:19 +0100 Subject: [PATCH 4/9] Add setting for showing max post swing --- HitScoreVisualizer/Config-Documentation.md | 7 +++++++ .../HarmonyPatches/FlyingScoreEffectPatch.cs | 5 ++++- HitScoreVisualizer/Settings/Configuration.cs | 4 ++++ README.md | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/HitScoreVisualizer/Config-Documentation.md b/HitScoreVisualizer/Config-Documentation.md index bcd2eb5..c08e0b6 100644 --- a/HitScoreVisualizer/Config-Documentation.md +++ b/HitScoreVisualizer/Config-Documentation.md @@ -34,6 +34,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 + +### "showMaxPostswingFirst" +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 @@ -69,6 +75,7 @@ Judgments for time dependence (score is from 0-1) "timeDependencyDecimalPrecision": 1, "timeDependencyDecimalOffset": 2, "doIntermediateUpdates": true, + "showMaxPostswingFirst": false, "judgments": [ { "threshold": 115, "text": "•", "color": [1, 1, 1, 1] }, { "threshold": 108, "text": "%B%c%A", "color": [1, 1, 1, 1] }, diff --git a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs index 87f7507..4023bf0 100644 --- a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs @@ -25,7 +25,10 @@ internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScore return true; } - var (text, color) = judgmentService.Judge(cutScoreBuffer, cutScoreBuffer.noteScoreDefinition.maxAfterCutScore); + // Decide whether to override the initial post-swing score or not + var (text, color) = configuration.ShowMaxPostswingFirst + ? judgmentService.Judge(cutScoreBuffer, cutScoreBuffer.noteScoreDefinition.maxAfterCutScore) + : judgmentService.Judge(cutScoreBuffer); __instance._text.text = text; __instance._color = color; __instance._cutScoreBuffer = cutScoreBuffer; diff --git a/HitScoreVisualizer/Settings/Configuration.cs b/HitScoreVisualizer/Settings/Configuration.cs index f2dc5d2..68da2a1 100644 --- a/HitScoreVisualizer/Settings/Configuration.cs +++ b/HitScoreVisualizer/Settings/Configuration.cs @@ -17,6 +17,7 @@ public class Configuration IsDefaultConfig = true, DisplayMode = "format", DoIntermediateUpdates = true, + ShowMaxPostswingFirst = false, TimeDependenceDecimalPrecision = 1, TimeDependenceDecimalOffset = 2, NormalJudgments = @@ -115,6 +116,9 @@ internal Version Version [JsonProperty("doIntermediateUpdates")] public bool DoIntermediateUpdates { get; internal set; } + [JsonProperty("showMaxPostswingFirst")] + public bool ShowMaxPostswingFirst { get; internal set; } + [JsonProperty("timeDependencyDecimalPrecision")] [DefaultValue(1)] public int TimeDependenceDecimalPrecision { get; internal set; } diff --git a/README.md b/README.md index 6af2579..4b1d37c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ You can use that file as a starting point in case you want to customize it. Just | fixedPosition| The coordinate object that defines the fixed location where the hit scores have to be shown.
Either leave this out or set as `null` to fully disable. | {
"x": 0.0,
"y": 3.0,
"z": -2.0
}

null | | targetPositionOffset| The coordinate object that indicates how much the hitscore fade animation target position has to be offset.
Note: If a fixed position is defined in the config, that one will take priority over this one and this will be fully ignored.
Either leave this out or set as `null` to fully disable. | {
"x": 0.0,
"y": 3.0,
"z": -2.0
}

null | | doIntermediateUpdates | When enabled, Judgments will be updated multiple times. This will make score popups more accurate during a brief period before the note's score is finalized, at some cost of performance. | true or false | +| showMaxPostswingFirst | When enabled, the first score number will show the maximum possible post-swing score for that note cut. This can be preferable when doIntermediateUpdates is false, your swings are very slow, and you don't want to automatically use lower score thresholds as the game calculates your swing. | true or false | | timeDependencyDecimalPrecision | The number of decimal places to show the time dependence to.
**Must be between 0 and 99, inclusive** | ints | | timeDependencyDecimalOffset | Which power of 10 to multiply the time dependence by (time dependence is from 0 - 1).
**Must be between 0 and 38, inclusive** | ints | | judgments | The list of Judgments that can be used to customize the Judgments in general. | Uses judgment objects.
More info below. | From 6d7d0eacbf345317b6c5e1e3eaababa891bbf1b2 Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:57:14 +0000 Subject: [PATCH 5/9] Revert primary constructors --- .../EffectPoolsManualInstallerPatch.cs | 13 +++++++++--- .../HarmonyPatches/FlyingScoreEffectPatch.cs | 12 ++++++++--- .../Installers/HsvAppInstaller.cs | 11 ++++++++-- .../Services/JudgmentService.cs | 11 ++++++++-- .../Settings/ChainHeadJudgment.cs | 20 +++++++++++++------ .../Settings/ChainLinkDisplay.cs | 14 +++++++++---- .../Settings/JudgmentSegment.cs | 14 +++++++++---- HitScoreVisualizer/Settings/NormalJudgment.cs | 20 +++++++++++++------ .../Settings/TimeDependenceJudgmentSegment.cs | 14 +++++++++---- 9 files changed, 95 insertions(+), 34 deletions(-) diff --git a/HitScoreVisualizer/HarmonyPatches/EffectPoolsManualInstallerPatch.cs b/HitScoreVisualizer/HarmonyPatches/EffectPoolsManualInstallerPatch.cs index edb3716..b97a8fe 100644 --- a/HitScoreVisualizer/HarmonyPatches/EffectPoolsManualInstallerPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/EffectPoolsManualInstallerPatch.cs @@ -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))] diff --git a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs index 4023bf0..89445b0 100644 --- a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs @@ -4,10 +4,16 @@ 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 diff --git a/HitScoreVisualizer/Installers/HsvAppInstaller.cs b/HitScoreVisualizer/Installers/HsvAppInstaller.cs index 7804768..330edeb 100644 --- a/HitScoreVisualizer/Installers/HsvAppInstaller.cs +++ b/HitScoreVisualizer/Installers/HsvAppInstaller.cs @@ -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() { diff --git a/HitScoreVisualizer/Services/JudgmentService.cs b/HitScoreVisualizer/Services/JudgmentService.cs index 409b2ca..c92612e 100644 --- a/HitScoreVisualizer/Services/JudgmentService.cs +++ b/HitScoreVisualizer/Services/JudgmentService.cs @@ -1,14 +1,21 @@ using System.Text; using HitScoreVisualizer.Extensions; using HitScoreVisualizer.Settings; +using JetBrains.Annotations; using SiraUtil.Logging; 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; diff --git a/HitScoreVisualizer/Settings/ChainHeadJudgment.cs b/HitScoreVisualizer/Settings/ChainHeadJudgment.cs index 4b924a8..f47fcdc 100644 --- a/HitScoreVisualizer/Settings/ChainHeadJudgment.cs +++ b/HitScoreVisualizer/Settings/ChainHeadJudgment.cs @@ -3,9 +3,17 @@ namespace HitScoreVisualizer.Settings { - [method: JsonConstructor] - public class ChainHeadJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) + public class ChainHeadJudgment { + [JsonConstructor] + public ChainHeadJudgment(int threshold = 0, string? text = null, List? 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); @@ -13,22 +21,22 @@ public class ChainHeadJudgment(int threshold = 0, string? text = null, List Color { get; } = color ?? [0, 0, 0, 0]; + public List 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; } } } \ No newline at end of file diff --git a/HitScoreVisualizer/Settings/ChainLinkDisplay.cs b/HitScoreVisualizer/Settings/ChainLinkDisplay.cs index 8f0e856..568a00a 100644 --- a/HitScoreVisualizer/Settings/ChainLinkDisplay.cs +++ b/HitScoreVisualizer/Settings/ChainLinkDisplay.cs @@ -3,19 +3,25 @@ namespace HitScoreVisualizer.Settings { - [method: JsonConstructor] - public class ChainLinkDisplay(string? text = null, List? color = null) + public class ChainLinkDisplay { + [JsonConstructor] + public ChainLinkDisplay(string? text = null, List? color = null) + { + Text = text ?? string.Empty; + Color = color ?? [0, 0, 0, 0]; + } + [JsonIgnore] internal static ChainLinkDisplay Default { get; } = new("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 Color { get; } = color ?? [0, 0, 0, 0]; + public List Color { get; } } } \ No newline at end of file diff --git a/HitScoreVisualizer/Settings/JudgmentSegment.cs b/HitScoreVisualizer/Settings/JudgmentSegment.cs index 43e8ca8..f5ace4c 100644 --- a/HitScoreVisualizer/Settings/JudgmentSegment.cs +++ b/HitScoreVisualizer/Settings/JudgmentSegment.cs @@ -2,19 +2,25 @@ namespace HitScoreVisualizer.Settings { - [method: JsonConstructor] - public class JudgmentSegment(int threshold = 0, string? text = null) + public class JudgmentSegment { + [JsonConstructor] + public JudgmentSegment(int threshold = 0, string? text = null) + { + Threshold = threshold; + Text = text ?? string.Empty; + } + [JsonIgnore] internal static JudgmentSegment Default { get; } = new(0, string.Empty); // This judgment will be applied only when the appropriate part of the swing contributes score >= this number. // If no judgment can be applied, the judgment for this segment will be "" (the empty string). [JsonProperty("threshold")] - public int Threshold { get; } = threshold; + public int Threshold { get; } // The text to replace the appropriate judgment specifier with (%B, %C, %A) when this judgment applies. [JsonProperty("text")] - public string? Text { get; } = text ?? string.Empty; + public string? Text { get; } } } \ No newline at end of file diff --git a/HitScoreVisualizer/Settings/NormalJudgment.cs b/HitScoreVisualizer/Settings/NormalJudgment.cs index b9f98c3..458bd1a 100644 --- a/HitScoreVisualizer/Settings/NormalJudgment.cs +++ b/HitScoreVisualizer/Settings/NormalJudgment.cs @@ -3,9 +3,17 @@ namespace HitScoreVisualizer.Settings { - [method: JsonConstructor] - public class NormalJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) + public class NormalJudgment { + [JsonConstructor] + public NormalJudgment(int threshold = 0, string? text = null, List? color = null, bool fade = false) + { + Threshold = threshold; + Text = text ?? string.Empty; + Color = color ?? [0, 0, 0, 0]; + Fade = fade; + } + [JsonIgnore] internal static NormalJudgment Default { get; } = new(0, "%s", [1, 1, 1, 1], false); @@ -13,22 +21,22 @@ public class NormalJudgment(int threshold = 0, string? text = null, List? // 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 Color { get; } = color ?? [0, 0, 0, 0]; + public List 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; } } } \ No newline at end of file diff --git a/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs b/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs index 7942c3c..3543f64 100644 --- a/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs +++ b/HitScoreVisualizer/Settings/TimeDependenceJudgmentSegment.cs @@ -2,19 +2,25 @@ namespace HitScoreVisualizer.Settings { - [method: JsonConstructor] - public class TimeDependenceJudgmentSegment(float threshold = 0f, string? text = null) + public class TimeDependenceJudgmentSegment { + [JsonConstructor] + public TimeDependenceJudgmentSegment(float threshold = 0f, string? text = null) + { + Threshold = threshold; + Text = text ?? string.Empty; + } + [JsonIgnore] internal static TimeDependenceJudgmentSegment Default { get; } = new(0, string.Empty); // This judgment will be applied only when the time dependence >= this number. // If no judgment can be applied, the judgment for this segment will be "" (the empty string). [JsonProperty("threshold")] - public float Threshold { get; } = threshold; + public float Threshold { get; } // The text to replace the appropriate judgment specifier with (%T) when this judgment applies. [JsonProperty("text")] - public string? Text { get; } = text ?? string.Empty; + public string? Text { get; } } } \ No newline at end of file From 12d09735046fcb3ac741290790370afe4bec8924 Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:31:38 +0000 Subject: [PATCH 6/9] ShowMaxPostswingFirst -> AssumeMaxPostSwing --- HitScoreVisualizer/Config-Documentation.md | 4 +-- .../HarmonyPatches/FlyingScoreEffectPatch.cs | 25 ++++++++++--------- .../Services/JudgmentService.cs | 11 ++++---- HitScoreVisualizer/Settings/Configuration.cs | 6 ++--- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/HitScoreVisualizer/Config-Documentation.md b/HitScoreVisualizer/Config-Documentation.md index c08e0b6..d086b5e 100644 --- a/HitScoreVisualizer/Config-Documentation.md +++ b/HitScoreVisualizer/Config-Documentation.md @@ -37,7 +37,7 @@ 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 -### "showMaxPostswingFirst" +### "assumeMaxPostSwing" When the note is first cut, should the post swing score show the max rating before it finishes calculating ### "judgments" @@ -75,7 +75,7 @@ Judgments for time dependence (score is from 0-1) "timeDependencyDecimalPrecision": 1, "timeDependencyDecimalOffset": 2, "doIntermediateUpdates": true, - "showMaxPostswingFirst": false, + "assumeMaxPostSwing": false, "judgments": [ { "threshold": 115, "text": "•", "color": [1, 1, 1, 1] }, { "threshold": 108, "text": "%B%c%A", "color": [1, 1, 1, 1] }, diff --git a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs index 89445b0..e1da7c2 100644 --- a/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs +++ b/HitScoreVisualizer/HarmonyPatches/FlyingScoreEffectPatch.cs @@ -31,10 +31,7 @@ internal bool InitAndPresent(ref FlyingScoreEffect __instance, IReadonlyCutScore return true; } - // Decide whether to override the initial post-swing score or not - var (text, color) = configuration.ShowMaxPostswingFirst - ? judgmentService.Judge(cutScoreBuffer, cutScoreBuffer.noteScoreDefinition.maxAfterCutScore) - : judgmentService.Judge(cutScoreBuffer); + var (text, color) = judgmentService.Judge(cutScoreBuffer, configuration.AssumeMaxPostSwing); __instance._text.text = text; __instance._color = color; __instance._cutScoreBuffer = cutScoreBuffer; @@ -75,13 +72,15 @@ internal bool HandleCutScoreBufferDidChange(FlyingScoreEffect __instance, CutSco return true; } - if (configuration.DoIntermediateUpdates) + if (!configuration.DoIntermediateUpdates) { - var (text, color) = judgmentService.Judge(cutScoreBuffer); - __instance._text.text = text; - __instance._color = color; + return false; } + var (text, color) = judgmentService.Judge(cutScoreBuffer, false); + __instance._text.text = text; + __instance._color = color; + return false; } @@ -89,12 +88,14 @@ internal bool HandleCutScoreBufferDidChange(FlyingScoreEffect __instance, CutSco [AffinityPatch(typeof(FlyingScoreEffect), nameof(FlyingScoreEffect.HandleCutScoreBufferDidFinish))] internal void HandleCutScoreBufferDidFinish(FlyingScoreEffect __instance, CutScoreBuffer cutScoreBuffer) { - if (configProvider.CurrentConfig != null) + if (configProvider.CurrentConfig == null) { - var (text, color) = judgmentService.Judge(cutScoreBuffer); - __instance._text.text = text; - __instance._color = color; + return; } + + var (text, color) = judgmentService.Judge(cutScoreBuffer, false); + __instance._text.text = text; + __instance._color = color; } } } diff --git a/HitScoreVisualizer/Services/JudgmentService.cs b/HitScoreVisualizer/Services/JudgmentService.cs index c92612e..9beea5b 100644 --- a/HitScoreVisualizer/Services/JudgmentService.cs +++ b/HitScoreVisualizer/Services/JudgmentService.cs @@ -19,14 +19,15 @@ private JudgmentService(ConfigProvider configProvider) private Configuration Config => configProvider.CurrentConfig ?? Configuration.Default; - public (string hitScoreText, Color hitScoreColor) Judge(IReadonlyCutScoreBuffer cutScoreBuffer, int? afterCutOverride = null) + public (string hitScoreText, Color hitScoreColor) Judge(IReadonlyCutScoreBuffer cutScoreBuffer, bool assumeMaxPostSwing) { - afterCutOverride ??= cutScoreBuffer.afterCutScore; + var afterCutScore = assumeMaxPostSwing ? cutScoreBuffer.noteScoreDefinition.maxAfterCutScore : cutScoreBuffer.afterCutScore; + return cutScoreBuffer.noteCutInfo.noteData.gameplayType switch { - NoteData.GameplayType.Normal => GetNormalDispaly(cutScoreBuffer, afterCutOverride.Value), - NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer, afterCutOverride.Value), - NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer, afterCutOverride.Value), + NoteData.GameplayType.Normal => GetNormalDispaly(cutScoreBuffer, afterCutScore), + NoteData.GameplayType.BurstSliderHead => GetChainHeadDisplay(cutScoreBuffer, afterCutScore), + NoteData.GameplayType.BurstSliderElement => GetChainSegmentDisplay(cutScoreBuffer, afterCutScore), _ => (string.Empty, Color.white), }; } diff --git a/HitScoreVisualizer/Settings/Configuration.cs b/HitScoreVisualizer/Settings/Configuration.cs index 68da2a1..23b3ae8 100644 --- a/HitScoreVisualizer/Settings/Configuration.cs +++ b/HitScoreVisualizer/Settings/Configuration.cs @@ -17,7 +17,7 @@ public class Configuration IsDefaultConfig = true, DisplayMode = "format", DoIntermediateUpdates = true, - ShowMaxPostswingFirst = false, + AssumeMaxPostSwing = false, TimeDependenceDecimalPrecision = 1, TimeDependenceDecimalOffset = 2, NormalJudgments = @@ -116,8 +116,8 @@ internal Version Version [JsonProperty("doIntermediateUpdates")] public bool DoIntermediateUpdates { get; internal set; } - [JsonProperty("showMaxPostswingFirst")] - public bool ShowMaxPostswingFirst { get; internal set; } + [JsonProperty("assumeMaxPostSwing")] + public bool AssumeMaxPostSwing { get; internal set; } [JsonProperty("timeDependencyDecimalPrecision")] [DefaultValue(1)] From fcb036882b3166d54d82a34e78ffacf03d404a47 Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:47:55 +0000 Subject: [PATCH 7/9] Fix conflict --- HitScoreVisualizer/Services/JudgmentService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HitScoreVisualizer/Services/JudgmentService.cs b/HitScoreVisualizer/Services/JudgmentService.cs index 5358195..82d5ec6 100644 --- a/HitScoreVisualizer/Services/JudgmentService.cs +++ b/HitScoreVisualizer/Services/JudgmentService.cs @@ -29,14 +29,14 @@ private JudgmentService(ConfigProvider configProvider) return cutScoreBuffer.noteCutInfo.noteData.gameplayType switch { - NoteData.GameplayType.Normal => GetNormalDispaly(cutScoreBuffer, afterCutScore), + 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; @@ -98,10 +98,10 @@ private JudgmentService(ConfigProvider configProvider) 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, int afterCutScore) From cf7d0f1d9097d88d50dc46577ebe6582f20e1f6c Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:49:53 +0000 Subject: [PATCH 8/9] Add %d to README --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f90ca48..895f20a 100644 --- a/README.md +++ b/README.md @@ -109,17 +109,18 @@ You can use that file as a starting point in case you want to customize it. Just ### Format tokens -| Token | Explanation / Info | -|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| %b | The score contributed by the swing before cutting the block. | -| %c | The score contributed by the accuracy of the cut. | -| %a | The score contributed by the part of the swing after cutting the block. | -| %t | The time dependence of the swing. This value indicates how depedent the accuracy part of the score is upon *when* you hit the block, measured from 0 - 1. A value of 0 indicates a completely time independent swing, while a value of 1 indicates that the accuracy part of the score would vary greatly if the block was hit even slightly earlier or later. | -| %B, %C, %A, %T | Uses the Judgment text that matches the threshold as specified in either `beforeCutAngleJudgments`, `accuracyJudgments`, `afterCutAngleJudgments`, or `timeDependencyJudgments` (depending on the used token). | -| %s | The total score of the cut. | -| %p | A number representing the percentage of the maximum total score. | -| %% | A literal percent symbol. | -| %n | A newline. | +| Token | Explanation / Info | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| %b | The score contributed by the swing before cutting the block. | +| %c | The score contributed by the accuracy of the cut. | +| %a | The score contributed by the part of the swing after cutting the block. | +| %t | The time dependence of the swing. This value indicates how dependent the accuracy part of the score is upon *when* you hit the block, measured from 0 - 1. A value of 0 indicates a completely time independent swing, while a value of 1 indicates that the accuracy part of the score would vary greatly if the block was hit even slightly earlier or later. | +| %B, %C, %A, %T | Uses the Judgment text that matches the threshold as specified in either `beforeCutAngleJudgments`, `accuracyJudgments`, `afterCutAngleJudgments`, or `timeDependencyJudgments` (depending on the used token). | +| %d | An arrow that points towards the center line of the note relative to the cut line. | +| %s | The total score of the cut. ~~~~ | +| %p | A number representing the percentage of the maximum total score. | +| %% | A literal percent symbol. | +| %n | A newline. | ## Developers From 13cab8209d6e433036e2e2dee2104013aaae1e2d Mon Sep 17 00:00:00 2001 From: qqrz997 <156676353+qqrz997@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:52:39 +0000 Subject: [PATCH 9/9] Remove strays --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 895f20a..6d05a55 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ You can use that file as a starting point in case you want to customize it. Just | %t | The time dependence of the swing. This value indicates how dependent the accuracy part of the score is upon *when* you hit the block, measured from 0 - 1. A value of 0 indicates a completely time independent swing, while a value of 1 indicates that the accuracy part of the score would vary greatly if the block was hit even slightly earlier or later. | | %B, %C, %A, %T | Uses the Judgment text that matches the threshold as specified in either `beforeCutAngleJudgments`, `accuracyJudgments`, `afterCutAngleJudgments`, or `timeDependencyJudgments` (depending on the used token). | | %d | An arrow that points towards the center line of the note relative to the cut line. | -| %s | The total score of the cut. ~~~~ | +| %s | The total score of the cut. | | %p | A number representing the percentage of the maximum total score. | | %% | A literal percent symbol. | | %n | A newline. |