1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 11:27:24 +08:00

Merge pull request #9086 from smoogipoo/results-screen-condensed-panel

Implement contracted score panel and integrate into results screen
This commit is contained in:
Dean Herbert 2020-05-30 23:46:02 +09:00 committed by GitHub
commit e64e44ecf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 978 additions and 192 deletions

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
internal class OsuScoreProcessor : ScoreProcessor
public class OsuScoreProcessor : ScoreProcessor
{
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);

View File

@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
createButton(false);
createButtonNoScore();
}
private void createButton(bool withReplay)
@ -40,6 +41,22 @@ namespace osu.Game.Tests.Visual.Gameplay
Origin = Anchor.Centre,
};
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
}
private void createButtonNoScore()
{
AddStep("create button with null score", () =>
{
Child = downloadButton = new TestReplayDownloadButton(null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
}
private ScoreInfo getScoreInfo(bool replayAvailable)

View File

@ -0,0 +1,61 @@
// 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.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Contracted;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneContractedPanelMiddleContent : OsuTestScene
{
[Resolved]
private RulesetStore rulesetStore { get; set; }
[Test]
public void TestShowPanel()
{
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
}
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
{
Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score);
}
private class ContractedPanelMiddleContentContainer : Container
{
[Cached]
private Bindable<WorkingBeatmap> workingBeatmap { get; set; }
public ContractedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
{
workingBeatmap = new Bindable<WorkingBeatmap>(beatmap);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 460);
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#353535"),
},
new ContractedPanelMiddleContent(score),
};
}
}
}
}

View File

@ -1,7 +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.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -14,10 +13,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Expanded;
@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
var author = new User { Username = "mapper_name" };
AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore()));
AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Text == "mapper_name"));
}
@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestMapWithUnknownMapper()
{
AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore()));
AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
AddAssert("mapped by text not present", () =>
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
@ -66,29 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking
return new TestWorkingBeatmap(beatmap);
}
private ScoreInfo createTestScore() => new ScoreInfo
{
User = new User
{
Id = 2,
Username = "peppy",
},
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
TotalScore = 999999,
Accuracy = 0.95,
MaxCombo = 999,
Rank = ScoreRank.S,
Date = DateTimeOffset.Now,
Statistics =
{
{ HitResult.Miss, 1 },
{ HitResult.Meh, 50 },
{ HitResult.Good, 100 },
{ HitResult.Great, 300 },
}
};
private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
private class ExpandedPanelMiddleContentContainer : Container

View File

@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#444"),
},
new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }),
new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User),
}
};
}

View File

@ -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.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -11,13 +9,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Ranking
{
@ -41,26 +36,7 @@ namespace osu.Game.Tests.Visual.Ranking
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
}
private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo
{
TotalScore = 2845370,
Accuracy = 0.98,
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<HitResult, int>
{
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
User = new User
{
Username = "peppy",
}
});
private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
[Test]
public void ResultsWithoutPlayer()

View File

@ -1,28 +1,23 @@
// 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;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneScorePanel : OsuTestScene
{
private ScorePanel panel;
[Test]
public void TestDRank()
{
var score = createScore();
score.Accuracy = 0.5;
score.Rank = ScoreRank.D;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D };
addPanelStep(score);
}
@ -30,9 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestCRank()
{
var score = createScore();
score.Accuracy = 0.75;
score.Rank = ScoreRank.C;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C };
addPanelStep(score);
}
@ -40,9 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestBRank()
{
var score = createScore();
score.Accuracy = 0.85;
score.Rank = ScoreRank.B;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B };
addPanelStep(score);
}
@ -50,9 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestARank()
{
var score = createScore();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
addPanelStep(score);
}
@ -60,9 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestSRank()
{
var score = createScore();
score.Accuracy = 0.975;
score.Rank = ScoreRank.S;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S };
addPanelStep(score);
}
@ -70,9 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestAlmostSSRank()
{
var score = createScore();
score.Accuracy = 0.9999;
score.Rank = ScoreRank.S;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S };
addPanelStep(score);
}
@ -80,9 +65,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestSSRank()
{
var score = createScore();
score.Accuracy = 1;
score.Rank = ScoreRank.X;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X };
addPanelStep(score);
}
@ -90,44 +73,42 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestAllHitResults()
{
var score = createScore();
score.Statistics[HitResult.Perfect] = 350;
score.Statistics[HitResult.Ok] = 200;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } };
addPanelStep(score);
}
private void addPanelStep(ScoreInfo score) => AddStep("add panel", () =>
[Test]
public void TestContractedPanel()
{
Child = new ScorePanel(score)
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
addPanelStep(score, PanelState.Contracted);
}
[Test]
public void TestExpandAndContract()
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
addPanelStep(score, PanelState.Contracted);
AddWaitStep("wait for transition", 10);
AddStep("expand panel", () => panel.State = PanelState.Expanded);
AddWaitStep("wait for transition", 10);
AddStep("contract panel", () => panel.State = PanelState.Contracted);
AddWaitStep("wait for transition", 10);
}
private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () =>
{
Child = panel = new ScorePanel(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = PanelState.Expanded
State = state
};
});
private ScoreInfo createScore() => new ScoreInfo
{
User = new User
{
Id = 2,
Username = "peppy",
},
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
TotalScore = 2845370,
Accuracy = 0.95,
MaxCombo = 999,
Rank = ScoreRank.S,
Date = DateTimeOffset.Now,
Statistics =
{
{ HitResult.Miss, 1 },
{ HitResult.Meh, 50 },
{ HitResult.Good, 100 },
{ HitResult.Great, 300 },
}
};
}
}

View File

@ -0,0 +1,208 @@
// 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;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneScorePanelList : OsuManualInputManagerTestScene
{
private ScorePanelList list;
[Test]
public void TestEmptyList()
{
createListStep(() => new ScorePanelList());
}
[Test]
public void TestEmptyListWithSelectedScore()
{
createListStep(() => new ScorePanelList
{
SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) }
});
}
[Test]
public void TestAddPanelAfterSelectingScore()
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList
{
SelectedScore = { Value = score }
});
AddStep("add panel", () => list.AddScore(score));
assertScoreState(score, true);
assertExpandedPanelCentred();
}
[Test]
public void TestAddPanelBeforeSelectingScore()
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList());
AddStep("add panel", () => list.AddScore(score));
assertScoreState(score, false);
assertFirstPanelCentred();
AddStep("select score", () => list.SelectedScore.Value = score);
assertScoreState(score, true);
assertExpandedPanelCentred();
}
[Test]
public void TestAddManyNonExpandedPanels()
{
createListStep(() => new ScorePanelList());
AddStep("add many scores", () =>
{
for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo));
});
assertFirstPanelCentred();
}
[Test]
public void TestAddManyScoresAfterExpandedPanel()
{
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList());
AddStep("add initial panel and select", () =>
{
list.AddScore(initialScore);
list.SelectedScore.Value = initialScore;
});
AddStep("add many scores", () =>
{
for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
});
assertScoreState(initialScore, true);
assertExpandedPanelCentred();
}
[Test]
public void TestAddManyScoresBeforeExpandedPanel()
{
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList());
AddStep("add initial panel and select", () =>
{
list.AddScore(initialScore);
list.SelectedScore.Value = initialScore;
});
AddStep("add scores", () =>
{
for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
});
assertScoreState(initialScore, true);
assertExpandedPanelCentred();
}
[Test]
public void TestAddManyPanelsOnBothSidesOfExpandedPanel()
{
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList());
AddStep("add initial panel and select", () =>
{
list.AddScore(initialScore);
list.SelectedScore.Value = initialScore;
});
AddStep("add scores after", () =>
{
for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
});
assertScoreState(initialScore, true);
assertExpandedPanelCentred();
}
[Test]
public void TestSelectMultipleScores()
{
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
createListStep(() => new ScorePanelList());
AddStep("add scores and select first", () =>
{
list.AddScore(firstScore);
list.AddScore(secondScore);
list.SelectedScore.Value = firstScore;
});
assertScoreState(firstScore, true);
assertScoreState(secondScore, false);
AddStep("select second score", () =>
{
InputManager.MoveMouseTo(list.ChildrenOfType<ScorePanel>().Single(p => p.Score == secondScore));
InputManager.Click(MouseButton.Left);
});
assertScoreState(firstScore, false);
assertScoreState(secondScore, true);
assertExpandedPanelCentred();
}
private void createListStep(Func<ScorePanelList> creationFunc)
{
AddStep("create list", () => Child = list = creationFunc().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
}));
AddUntilStep("wait for load", () => list.IsLoaded);
}
private void assertExpandedPanelCentred() => AddUntilStep("expanded panel centred", () =>
{
var expandedPanel = list.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1);
});
private void assertFirstPanelCentred()
=> AddUntilStep("first panel centred", () => Precision.AlmostEquals(list.ChildrenOfType<ScorePanel>().First().ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1));
private void assertScoreState(ScoreInfo score, bool expanded)
=> AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType<ScorePanel>().Single(p => p.Score == score).State == PanelState.Expanded) == expanded);
}
}

View File

@ -479,7 +479,7 @@ namespace osu.Game.Screens.Play
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score);
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score);
#region Fail Logic

View File

@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play
this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
}
protected override ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score, false);
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
protected override ScoreInfo CreateScore() => score.ScoreInfo;
}

View File

@ -0,0 +1,222 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Contracted
{
/// <summary>
/// The content that appears in the middle of a contracted <see cref="ScorePanel"/>.
/// </summary>
public class ContractedPanelMiddleContent : CompositeDrawable
{
private readonly ScoreInfo score;
/// <summary>
/// Creates a new <see cref="ContractedPanelMiddleContent"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to display.</param>
public ContractedPanelMiddleContent(ScoreInfo score)
{
this.score = score;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerExponent = 2.5f,
CornerRadius = 20,
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.25f),
Type = EdgeEffectType.Shadow,
Radius = 1,
Offset = new Vector2(0, 4)
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("444")
},
new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
User = score.User,
Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0))
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new UpdateableAvatar(score.User)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(110),
Masking = true,
CornerExponent = 2.5f,
CornerRadius = 20,
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.25f),
Type = EdgeEffectType.Shadow,
Radius = 8,
Offset = new Vector2(0, 4),
}
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = score.UserString,
Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold)
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString()))
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 10 },
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new[]
{
createStatistic("Max Combo", $"x{score.MaxCombo}"),
createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"),
}
},
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
ExpansionMode = ExpansionMode.AlwaysExpanded,
DisplayUnrankedText = false,
Current = { Value = score.Mods },
Scale = new Vector2(0.5f),
}
}
}
}
},
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = 5 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = score.TotalScore.ToString("N0"),
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
Spacing = new Vector2(-1, 0)
},
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 2 },
Child = new DrawableRank(score.Rank)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
},
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
}
}
},
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 45),
}
};
}
private Drawable createStatistic(string key, string value) => new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Text = key,
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)
},
new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Text = value,
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
Colour = Color4Extensions.FromHex("#FFDD55")
}
}
};
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@ -13,6 +14,8 @@ namespace osu.Game.Screens.Ranking
{
public class ReplayDownloadButton : DownloadTrackingComposite<ScoreInfo, ScoreManager>
{
public Bindable<ScoreInfo> Score => Model;
private DownloadButton button;
private ShakeContainer shakeContainer;
@ -23,7 +26,7 @@ namespace osu.Game.Screens.Ranking
if (State.Value == DownloadState.LocallyAvailable)
return ReplayAvailability.Local;
if (!string.IsNullOrEmpty(Model.Value.Hash))
if (!string.IsNullOrEmpty(Model.Value?.Hash))
return ReplayAvailability.Online;
return ReplayAvailability.NotAvailable;

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -10,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Scoring;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
@ -17,7 +20,7 @@ using osuTK;
namespace osu.Game.Screens.Ranking
{
public class ResultsScreen : OsuScreen
public abstract class ResultsScreen : OsuScreen
{
protected const float BACKGROUND_BLUR = 20;
@ -28,19 +31,26 @@ namespace osu.Game.Screens.Ranking
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
public readonly ScoreInfo Score;
private readonly bool allowRetry;
[Resolved(CanBeNull = true)]
private Player player { get; set; }
public readonly ScoreInfo Score;
private readonly bool allowRetry;
[Resolved]
private IAPIProvider api { get; set; }
private Drawable bottomPanel;
private ScorePanelList panels;
public ResultsScreen(ScoreInfo score, bool allowRetry = true)
protected ResultsScreen(ScoreInfo score, bool allowRetry = true)
{
Score = score;
this.allowRetry = allowRetry;
SelectedScore.Value = score;
}
[BackgroundDependencyLoader]
@ -48,47 +58,68 @@ namespace osu.Game.Screens.Ranking
{
FillFlowContainer buttons;
InternalChildren = new[]
InternalChild = new GridContainer
{
new ResultsScrollContainer
RelativeSizeAxes = Axes.Both,
Content = new[]
{
Child = new ScorePanel(Score)
new Drawable[]
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = PanelState.Expanded
new ResultsScrollContainer
{
Child = panels = new ScorePanelList
{
RelativeSizeAxes = Axes.Both,
SelectedScore = { BindTarget = SelectedScore }
}
}
},
},
bottomPanel = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = TwoLayerButton.SIZE_EXTENDED.Y,
Alpha = 0,
Children = new Drawable[]
new[]
{
new Box
bottomPanel = new Container
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333")
},
buttons = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5),
Direction = FillDirection.Horizontal,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = TwoLayerButton.SIZE_EXTENDED.Y,
Alpha = 0,
Children = new Drawable[]
{
new ReplayDownloadButton(Score) { Width = 300 },
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333")
},
buttons = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ReplayDownloadButton(null)
{
Score = { BindTarget = SelectedScore },
Width = 300
},
}
}
}
}
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
}
};
if (Score != null)
panels.AddScore(Score);
if (player != null && allowRetry)
{
buttons.Add(new RetryButton { Width = 300 });
@ -105,6 +136,27 @@ namespace osu.Game.Screens.Ranking
}
}
protected override void LoadComplete()
{
base.LoadComplete();
var req = FetchScores(scores => Schedule(() =>
{
foreach (var s in scores)
panels.AddScore(s);
}));
if (req != null)
api.Queue(req);
}
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected virtual APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback) => null;
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
@ -142,7 +194,7 @@ namespace osu.Game.Screens.Ranking
protected override void Update()
{
base.Update();
content.Height = Math.Max(768, DrawHeight);
content.Height = Math.Max(768 - TwoLayerButton.SIZE_EXTENDED.Y, DrawHeight);
}
}
}

View File

@ -9,7 +9,9 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Contracted;
using osu.Game.Screens.Ranking.Expanded;
using osuTK;
using osuTK.Graphics;
@ -21,12 +23,12 @@ namespace osu.Game.Screens.Ranking
/// <summary>
/// Width of the panel when contracted.
/// </summary>
private const float contracted_width = 160;
public const float CONTRACTED_WIDTH = 130;
/// <summary>
/// Height of the panel when contracted.
/// </summary>
private const float contracted_height = 320;
private const float contracted_height = 355;
/// <summary>
/// Width of the panel when expanded.
@ -46,7 +48,7 @@ namespace osu.Game.Screens.Ranking
/// <summary>
/// Height of the top layer when the panel is contracted.
/// </summary>
private const float contracted_top_layer_height = 40;
private const float contracted_top_layer_height = 30;
/// <summary>
/// Duration for the panel to resize into its expanded/contracted size.
@ -71,11 +73,12 @@ namespace osu.Game.Screens.Ranking
private static readonly ColourInfo expanded_top_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#444"), Color4Extensions.FromHex("#333"));
private static readonly ColourInfo expanded_middle_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333"));
private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535");
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#444");
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535");
public event Action<PanelState> StateChanged;
public readonly ScoreInfo Score;
private readonly ScoreInfo score;
private Container content;
private Container topLayerContainer;
private Drawable topLayerBackground;
@ -89,47 +92,54 @@ namespace osu.Game.Screens.Ranking
public ScorePanel(ScoreInfo score)
{
this.score = score;
Score = score;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
InternalChild = content = new Container
{
topLayerContainer = new Container
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(40),
Children = new Drawable[]
{
Name = "Top layer",
RelativeSizeAxes = Axes.X,
Height = 120,
Children = new Drawable[]
topLayerContainer = new Container
{
new Container
Name = "Top layer",
RelativeSizeAxes = Axes.X,
Alpha = 0,
Height = 120,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both }
},
topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
}
},
middleLayerContainer = new Container
{
Name = "Middle layer",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both }
},
topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
}
},
middleLayerContainer = new Container
{
new Container
Name = "Middle layer",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }
},
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }
},
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
}
}
}
};
@ -174,34 +184,40 @@ namespace osu.Game.Screens.Ranking
private void updateState()
{
topLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint);
middleLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint);
topLayerContent?.FadeOut(content_fade_duration).Expire();
middleLayerContent?.FadeOut(content_fade_duration).Expire();
switch (state)
{
case PanelState.Expanded:
this.ResizeTo(new Vector2(EXPANDED_WIDTH, expanded_height), resize_duration, Easing.OutQuint);
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint);
middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint);
topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(score.User).With(d => d.Alpha = 0));
middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(score).With(d => d.Alpha = 0));
topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0));
middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0));
break;
case PanelState.Contracted:
this.ResizeTo(new Vector2(contracted_width, contracted_height), resize_duration, Easing.OutQuint);
Size = new Vector2(CONTRACTED_WIDTH, contracted_height);
topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint);
middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint);
middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0));
break;
}
using (BeginDelayedSequence(resize_duration + top_layer_expand_delay, true))
content.ResizeTo(Size, resize_duration, Easing.OutQuint);
bool topLayerExpanded = topLayerContainer.Y < 0;
// If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state.
using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true))
{
topLayerContainer.FadeIn();
switch (state)
{
case PanelState.Expanded:
@ -219,5 +235,13 @@ namespace osu.Game.Screens.Ranking
middleLayerContent?.FadeIn(content_fade_duration);
}
}
protected override bool OnClick(ClickEvent e)
{
if (State == PanelState.Contracted)
State = PanelState.Expanded;
return true;
}
}
}

View File

@ -0,0 +1,185 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking
{
public class ScorePanelList : CompositeDrawable
{
/// <summary>
/// Normal spacing between all panels.
/// </summary>
private const float panel_spacing = 5;
/// <summary>
/// Spacing around both sides of the expanded panel. This is added on top of <see cref="panel_spacing"/>.
/// </summary>
private const float expanded_panel_spacing = 15;
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
private readonly Flow flow;
private readonly Scroll scroll;
private ScorePanel expandedPanel;
public ScorePanelList()
{
RelativeSizeAxes = Axes.Both;
InternalChild = scroll = new Scroll
{
RelativeSizeAxes = Axes.Both,
Child = flow = new Flow
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(panel_spacing, 0),
AutoSizeAxes = Axes.Both,
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedScore.BindValueChanged(selectedScoreChanged, true);
}
/// <summary>
/// Adds a <see cref="ScoreInfo"/> to this list.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to add.</param>
public void AddScore(ScoreInfo score)
{
flow.Add(new ScorePanel(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}.With(p =>
{
p.StateChanged += s =>
{
if (s == PanelState.Expanded)
SelectedScore.Value = p.Score;
};
}));
if (SelectedScore.Value == score)
selectedScoreChanged(new ValueChangedEvent<ScoreInfo>(SelectedScore.Value, SelectedScore.Value));
else
{
// We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done.
// But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel.
if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score))
{
// A somewhat hacky property is used here because we need to:
// 1) Scroll after the scroll container's visible range is updated.
// 2) Scroll before the scroll container's scroll position is updated.
// Without this, we would have a 1-frame positioning error which looks very jarring.
scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
}
}
}
/// <summary>
/// Brings a <see cref="ScoreInfo"/> to the centre of the screen and expands it.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to present.</param>
private void selectedScoreChanged(ValueChangedEvent<ScoreInfo> score)
{
// Contract the old panel.
foreach (var p in flow.Where(p => p.Score == score.OldValue))
{
p.State = PanelState.Contracted;
p.Margin = new MarginPadding();
}
// Find the panel corresponding to the new score.
expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue);
if (expandedPanel == null)
return;
// Expand the new panel.
expandedPanel.State = PanelState.Expanded;
expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
// Scroll to the new panel. This is done manually since we need:
// 1) To scroll after the scroll container's visible range is updated.
// 2) To account for the centre anchor/origins of panels.
// In the end, it's easier to compute the scroll position manually.
float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing);
scroll.ScrollTo(scrollOffset);
}
protected override void Update()
{
base.Update();
float offset = DrawWidth / 2f;
// Add padding to both sides such that the centre of an expanded panel on either side is in the middle of the screen.
if (SelectedScore.Value != null)
{
// The expanded panel has extra padding applied to it, so it needs to be included into the offset.
offset -= ScorePanel.EXPANDED_WIDTH / 2f + expanded_panel_spacing;
}
else
offset -= ScorePanel.CONTRACTED_WIDTH / 2f;
flow.Padding = new MarginPadding { Horizontal = offset };
}
private class Flow : FillFlowContainer<ScorePanel>
{
public override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren);
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count();
private IEnumerable<ScorePanel> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanel>()
.OrderByDescending(s => s.Score.TotalScore)
.ThenBy(s => s.Score.OnlineScoreID);
}
private class Scroll : OsuScrollContainer
{
public new float Target => base.Target;
public Scroll()
: base(Direction.Horizontal)
{
}
/// <summary>
/// The target that will be scrolled to instantaneously next frame.
/// </summary>
public float? InstantScrollTarget;
protected override void UpdateAfterChildren()
{
if (InstantScrollTarget != null)
{
ScrollTo(InstantScrollTarget.Value, false);
InstantScrollTarget = null;
}
base.UpdateAfterChildren();
}
public override bool HandlePositionalInput => false;
public override bool HandleNonPositionalInput => false;
}
}
}

View File

@ -0,0 +1,32 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Scoring;
namespace osu.Game.Screens.Ranking
{
public class SoloResultsScreen : ResultsScreen
{
[Resolved]
private RulesetStore rulesets { get; set; }
public SoloResultsScreen(ScoreInfo score, bool allowRetry = true)
: base(score, allowRetry)
{
}
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
{
var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return req;
}
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select
}
protected void PresentScore(ScoreInfo score) =>
FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new ResultsScreen(score)));
FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score)));
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();

View File

@ -0,0 +1,52 @@
// 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;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
namespace osu.Game.Tests
{
public class TestScoreInfo : ScoreInfo
{
public TestScoreInfo(RulesetInfo ruleset)
{
User = new User
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
};
Beatmap = new TestBeatmap(ruleset).BeatmapInfo;
Ruleset = ruleset;
RulesetID = ruleset.ID ?? 0;
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
TotalScore = 2845370;
Accuracy = 0.95;
MaxCombo = 999;
Rank = ScoreRank.S;
Date = DateTimeOffset.Now;
Statistics[HitResult.Miss] = 1;
Statistics[HitResult.Meh] = 50;
Statistics[HitResult.Good] = 100;
Statistics[HitResult.Great] = 300;
}
private class TestModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1;
}
private class TestModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => 1;
}
}
}