mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into framework-clipboard
This commit is contained in:
commit
ded7ec3aa6
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2022.809.0",
|
||||
"version": "2023.712.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
|
||||
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } },
|
||||
new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||
|
@ -26,6 +26,8 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
@ -91,6 +93,9 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
yield return new CatchModRelax();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
@ -140,6 +145,12 @@ namespace osu.Game.Rulesets.Catch
|
||||
new CatchModNoScope(),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
return new Mod[]
|
||||
{
|
||||
new ModScoreV2(),
|
||||
};
|
||||
|
||||
default:
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
@ -209,5 +220,17 @@ namespace osu.Game.Rulesets.Catch
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||
|
||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
@ -24,7 +23,5 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
: base(new THitObject())
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@ -36,7 +37,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { LegacyMods.Key3, new[] { typeof(ManiaModKey3) } },
|
||||
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
|
||||
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } }
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } },
|
||||
new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||
|
147
osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs
Normal file
147
osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs
Normal file
@ -0,0 +1,147 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public partial class TestSceneMaximumScore : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
[Test]
|
||||
public void TestSimultaneousTickAndNote()
|
||||
{
|
||||
performTest(
|
||||
new List<ManiaHitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 2000,
|
||||
Column = 0,
|
||||
},
|
||||
new Note
|
||||
{
|
||||
StartTime = 2000,
|
||||
Column = 1
|
||||
}
|
||||
},
|
||||
new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000, ManiaAction.Key1, ManiaAction.Key2),
|
||||
new ManiaReplayFrame(2001, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(3000)
|
||||
});
|
||||
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is 1 million", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimultaneousLongNotes()
|
||||
{
|
||||
performTest(
|
||||
new List<ManiaHitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 2000,
|
||||
Column = 0,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 2000,
|
||||
Duration = 2000,
|
||||
Column = 1
|
||||
}
|
||||
},
|
||||
new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000, ManiaAction.Key1, ManiaAction.Key2),
|
||||
new ManiaReplayFrame(3000, ManiaAction.Key2),
|
||||
new ManiaReplayFrame(4000)
|
||||
});
|
||||
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is 1 million", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
||||
{
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects = hitObjects,
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -157,6 +157,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Mirror))
|
||||
yield return new ManiaModMirror();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||
@ -285,6 +288,12 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ModAdaptiveSpeed()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
return new Mod[]
|
||||
{
|
||||
new ModScoreV2(),
|
||||
};
|
||||
|
||||
default:
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
@ -403,7 +412,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}, true),
|
||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new AverageHitError(score.HitEvents),
|
||||
new UnstableRate(score.HitEvents)
|
||||
|
@ -2,7 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Scoring
|
||||
@ -16,6 +21,9 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<HitObject> EnumerateHitObjects(IBeatmap beatmap)
|
||||
=> base.EnumerateHitObjects(beatmap).OrderBy(ho => (ManiaHitObject)ho, JudgementOrderComparer.DEFAULT);
|
||||
|
||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||
{
|
||||
return 10000 * comboProgress
|
||||
@ -25,5 +33,27 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||
|
||||
private class JudgementOrderComparer : IComparer<ManiaHitObject>
|
||||
{
|
||||
public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer();
|
||||
|
||||
public int Compare(ManiaHitObject? x, ManiaHitObject? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (ReferenceEquals(x, null)) return -1;
|
||||
if (ReferenceEquals(y, null)) return 1;
|
||||
|
||||
int result = x.GetEndTime().CompareTo(y.GetEndTime());
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
// due to the way input is handled in mania, notes take precedence over ticks in judging order.
|
||||
if (x is Note && y is not Note) return -1;
|
||||
if (x is not Note && y is Note) return 1;
|
||||
|
||||
return x.Column.CompareTo(y.Column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointType(0, PathType.Linear);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlaceWithMouseMovementOutsidePlayfield()
|
||||
{
|
||||
addMovementStep(new Vector2(200));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
AddStep("move mouse out of screen", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + Vector2.One));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.Linear);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlaceNormalControlPoint()
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
@ -28,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } },
|
||||
new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } },
|
||||
new object[] { LegacyMods.Target, new[] { typeof(OsuModTargetPractice) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } },
|
||||
new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(osu_mod_mapping))]
|
||||
|
@ -214,17 +214,24 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
? currSlider.Position + currSlider.Path.PositionAt(1)
|
||||
: currHitObject.Position;
|
||||
|
||||
// Note the use of `StartTime` in the code below doesn't match stable's use of `EndTime`.
|
||||
// This is because in the stable implementation, `UpdateCalculations` is not called on the inner-loop hitobject (j)
|
||||
// and therefore it does not have a correct `EndTime`, but instead the default of `EndTime = StartTime`.
|
||||
//
|
||||
// Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where
|
||||
// if we use `EndTime` here it would result in unexpected stacking.
|
||||
|
||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||
{
|
||||
currHitObject.StackHeight++;
|
||||
startTime = beatmap.HitObjects[j].GetEndTime();
|
||||
startTime = beatmap.HitObjects[j].StartTime;
|
||||
}
|
||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
||||
{
|
||||
// Case for sliders - bump notes down and right, rather than up and left.
|
||||
sliderStack++;
|
||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = beatmap.HitObjects[j].GetEndTime();
|
||||
startTime = beatmap.HitObjects[j].StartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,9 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.TouchDevice))
|
||||
yield return new OsuModTouchDevice();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||
@ -212,6 +215,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
return new Mod[]
|
||||
{
|
||||
new OsuModTouchDevice(),
|
||||
new ModScoreV2(),
|
||||
};
|
||||
|
||||
default:
|
||||
@ -315,7 +319,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}, true),
|
||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new AverageHitError(timedHitEvents),
|
||||
new UnstableRate(timedHitEvents)
|
||||
|
@ -114,5 +114,75 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
||||
|
||||
AddAssert("all tick offsets are 0", () => JudgementResults.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure input is correctly sent to subsequent hits if a swell is fully completed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitSwellThenHitHit()
|
||||
{
|
||||
const double swell_time = 1000;
|
||||
const double hit_time = 1150;
|
||||
|
||||
Swell swell = new Swell
|
||||
{
|
||||
StartTime = swell_time,
|
||||
Duration = 100,
|
||||
RequiredHits = 1
|
||||
};
|
||||
|
||||
Hit hit = new Hit
|
||||
{
|
||||
StartTime = hit_time
|
||||
};
|
||||
|
||||
List<ReplayFrame> frames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(swell_time, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(hit_time, TaikoAction.RightCentre),
|
||||
};
|
||||
|
||||
PerformTest(frames, CreateBeatmap(swell, hit));
|
||||
|
||||
AssertJudgementCount(3);
|
||||
|
||||
AssertResult<SwellTick>(0, HitResult.IgnoreHit);
|
||||
AssertResult<Swell>(0, HitResult.LargeBonus);
|
||||
AssertResult<Hit>(0, HitResult.Great);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissSwellThenHitHit()
|
||||
{
|
||||
const double swell_time = 1000;
|
||||
const double hit_time = 1150;
|
||||
|
||||
Swell swell = new Swell
|
||||
{
|
||||
StartTime = swell_time,
|
||||
Duration = 100,
|
||||
RequiredHits = 1
|
||||
};
|
||||
|
||||
Hit hit = new Hit
|
||||
{
|
||||
StartTime = hit_time
|
||||
};
|
||||
|
||||
List<ReplayFrame> frames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time, TaikoAction.RightCentre),
|
||||
};
|
||||
|
||||
PerformTest(frames, CreateBeatmap(swell, hit));
|
||||
|
||||
AssertJudgementCount(3);
|
||||
|
||||
AssertResult<SwellTick>(0, HitResult.IgnoreMiss);
|
||||
AssertResult<Swell>(0, HitResult.IgnoreMiss);
|
||||
AssertResult<Hit>(0, HitResult.Great);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } },
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } },
|
||||
new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } }
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } },
|
||||
new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(taiko_mod_mapping))]
|
||||
|
@ -276,6 +276,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
if (Time.Current < HitObject.StartTime)
|
||||
return false;
|
||||
|
||||
if (AllJudged)
|
||||
return false;
|
||||
|
||||
bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
|
||||
|
||||
// Ensure alternating centre and rim hits
|
||||
|
@ -116,6 +116,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Random))
|
||||
yield return new TaikoModRandom();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||
@ -176,6 +179,12 @@ namespace osu.Game.Rulesets.Taiko
|
||||
new ModAdaptiveSpeed()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
return new Mod[]
|
||||
{
|
||||
new ModScoreV2(),
|
||||
};
|
||||
|
||||
default:
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
@ -247,7 +256,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}, true),
|
||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new AverageHitError(timedHitEvents),
|
||||
new UnstableRate(timedHitEvents)
|
||||
|
85
osu.Game.Tests/Editing/Checks/CheckDrainTimeTest.cs
Normal file
85
osu.Game.Tests/Editing/Checks/CheckDrainTimeTest.cs
Normal file
@ -0,0 +1,85 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
public class CheckDrainTimeTest
|
||||
{
|
||||
private CheckDrainTime check = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckDrainTime();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrainTimeShort()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 29_999 }
|
||||
}
|
||||
};
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckDrainTime.IssueTemplateTooShort);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrainTimeBreak()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 40_000 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(10_000, 21_000)
|
||||
}
|
||||
};
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckDrainTime.IssueTemplateTooShort);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrainTimeCorrect()
|
||||
{
|
||||
var hitObjects = new List<HitObject>();
|
||||
|
||||
for (int i = 0; i <= 30; ++i)
|
||||
hitObjects.Add(new HitCircle { StartTime = 1000 * i });
|
||||
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = hitObjects };
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Is.Empty);
|
||||
}
|
||||
}
|
||||
}
|
@ -69,6 +69,35 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
}));
|
||||
}
|
||||
|
||||
private int onlineScoreID = 1;
|
||||
|
||||
[TestCase(1, ScoreRank.X)]
|
||||
[TestCase(0.9999, ScoreRank.S)]
|
||||
[TestCase(0.975, ScoreRank.S)]
|
||||
[TestCase(0.925, ScoreRank.A)]
|
||||
[TestCase(0.85, ScoreRank.B)]
|
||||
[TestCase(0.75, ScoreRank.C)]
|
||||
[TestCase(0.5, ScoreRank.D)]
|
||||
[TestCase(0.2, ScoreRank.D)]
|
||||
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
|
||||
{
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
loadResultsScreen(() =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
score.OnlineID = onlineScoreID++;
|
||||
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
|
||||
score.Accuracy = accuracy;
|
||||
score.Rank = rank;
|
||||
|
||||
return screen = createResultsScreen(score);
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResultsWithoutPlayer()
|
||||
{
|
||||
@ -82,34 +111,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
stack.Push(screen = createResultsScreen());
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
stack.Push(screen = createResultsScreen(score));
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||
}
|
||||
|
||||
[TestCase(0.2, ScoreRank.D)]
|
||||
[TestCase(0.5, ScoreRank.D)]
|
||||
[TestCase(0.75, ScoreRank.C)]
|
||||
[TestCase(0.85, ScoreRank.B)]
|
||||
[TestCase(0.925, ScoreRank.A)]
|
||||
[TestCase(0.975, ScoreRank.S)]
|
||||
[TestCase(0.9999, ScoreRank.S)]
|
||||
[TestCase(1, ScoreRank.X)]
|
||||
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
|
||||
{
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
score.Accuracy = accuracy;
|
||||
score.Rank = rank;
|
||||
|
||||
loadResultsScreen(() => screen = createResultsScreen(score));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResultsForUnranked()
|
||||
{
|
||||
@ -328,13 +337,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestResultsScreen : ResultsScreen
|
||||
private partial class TestResultsScreen : SoloResultsScreen
|
||||
{
|
||||
public HotkeyRetryOverlay RetryOverlay;
|
||||
|
||||
public TestResultsScreen(ScoreInfo score)
|
||||
: base(score, true)
|
||||
{
|
||||
ShowUserStatistics = true;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -23,6 +24,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -30,19 +32,20 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
public partial class TestSceneStatisticsPanel : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestScoreWithTimeStatistics()
|
||||
public void TestScoreWithPositionStatistics()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents();
|
||||
score.OnlineID = 1234;
|
||||
score.HitEvents = CreatePositionDistributedHitEvents();
|
||||
|
||||
loadPanel(score);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithPositionStatistics()
|
||||
public void TestScoreWithTimeStatistics()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.HitEvents = createPositionDistributedHitEvents();
|
||||
score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents();
|
||||
|
||||
loadPanel(score);
|
||||
}
|
||||
@ -79,28 +82,67 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||
{
|
||||
Child = new StatisticsPanel
|
||||
Child = new SoloStatisticsPanel(score)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score }
|
||||
Score = { Value = score },
|
||||
StatisticsUpdate =
|
||||
{
|
||||
Value = new SoloStatisticsUpdate(score, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
Current = 5,
|
||||
Progress = 20,
|
||||
},
|
||||
GlobalRank = 38000,
|
||||
CountryRank = 12006,
|
||||
PP = 2134,
|
||||
RankedScore = 21123849,
|
||||
Accuracy = 0.985,
|
||||
PlayCount = 13375,
|
||||
PlayTime = 354490,
|
||||
TotalScore = 128749597,
|
||||
TotalHits = 0,
|
||||
MaxCombo = 1233,
|
||||
}, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
Current = 5,
|
||||
Progress = 30,
|
||||
},
|
||||
GlobalRank = 36000,
|
||||
CountryRank = 12000,
|
||||
PP = (decimal)2134.5,
|
||||
RankedScore = 23897015,
|
||||
Accuracy = 0.984,
|
||||
PlayCount = 13376,
|
||||
PlayTime = 35789,
|
||||
TotalScore = 132218497,
|
||||
TotalHits = 0,
|
||||
MaxCombo = 1233,
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private static List<HitEvent> createPositionDistributedHitEvents()
|
||||
public static List<HitEvent> CreatePositionDistributedHitEvents()
|
||||
{
|
||||
var hitEvents = new List<HitEvent>();
|
||||
var hitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents();
|
||||
|
||||
// Use constant seed for reproducibility
|
||||
var random = new Random(0);
|
||||
|
||||
for (int i = 0; i < 500; i++)
|
||||
for (int i = 0; i < hitEvents.Count; i++)
|
||||
{
|
||||
double angle = random.NextDouble() * 2 * Math.PI;
|
||||
double radius = random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS;
|
||||
|
||||
var position = new Vector2((float)(radius * Math.Cos(angle)), (float)(radius * Math.Sin(angle)));
|
||||
|
||||
hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position));
|
||||
hitEvents[i] = hitEvents[i].With(position);
|
||||
}
|
||||
|
||||
return hitEvents;
|
||||
|
@ -9,8 +9,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
@ -304,11 +304,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("preset is not changed", () => panel.Preset.Value.Name == presetName);
|
||||
AddUntilStep("popover is unchanged", () => this.ChildrenOfType<OsuPopover>().FirstOrDefault() == popover);
|
||||
AddStep("edit preset name", () => popover.ChildrenOfType<LabelledTextBox>().First().Current.Value = "something new");
|
||||
AddStep("attempt preset edit", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddStep("commit changes to textbox", () => InputManager.Key(Key.Enter));
|
||||
AddStep("attempt preset edit via select binding", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
|
||||
AddAssert("preset is changed", () => panel.Preset.Value.Name != presetName);
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("clear search", () => modSelectOverlay.SearchTerm = string.Empty);
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("mod select still visible", () => modSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -431,8 +431,9 @@ namespace osu.Game.Beatmaps
|
||||
beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified;
|
||||
beatmapInfo.ResetOnlineInfo();
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
Realm.Write(r =>
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
|
||||
|
||||
@ -458,8 +459,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
updateHashAndMarkDirty(setInfo);
|
||||
|
||||
Realm.Write(r =>
|
||||
{
|
||||
var liveBeatmapSet = r.Find<BeatmapSetInfo>(setInfo.ID)!;
|
||||
|
||||
setInfo.CopyChangesToRealm(liveBeatmapSet);
|
||||
@ -474,7 +473,6 @@ namespace osu.Game.Beatmaps
|
||||
// this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst.
|
||||
ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None);
|
||||
});
|
||||
}
|
||||
|
||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||
|
||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
Key1 = 1 << 26,
|
||||
Key3 = 1 << 27,
|
||||
Key2 = 1 << 28,
|
||||
ScoreV2 = 1 << 29,
|
||||
Mirror = 1 << 30,
|
||||
}
|
||||
}
|
||||
|
@ -15,17 +15,19 @@ using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Statistics;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -79,8 +81,9 @@ namespace osu.Game.Database
|
||||
/// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes.
|
||||
/// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations.
|
||||
/// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores.
|
||||
/// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files.
|
||||
/// </summary>
|
||||
private const int schema_version = 31;
|
||||
private const int schema_version = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@ -730,6 +733,8 @@ namespace osu.Game.Database
|
||||
Logger.Log($"Running realm migration to version {targetVersion}...");
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
var files = new RealmFileStore(this, storage);
|
||||
|
||||
stopwatch.Start();
|
||||
|
||||
switch (targetVersion)
|
||||
@ -904,36 +909,17 @@ namespace osu.Game.Database
|
||||
|
||||
case 28:
|
||||
{
|
||||
var files = new RealmFileStore(this, storage);
|
||||
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||
|
||||
foreach (var score in scores)
|
||||
{
|
||||
string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath();
|
||||
if (replayFilename == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = files.Store.GetStream(replayFilename))
|
||||
{
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
// Trimmed down logic from LegacyScoreDecoder to extract the version from replays.
|
||||
using (SerializationReader sr = new SerializationReader(stream))
|
||||
score.PopulateFromReplay(files, sr =>
|
||||
{
|
||||
sr.ReadByte(); // Ruleset.
|
||||
int version = sr.ReadInt32();
|
||||
if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
|
||||
score.IsLegacyScore = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
@ -986,6 +972,46 @@ namespace osu.Game.Database
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 32:
|
||||
{
|
||||
foreach (var score in migration.NewRealm.All<ScoreInfo>())
|
||||
{
|
||||
if (!score.IsLegacyScore || !score.Ruleset.IsLegacyRuleset())
|
||||
continue;
|
||||
|
||||
score.PopulateFromReplay(files, sr =>
|
||||
{
|
||||
sr.ReadByte(); // Ruleset.
|
||||
sr.ReadInt32(); // Version.
|
||||
sr.ReadString(); // Beatmap hash.
|
||||
sr.ReadString(); // Username.
|
||||
sr.ReadString(); // MD5Hash.
|
||||
sr.ReadUInt16(); // Count300.
|
||||
sr.ReadUInt16(); // Count100.
|
||||
sr.ReadUInt16(); // Count50.
|
||||
sr.ReadUInt16(); // CountGeki.
|
||||
sr.ReadUInt16(); // CountKatu.
|
||||
sr.ReadUInt16(); // CountMiss.
|
||||
|
||||
// we should have this in LegacyTotalScore already, but if we're reading through this anyways...
|
||||
int totalScore = sr.ReadInt32();
|
||||
|
||||
sr.ReadUInt16(); // Max combo.
|
||||
sr.ReadBoolean(); // Perfect.
|
||||
|
||||
var legacyMods = (LegacyMods)sr.ReadInt32();
|
||||
|
||||
if (!legacyMods.HasFlagFast(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2"))
|
||||
return;
|
||||
|
||||
score.APIMods = score.APIMods.Append(new APIMod(new ModScoreV2())).ToArray();
|
||||
score.LegacyTotalScore = score.TotalScore = totalScore;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
@ -5,10 +5,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
@ -205,6 +209,10 @@ namespace osu.Game.Database
|
||||
if (ruleset is not ILegacyRuleset legacyRuleset)
|
||||
return score.TotalScore;
|
||||
|
||||
var mods = score.Mods;
|
||||
if (mods.Any(mod => mod is ModScoreV2))
|
||||
return score.TotalScore;
|
||||
|
||||
var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
|
||||
|
||||
if (playableBeatmap.HitObjects.Count == 0)
|
||||
@ -212,7 +220,7 @@ namespace osu.Game.Database
|
||||
|
||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||
|
||||
sv1Simulator.Simulate(beatmap, playableBeatmap, score.Mods);
|
||||
sv1Simulator.Simulate(beatmap, playableBeatmap, mods);
|
||||
|
||||
return ConvertFromLegacyTotalScore(score, new DifficultyAttributes
|
||||
{
|
||||
@ -282,6 +290,38 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
|
||||
/// </summary>
|
||||
/// <param name="score">The score to run population from replay for.</param>
|
||||
/// <param name="files">A <see cref="RealmFileStore"/> instance to use for fetching replay.</param>
|
||||
/// <param name="populationFunc">
|
||||
/// Delegate describing the population to execute.
|
||||
/// The delegate's argument is a <see cref="SerializationReader"/> instance which permits to read data from the replay stream.
|
||||
/// </param>
|
||||
public static void PopulateFromReplay(this ScoreInfo score, RealmFileStore files, Action<SerializationReader> populationFunc)
|
||||
{
|
||||
string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath();
|
||||
if (replayFilename == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = files.Store.GetStream(replayFilename))
|
||||
{
|
||||
if (stream == null)
|
||||
return;
|
||||
|
||||
using (SerializationReader sr = new SerializationReader(stream))
|
||||
populationFunc.Invoke(sr);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database);
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeHit : HitObject
|
||||
{
|
||||
private readonly Judgement judgement;
|
||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
@ -8,10 +8,12 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -95,6 +97,18 @@ namespace osu.Game.Overlays.Mods
|
||||
}, true);
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
createButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
}
|
||||
|
||||
private void createPreset()
|
||||
{
|
||||
realm.Write(r => r.Add(new ModPreset
|
||||
|
@ -8,11 +8,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
@ -130,6 +132,25 @@ namespace osu.Game.Overlays.Mods
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
saveButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
}
|
||||
|
||||
private void useCurrentMods()
|
||||
{
|
||||
saveableMods = selectedMods.Value.ToHashSet();
|
||||
@ -150,13 +171,6 @@ namespace osu.Game.Overlays.Mods
|
||||
return !saveableMods.SetEquals(selectedMods.Value);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
||||
}
|
||||
|
||||
private void save()
|
||||
{
|
||||
preset.PerformWrite(s =>
|
||||
|
@ -48,7 +48,8 @@ namespace osu.Game.Overlays.Mods
|
||||
/// Contrary to <see cref="OsuGameBase.AvailableMods"/> and <see cref="globalAvailableMods"/>, the <see cref="Mod"/> instances
|
||||
/// inside the <see cref="ModState"/> objects are owned solely by this <see cref="ModSelectOverlay"/> instance.
|
||||
/// </remarks>
|
||||
public Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> AvailableMods { get; } = new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>(new Dictionary<ModType, IReadOnlyList<ModState>>());
|
||||
public Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> AvailableMods { get; } =
|
||||
new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>(new Dictionary<ModType, IReadOnlyList<ModState>>());
|
||||
|
||||
private Func<Mod, bool> isValidMod = _ => true;
|
||||
|
||||
@ -636,12 +637,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
case GlobalAction.Select:
|
||||
{
|
||||
// Pressing select should select first filtered mod or completely hide the overlay in one shot if search term is empty.
|
||||
// Pressing select should select first filtered mod if a search is in progress.
|
||||
if (string.IsNullOrEmpty(SearchTerm))
|
||||
{
|
||||
hideOverlay(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
ModState? firstMod = columnFlow.Columns.OfType<ModColumn>().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible);
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
/// <summary>
|
||||
@ -19,5 +17,16 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// Performance of a perfect play for comparison.
|
||||
/// </summary>
|
||||
public PerformanceAttributes PerfectPerformance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new performance breakdown.
|
||||
/// </summary>
|
||||
/// <param name="performance">Actual gameplay performance.</param>
|
||||
/// <param name="perfectPerformance">Performance of a perfect play for comparison.</param>
|
||||
public PerformanceBreakdown(PerformanceAttributes performance, PerformanceAttributes perfectPerformance)
|
||||
{
|
||||
Performance = performance;
|
||||
PerfectPerformance = perfectPerformance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
getPerfectPerformance(score, cancellationToken)
|
||||
).ConfigureAwait(false);
|
||||
|
||||
return new PerformanceBreakdown { Performance = performanceArray[0], PerfectPerformance = performanceArray[1] };
|
||||
return new PerformanceBreakdown(performanceArray[0] ?? new PerformanceAttributes(), performanceArray[1] ?? new PerformanceAttributes());
|
||||
}
|
||||
|
||||
[ItemCanBeNull]
|
||||
|
@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
new CheckUnsnappedObjects(),
|
||||
new CheckConcurrentObjects(),
|
||||
new CheckZeroLengthObjects(),
|
||||
new CheckDrainTime(),
|
||||
|
||||
// Timing
|
||||
new CheckPreviewTime(),
|
||||
|
38
osu.Game/Rulesets/Edit/Checks/CheckDrainTime.cs
Normal file
38
osu.Game/Rulesets/Edit/Checks/CheckDrainTime.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckDrainTime : ICheck
|
||||
{
|
||||
private const int min_drain_threshold = 30 * 1000;
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Compose, "Too short drain time");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateTooShort(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
double drainTime = context.Beatmap.CalculatePlayableLength() - context.Beatmap.TotalBreakTime;
|
||||
|
||||
if (drainTime < min_drain_threshold)
|
||||
yield return new IssueTemplateTooShort(this).Create((int)(drainTime / 1000));
|
||||
}
|
||||
|
||||
public class IssueTemplateTooShort : IssueTemplate
|
||||
{
|
||||
public IssueTemplateTooShort(ICheck check)
|
||||
: base(check, IssueType.Problem, "Less than 30 seconds of drain time, currently {0}.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(int drainTimeSeconds) => new Issue(this, drainTimeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) == true;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
|
20
osu.Game/Rulesets/Mods/ModScoreV2.cs
Normal file
20
osu.Game/Rulesets/Mods/ModScoreV2.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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 osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <remarks>
|
||||
/// This mod is used strictly to mark osu!stable scores set with the "Score V2" mod active.
|
||||
/// It should not be used in any real capacity going forward.
|
||||
/// </remarks>
|
||||
public class ModScoreV2 : Mod
|
||||
{
|
||||
public override string Name => "Score V2";
|
||||
public override string Acronym => @"SV2";
|
||||
public override ModType Type => ModType.System;
|
||||
public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
@ -192,6 +192,10 @@ namespace osu.Game.Rulesets
|
||||
case ModAutoplay:
|
||||
value |= LegacyMods.Autoplay;
|
||||
break;
|
||||
|
||||
case ModScoreV2:
|
||||
value |= LegacyMods.ScoreV2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
@ -137,30 +138,17 @@ namespace osu.Game.Rulesets.Scoring
|
||||
JudgedHits += count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> which was judged.</param>
|
||||
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
|
||||
protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
|
||||
|
||||
/// <summary>
|
||||
/// Simulates an autoplay of the <see cref="IBeatmap"/> to determine scoring values.
|
||||
/// </summary>
|
||||
/// <remarks>This provided temporarily. DO NOT USE.</remarks>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to simulate.</param>
|
||||
protected virtual void SimulateAutoplay(IBeatmap beatmap)
|
||||
protected void SimulateAutoplay(IBeatmap beatmap)
|
||||
{
|
||||
IsSimulating = true;
|
||||
|
||||
foreach (var obj in beatmap.HitObjects)
|
||||
simulate(obj);
|
||||
|
||||
void simulate(HitObject obj)
|
||||
foreach (var obj in EnumerateHitObjects(beatmap))
|
||||
{
|
||||
foreach (var nested in obj.NestedHitObjects)
|
||||
simulate(nested);
|
||||
|
||||
var judgement = obj.CreateJudgement();
|
||||
|
||||
var result = CreateResult(obj, judgement);
|
||||
@ -174,6 +162,43 @@ namespace osu.Game.Rulesets.Scoring
|
||||
IsSimulating = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all <see cref="HitObject"/>s in the given <paramref name="beatmap"/> in the order in which they are to be judged.
|
||||
/// Used in <see cref="SimulateAutoplay"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In Score V2, the score awarded for each object includes a component based on the combo value after the judgement of that object.
|
||||
/// This means that the score is dependent on the order of evaluation of judgements.
|
||||
/// This method is provided so that rulesets can specify custom ordering that is correct for them and matches processing order during actual gameplay.
|
||||
/// </remarks>
|
||||
protected virtual IEnumerable<HitObject> EnumerateHitObjects(IBeatmap beatmap)
|
||||
=> enumerateRecursively(beatmap.HitObjects);
|
||||
|
||||
private IEnumerable<HitObject> enumerateRecursively(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
foreach (var hitObject in hitObjects)
|
||||
{
|
||||
foreach (var nested in enumerateRecursively(hitObject.NestedHitObjects))
|
||||
yield return nested;
|
||||
|
||||
yield return hitObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> which was judged.</param>
|
||||
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
|
||||
protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a simulated <see cref="HitResult"/> for a judgement. Used during <see cref="SimulateAutoplay"/> to simulate a "perfect" play.
|
||||
/// </summary>
|
||||
/// <param name="judgement">The judgement to simulate a <see cref="HitResult"/> for.</param>
|
||||
/// <returns>The simulated <see cref="HitResult"/> for the judgement.</returns>
|
||||
protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -184,12 +209,5 @@ namespace osu.Game.Rulesets.Scoring
|
||||
// Last applied result is guaranteed to be non-null when JudgedHits > 0.
|
||||
|| lastAppliedResult.AsNonNull().TimeAbsolute < Clock.CurrentTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a simulated <see cref="HitResult"/> for a judgement. Used during <see cref="SimulateAutoplay"/> to simulate a "perfect" play.
|
||||
/// </summary>
|
||||
/// <param name="judgement">The judgement to simulate a <see cref="HitResult"/> for.</param>
|
||||
/// <returns>The simulated <see cref="HitResult"/> for the judgement.</returns>
|
||||
protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult;
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -113,17 +115,19 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
}
|
||||
}
|
||||
|
||||
if (barDrawables != null)
|
||||
if (barDrawables == null)
|
||||
createBarDrawables();
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < barDrawables.Length; i++)
|
||||
{
|
||||
barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
private void createBarDrawables()
|
||||
{
|
||||
int maxCount = bins.Max(b => b.Values.Sum());
|
||||
barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray();
|
||||
barDrawables = bins.Select((_, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray();
|
||||
|
||||
Container axisFlow;
|
||||
|
||||
@ -202,7 +206,6 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class Bar : CompositeDrawable
|
||||
{
|
||||
@ -211,13 +214,16 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
private readonly bool isCentre;
|
||||
private readonly float totalValue;
|
||||
|
||||
private float basalHeight;
|
||||
private const float minimum_height = 0.02f;
|
||||
|
||||
private float offsetAdjustment;
|
||||
|
||||
private Circle[] boxOriginals = null!;
|
||||
|
||||
private Circle? boxAdjustment;
|
||||
|
||||
private float? lastDrawHeight;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@ -256,15 +262,17 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
else
|
||||
{
|
||||
// A bin with no value draws a grey dot instead.
|
||||
Circle dot = new Circle
|
||||
InternalChildren = boxOriginals = new[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Colour = isCentre ? Color4.White : Color4.Gray,
|
||||
Height = 0,
|
||||
}
|
||||
};
|
||||
InternalChildren = boxOriginals = new[] { dot };
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,31 +280,18 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (!values.Any())
|
||||
return;
|
||||
|
||||
updateBasalHeight();
|
||||
|
||||
foreach (var boxOriginal in boxOriginals)
|
||||
{
|
||||
boxOriginal.Y = 0;
|
||||
boxOriginal.Height = basalHeight;
|
||||
Scheduler.AddOnce(updateMetrics, true);
|
||||
}
|
||||
|
||||
float offsetValue = 0;
|
||||
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint);
|
||||
boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint);
|
||||
offsetValue -= values[i].Value;
|
||||
}
|
||||
if (invalidation.HasFlagFast(Invalidation.DrawSize))
|
||||
{
|
||||
if (lastDrawHeight != null && lastDrawHeight != DrawHeight)
|
||||
Scheduler.AddOnce(updateMetrics, false);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
updateBasalHeight();
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
}
|
||||
|
||||
public void UpdateOffset(float adjustment)
|
||||
@ -321,45 +316,32 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
}
|
||||
|
||||
offsetAdjustment = adjustment;
|
||||
drawAdjustmentBar();
|
||||
|
||||
Scheduler.AddOnce(updateMetrics, true);
|
||||
}
|
||||
|
||||
private void updateBasalHeight()
|
||||
{
|
||||
float newBasalHeight = DrawHeight > DrawWidth ? DrawWidth / DrawHeight : 1;
|
||||
|
||||
if (newBasalHeight == basalHeight)
|
||||
return;
|
||||
|
||||
basalHeight = newBasalHeight;
|
||||
foreach (var dot in boxOriginals)
|
||||
dot.Height = basalHeight;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
private float offsetForValue(float value) => (1 - basalHeight) * value / maxValue;
|
||||
|
||||
private float heightForValue(float value) => MathF.Max(basalHeight + offsetForValue(value), 0);
|
||||
|
||||
private void draw()
|
||||
{
|
||||
resizeBars();
|
||||
|
||||
if (boxAdjustment != null)
|
||||
drawAdjustmentBar();
|
||||
}
|
||||
|
||||
private void resizeBars()
|
||||
private void updateMetrics(bool animate = true)
|
||||
{
|
||||
float offsetValue = 0;
|
||||
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
for (int i = 0; i < boxOriginals.Length; i++)
|
||||
{
|
||||
boxOriginals[i].Y = offsetForValue(offsetValue) * DrawHeight;
|
||||
boxOriginals[i].Height = heightForValue(values[i].Value);
|
||||
offsetValue -= values[i].Value;
|
||||
int value = i < values.Count ? values[i].Value : 0;
|
||||
|
||||
var box = boxOriginals[i];
|
||||
|
||||
box.MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint);
|
||||
box.ResizeHeightTo(heightForValue(value), duration, Easing.OutQuint);
|
||||
offsetValue -= value;
|
||||
}
|
||||
|
||||
if (boxAdjustment != null)
|
||||
drawAdjustmentBar();
|
||||
|
||||
if (!animate)
|
||||
FinishTransforms(true);
|
||||
|
||||
lastDrawHeight = DrawHeight;
|
||||
}
|
||||
|
||||
private void drawAdjustmentBar()
|
||||
@ -369,6 +351,10 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment), duration, Easing.OutQuint);
|
||||
boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue;
|
||||
|
||||
private float heightForValue(float value) => minimum_height + offsetForValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.809.0">
|
||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2023.712.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
Loading…
Reference in New Issue
Block a user