Skip to content

Commit 8fd8bf2

Browse files
v2.0.0 release!
Add: - JSON config file - Customizable thresholds - Color fading - Judgment text
1 parent 7d5d174 commit 8fd8bf2

File tree

5 files changed

+316
-9
lines changed

5 files changed

+316
-9
lines changed

HitScoreVisualizer/Config.cs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using UnityEngine;
10+
11+
namespace HitScoreVisualizer
12+
{
13+
class Config
14+
{
15+
public static Config instance;
16+
17+
// If true, this config will not overwrite the existing config file.
18+
// (This gets set if a config from a newer version is detected.)
19+
[JsonIgnore]
20+
public bool noSerialize;
21+
22+
public struct Judgment
23+
{
24+
// This judgment will be applied only to notes hit with score >= this number.
25+
// Note that if no judgment can be applied to a note, the text will appear as in the unmodded
26+
// game.
27+
[DefaultValue(0)]
28+
public int threshold;
29+
30+
// The text to display (if judgment text is enabled).
31+
[DefaultValue("")]
32+
public string text;
33+
34+
// 4 floats, 0-1; red, green, blue, glow (not transparency!)
35+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)] // leaving this out should look obviously wrong
36+
public float[] color;
37+
38+
// If true, the text color will be lerped between this judgment's color and the previous
39+
// based on how close to the next threshold it is.
40+
// Specifying fade : true for the first judgment in the array is an error, and will crash the
41+
// plugin.
42+
[DefaultValue(false)]
43+
public bool fade;
44+
}
45+
46+
// If the version number (excluding patch version) of the config is higher than that of the plugin,
47+
// the config will not be loaded. If the version number of the config is lower than that of the
48+
// plugin, the file will be automatically converted. Conversion is not guaranteed to occur, or be
49+
// accurate, across major versions.
50+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
51+
public int majorVersion;
52+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
53+
public int minorVersion;
54+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
55+
public int patchVersion;
56+
57+
// If this is true, the config will be overwritten with the plugin's default settings after an
58+
// update rather than being converted.
59+
public bool isDefaultConfig;
60+
61+
// If set to "numeric", displays only the note score.
62+
// If set to "textOnly", displays only the judgment text.
63+
// If set to "scoreOnTop", displays both (numeric score above judgment text).
64+
// Otherwise, displays both (judgment text above numeric score).
65+
[DefaultValue("")]
66+
public string displayMode;
67+
68+
// Order from highest threshold to lowest; the first matching judgment will be applied
69+
public Judgment[] judgments;
70+
71+
// path to where the config is saved
72+
private const string FILE_PATH = "/UserData/HitScoreVisualizerConfig.json";
73+
74+
private const string DEFAULT_JSON = @"{
75+
""majorVersion"": 2,
76+
""minorVersion"": 0,
77+
""patchVersion"": 0,
78+
""isDefaultConfig"": true,
79+
""displayMode"": ""textOnTop"",
80+
""judgments"": [
81+
{
82+
""threshold"": 110,
83+
""text"": ""Fantastic"",
84+
""color"": [
85+
1.0,
86+
1.0,
87+
1.0,
88+
1.0
89+
]
90+
},
91+
{
92+
""threshold"": 101,
93+
""text"": ""<size=80%>Excellent</size>"",
94+
""color"": [
95+
0.0,
96+
1.0,
97+
0.0,
98+
1.0
99+
]
100+
},
101+
{
102+
""threshold"": 90,
103+
""text"": ""<size=80%>Great</size>"",
104+
""color"": [
105+
1.0,
106+
0.980392158,
107+
0.0,
108+
1.0
109+
]
110+
},
111+
{
112+
""threshold"": 80,
113+
""text"": ""<size=80%>Good</size>"",
114+
""color"": [
115+
1.0,
116+
0.6,
117+
0.0,
118+
1.0
119+
],
120+
""fade"": true
121+
},
122+
{
123+
""threshold"": 60,
124+
""text"": ""<size=80%>Decent</size>"",
125+
""color"": [
126+
1.0,
127+
0.0,
128+
0.0,
129+
1.0
130+
],
131+
""fade"": true
132+
},
133+
{
134+
""text"": ""<size=80%>Way Off</size>"",
135+
""color"": [
136+
0.5,
137+
0.0,
138+
0.0,
139+
1.0
140+
],
141+
""fade"": true
142+
}
143+
]
144+
}";
145+
public static readonly Config DEFAULT_CONFIG = JsonConvert.DeserializeObject<Config>(DEFAULT_JSON,
146+
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });
147+
148+
public static readonly Judgment DEFAULT_JUDGMENT = new Judgment
149+
{
150+
threshold = 0,
151+
text = "",
152+
color = new float[] { 1f, 1f, 1f, 1f },
153+
fade = false
154+
};
155+
156+
public static string fullPath => Environment.CurrentDirectory.Replace('\\', '/') + FILE_PATH;
157+
158+
public static void load()
159+
{
160+
Plugin.log("Loading config...");
161+
if (!File.Exists(fullPath))
162+
{
163+
Plugin.log("Writing default config.");
164+
// if the config file doesn't exist, save the default one
165+
resetToDefault();
166+
save(true);
167+
return;
168+
}
169+
string configJson = File.ReadAllText(fullPath);
170+
Config loaded = JsonConvert.DeserializeObject<Config>(configJson,
171+
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });
172+
if (!validate(loaded))
173+
{
174+
Plugin.log("Falling back to default config (file will not be overwritten)");
175+
// don't try to modify the original default when disabling serialization
176+
instance = DEFAULT_CONFIG.MemberwiseClone() as Config;
177+
// since we couldn't read the existing config, don't overwrite it
178+
instance.noSerialize = true;
179+
return;
180+
}
181+
if (outdated(loaded))
182+
{
183+
// put config update logic here
184+
}
185+
instance = loaded;
186+
}
187+
188+
public static void save(bool force = false)
189+
{
190+
Plugin.log("Writing file...");
191+
if (instance.noSerialize && !force) return;
192+
File.WriteAllText(fullPath, JsonConvert.SerializeObject(instance,
193+
Formatting.Indented,
194+
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate }));
195+
Plugin.log("File written.");
196+
}
197+
198+
public static bool validate(Config config)
199+
{
200+
if (tooNew(config))
201+
{
202+
Plugin.log("Config is for a newer version of HitScoreVisualizer!");
203+
return false;
204+
}
205+
bool judgmentsValid = true;
206+
foreach (Judgment j in config.judgments)
207+
{
208+
if (!validateJudgment(j)) judgmentsValid = false;
209+
}
210+
if (!judgmentsValid) return false;
211+
return true;
212+
}
213+
214+
public static bool validateJudgment(Judgment judgment)
215+
{
216+
if (judgment.color.Length != 4)
217+
{
218+
Console.WriteLine("Judgment \"" + judgment.text + "\" with threshold " + judgment.threshold +
219+
"has invalid color!");
220+
Console.WriteLine("Make sure to include exactly 4 numbers for each judgment's color!");
221+
return false;
222+
}
223+
return true;
224+
}
225+
226+
public static bool outdated(Config config)
227+
{
228+
if (config.majorVersion < Plugin.majorVersion) return true;
229+
if (config.minorVersion < Plugin.minorVersion) return true;
230+
if (config.patchVersion < Plugin.patchVersion) return true;
231+
return false;
232+
}
233+
234+
public static bool tooNew(Config config)
235+
{
236+
if (config.majorVersion > Plugin.majorVersion) return true;
237+
if (config.minorVersion > Plugin.minorVersion) return true;
238+
return false;
239+
}
240+
241+
public static void resetToDefault()
242+
{
243+
instance = DEFAULT_CONFIG;
244+
}
245+
246+
public static void judge(FlyingScoreTextEffect text, ref Color color, int score)
247+
{
248+
Judgment judgment = DEFAULT_JUDGMENT;
249+
Judgment fadeJudgment = DEFAULT_JUDGMENT;
250+
for (int i = 0; i < instance.judgments.Length; i++)
251+
{
252+
Judgment j = instance.judgments[i];
253+
if (score >= j.threshold)
254+
{
255+
if (j.fade) fadeJudgment = instance.judgments[i-1];
256+
else fadeJudgment = j;
257+
258+
judgment = j;
259+
break;
260+
}
261+
}
262+
263+
Color baseColor = toColor(judgment.color);
264+
Color fadeColor = toColor(fadeJudgment.color);
265+
float lerpDistance = Mathf.InverseLerp(judgment.threshold, fadeJudgment.threshold, score);
266+
color = Color.Lerp(baseColor, fadeColor, lerpDistance);
267+
268+
if (instance.displayMode == "textOnly")
269+
{
270+
text.text = judgment.text;
271+
return;
272+
}
273+
if (instance.displayMode == "numeric")
274+
{
275+
return;
276+
}
277+
if (instance.displayMode == "scoreOnTop")
278+
{
279+
text.text = score + "\n" + judgment.text + "\n";
280+
return;
281+
}
282+
text.text = judgment.text + "\n" + score + "\n";
283+
}
284+
285+
public static Color toColor(float[] rgba)
286+
{
287+
return new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
288+
}
289+
}
290+
}

HitScoreVisualizer/Harmony Patches/FlyingScoreTextEffectHandleSaberAfterCutSwingRatingCounterDidChangeEvent.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@ static void Postfix(SaberAfterCutSwingRatingCounter afterCutRating, FlyingScoreT
1717
{
1818
ScoreController.ScoreWithoutMultiplier(____noteCutInfo, afterCutRating, out int before, out int after);
1919
int total = before + after;
20-
____color =
21-
(
22-
total < 100 ? Color.red :
23-
total <= 105 ? new Color(1, 165f / 255f, 0) :
24-
total < 110 ? new Color(0, 0.5f, 1) :
25-
Color.green
26-
);
20+
Config.judge(__instance, ref ____color, total);
2721
}
2822
}
2923
}

HitScoreVisualizer/HitScoreVisualizer.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
<Reference Include="IllusionPlugin">
4343
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\IllusionPlugin.dll</HintPath>
4444
</Reference>
45+
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
46+
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
47+
</Reference>
4548
<Reference Include="System" />
4649
<Reference Include="System.Core" />
4750
<Reference Include="System.Xml.Linq" />
@@ -58,10 +61,13 @@
5861
</Reference>
5962
</ItemGroup>
6063
<ItemGroup>
64+
<Compile Include="Config.cs" />
6165
<Compile Include="Harmony Patches\FlyingScoreTextEffectHandleSaberAfterCutSwingRatingCounterDidChangeEvent.cs" />
6266
<Compile Include="Plugin.cs" />
6367
<Compile Include="Properties\AssemblyInfo.cs" />
6468
</ItemGroup>
65-
<ItemGroup />
69+
<ItemGroup>
70+
<None Include="packages.config" />
71+
</ItemGroup>
6672
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
6773
</Project>

HitScoreVisualizer/Plugin.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ namespace HitScoreVisualizer
99
public class Plugin : IPlugin
1010
{
1111
public string Name => "HitScoreVisualizer";
12-
public string Version => "1.0.0";
12+
public string Version => "2.0.0";
13+
14+
internal const int majorVersion = 2;
15+
internal const int minorVersion = 0;
16+
internal const int patchVersion = 0;
17+
1318
public void OnApplicationStart()
1419
{
1520
SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
@@ -25,6 +30,7 @@ public void OnApplicationStart()
2530
"installed the plugin properly, as the Harmony DLL should have been installed with it.");
2631
Console.WriteLine(e);
2732
}
33+
Config.load();
2834
}
2935

3036
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene arg1)
@@ -57,5 +63,12 @@ public void OnUpdate()
5763
public void OnFixedUpdate()
5864
{
5965
}
66+
67+
internal static void log(object message)
68+
{
69+
#if DEBUG
70+
Console.WriteLine("[HitScoreVisualizer] " + message);
71+
#endif
72+
}
6073
}
6174
}

HitScoreVisualizer/packages.config

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<packages>
3+
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net46" />
4+
</packages>

0 commit comments

Comments
 (0)