mirror of
https://github.com/Theaninova/HitScoreVisualizer.git
synced 2025-12-27 11:06:14 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f15dd367a2 | ||
|
|
45658d5d46 | ||
|
|
2ee7d1346b | ||
|
|
a30fd6a772 | ||
|
|
d1d7070548 |
@@ -43,6 +43,17 @@ namespace HitScoreVisualizer
|
||||
public bool fade;
|
||||
}
|
||||
|
||||
// Judgments for individual parts of the swing (angle before, angle after, accuracy).
|
||||
public struct SegmentJudgment
|
||||
{
|
||||
// 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).
|
||||
[DefaultValue(0)]
|
||||
public int threshold;
|
||||
// The text to replace the appropriate judgment specifier with (%B, %C, %A) when this judgment applies.
|
||||
public string text;
|
||||
}
|
||||
|
||||
// If the version number (excluding patch version) of the config is higher than that of the plugin,
|
||||
// the config will not be loaded. If the version number of the config is lower than that of the
|
||||
// plugin, the file will be automatically converted. Conversion is not guaranteed to occur, or be
|
||||
@@ -58,6 +69,15 @@ namespace HitScoreVisualizer
|
||||
// update rather than being converted.
|
||||
public bool isDefaultConfig;
|
||||
|
||||
// If set to "format", displays the judgment text, with the following format specifiers allowed:
|
||||
// - %b: The score contributed by the part of 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.
|
||||
// - %B, %C, %A: As above, except using the appropriate judgment from that part of the swing (as configured for "beforeCutAngleJudgments", "accuracyJudgments", or "afterCutAngleJudgments").
|
||||
// - %s: The total score for the cut.
|
||||
// - %%: A literal percent symbol.
|
||||
// - %n: A newline.
|
||||
//
|
||||
// If set to "numeric", displays only the note score.
|
||||
// If set to "textOnly", displays only the judgment text.
|
||||
// If set to "scoreOnTop", displays both (numeric score above judgment text).
|
||||
@@ -68,19 +88,30 @@ namespace HitScoreVisualizer
|
||||
// Order from highest threshold to lowest; the first matching judgment will be applied
|
||||
public Judgment[] judgments;
|
||||
|
||||
// Judgments for the part of the swing before cutting the block (score is from 0-70).
|
||||
// Format specifier: %B
|
||||
public SegmentJudgment[] beforeCutAngleJudgments;
|
||||
|
||||
// Judgments for the accuracy of the cut (how close to the center of the block the cut was, score is from 0-10).
|
||||
// Format specifier: %C
|
||||
public SegmentJudgment[] accuracyJudgments;
|
||||
|
||||
// Judgments for the part of the swing after cutting the block (score is from 0-30).
|
||||
// Format specifier: %A
|
||||
public SegmentJudgment[] afterCutAngleJudgments;
|
||||
|
||||
// path to where the config is saved
|
||||
private const string FILE_PATH = "/UserData/HitScoreVisualizerConfig.json";
|
||||
|
||||
private const string DEFAULT_JSON = @"{
|
||||
""majorVersion"": 2,
|
||||
""minorVersion"": 0,
|
||||
""minorVersion"": 1,
|
||||
""patchVersion"": 0,
|
||||
""isDefaultConfig"": true,
|
||||
""displayMode"": ""textOnTop"",
|
||||
""displayMode"": ""format"",
|
||||
""judgments"": [
|
||||
{
|
||||
""threshold"": 110,
|
||||
""text"": ""Fantastic"",
|
||||
""text"": ""Fantastic%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
1.0,
|
||||
@@ -90,7 +121,7 @@ namespace HitScoreVisualizer
|
||||
},
|
||||
{
|
||||
""threshold"": 101,
|
||||
""text"": ""<size=80%>Excellent</size>"",
|
||||
""text"": ""<size=80%>Excellent</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
0.0,
|
||||
1.0,
|
||||
@@ -100,7 +131,7 @@ namespace HitScoreVisualizer
|
||||
},
|
||||
{
|
||||
""threshold"": 90,
|
||||
""text"": ""<size=80%>Great</size>"",
|
||||
""text"": ""<size=80%>Great</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.980392158,
|
||||
@@ -110,7 +141,7 @@ namespace HitScoreVisualizer
|
||||
},
|
||||
{
|
||||
""threshold"": 80,
|
||||
""text"": ""<size=80%>Good</size>"",
|
||||
""text"": ""<size=80%>Good</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.6,
|
||||
@@ -121,7 +152,7 @@ namespace HitScoreVisualizer
|
||||
},
|
||||
{
|
||||
""threshold"": 60,
|
||||
""text"": ""<size=80%>Decent</size>"",
|
||||
""text"": ""<size=80%>Decent</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.0,
|
||||
@@ -131,7 +162,7 @@ namespace HitScoreVisualizer
|
||||
""fade"": true
|
||||
},
|
||||
{
|
||||
""text"": ""<size=80%>Way Off</size>"",
|
||||
""text"": ""<size=80%>Way Off</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
0.5,
|
||||
0.0,
|
||||
@@ -140,6 +171,48 @@ namespace HitScoreVisualizer
|
||||
],
|
||||
""fade"": true
|
||||
}
|
||||
],
|
||||
""beforeCutAngleJudgments"": [
|
||||
{
|
||||
""threshold"": 70,
|
||||
""text"": ""+""
|
||||
},
|
||||
{
|
||||
""threshold"": 35,
|
||||
""text"": "" ""
|
||||
},
|
||||
{
|
||||
""threshold"": 0,
|
||||
""text"": ""-""
|
||||
}
|
||||
],
|
||||
""accuracyJudgments"": [
|
||||
{
|
||||
""threshold"": 10,
|
||||
""text"": ""+""
|
||||
},
|
||||
{
|
||||
""threshold"": 5,
|
||||
""text"": "" ""
|
||||
},
|
||||
{
|
||||
""threshold"": 0,
|
||||
""text"": ""-""
|
||||
}
|
||||
],
|
||||
""afterCutAngleJudgments"": [
|
||||
{
|
||||
""threshold"": 30,
|
||||
""text"": ""+""
|
||||
},
|
||||
{
|
||||
""threshold"": 15,
|
||||
""text"": "" ""
|
||||
},
|
||||
{
|
||||
""threshold"": 0,
|
||||
""text"": ""-""
|
||||
}
|
||||
]
|
||||
}";
|
||||
public static readonly Config DEFAULT_CONFIG = JsonConvert.DeserializeObject<Config>(DEFAULT_JSON,
|
||||
@@ -153,6 +226,12 @@ namespace HitScoreVisualizer
|
||||
fade = false
|
||||
};
|
||||
|
||||
public static readonly SegmentJudgment DEFAULT_SEGMENT_JUDGMENT = new SegmentJudgment
|
||||
{
|
||||
threshold = 0,
|
||||
text = ""
|
||||
};
|
||||
|
||||
public static string fullPath => Environment.CurrentDirectory.Replace('\\', '/') + FILE_PATH;
|
||||
|
||||
public static void load()
|
||||
@@ -180,7 +259,25 @@ namespace HitScoreVisualizer
|
||||
}
|
||||
if (outdated(loaded))
|
||||
{
|
||||
if (loaded.isDefaultConfig)
|
||||
{
|
||||
loaded = DEFAULT_CONFIG;
|
||||
instance = loaded;
|
||||
save();
|
||||
return;
|
||||
}
|
||||
// put config update logic here
|
||||
if (loaded.majorVersion == 2 && loaded.minorVersion == 0)
|
||||
{
|
||||
loaded.beforeCutAngleJudgments = new SegmentJudgment[] { DEFAULT_SEGMENT_JUDGMENT };
|
||||
loaded.accuracyJudgments = new SegmentJudgment[] { DEFAULT_SEGMENT_JUDGMENT };
|
||||
loaded.afterCutAngleJudgments = new SegmentJudgment[] { DEFAULT_SEGMENT_JUDGMENT };
|
||||
loaded.minorVersion = 1;
|
||||
loaded.patchVersion = 0;
|
||||
instance = loaded;
|
||||
save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
instance = loaded;
|
||||
}
|
||||
@@ -227,7 +324,6 @@ namespace HitScoreVisualizer
|
||||
{
|
||||
if (config.majorVersion < Plugin.majorVersion) return true;
|
||||
if (config.minorVersion < Plugin.minorVersion) return true;
|
||||
if (config.patchVersion < Plugin.patchVersion) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -243,27 +339,99 @@ namespace HitScoreVisualizer
|
||||
instance = DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
public static void judge(FlyingScoreTextEffect text, ref Color color, int score)
|
||||
public static void judge(FlyingScoreTextEffect text, NoteCutInfo noteCutInfo, SaberAfterCutSwingRatingCounter saberAfterCutSwingRatingCounter, ref Color color, int score)
|
||||
{
|
||||
Judgment judgment = DEFAULT_JUDGMENT;
|
||||
Judgment fadeJudgment = DEFAULT_JUDGMENT;
|
||||
for (int i = 0; i < instance.judgments.Length; i++)
|
||||
int index; // save in case we need to fade
|
||||
for (index = 0; index < instance.judgments.Length; index++)
|
||||
{
|
||||
Judgment j = instance.judgments[i];
|
||||
Judgment j = instance.judgments[index];
|
||||
if (score >= j.threshold)
|
||||
{
|
||||
if (j.fade) fadeJudgment = instance.judgments[i-1];
|
||||
else fadeJudgment = j;
|
||||
|
||||
judgment = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (judgment.fade)
|
||||
{
|
||||
Judgment fadeJudgment = instance.judgments[index - 1];
|
||||
Color baseColor = toColor(judgment.color);
|
||||
Color fadeColor = toColor(fadeJudgment.color);
|
||||
float lerpDistance = Mathf.InverseLerp(judgment.threshold, fadeJudgment.threshold, score);
|
||||
color = Color.Lerp(baseColor, fadeColor, lerpDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = toColor(judgment.color);
|
||||
}
|
||||
|
||||
Color baseColor = toColor(judgment.color);
|
||||
Color fadeColor = toColor(fadeJudgment.color);
|
||||
float lerpDistance = Mathf.InverseLerp(judgment.threshold, fadeJudgment.threshold, score);
|
||||
color = Color.Lerp(baseColor, fadeColor, lerpDistance);
|
||||
if (instance.displayMode == "format")
|
||||
{
|
||||
int beforeCutScore, accuracyScore, afterCutScore;
|
||||
|
||||
beforeCutScore = Mathf.RoundToInt(70f * noteCutInfo.swingRating);
|
||||
float accuracy = 1f - Mathf.Clamp01(noteCutInfo.cutDistanceToCenter / 0.2f);
|
||||
accuracyScore = Mathf.RoundToInt(10f * accuracy);
|
||||
afterCutScore = 0;
|
||||
if (saberAfterCutSwingRatingCounter != null)
|
||||
{
|
||||
afterCutScore = Mathf.RoundToInt(30f * saberAfterCutSwingRatingCounter.rating);
|
||||
}
|
||||
|
||||
StringBuilder formattedBuilder = new StringBuilder();
|
||||
string formatString = judgment.text;
|
||||
int nextPercentIndex = formatString.IndexOf('%');
|
||||
while (nextPercentIndex != -1)
|
||||
{
|
||||
formattedBuilder.Append(formatString.Substring(0, nextPercentIndex));
|
||||
if (formatString.Length == nextPercentIndex + 1)
|
||||
{
|
||||
formatString += " ";
|
||||
}
|
||||
char specifier = formatString[nextPercentIndex + 1];
|
||||
|
||||
switch (specifier)
|
||||
{
|
||||
case 'b':
|
||||
formattedBuilder.Append(beforeCutScore);
|
||||
break;
|
||||
case 'c':
|
||||
formattedBuilder.Append(accuracyScore);
|
||||
break;
|
||||
case 'a':
|
||||
formattedBuilder.Append(afterCutScore);
|
||||
break;
|
||||
case 'B':
|
||||
formattedBuilder.Append(judgeSegment(beforeCutScore, instance.beforeCutAngleJudgments));
|
||||
break;
|
||||
case 'C':
|
||||
formattedBuilder.Append(judgeSegment(accuracyScore, instance.accuracyJudgments));
|
||||
break;
|
||||
case 'A':
|
||||
formattedBuilder.Append(judgeSegment(afterCutScore, instance.afterCutAngleJudgments));
|
||||
break;
|
||||
case 's':
|
||||
formattedBuilder.Append(score);
|
||||
break;
|
||||
case '%':
|
||||
formattedBuilder.Append("%");
|
||||
break;
|
||||
case 'n':
|
||||
formattedBuilder.Append("\n");
|
||||
break;
|
||||
default:
|
||||
formattedBuilder.Append("%" + specifier);
|
||||
break;
|
||||
}
|
||||
|
||||
formatString = formatString.Remove(0, nextPercentIndex + 2);
|
||||
nextPercentIndex = formatString.IndexOf('%');
|
||||
}
|
||||
formattedBuilder.Append(formatString);
|
||||
|
||||
text.text = formattedBuilder.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance.displayMode == "textOnly")
|
||||
{
|
||||
@@ -286,5 +454,15 @@ namespace HitScoreVisualizer
|
||||
{
|
||||
return new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
}
|
||||
|
||||
public static string judgeSegment(int scoreForSegment, SegmentJudgment[] judgments)
|
||||
{
|
||||
if (judgments == null) return "";
|
||||
foreach(SegmentJudgment j in judgments)
|
||||
{
|
||||
if (scoreForSegment >= j.threshold) return j.text;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace HitScoreVisualizer.Harmony_Patches
|
||||
new Type[] { typeof(SaberAfterCutSwingRatingCounter), typeof(float) })]
|
||||
class FlyingScoreTextEffectHandleSaberAfterCutSwingRatingCounterDidChangeEvent
|
||||
{
|
||||
static void Postfix(SaberAfterCutSwingRatingCounter afterCutRating, FlyingScoreTextEffect __instance,
|
||||
ref Color ____color, NoteCutInfo ____noteCutInfo, int ____multiplier)
|
||||
static bool Prefix(SaberAfterCutSwingRatingCounter afterCutRating, FlyingScoreTextEffect __instance, ref Color ____color, NoteCutInfo ____noteCutInfo, int ____multiplier)
|
||||
{
|
||||
ScoreController.ScoreWithoutMultiplier(____noteCutInfo, afterCutRating, out int before, out int after);
|
||||
int total = before + after;
|
||||
Config.judge(__instance, ref ____color, total);
|
||||
Config.judge(__instance, ____noteCutInfo, afterCutRating, ref ____color, total);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Harmony;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace HitScoreVisualizer.Harmony_Patches
|
||||
{
|
||||
/*
|
||||
[HarmonyPatch(typeof(FlyingScoreTextEffect), "InitAndPresent",
|
||||
new Type[] {
|
||||
typeof(NoteCutInfo),
|
||||
typeof(int),
|
||||
typeof(Vector3),
|
||||
typeof(Color),
|
||||
typeof(SaberAfterCutSwingRatingCounter)})]
|
||||
*/
|
||||
class FlyingScoreTextEffectInitAndPresent
|
||||
{
|
||||
static void Postfix(SaberAfterCutSwingRatingCounter saberAfterCutSwingRatingCounter, FlyingScoreTextEffect __instance, ref Color ____color, NoteCutInfo noteCutInfo)
|
||||
{
|
||||
ScoreController.ScoreWithoutMultiplier(noteCutInfo, saberAfterCutSwingRatingCounter, out int before, out int after);
|
||||
int total = before + after;
|
||||
Config.judge(__instance, noteCutInfo, saberAfterCutSwingRatingCounter, ref ____color, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Config.cs" />
|
||||
<Compile Include="Harmony Patches\FlyingScoreTextEffectHandleSaberAfterCutSwingRatingCounterDidChangeEvent.cs" />
|
||||
<Compile Include="Harmony Patches\FlyingScoreTextEffectInitAndPresent.cs" />
|
||||
<Compile Include="Plugin.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace HitScoreVisualizer
|
||||
public class Plugin : IPlugin
|
||||
{
|
||||
public string Name => "HitScoreVisualizer";
|
||||
public string Version => "2.0.0";
|
||||
public string Version => "2.1.0";
|
||||
|
||||
internal const int majorVersion = 2;
|
||||
internal const int minorVersion = 0;
|
||||
internal const int minorVersion = 1;
|
||||
internal const int patchVersion = 0;
|
||||
|
||||
public void OnApplicationStart()
|
||||
|
||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### Changelog
|
||||
2.0.2: Bug fixes and performance improvements. If you've been noticing lag, try disabling the fade option on all judgments in the config.
|
||||
2.1.0: Added display mode "format" (see below).
|
||||
***
|
||||
Colors the score popups that appear when hitting notes. Also adds judgment text above (or below) the score.
|
||||
|
||||
After installing the plugin, run the game once to generate the config file at `UserData/HitScoreVisualizerConfig.json` in your Beat Saber install folder. A graphical editor for this file is in the works; until then, editing the config is considered an advanced feature, as JSON is not particularly human-friendly. If you want to edit it anyway, here's the documentation:
|
||||
* Remove the `isDefaultConfig` line. If this option is set to true, your config will get overwritten by the updated default config with every plugin update.
|
||||
* Valid options for `displayMode` are "numeric" (displays only the score), "textOnly" (displays only the judgment text), "scoreOnTop" (displays the score above the judgment text), "format" (see "Formatting" below), or any other string (displays the judgment text above the score).
|
||||
* Put your judgments in descending order; the first one encountered with `threshold` <= the score earned for a note will be applied.
|
||||
* Include exactly 4 numbers in each judgment's `color` array. These represent red, green, blue, and a 4th channel which, knowing Beat Saber's shaders, could be glow, transparency, or even both at once. Modify the 4th number if you like, but I can't say what effect it will have.
|
||||
* If you include more or fewer than 4 numbers in a judgment's color array, your entire config will be ignored (but not overwritten).
|
||||
* If you include the line `"fade": true` in a judgment, the color for scores earning that judgment will be interpolated between that judgment's color and the color of the previous judgment in the list, based on how close to the threshold for that judgment the score was. Use this to create a smooth gradient of color if you want one.
|
||||
* If you enable `fade` for the first judgment in the list, the plugin and Beat Saber itself will quite possibly die in a fire if that judgment is ever earned. Don't enable `fade` for the first judgment in the list. You have been warned.
|
||||
* Judgment text supports [TextMeshPro formatting!](http://digitalnativestudios.com/textmeshpro/docs/rich-text/)
|
||||
* **Formatting**
|
||||
In displayMode "format", a number of HitScoreVisualizer format specifiers are available (as well as TextMeshPro's formatting tools). Your formatted string will replace the score text (so include %s if you want to see your score).
|
||||
* %b: The score contributed by the part of 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.
|
||||
* %B, %C, %A: As above, except using the appropriate judgment from that part of the swing (as configured for "beforeCutAngleJudgments", "accuracyJudgments", or "afterCutAngleJudgments").
|
||||
* %s: The total score for the cut.
|
||||
* %%: A literal percent symbol.
|
||||
* %n: A newline.
|
||||
* Note that before-swing/accuracy/after-swing judgments don't support color or fading, although you can put TextMeshPro color tags in them if you want to.
|
||||
If you make your own config, feel free to share it in #other-files!
|
||||
|
||||
Plugin originally requested by @AntRazor on the modding discord. Thanks go to @AntRazor and @wulkanat for input on the default config released with this update, as well as everyone in #mod-development and the entire server for the love and support. :')
|
||||
Reference in New Issue
Block a user