mirror of
https://github.com/ppy/osu
synced 2025-03-30 07:06:56 +00:00
Merge branch 'master' into beatmap-card-basics
This commit is contained in:
commit
cf7545e36c
@ -16,7 +16,7 @@
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -20,6 +20,7 @@ namespace osu.Android
|
||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
|
||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
|
||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
|
||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
|
||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.5867229481955389d, "diffcalc-test")]
|
||||
[TestCase(1.0416315570967911d, "zero-length-sliders")]
|
||||
[TestCase(6.5295339534769958d, "diffcalc-test")]
|
||||
[TestCase(1.1514260533755143d, "zero-length-sliders")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(8.2730989071947896d, "diffcalc-test")]
|
||||
[TestCase(1.2726413186221039d, "zero-length-sliders")]
|
||||
[TestCase(9.047752485219954d, "diffcalc-test")]
|
||||
[TestCase(1.3985711787077566d, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new OsuModDoubleTime());
|
||||
|
||||
|
34
osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
Normal file
34
osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneNoSpinnerStacking : TestSceneOsuPlayer
|
||||
{
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
|
||||
Ruleset = ruleset
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
if (i % 32 < 20)
|
||||
beatmap.HitObjects.Add(new Spinner { Position = new Vector2(256, 192), StartTime = i * 200, EndTime = (i * 200) + 100 });
|
||||
}
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
|
@ -12,20 +12,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalized_radius = 52;
|
||||
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int min_delta_time = 25;
|
||||
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms to account for simultaneous <see cref="OsuDifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
public double StrainTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double JumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double MovementDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
@ -37,6 +38,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the end time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double MovementTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/> to the end time of the same previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
private readonly OsuHitObject lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
|
||||
@ -46,13 +62,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
this.lastLastObject = (OsuHitObject)lastLastObject;
|
||||
this.lastObject = (OsuHitObject)lastObject;
|
||||
|
||||
setDistances();
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
StrainTime = Math.Max(DeltaTime, min_delta_time);
|
||||
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
|
||||
StrainTime = Math.Max(DeltaTime, 25);
|
||||
setDistances(clockRate);
|
||||
}
|
||||
|
||||
private void setDistances()
|
||||
private void setDistances(double clockRate)
|
||||
{
|
||||
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
||||
if (BaseObject is Spinner || lastObject is Spinner)
|
||||
@ -67,15 +83,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
scalingFactor *= 1 + smallCircleBonus;
|
||||
}
|
||||
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
|
||||
if (lastObject is Slider lastSlider)
|
||||
{
|
||||
computeSliderCursorPosition(lastSlider);
|
||||
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
|
||||
TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
|
||||
|
||||
// Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
|
||||
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
|
||||
|
||||
// For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
|
||||
// such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
|
||||
// In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
|
||||
MovementDistance = Math.Min(JumpDistance, tailJumpDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
MovementTime = StrainTime;
|
||||
MovementDistance = JumpDistance;
|
||||
}
|
||||
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
|
||||
if (lastLastObject != null && !(lastLastObject is Spinner))
|
||||
{
|
||||
@ -98,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
slider.LazyEndPosition = slider.StackedPosition;
|
||||
|
||||
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
||||
float followCircleRadius = (float)(slider.Radius * 2.4);
|
||||
var computeVertex = new Action<double>(t =>
|
||||
{
|
||||
double progress = (t - slider.StartTime) / slider.SpanDuration;
|
||||
@ -111,11 +141,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
|
||||
float dist = diff.Length;
|
||||
|
||||
if (dist > approxFollowCircleRadius)
|
||||
slider.LazyTravelTime = t - slider.StartTime;
|
||||
|
||||
if (dist > followCircleRadius)
|
||||
{
|
||||
// The cursor would be outside the follow circle, we need to move it
|
||||
diff.Normalize(); // Obtain direction of diff
|
||||
dist -= approxFollowCircleRadius;
|
||||
dist -= followCircleRadius;
|
||||
slider.LazyEndPosition += diff * dist;
|
||||
slider.LazyTravelDistance += dist;
|
||||
}
|
||||
|
@ -14,53 +14,96 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Aim : OsuStrainSkill
|
||||
{
|
||||
private const double angle_bonus_begin = Math.PI / 3;
|
||||
private const double timing_threshold = 107;
|
||||
|
||||
public Aim(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
protected override int HistoryLength => 2;
|
||||
|
||||
private const double wide_angle_multiplier = 1.5;
|
||||
private const double acute_angle_multiplier = 2.0;
|
||||
|
||||
private double currentStrain = 1;
|
||||
|
||||
private double skillMultiplier => 26.25;
|
||||
private double skillMultiplier => 23.25;
|
||||
private double strainDecayBase => 0.15;
|
||||
|
||||
private double strainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
|
||||
|
||||
double aimStrain = 0;
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
|
||||
|
||||
if (Previous.Count > 0)
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider)
|
||||
{
|
||||
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
|
||||
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
|
||||
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
|
||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
|
||||
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
|
||||
}
|
||||
|
||||
// As above, do the same for the previous hitobject.
|
||||
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider)
|
||||
{
|
||||
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
|
||||
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
|
||||
}
|
||||
|
||||
double angleBonus = 0;
|
||||
double aimStrain = currVelocity; // Start strain with regular velocity.
|
||||
|
||||
if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
|
||||
{
|
||||
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
|
||||
{
|
||||
const double scale = 90;
|
||||
double currAngle = osuCurrObj.Angle.Value;
|
||||
double lastAngle = osuLastObj.Angle.Value;
|
||||
double lastLastAngle = osuLastLastObj.Angle.Value;
|
||||
|
||||
double angleBonus = Math.Sqrt(
|
||||
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
||||
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
||||
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
||||
aimStrain = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
||||
// Rewarding angles, take the smaller velocity as base.
|
||||
angleBonus = Math.Min(currVelocity, prevVelocity);
|
||||
|
||||
double wideAngleBonus = calcWideAngleBonus(currAngle);
|
||||
double acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
||||
|
||||
if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
|
||||
acuteAngleBonus = 0;
|
||||
else
|
||||
{
|
||||
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
}
|
||||
|
||||
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
|
||||
|
||||
angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together.
|
||||
}
|
||||
}
|
||||
|
||||
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
|
||||
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
||||
aimStrain += angleBonus; // Add in angle bonus.
|
||||
|
||||
return Math.Max(
|
||||
aimStrain + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
||||
);
|
||||
return aimStrain;
|
||||
}
|
||||
|
||||
private double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2);
|
||||
|
||||
private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);
|
||||
|
||||
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
set => StackHeightBindable.Value = value;
|
||||
}
|
||||
|
||||
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
||||
public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
||||
|
||||
public double Radius => OBJECT_RADIUS * Scale;
|
||||
|
||||
|
@ -79,6 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
internal float LazyTravelDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The time taken by the cursor upon completion of this <see cref="Slider"/> if it was hit
|
||||
/// with as few movements as possible. This is set and used by difficulty calculation.
|
||||
/// </summary>
|
||||
internal double LazyTravelTime;
|
||||
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
|
||||
[JsonIgnore]
|
||||
|
@ -8,6 +8,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public int MaximumBonusSpins { get; protected set; } = 1;
|
||||
|
||||
public override Vector2 StackOffset => Vector2.Zero;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -38,6 +38,15 @@ namespace osu.Game.Tests.Skins.IO
|
||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
|
||||
|
||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
@ -199,21 +208,23 @@ namespace osu.Game.Tests.Skins.IO
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini")
|
||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini", bool includeSectionHeader = true)
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique));
|
||||
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique, includeSectionHeader));
|
||||
zip.SaveTo(zipStream);
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true)
|
||||
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true, bool includeSectionHeader = true)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
|
||||
writer.WriteLine("[General]");
|
||||
if (includeSectionHeader)
|
||||
writer.WriteLine("[General]");
|
||||
|
||||
writer.WriteLine($"Name: {name}");
|
||||
writer.WriteLine($"Author: {author}");
|
||||
|
||||
|
@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
|
||||
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -290,6 +293,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
|
||||
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
|
||||
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -45,11 +45,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddNullUser()
|
||||
public void TestAddUnresolvedUser()
|
||||
{
|
||||
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
|
||||
|
||||
AddStep("add non-resolvable user", () => Client.AddNullUser());
|
||||
AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser());
|
||||
AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
|
||||
|
||||
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
|
||||
|
@ -3,7 +3,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
|
@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps
|
||||
var localRulesetInfo = rulesetInfo as RulesetInfo;
|
||||
|
||||
// Difficulty can only be computed if the beatmap and ruleset are locally available.
|
||||
if (localBeatmapInfo == null || localRulesetInfo == null)
|
||||
if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null)
|
||||
{
|
||||
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
||||
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
||||
|
@ -249,6 +249,23 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
|
||||
|
||||
// Temporary method until this class supports IBeatmapSetInfo or otherwise.
|
||||
public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false)
|
||||
{
|
||||
return beatmapModelDownloader.Download(new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = model.OnlineID,
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = model.Metadata?.Title,
|
||||
Artist = model.Metadata?.Artist,
|
||||
TitleUnicode = model.Metadata?.TitleUnicode,
|
||||
ArtistUnicode = model.Metadata?.ArtistUnicode,
|
||||
Author = new User { Username = model.Metadata?.Author },
|
||||
}
|
||||
}, minimiseDownloadSize);
|
||||
}
|
||||
|
||||
public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false)
|
||||
{
|
||||
return beatmapModelDownloader.Download(model, minimiseDownloadSize);
|
||||
|
@ -60,8 +60,9 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
|
||||
/// <param name="mods">The mods to show the difficulty with.</param>
|
||||
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
||||
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
|
||||
: this(beatmapInfo, shouldShowTooltip)
|
||||
/// <param name="performBackgroundDifficultyLookup">Whether to perform difficulty lookup (including calculation if necessary).</param>
|
||||
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
|
||||
: this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup)
|
||||
{
|
||||
this.ruleset = ruleset ?? beatmapInfo.Ruleset;
|
||||
this.mods = mods ?? Array.Empty<Mod>();
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
protected override void ParseStreamInto(LineBufferedReader stream, T output)
|
||||
{
|
||||
Section section = Section.None;
|
||||
Section section = Section.General;
|
||||
|
||||
string line;
|
||||
|
||||
@ -47,10 +47,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (line.StartsWith('[') && line.EndsWith(']'))
|
||||
{
|
||||
if (!Enum.TryParse(line[1..^1], out section))
|
||||
{
|
||||
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
|
||||
section = Section.None;
|
||||
}
|
||||
|
||||
OnBeginNewSection(section);
|
||||
continue;
|
||||
@ -148,7 +145,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
protected enum Section
|
||||
{
|
||||
None,
|
||||
General,
|
||||
Editor,
|
||||
Metadata,
|
||||
|
@ -107,7 +107,8 @@ namespace osu.Game.Online.API
|
||||
WebRequest = CreateWebRequest();
|
||||
WebRequest.Failed += Fail;
|
||||
WebRequest.AllowRetryOnTimeout = false;
|
||||
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
||||
if (!string.IsNullOrEmpty(API.AccessToken))
|
||||
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
||||
|
||||
if (isFailing) return;
|
||||
|
||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
[CanBeNull]
|
||||
public APIBeatmap Beatmap { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
@ -46,6 +47,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
[CanBeNull]
|
||||
public APIBeatmapSet BeatmapSet
|
||||
{
|
||||
set
|
||||
@ -96,7 +98,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
TotalScore = TotalScore,
|
||||
MaxCombo = MaxCombo,
|
||||
BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets),
|
||||
BeatmapInfo = Beatmap?.ToBeatmapInfo(rulesets),
|
||||
User = User,
|
||||
Accuracy = Accuracy,
|
||||
OnlineScoreID = OnlineID,
|
||||
|
@ -148,6 +148,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
Room = joinedRoom;
|
||||
APIRoom = room;
|
||||
|
||||
Debug.Assert(LocalUser != null);
|
||||
addUserToAPIRoom(LocalUser);
|
||||
|
||||
foreach (var user in joinedRoom.Users)
|
||||
updateUserPlayingState(user.UserID, user.State);
|
||||
|
||||
@ -372,6 +376,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Room.Users.Add(user);
|
||||
|
||||
addUserToAPIRoom(user);
|
||||
|
||||
UserJoined?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
@ -391,6 +397,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
return handleUserLeft(user, UserKicked);
|
||||
}
|
||||
|
||||
private void addUserToAPIRoom(MultiplayerRoomUser user)
|
||||
{
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
APIRoom.RecentParticipants.Add(user.User ?? new User
|
||||
{
|
||||
Id = user.UserID,
|
||||
Username = "[Unresolved]"
|
||||
});
|
||||
APIRoom.ParticipantCount.Value++;
|
||||
}
|
||||
|
||||
private Task handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
|
||||
{
|
||||
if (Room == null)
|
||||
@ -404,6 +422,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Users.Remove(user);
|
||||
PlayingUserIds.Remove(user.UserID);
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID);
|
||||
APIRoom.ParticipantCount.Value--;
|
||||
|
||||
callback?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
|
@ -53,7 +53,10 @@ namespace osu.Game.Online.Rooms
|
||||
downloadTracker?.RemoveAndDisposeImmediately();
|
||||
|
||||
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
|
||||
downloadTracker.State.BindValueChanged(_ => updateAvailability());
|
||||
|
||||
AddInternal(downloadTracker);
|
||||
|
||||
downloadTracker.State.BindValueChanged(_ => updateAvailability(), true);
|
||||
downloadTracker.Progress.BindValueChanged(_ =>
|
||||
{
|
||||
if (downloadTracker.State.Value != DownloadState.Downloading)
|
||||
@ -63,9 +66,7 @@ namespace osu.Game.Online.Rooms
|
||||
// we don't want to flood the network with this, so rate limit how often we send progress updates.
|
||||
if (progressUpdate?.Completed != false)
|
||||
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
||||
});
|
||||
|
||||
AddInternal(downloadTracker);
|
||||
}, true);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
@ -14,14 +15,14 @@ namespace osu.Game.Online.Rooms
|
||||
private readonly long scoreId;
|
||||
private readonly long roomId;
|
||||
private readonly long playlistItemId;
|
||||
private readonly ScoreInfo scoreInfo;
|
||||
private readonly SubmittableScore score;
|
||||
|
||||
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
|
||||
{
|
||||
this.scoreId = scoreId;
|
||||
this.roomId = roomId;
|
||||
this.playlistItemId = playlistItemId;
|
||||
this.scoreInfo = scoreInfo;
|
||||
score = new SubmittableScore(scoreInfo);
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -31,7 +32,7 @@ namespace osu.Game.Online.Rooms
|
||||
req.ContentType = "application/json";
|
||||
req.Method = HttpMethod.Put;
|
||||
|
||||
req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings
|
||||
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
}));
|
||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
break;
|
||||
|
||||
default:
|
||||
beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideoSetting.Value);
|
||||
beatmaps.Download(beatmapSet, noVideoSetting.Value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
return;
|
||||
}
|
||||
|
||||
beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideo);
|
||||
beatmaps.Download(beatmapSet, noVideo);
|
||||
};
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = $"{Score.Beatmap.DifficultyName}",
|
||||
Text = $"{Score.Beatmap?.DifficultyName}",
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
||||
Colour = colours.Yellow
|
||||
},
|
||||
|
@ -58,6 +58,11 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of the beatmap and returns a set of <see cref="TimedDifficultyAttributes"/> representing the difficulty at every relevant time value in the beatmap.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mods that should be applied to the beatmap.</param>
|
||||
/// <returns>The set of <see cref="TimedDifficultyAttributes"/>.</returns>
|
||||
public List<TimedDifficultyAttributes> CalculateTimed(params Mod[] mods)
|
||||
{
|
||||
preProcess(mods);
|
||||
@ -77,7 +82,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
foreach (var skill in skills)
|
||||
skill.ProcessInternal(hitObject);
|
||||
|
||||
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||
}
|
||||
|
||||
return attribs;
|
||||
|
@ -11,9 +11,21 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// </summary>
|
||||
public class TimedDifficultyAttributes : IComparable<TimedDifficultyAttributes>
|
||||
{
|
||||
/// <summary>
|
||||
/// The non-clock-adjusted time value at which the attributes take effect.
|
||||
/// </summary>
|
||||
public readonly double Time;
|
||||
|
||||
/// <summary>
|
||||
/// The attributes.
|
||||
/// </summary>
|
||||
public readonly DifficultyAttributes Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new <see cref="TimedDifficultyAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The non-clock-adjusted time value at which the attributes take effect.</param>
|
||||
/// <param name="attributes">The attributes.</param>
|
||||
public TimedDifficultyAttributes(double time, DifficultyAttributes attributes)
|
||||
{
|
||||
Time = time;
|
||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
|
||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
|
||||
|
||||
beatmapText.Clear();
|
||||
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
|
||||
|
@ -194,68 +194,61 @@ namespace osu.Game.Skinning
|
||||
string nameLine = @$"Name: {item.Name}";
|
||||
string authorLine = @$"Author: {item.Creator}";
|
||||
|
||||
string[] newLines =
|
||||
{
|
||||
@"// The following content was automatically added by osu! during import, based on filename / folder metadata.",
|
||||
@"[General]",
|
||||
nameLine,
|
||||
authorLine,
|
||||
};
|
||||
|
||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existingFile != null)
|
||||
if (existingFile == null)
|
||||
{
|
||||
List<string> outputLines = new List<string>();
|
||||
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
||||
writeNewSkinIni();
|
||||
return;
|
||||
}
|
||||
|
||||
bool addedName = false;
|
||||
bool addedAuthor = false;
|
||||
|
||||
using (var stream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath))
|
||||
using (var sr = new StreamReader(stream))
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath))
|
||||
using (var sr = new StreamReader(existingStream))
|
||||
{
|
||||
if (line.StartsWith(@"Name:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(nameLine);
|
||||
addedName = true;
|
||||
}
|
||||
else if (line.StartsWith(@"Author:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(authorLine);
|
||||
addedAuthor = true;
|
||||
}
|
||||
else
|
||||
outputLines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (!addedName || !addedAuthor)
|
||||
{
|
||||
outputLines.AddRange(new[]
|
||||
{
|
||||
@"[General]",
|
||||
nameLine,
|
||||
authorLine,
|
||||
});
|
||||
}
|
||||
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
foreach (string line in outputLines)
|
||||
string line;
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
|
||||
ReplaceFile(item, existingFile, stream);
|
||||
sw.WriteLine();
|
||||
|
||||
foreach (string line in newLines)
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
|
||||
ReplaceFile(item, existingFile, stream);
|
||||
|
||||
// can be removed 20220502.
|
||||
if (!ensureIniWasUpdated(item))
|
||||
{
|
||||
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
||||
|
||||
DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)));
|
||||
writeNewSkinIni();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
void writeNewSkinIni()
|
||||
{
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
sw.WriteLine(@"[General]");
|
||||
sw.WriteLine(nameLine);
|
||||
sw.WriteLine(authorLine);
|
||||
sw.WriteLine(@"Version: latest");
|
||||
foreach (string line in newLines)
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
|
||||
AddFile(item, stream, @"skin.ini");
|
||||
@ -263,6 +256,17 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
private bool ensureIniWasUpdated(SkinInfo item)
|
||||
{
|
||||
// This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
|
||||
// With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
|
||||
// are no other cases let's avoid a hard startup crash by bailing and alerting.
|
||||
|
||||
var instance = GetSkin(item);
|
||||
|
||||
return instance.Configuration.SkinInfo.Name == item.Name;
|
||||
}
|
||||
|
||||
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var instance = GetSkin(model);
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return roomUser;
|
||||
}
|
||||
|
||||
public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
||||
public void TestAddUnresolvedUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.UNRESOLVED_USER_ID));
|
||||
|
||||
private void addUser(MultiplayerRoomUser user)
|
||||
{
|
||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual
|
||||
/// A special user ID which <see cref="ComputeValueAsync"/> would return a <see langword="null"/> <see cref="User"/> for.
|
||||
/// As a simulation to what a regular <see cref="UserLookupCache"/> would return in the case of failing to fetch the user.
|
||||
/// </summary>
|
||||
public const int NULL_USER_ID = -1;
|
||||
public const int UNRESOLVED_USER_ID = -1;
|
||||
|
||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||
{
|
||||
if (lookup == NULL_USER_ID)
|
||||
if (lookup == UNRESOLVED_USER_ID)
|
||||
return Task.FromResult((User)null);
|
||||
|
||||
return Task.FromResult(new User
|
||||
|
@ -23,9 +23,9 @@
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.37" />
|
||||
<PackageReference Include="Humanizer" Version="2.11.10" />
|
||||
<PackageReference Include="MessagePack" Version="2.3.85" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
@ -38,8 +38,8 @@
|
||||
<PackageReference Include="Realm" Version="10.6.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||
<PackageReference Include="Sentry" Version="3.9.4" />
|
||||
<PackageReference Include="SharpCompress" Version="0.29.0" />
|
||||
<PackageReference Include="Sentry" Version="3.10.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||
|
@ -94,7 +94,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user