Skip to content

Commit

Permalink
Migration to .NET 5 & diffcalc update (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotrekol authored Jan 29, 2021
1 parent 07f8938 commit 025e44e
Show file tree
Hide file tree
Showing 128 changed files with 1,822 additions and 4,199 deletions.
60 changes: 60 additions & 0 deletions PpCalculator/CtbCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Objects;

namespace PpCalculator
{
public class CtbCalculator : PpCalculator
{
public override Ruleset Ruleset { get; } = new CatchRuleset();

protected override int GetMaxCombo(IReadOnlyList<HitObject> hitObjects) =>
hitObjects.Count(h => h is Fruit)
+ hitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet));

protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood, int? countKatsu = null)
{
var maxCombo = GetMaxCombo(hitObjects);
int maxTinyDroplets = hitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<TinyDroplet>().Count());
int maxDroplets = hitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<Droplet>().Count()) - maxTinyDroplets;
int maxFruits = hitObjects.OfType<Fruit>().Count() + 2 * hitObjects.OfType<JuiceStream>().Count() + hitObjects.OfType<JuiceStream>().Sum(s => s.RepeatCount);

// Either given or max value minus misses
int countDroplets = countGood ?? Math.Max(0, maxDroplets - countMiss);

// Max value minus whatever misses are left. Negative if impossible missCount
int countFruits = maxFruits - (countMiss - (maxDroplets - countDroplets));

// Either given or the max amount of hit objects with respect to accuracy minus the already calculated fruits and drops.
// Negative if accuracy not feasable with missCount.
int countTinyDroplets = countMeh ?? (int)Math.Round(accuracy * (maxCombo + maxTinyDroplets)) - countFruits - countDroplets;

// Whatever droplets are left
int countTinyMisses = countKatsu ?? maxTinyDroplets - countTinyDroplets;

return new Dictionary<HitResult, int>
{
{ HitResult.Great, countFruits },
{ HitResult.LargeTickHit, countDroplets },
{ HitResult.SmallTickHit, countTinyDroplets },
{ HitResult.SmallTickMiss, countTinyMisses },
{ HitResult.Miss, countMiss }
};
}

protected override double GetAccuracy(Dictionary<HitResult, int> statistics)
{
double hits = statistics[HitResult.Great] + statistics[HitResult.LargeTickHit] + statistics[HitResult.SmallTickHit];
double total = hits + statistics[HitResult.Miss] + statistics[HitResult.SmallTickMiss];

return hits / total;
}
}
}
2 changes: 1 addition & 1 deletion PpCalculator/ManiaCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ManiaCalculator : PpCalculator

protected override int GetMaxCombo(IReadOnlyList<HitObject> hitObjects) => 0;

protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood)
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood, int? countKatsu = null)
{
var totalHits = hitObjects.Count;

Expand Down
6 changes: 3 additions & 3 deletions PpCalculator/OsuCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protected override int GetMaxCombo(IReadOnlyList<HitObject> hitObjects) =>



protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood)
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood, int? countKatsu = null)
{
int countGreat;

Expand Down Expand Up @@ -48,15 +48,15 @@ protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy
return new Dictionary<HitResult, int>
{
{ HitResult.Great, countGreat },
{ HitResult.Good, countGood ?? 0 },
{ HitResult.Ok, countGood ?? 0 },
{ HitResult.Meh, countMeh ?? 0 },
{ HitResult.Miss, countMiss }
};
}
protected override double GetAccuracy(Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Good];
var countGood = statistics[HitResult.Ok];
var countMeh = statistics[HitResult.Meh];
var countMiss = statistics[HitResult.Miss];
var total = countGreat + countGood + countMeh + countMiss;
Expand Down
19 changes: 13 additions & 6 deletions PpCalculator/PpCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ public virtual string[] Mods
public virtual int? Mehs { get; set; }

public virtual int? Goods { get; set; }
public int? Katsus { get; set; }

protected virtual ScoreInfo ScoreInfo { get; set; } = new ScoreInfo();

protected virtual PerformanceCalculator PerformanceCalculator { get; set; }
protected List<TimedDifficultyAttributes> TimedDifficultyAttributes { get; set; }

public int? RulesetId => Ruleset.LegacyID;
public int? RulesetId => Ruleset.RulesetInfo.ID;


public void PreProcess(ProcessorWorkingBeatmap workingBeatmap)
Expand Down Expand Up @@ -84,6 +86,8 @@ public double Calculate(double startTime, double endTime = double.NaN, Dictionar
return result;
}

private DifficultyAttributes DummyAtributtes { get; } = new DifficultyAttributes();

public double Calculate(double? endTime = null, Dictionary<string, double> categoryAttribs = null)
{
if (WorkingBeatmap == null)
Expand Down Expand Up @@ -118,7 +122,7 @@ public double Calculate(double? endTime = null, Dictionary<string, double> categ
int beatmapMaxCombo = GetMaxCombo(hitObjects);

var maxCombo = Combo ?? (int)Math.Round(PercentCombo / 100 * beatmapMaxCombo);
var statistics = GenerateHitResults(Accuracy / 100, hitObjects, Misses, Mehs, Goods);
var statistics = GenerateHitResults(Accuracy / 100, hitObjects, Misses, Mehs, Goods, Katsus);

var score = Score;
var accuracy = GetAccuracy(statistics);
Expand All @@ -131,14 +135,17 @@ public double Calculate(double? endTime = null, Dictionary<string, double> categ

if (createPerformanceCalculator)
{
PerformanceCalculator = ruleset.CreatePerformanceCalculator(WorkingBeatmap, ScoreInfo);
(PerformanceCalculator, TimedDifficultyAttributes) = ruleset.CreatePerformanceCalculator(WorkingBeatmap, ScoreInfo);
ResetPerformanceCalculator = false;
}

try
{
return endTime.HasValue
? PerformanceCalculator.Calculate(endTime.Value, categoryAttribs)

return endTime.HasValue
? PerformanceCalculator.Calculate(endTime.Value,
TimedDifficultyAttributes.LastOrDefault(a => endTime.Value >= a.Time)?.Attributes ?? TimedDifficultyAttributes.First().Attributes,
categoryAttribs)
: PerformanceCalculator.Calculate(categoryAttribs);
}
catch (InvalidOperationException)
Expand Down Expand Up @@ -189,7 +196,7 @@ protected int GetComboToTime(IBeatmap beatmap, int toTime) =>

protected abstract int GetMaxCombo(IReadOnlyList<HitObject> hitObjects);

protected abstract Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood);
protected abstract Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood, int? countKatsu = null);

protected abstract double GetAccuracy(Dictionary<HitResult, int> statistics);

Expand Down
30 changes: 13 additions & 17 deletions PpCalculator/PpCalculator.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game\osu.Game.csproj">
<EmbedInteropTypes>false</EmbedInteropTypes>
<Private>true</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType>
<Platforms>AnyCPU;x86</Platforms>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\submodules\osu\osu.Game\osu.Game.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\submodules\osu\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions PpCalculator/PpCalculatorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private PpCalculatorHelpers() { }
/// gamemode id<para/>
/// 0 = osu<para/>
/// 1 = Taiko <para/>
/// 2 = Ctb(unsupported - returns null)<para/>
/// 2 = Ctb<para/>
/// 3 = Mania
/// </param>
/// <returns></returns>
Expand All @@ -29,7 +29,7 @@ public static PpCalculator GetPpCalculator(int rulesetId)
case 1:
return new TaikoCalculator();
case 2:
return null;
return new CtbCalculator();
case 3:
return new ManiaCalculator();
}
Expand Down
7 changes: 2 additions & 5 deletions PpCalculator/ProcessorWorkingBeatmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using System.IO;
using System.Linq;
using System.Threading;
using osu.Framework.Graphics.Video;
using osu.Game.IO;
using osu.Game.Rulesets.Objects.Types;

Expand All @@ -35,7 +34,7 @@ public double Length
return 0;

var hitObject = beatmap.HitObjects.Last();
return (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime;
return (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime;
}
}

Expand Down Expand Up @@ -84,9 +83,7 @@ private static Beatmap readFromFile(string filename,int retryCount=0)

protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override VideoSprite GetVideo() => null;

protected override Track GetTrack() => null;
protected override Track GetBeatmapTrack() => null;

public static Ruleset GetRulesetFromLegacyID(int id)
{
Expand Down
6 changes: 3 additions & 3 deletions PpCalculator/TaikoCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class TaikoCalculator : PpCalculator
protected override int GetMaxCombo(IReadOnlyList<HitObject> hitObjects) =>
hitObjects.OfType<Hit>().Count();

protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood)
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IReadOnlyList<HitObject> hitObjects, int countMiss, int? countMeh, int? countGood, int? countKatsu = null)
{
var totalResultCount = GetMaxCombo(hitObjects);

Expand All @@ -38,7 +38,7 @@ protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy
return new Dictionary<HitResult, int>
{
{ HitResult.Great, countGreat },
{ HitResult.Good, (int)countGood },
{ HitResult.Ok, (int)countGood },
{ HitResult.Meh, 0 },
{ HitResult.Miss, countMiss }
};
Expand All @@ -47,7 +47,7 @@ protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy
protected override double GetAccuracy(Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Good];
var countGood = statistics[HitResult.Ok];
var countMiss = statistics[HitResult.Miss];
var total = countGreat + countGood + countMiss;

Expand Down
71 changes: 71 additions & 0 deletions PpCalculatorTests/PpCalculatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.IO;
using CollectionManager.DataTypes;
using NUnit.Framework;
using osu.Framework.IO.Network;
using Beatmap = StreamCompanionTypes.DataTypes.Beatmap;


namespace PpCalculator.Tests
{
[TestFixture()]
public class PpCalculatorTests
{
private const string base_url = "https://osu.ppy.sh";

[Test]
[TestCase(5, 0, 0, 595, "HD,DT", 628.534, 1185330)]
[TestCase(9, 0, 0, 858, "HD,DT", 698.977, 2333273)]
public void CalculateOsuTest(int c100, int c50, int cMiss, int combo, string mods, double expectedPp, int mapId)
=> CalculateTest(c100, c50, cMiss, combo, mods, expectedPp, mapId, new OsuCalculator());

[Test]
[TestCase(76, 0, 2, 1679, "HD,DT", 594.078, 1251239)]
[TestCase(36, 0, 0, 2110, "HD,DT", 521.732, 2495119)]
public void CalculateTaikoTest(int c100, int c50, int cMiss, int combo, string mods, double expectedPp, int mapId)
=> CalculateTest(c100, c50, cMiss, combo, mods, expectedPp, mapId, new TaikoCalculator());

[Test]
[TestCase(73, 79, 0, 1241, "HR", 822.357, 1972148)]
[TestCase(25, 216, 0, 567, "HD,HR", 378.109, 2424031)]
public void CalculateCtbTest(int c100, int c50, int cMiss, int combo, string mods, double expectedPp, int mapId)
=> CalculateTest(c100, c50, cMiss, combo, mods, expectedPp, mapId, new CtbCalculator());

[Ignore("Star rating is diferent in osu and on site itself, resulting in slightly changed pp values(calculator SR has same values as one in osu! itself)")]
[Test]
[TestCase(1, 0, 0, 2782, "", 641.782, 1270895, 993209)]
[TestCase(6, 2, 4, 1830, "", 768.33, 2513195, 948258)]
public void CalculateManiaTest(int c100, int c50, int cMiss, int combo, string mods, double expectedPp, int mapId, int score)
=> CalculateTest(c100, c50, cMiss, combo, mods, expectedPp, mapId, new ManiaCalculator(), score);

public void CalculateTest(int c100, int c50, int cMiss, int combo, string mods, double expectedPp, int mapId, PpCalculator ppCalculator, int score = 0)
{
ppCalculator.PreProcess(GetMapPath(mapId));
ppCalculator.Goods = c100;
ppCalculator.Mehs = c50;
ppCalculator.Misses = cMiss;
ppCalculator.Combo = combo;
ppCalculator.Score = score;
ppCalculator.Mods = mods.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);

var calculatedPp = ppCalculator.Calculate();

Assert.That(calculatedPp, Is.EqualTo(expectedPp).Within(0.01));
}

private string GetMapPath(int mapId)
{
if (!Directory.Exists("cache"))
Directory.CreateDirectory("cache");

string cachePath = Path.Combine("cache", $"{mapId}.osu");
if (!File.Exists(cachePath))
{
new FileWebRequest(cachePath, $"{base_url}/osu/{mapId}").Perform();
}

return cachePath;
}
}
}
46 changes: 46 additions & 0 deletions PpCalculatorTests/PpCalculatorTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit">
<Version>3.12.0</Version>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter">
<Version>3.15.1</Version>
</PackageReference>
<PackageReference Include="StreamCompanionTypes">
<Version>5.2.1</Version>
</PackageReference>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\PpCalculator\PpCalculator.csproj" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
</Project>
Loading

0 comments on commit 025e44e

Please sign in to comment.