mirror of
https://github.com/Theaninova/HitScoreVisualizer.git
synced 2025-12-26 18:46:14 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f15dd367a2 | ||
|
|
45658d5d46 | ||
|
|
2ee7d1346b | ||
|
|
a30fd6a772 | ||
|
|
d1d7070548 | ||
|
|
8fd8bf25dd | ||
|
|
7d5d1748ac |
468
HitScoreVisualizer/Config.cs
Normal file
468
HitScoreVisualizer/Config.cs
Normal file
@@ -0,0 +1,468 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace HitScoreVisualizer
|
||||
{
|
||||
class Config
|
||||
{
|
||||
public static Config instance;
|
||||
|
||||
// If true, this config will not overwrite the existing config file.
|
||||
// (This gets set if a config from a newer version is detected.)
|
||||
[JsonIgnore]
|
||||
public bool noSerialize;
|
||||
|
||||
public struct Judgment
|
||||
{
|
||||
// This judgment will be applied only to notes 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.
|
||||
[DefaultValue(0)]
|
||||
public int threshold;
|
||||
|
||||
// The text to display (if judgment text is enabled).
|
||||
[DefaultValue("")]
|
||||
public string text;
|
||||
|
||||
// 4 floats, 0-1; red, green, blue, glow (not transparency!)
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)] // leaving this out should look obviously wrong
|
||||
public float[] color;
|
||||
|
||||
// If true, the text color will be lerped 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.
|
||||
[DefaultValue(false)]
|
||||
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
|
||||
// accurate, across major versions.
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public int majorVersion;
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public int minorVersion;
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public int patchVersion;
|
||||
|
||||
// If this is true, the config will be overwritten with the plugin's default settings after an
|
||||
// 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).
|
||||
// Otherwise, displays both (judgment text above numeric score).
|
||||
[DefaultValue("")]
|
||||
public string displayMode;
|
||||
|
||||
// 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"": 1,
|
||||
""patchVersion"": 0,
|
||||
""displayMode"": ""format"",
|
||||
""judgments"": [
|
||||
{
|
||||
""threshold"": 110,
|
||||
""text"": ""Fantastic%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
""threshold"": 101,
|
||||
""text"": ""<size=80%>Excellent</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
""threshold"": 90,
|
||||
""text"": ""<size=80%>Great</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.980392158,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
""threshold"": 80,
|
||||
""text"": ""<size=80%>Good</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.6,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
""fade"": true
|
||||
},
|
||||
{
|
||||
""threshold"": 60,
|
||||
""text"": ""<size=80%>Decent</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
""fade"": true
|
||||
},
|
||||
{
|
||||
""text"": ""<size=80%>Way Off</size>%n%s%n%B %C %A"",
|
||||
""color"": [
|
||||
0.5,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
""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,
|
||||
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });
|
||||
|
||||
public static readonly Judgment DEFAULT_JUDGMENT = new Judgment
|
||||
{
|
||||
threshold = 0,
|
||||
text = "",
|
||||
color = new float[] { 1f, 1f, 1f, 1f },
|
||||
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()
|
||||
{
|
||||
Plugin.log("Loading config...");
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
Plugin.log("Writing default config.");
|
||||
// if the config file doesn't exist, save the default one
|
||||
resetToDefault();
|
||||
save(true);
|
||||
return;
|
||||
}
|
||||
string configJson = File.ReadAllText(fullPath);
|
||||
Config loaded = JsonConvert.DeserializeObject<Config>(configJson,
|
||||
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });
|
||||
if (!validate(loaded))
|
||||
{
|
||||
Plugin.log("Falling back to default config (file will not be overwritten)");
|
||||
// don't try to modify the original default when disabling serialization
|
||||
instance = DEFAULT_CONFIG.MemberwiseClone() as Config;
|
||||
// since we couldn't read the existing config, don't overwrite it
|
||||
instance.noSerialize = true;
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public static void save(bool force = false)
|
||||
{
|
||||
Plugin.log("Writing file...");
|
||||
if (instance.noSerialize && !force) return;
|
||||
File.WriteAllText(fullPath, JsonConvert.SerializeObject(instance,
|
||||
Formatting.Indented,
|
||||
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate }));
|
||||
Plugin.log("File written.");
|
||||
}
|
||||
|
||||
public static bool validate(Config config)
|
||||
{
|
||||
if (tooNew(config))
|
||||
{
|
||||
Plugin.log("Config is for a newer version of HitScoreVisualizer!");
|
||||
return false;
|
||||
}
|
||||
bool judgmentsValid = true;
|
||||
foreach (Judgment j in config.judgments)
|
||||
{
|
||||
if (!validateJudgment(j)) judgmentsValid = false;
|
||||
}
|
||||
if (!judgmentsValid) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool validateJudgment(Judgment judgment)
|
||||
{
|
||||
if (judgment.color.Length != 4)
|
||||
{
|
||||
Console.WriteLine("Judgment \"" + judgment.text + "\" with threshold " + judgment.threshold +
|
||||
"has invalid color!");
|
||||
Console.WriteLine("Make sure to include exactly 4 numbers for each judgment's color!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool outdated(Config config)
|
||||
{
|
||||
if (config.majorVersion < Plugin.majorVersion) return true;
|
||||
if (config.minorVersion < Plugin.minorVersion) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool tooNew(Config config)
|
||||
{
|
||||
if (config.majorVersion > Plugin.majorVersion) return true;
|
||||
if (config.minorVersion > Plugin.minorVersion) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void resetToDefault()
|
||||
{
|
||||
instance = DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
public static void judge(FlyingScoreTextEffect text, NoteCutInfo noteCutInfo, SaberAfterCutSwingRatingCounter saberAfterCutSwingRatingCounter, ref Color color, int score)
|
||||
{
|
||||
Judgment judgment = DEFAULT_JUDGMENT;
|
||||
int index; // save in case we need to fade
|
||||
for (index = 0; index < instance.judgments.Length; index++)
|
||||
{
|
||||
Judgment j = instance.judgments[index];
|
||||
if (score >= j.threshold)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
text.text = judgment.text;
|
||||
return;
|
||||
}
|
||||
if (instance.displayMode == "numeric")
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (instance.displayMode == "scoreOnTop")
|
||||
{
|
||||
text.text = score + "\n" + judgment.text + "\n";
|
||||
return;
|
||||
}
|
||||
text.text = judgment.text + "\n" + score + "\n";
|
||||
}
|
||||
|
||||
public static Color toColor(float[] rgba)
|
||||
{
|
||||
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,18 +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;
|
||||
____color =
|
||||
(
|
||||
total < 100 ? Color.red :
|
||||
total <= 105 ? new Color(1, 165f / 255f, 0) :
|
||||
total < 110 ? new Color(0, 0.5f, 1) :
|
||||
Color.green
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@
|
||||
<Reference Include="IllusionPlugin">
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\IllusionPlugin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -58,10 +61,14 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<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>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -9,7 +9,12 @@ namespace HitScoreVisualizer
|
||||
public class Plugin : IPlugin
|
||||
{
|
||||
public string Name => "HitScoreVisualizer";
|
||||
public string Version => "0.0.1";
|
||||
public string Version => "2.1.0";
|
||||
|
||||
internal const int majorVersion = 2;
|
||||
internal const int minorVersion = 1;
|
||||
internal const int patchVersion = 0;
|
||||
|
||||
public void OnApplicationStart()
|
||||
{
|
||||
SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
|
||||
@@ -25,6 +30,7 @@ namespace HitScoreVisualizer
|
||||
"installed the plugin properly, as the Harmony DLL should have been installed with it.");
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
Config.load();
|
||||
}
|
||||
|
||||
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene arg1)
|
||||
@@ -57,5 +63,12 @@ namespace HitScoreVisualizer
|
||||
public void OnFixedUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
internal static void log(object message)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("[HitScoreVisualizer] " + message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
HitScoreVisualizer/packages.config
Normal file
4
HitScoreVisualizer/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net46" />
|
||||
</packages>
|
||||
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