13 Commits

Author SHA1 Message Date
254c78e860 Disabled Output 2018-10-07 20:27:25 +02:00
dc2f5882b5 First Release 2018-10-06 14:41:19 +02:00
97d8ee28b6 Bugfixing 2018-08-27 05:17:46 +02:00
3585f72bfd Plugin works now as intended 2018-08-27 05:10:02 +02:00
41bc6e83d1 Different colors are now fading for how good the cut was 2018-08-27 05:00:29 +02:00
7f573086f6 Plugin should work in theory 2018-08-27 04:28:39 +02:00
b815891871 Basic functionality obtained 2018-08-27 03:59:58 +02:00
4c3cfbc7ad Started working on competetive mode 2018-08-27 03:41:01 +02:00
4ffe8423e8 Cleaning up 2018-08-27 03:30:05 +02:00
artemiswkearney
f4546fecd6 Update README for 2.1.0 2018-08-26 16:02:11 -07:00
Arti Kearney
0109887047 Merge branch 'master' of github.com:artemiswkearney/HitScoreVisualizer
merge readme (added on GitHub)
2018-08-26 16:00:36 -07:00
Arti Kearney
2ee7d1346b Add "format" display mode, judgments for swing segments 2018-08-26 16:00:01 -07:00
artemiswkearney
79a7a8c355 Create README.md 2018-08-24 15:20:44 -07:00
6 changed files with 350 additions and 35 deletions

View File

@@ -14,6 +14,28 @@ namespace HitScoreVisualizer
{
public static Config instance;
public const string mode_hardcoreCompetetive = "hardcoreCompetetive";
public const string mode_format = "format";
public const string mode_textOnly = "textOnly";
public const string mode_numeric = "numeric";
public const string mode_scoreOnTop = "scoreOnTop";
public const char format_indicator = '%';
public const char format_beforeCutScore = 'b';
public const char format_accuracyScore = 'c';
public const char format_afterCutScore = 'a';
public const char FORMAT_BEFORECUTSCORE = 'B';
public const char FORMAT_ACCUTRACYSCORE = 'C';
public const char FORMAT_AFTERCUTSCORE = 'A';
public const char format_score = 's';
public const char format_indicatorChar = format_indicator;
public const char format_lineBreak = 'n';
public const bool percentages = true;
public const int maxBeforeCutScore = 70;
public const int maxAfterCutScore = 30;
public const int maxAccuracyScore = 10;
// If true, this config will not overwrite the existing config file.
// (This gets set if a config from a newer version is detected.)
[JsonIgnore]
@@ -43,6 +65,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 +91,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 +110,31 @@ 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 FILE_PATH_RATINGS = "\\Ratings\\Latest.txt";
private const string DEFAULT_JSON = @"{
""majorVersion"": 2,
""minorVersion"": 0,
""patchVersion"": 2,
""isDefaultConfig"": true,
""displayMode"": ""textOnTop"",
""minorVersion"": 1,
""patchVersion"": 0,
""displayMode"": ""format"",
""judgments"": [
{
""threshold"": 110,
""text"": ""Fantastic"",
""text"": ""Fantastic%n%s%n%B %C %A"",
""color"": [
1.0,
1.0,
@@ -90,7 +144,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 +154,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 +164,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 +175,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 +185,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 +194,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,7 +249,14 @@ 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 string fullPathRatings => Environment.CurrentDirectory + FILE_PATH_RATINGS;
public static void load()
{
@@ -180,7 +283,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;
}
@@ -242,7 +363,12 @@ namespace HitScoreVisualizer
instance = DEFAULT_CONFIG;
}
public static void judge(FlyingScoreTextEffect text, ref Color color, int score)
public static string floatToHexColor(float dec)
{
return ((int)(255f * dec)).ToString("X2");
}
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
@@ -268,26 +394,166 @@ namespace HitScoreVisualizer
color = toColor(judgment.color);
}
if (instance.displayMode == "textOnly")
//Hardcore Competetive Mode by wulkanat
if (instance.displayMode == mode_hardcoreCompetetive)
{
float beforeCut = noteCutInfo.swingRating;
float accuracy = 1f - Mathf.Clamp01(noteCutInfo.cutDistanceToCenter / 0.2f);
float afterCut = 0f;
if (saberAfterCutSwingRatingCounter != null)
afterCut = saberAfterCutSwingRatingCounter.rating;
int beforeCutScore, accuracyScore, afterCutScore;
int beforeMax, accuracyMax, afterMax;
if (percentages)
{
beforeMax = 100;
accuracyMax = 100;
afterMax = 100;
}
else
{
beforeMax = 70;
accuracyMax = 10;
afterMax = 30;
}
beforeCutScore = Mathf.RoundToInt(beforeMax * beforeCut);
accuracyScore = Mathf.RoundToInt(accuracyMax * accuracy);
afterCutScore = Mathf.RoundToInt(afterMax * afterCut);
if (beforeCutScore == beforeMax && accuracyScore == accuracyMax && afterCutScore == afterMax)
{
text.text = "Perfect";
return;
}
StringBuilder formattedBuilder = new StringBuilder();
if (percentages)
formattedBuilder.Append( "<color=#" + floatToHexColor(1f - score / 110f) + floatToHexColor(score / 110f) + "00>" + (int) ((score / 110f) * 100f) + "%\n");
else
formattedBuilder.Append("<color=#" + floatToHexColor(1f - score / 110f) + floatToHexColor(score / 110f) + "00>" + score + "\n");
if (beforeCutScore == beforeMax)
formattedBuilder.Append("<color=#FFFFFF>P ");
else
formattedBuilder.Append("<color=#" + floatToHexColor(1f - beforeCut) + (floatToHexColor(beforeCut) + "00>" + beforeCutScore + " "));
if (accuracyScore == accuracyMax)
formattedBuilder.Append("<color=#FFFFFF>P ");
else
formattedBuilder.Append("<color=#" + floatToHexColor(1f - accuracy) + floatToHexColor(accuracy) + "00>" + accuracyScore + " ");
if (afterCutScore == afterMax)
formattedBuilder.Append("<color=#FFFFFF>P");
else
formattedBuilder.Append("<color=#" + floatToHexColor(1f - afterCut) + floatToHexColor(afterCut) + "00>" + afterCutScore);
text.text = formattedBuilder.ToString();
//Feature doesn't work
/*if (saberAfterCutSwingRatingCounter.didFinish)
{
using (StreamWriter file = File.AppendText(fullPathRatings))
{
file.WriteLine(saberAfterCutSwingRatingCounter.RequestId + " <<>> " + beforeCut + " " + accuracy + " " + afterCut + " | " + score);
}
}*/
return;
}
else if (instance.displayMode == mode_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(format_indicator);
while (nextPercentIndex != -1)
{
formattedBuilder.Append(formatString.Substring(0, nextPercentIndex));
if (formatString.Length == nextPercentIndex + 1)
{
formatString += " ";
}
char specifier = formatString[nextPercentIndex + 1];
switch (specifier)
{
case format_beforeCutScore:
formattedBuilder.Append(beforeCutScore);
break;
case format_accuracyScore:
formattedBuilder.Append(accuracyScore);
break;
case format_afterCutScore:
formattedBuilder.Append(afterCutScore);
break;
case FORMAT_BEFORECUTSCORE:
formattedBuilder.Append(judgeSegment(beforeCutScore, instance.beforeCutAngleJudgments));
break;
case FORMAT_ACCUTRACYSCORE:
formattedBuilder.Append(judgeSegment(accuracyScore, instance.accuracyJudgments));
break;
case FORMAT_AFTERCUTSCORE:
formattedBuilder.Append(judgeSegment(afterCutScore, instance.afterCutAngleJudgments));
break;
case format_score:
formattedBuilder.Append(score);
break;
case format_indicatorChar:
formattedBuilder.Append(format_indicatorChar);
break;
case format_lineBreak:
formattedBuilder.Append("\n");
break;
default:
formattedBuilder.Append(format_indicator + specifier);
break;
}
formatString = formatString.Remove(0, nextPercentIndex + 2);
nextPercentIndex = formatString.IndexOf(format_indicator);
}
formattedBuilder.Append(formatString);
text.text = formattedBuilder.ToString();
return;
}
else if (instance.displayMode == mode_textOnly)
text.text = judgment.text;
else if (instance.displayMode == mode_numeric)
return;
}
if (instance.displayMode == "numeric")
{
return;
}
if (instance.displayMode == "scoreOnTop")
{
else if (instance.displayMode == mode_scoreOnTop)
text.text = score + "\n" + judgment.text + "\n";
return;
}
text.text = judgment.text + "\n" + score + "\n";
else
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 "";
}
}
}

View File

@@ -16,7 +16,7 @@ namespace HitScoreVisualizer.Harmony_Patches
{
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;
}
}

View File

@@ -23,7 +23,7 @@ namespace HitScoreVisualizer.Harmony_Patches
{
ScoreController.ScoreWithoutMultiplier(noteCutInfo, saberAfterCutSwingRatingCounter, out int before, out int after);
int total = before + after;
Config.judge(__instance, ref ____color, total);
Config.judge(__instance, noteCutInfo, saberAfterCutSwingRatingCounter, ref ____color, total);
}
}
}

View File

@@ -16,7 +16,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<OutputPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Plugins\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
@@ -31,16 +31,16 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\0Harmony.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Beat Saber_Data\Managed\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="IllusionPlugin">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\IllusionPlugin.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\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>
@@ -54,10 +54,10 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<HintPath>..\..\..\..\Steam Games\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>

View File

@@ -9,14 +9,16 @@ namespace HitScoreVisualizer
public class Plugin : IPlugin
{
public string Name => "HitScoreVisualizer";
public string Version => "2.0.2";
public string Version => "2.1.0";
internal const int majorVersion = 2;
internal const int minorVersion = 0;
internal const int patchVersion = 2;
internal const int minorVersion = 1;
internal const int patchVersion = 0;
public void OnApplicationStart()
{
System.IO.File.WriteAllText(Config.fullPathRatings, "NO DATA");
SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
try
@@ -33,8 +35,27 @@ namespace HitScoreVisualizer
Config.load();
}
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene arg1)
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene newScene)
{
switch (newScene.buildIndex)
{
case 1:
case 2:
// Return if we're not playing in a song.
return;
case 8:
case 9:
// We're in a song
//System.IO.File.WriteAllText(Config.fullPathRatings, newScene.name);
return;
default:
// Unknown, fallback to matching the name.
if (newScene.name.Contains("Environment"))
goto case 8;
else
return;
}
}
private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1)

28
README.md Normal file
View 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. :')