mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-15 03:20:27 +00:00
同步更新,整理资源
This commit is contained in:
@@ -21,3 +21,7 @@ M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberiz
|
||||
M:osuTK.MathHelper.Clamp(System.Int32,System.Int32,System.Int32)~System.Int32;Use Math.Clamp() instead.
|
||||
M:osuTK.MathHelper.Clamp(System.Single,System.Single,System.Single)~System.Single;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
|
||||
M:osuTK.MathHelper.Clamp(System.Double,System.Double,System.Double)~System.Double;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
|
||||
M:TagLib.File.Create(System.String);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(TagLib.File.IFileAbstraction);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(System.String,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(TagLib.File.IFileAbstraction,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
|
||||
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@@ -36,6 +37,24 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
if (!ApproachRate.IsDefault) return format("AR", ApproachRate);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
// needs to be scaled down to remain playable.
|
||||
const float base_aspect_ratio = 1024f / 768f;
|
||||
float aspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y;
|
||||
scaleContainer.Scale = new Vector2(base_aspect_ratio / aspectRatio);
|
||||
scaleContainer.Scale = new Vector2(Math.Min(1, base_aspect_ratio / aspectRatio));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -297,34 +297,202 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 3.1f, -123d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_hard_rock_test_cases =
|
||||
{
|
||||
// OD = 5 test cases.
|
||||
// This leads to "effective" OD of 7.
|
||||
// PERFECT hit window is [-11ms, 11ms]
|
||||
// GREAT hit window is [-35ms, 35ms]
|
||||
// GOOD hit window is [-58ms, 58ms]
|
||||
// OK hit window is [-80ms, 80ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-97ms, ----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -10d, HitResult.Perfect },
|
||||
new object[] { 5f, -11d, HitResult.Perfect },
|
||||
new object[] { 5f, -12d, HitResult.Great },
|
||||
new object[] { 5f, -13d, HitResult.Great },
|
||||
new object[] { 5f, -34d, HitResult.Great },
|
||||
new object[] { 5f, -35d, HitResult.Great },
|
||||
new object[] { 5f, -36d, HitResult.Good },
|
||||
new object[] { 5f, -37d, HitResult.Good },
|
||||
new object[] { 5f, -57d, HitResult.Good },
|
||||
new object[] { 5f, -58d, HitResult.Good },
|
||||
new object[] { 5f, -59d, HitResult.Ok },
|
||||
new object[] { 5f, -60d, HitResult.Ok },
|
||||
new object[] { 5f, -79d, HitResult.Ok },
|
||||
new object[] { 5f, -80d, HitResult.Ok },
|
||||
new object[] { 5f, -81d, HitResult.Meh },
|
||||
new object[] { 5f, -82d, HitResult.Meh },
|
||||
new object[] { 5f, -96d, HitResult.Meh },
|
||||
new object[] { 5f, -97d, HitResult.Meh },
|
||||
new object[] { 5f, -98d, HitResult.Miss },
|
||||
new object[] { 5f, -99d, HitResult.Miss },
|
||||
new object[] { 5f, 79d, HitResult.Ok },
|
||||
new object[] { 5f, 80d, HitResult.Miss },
|
||||
new object[] { 5f, 81d, HitResult.Miss },
|
||||
new object[] { 5f, 82d, HitResult.Miss },
|
||||
new object[] { 5f, 96d, HitResult.Miss },
|
||||
new object[] { 5f, 97d, HitResult.Miss },
|
||||
new object[] { 5f, 98d, HitResult.Miss },
|
||||
new object[] { 5f, 99d, HitResult.Miss },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// This leads to "effective" OD of 13.02.
|
||||
// Note that contrary to other rulesets this does NOT cap out to OD 10!
|
||||
// PERFECT hit window is [-11ms, 11ms]
|
||||
// GREAT hit window is [-25ms, 25ms]
|
||||
// GOOD hit window is [-49ms, 49ms]
|
||||
// OK hit window is [-70ms, 70ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-87ms, ----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 9.3f, 10d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 11d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 12d, HitResult.Great },
|
||||
new object[] { 9.3f, 13d, HitResult.Great },
|
||||
new object[] { 9.3f, 24d, HitResult.Great },
|
||||
new object[] { 9.3f, 25d, HitResult.Great },
|
||||
new object[] { 9.3f, 26d, HitResult.Good },
|
||||
new object[] { 9.3f, 27d, HitResult.Good },
|
||||
new object[] { 9.3f, 48d, HitResult.Good },
|
||||
new object[] { 9.3f, 49d, HitResult.Good },
|
||||
new object[] { 9.3f, 50d, HitResult.Ok },
|
||||
new object[] { 9.3f, 51d, HitResult.Ok },
|
||||
new object[] { 9.3f, 69d, HitResult.Ok },
|
||||
new object[] { 9.3f, 70d, HitResult.Miss },
|
||||
new object[] { 9.3f, 71d, HitResult.Miss },
|
||||
new object[] { 9.3f, 72d, HitResult.Miss },
|
||||
new object[] { 9.3f, 86d, HitResult.Miss },
|
||||
new object[] { 9.3f, 87d, HitResult.Miss },
|
||||
new object[] { 9.3f, 88d, HitResult.Miss },
|
||||
new object[] { 9.3f, 89d, HitResult.Miss },
|
||||
new object[] { 9.3f, -69d, HitResult.Ok },
|
||||
new object[] { 9.3f, -70d, HitResult.Ok },
|
||||
new object[] { 9.3f, -71d, HitResult.Meh },
|
||||
new object[] { 9.3f, -72d, HitResult.Meh },
|
||||
new object[] { 9.3f, -86d, HitResult.Meh },
|
||||
new object[] { 9.3f, -87d, HitResult.Meh },
|
||||
new object[] { 9.3f, -88d, HitResult.Miss },
|
||||
new object[] { 9.3f, -89d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_easy_test_cases =
|
||||
{
|
||||
// Assume OD = 5 (other values are not tested, even OD 5 is enough to exercise the flooring logic).
|
||||
// PERFECT hit window is [ -22ms, 22ms]
|
||||
// GREAT hit window is [ -68ms, 68ms]
|
||||
// GOOD hit window is [-114ms, 114ms]
|
||||
// OK hit window is [-156ms, 156ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-190ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -21d, HitResult.Perfect },
|
||||
new object[] { 5f, -22d, HitResult.Perfect },
|
||||
new object[] { 5f, -23d, HitResult.Great },
|
||||
new object[] { 5f, -24d, HitResult.Great },
|
||||
new object[] { 5f, -67d, HitResult.Great },
|
||||
new object[] { 5f, -68d, HitResult.Great },
|
||||
new object[] { 5f, -69d, HitResult.Good },
|
||||
new object[] { 5f, -70d, HitResult.Good },
|
||||
new object[] { 5f, -113d, HitResult.Good },
|
||||
new object[] { 5f, -114d, HitResult.Good },
|
||||
new object[] { 5f, -115d, HitResult.Ok },
|
||||
new object[] { 5f, -116d, HitResult.Ok },
|
||||
new object[] { 5f, -155d, HitResult.Ok },
|
||||
new object[] { 5f, -156d, HitResult.Ok },
|
||||
new object[] { 5f, -157d, HitResult.Meh },
|
||||
new object[] { 5f, -158d, HitResult.Meh },
|
||||
new object[] { 5f, -189d, HitResult.Meh },
|
||||
new object[] { 5f, -190d, HitResult.Meh },
|
||||
new object[] { 5f, -191d, HitResult.Miss },
|
||||
new object[] { 5f, -192d, HitResult.Miss },
|
||||
new object[] { 5f, 155d, HitResult.Ok },
|
||||
new object[] { 5f, 156d, HitResult.Miss },
|
||||
new object[] { 5f, 157d, HitResult.Miss },
|
||||
new object[] { 5f, 158d, HitResult.Miss },
|
||||
new object[] { 5f, 189d, HitResult.Miss },
|
||||
new object[] { 5f, 190d, HitResult.Miss },
|
||||
new object[] { 5f, 191d, HitResult.Miss },
|
||||
new object[] { 5f, 192d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_double_time_test_cases =
|
||||
{
|
||||
// Assume OD = 5 (other values are not tested, even OD 5 is enough to exercise the flooring logic).
|
||||
// PERFECT hit window is [ -24ms, 24ms]
|
||||
// GREAT hit window is [ -73ms, 73ms]
|
||||
// GOOD hit window is [-123ms, 123ms]
|
||||
// OK hit window is [-168ms, 168ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-204ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -23d, HitResult.Perfect },
|
||||
new object[] { 5f, -24d, HitResult.Perfect },
|
||||
new object[] { 5f, -25d, HitResult.Great },
|
||||
new object[] { 5f, -26d, HitResult.Great },
|
||||
new object[] { 5f, -72d, HitResult.Great },
|
||||
new object[] { 5f, -73d, HitResult.Great },
|
||||
new object[] { 5f, -74d, HitResult.Good },
|
||||
new object[] { 5f, -75d, HitResult.Good },
|
||||
new object[] { 5f, -122d, HitResult.Good },
|
||||
new object[] { 5f, -123d, HitResult.Good },
|
||||
new object[] { 5f, -124d, HitResult.Ok },
|
||||
new object[] { 5f, -125d, HitResult.Ok },
|
||||
new object[] { 5f, -167d, HitResult.Ok },
|
||||
new object[] { 5f, -168d, HitResult.Ok },
|
||||
new object[] { 5f, -169d, HitResult.Meh },
|
||||
new object[] { 5f, -170d, HitResult.Meh },
|
||||
new object[] { 5f, -203d, HitResult.Meh },
|
||||
new object[] { 5f, -204d, HitResult.Meh },
|
||||
new object[] { 5f, -205d, HitResult.Miss },
|
||||
new object[] { 5f, -206d, HitResult.Miss },
|
||||
new object[] { 5f, 167d, HitResult.Ok },
|
||||
new object[] { 5f, 168d, HitResult.Miss },
|
||||
new object[] { 5f, 169d, HitResult.Miss },
|
||||
new object[] { 5f, 170d, HitResult.Miss },
|
||||
new object[] { 5f, 203d, HitResult.Miss },
|
||||
new object[] { 5f, 204d, HitResult.Miss },
|
||||
new object[] { 5f, 205d, HitResult.Miss },
|
||||
new object[] { 5f, 206d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_half_time_test_cases =
|
||||
{
|
||||
// Assume OD = 5 (other values are not tested, even OD 5 is enough to exercise the flooring logic).
|
||||
// PERFECT hit window is [ -12ms, 12ms]
|
||||
// GREAT hit window is [ -36ms, 36ms]
|
||||
// GOOD hit window is [ -61ms, 61ms]
|
||||
// OK hit window is [ -84ms, 84ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-102ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -11d, HitResult.Perfect },
|
||||
new object[] { 5f, -12d, HitResult.Perfect },
|
||||
new object[] { 5f, -13d, HitResult.Great },
|
||||
new object[] { 5f, -14d, HitResult.Great },
|
||||
new object[] { 5f, -35d, HitResult.Great },
|
||||
new object[] { 5f, -36d, HitResult.Great },
|
||||
new object[] { 5f, -37d, HitResult.Good },
|
||||
new object[] { 5f, -38d, HitResult.Good },
|
||||
new object[] { 5f, -60d, HitResult.Good },
|
||||
new object[] { 5f, -61d, HitResult.Good },
|
||||
new object[] { 5f, -62d, HitResult.Ok },
|
||||
new object[] { 5f, -63d, HitResult.Ok },
|
||||
new object[] { 5f, -83d, HitResult.Ok },
|
||||
new object[] { 5f, -84d, HitResult.Ok },
|
||||
new object[] { 5f, -85d, HitResult.Meh },
|
||||
new object[] { 5f, -86d, HitResult.Meh },
|
||||
new object[] { 5f, -101d, HitResult.Meh },
|
||||
new object[] { 5f, -102d, HitResult.Meh },
|
||||
new object[] { 5f, -103d, HitResult.Miss },
|
||||
new object[] { 5f, -104d, HitResult.Miss },
|
||||
new object[] { 5f, 83d, HitResult.Ok },
|
||||
new object[] { 5f, 84d, HitResult.Miss },
|
||||
new object[] { 5f, 85d, HitResult.Miss },
|
||||
new object[] { 5f, 86d, HitResult.Miss },
|
||||
new object[] { 5f, 101d, HitResult.Miss },
|
||||
new object[] { 5f, 102d, HitResult.Miss },
|
||||
new object[] { 5f, 103d, HitResult.Miss },
|
||||
new object[] { 5f, 104d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private const double note_time = 300;
|
||||
|
||||
[TestCaseSource(nameof(score_v2_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV2(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
@@ -352,31 +520,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
@@ -403,29 +547,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestCaseSource(nameof(score_v1_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new FakeCircle
|
||||
{
|
||||
StartTime = note_time,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new RulesetInfo { OnlineID = 0 }
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
var beatmap = createConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
@@ -450,6 +572,172 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ManiaModHardRock()],
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ManiaModEasy()],
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ManiaModDoubleTime()],
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createNonConvertBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ManiaModHalfTime()],
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1+HT single note @ OD{overallDifficulty}", beatmap, $@"SV1+HT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
private static ManiaBeatmap createNonConvertBeatmap(float overallDifficulty)
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private static Beatmap createConvertBeatmap(float overallDifficulty)
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new FakeCircle
|
||||
{
|
||||
StartTime = note_time,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new RulesetInfo { OnlineID = 0 }
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private class FakeCircle : HitObject, IHasPosition
|
||||
{
|
||||
public float X
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
},
|
||||
new SettingsEnumDropdown<ManiaScrollingStyle>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollingStyle,
|
||||
LabelText = "Scrolling style",
|
||||
Current = config.GetBindable<ManiaScrollingStyle>(ManiaRulesetSetting.ScrollStyle)
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
@@ -65,13 +65,13 @@ namespace osu.Game.Rulesets.Mania
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollBaseSpeedSlider>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollBaseSpeed,
|
||||
LabelText = "Scroll Base MS (when 200 Speed)",
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollBaseSpeed),
|
||||
KeyboardStep = 1,
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollTimePerSpeedSlider>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollTimePrecision,
|
||||
LabelText = "MS / Scroll Speed",
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTimePerSpeed),
|
||||
KeyboardStep = 1,
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
public partial class EzComComboCounter : ComboCounter
|
||||
{
|
||||
[SettingSource("Font", "Font", SettingControlType = typeof(OffsetNumberNameSelector))]
|
||||
public Bindable<string> NameDropdown { get; } = new Bindable<string>("EZ2DJ-4th");
|
||||
public Bindable<OffsetNumberName> NameDropdown { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.EZ2DJ_4th);
|
||||
|
||||
[SettingSource("Effect Type", "Effect Type")]
|
||||
public Bindable<EffectType> Effect { get; } = new Bindable<EffectType>(EffectType.Scale);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
public partial class EzComComboSprite : HitErrorMeter
|
||||
{
|
||||
[SettingSource("Combo Text Font", "Combo Text Font", SettingControlType = typeof(OffsetNumberNameSelector))]
|
||||
public Bindable<string> NameDropdown { get; } = new Bindable<string>("EZ2DJ-4th");
|
||||
public Bindable<OffsetNumberName> NameDropdown { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.EZ2DJ_4th);
|
||||
|
||||
[SettingSource("Effect Type", "Effect Type")]
|
||||
public Bindable<EffectType> Effect { get; } = new Bindable<EffectType>(EffectType.Scale);
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
Scale = new Vector2(1.5f),
|
||||
// Y = -20,
|
||||
Alpha = 0, // 初始隐藏
|
||||
Texture = textures.Get("Gameplay/AllCombo/ALL-COMBO2"), // 替换为你的贴图路径
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
public partial class EzComHitTiming : HitErrorMeter
|
||||
{
|
||||
[SettingSource("Offset Number Font", "Offset Number Font", SettingControlType = typeof(OffsetNumberNameSelector))]
|
||||
public Bindable<string> NumberNameDropdown { get; } = new Bindable<string>("Tomato");
|
||||
public Bindable<OffsetNumberName> NumberNameDropdown { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.Tomato);
|
||||
|
||||
[SettingSource("Offset Text Font", "Offset Text Font", SettingControlType = typeof(OffsetTextNameSelector))]
|
||||
public Bindable<string> TextNameDropdown { get; } = new Bindable<string>("Tomato");
|
||||
public Bindable<OffsetNumberName> TextNameDropdown { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.Tomato);
|
||||
|
||||
[SettingSource("AloneShow", "Show only Early or: Late separately")]
|
||||
public Bindable<AloneShowMenu> AloneShow { get; } = new Bindable<AloneShowMenu>(AloneShowMenu.None);
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
public BindableNumber<double> DisplayDuration { get; } = new BindableNumber<double>(300)
|
||||
{
|
||||
MinValue = 10,
|
||||
MaxValue = 1000, // 最大持续时间
|
||||
MaxValue = 10000, // 最大持续时间
|
||||
Precision = 1, // 精度
|
||||
};
|
||||
|
||||
@@ -101,6 +101,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.8f),
|
||||
// Spacing = new Vector2(SymmetryOffset.Value),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -133,6 +134,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.2f),
|
||||
Text = "±000",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using EffectType = osu.Game.Skinning.Components.EffectType;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
{
|
||||
@@ -38,6 +41,29 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
case GlobalSkinnableContainers.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var hitTiming = container.ChildrenOfType<EzComHitTiming>().ToArray();
|
||||
|
||||
if (hitTiming.Length >= 2)
|
||||
{
|
||||
var hitTiming1 = hitTiming[0];
|
||||
var hitTiming2 = hitTiming[1];
|
||||
const float mirror_x = 350;
|
||||
|
||||
hitTiming1.Anchor = Anchor.Centre;
|
||||
hitTiming1.Origin = Anchor.Centre;
|
||||
hitTiming1.DisplayDuration.Value = hitTiming1.DisplayDuration.MinValue;
|
||||
hitTiming1.X = -mirror_x;
|
||||
// hitTiming1.Scale = new Vector2(2);
|
||||
hitTiming1.AloneShow.Value = AloneShowMenu.Early;
|
||||
|
||||
hitTiming2.Anchor = Anchor.Centre;
|
||||
hitTiming2.Origin = Anchor.Centre;
|
||||
hitTiming2.DisplayDuration.Value = hitTiming2.DisplayDuration.MinValue;
|
||||
hitTiming2.X = mirror_x;
|
||||
// hitTiming2.Scale = new Vector2(2);
|
||||
hitTiming2.AloneShow.Value = AloneShowMenu.Late;
|
||||
}
|
||||
|
||||
var combo1 = container.OfType<EzComComboCounter>().FirstOrDefault();
|
||||
|
||||
if (combo1 != null)
|
||||
@@ -45,25 +71,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
combo1.Anchor = Anchor.TopCentre;
|
||||
combo1.Origin = Anchor.Centre;
|
||||
combo1.Y = 200;
|
||||
combo1.Alpha = 1f;
|
||||
combo1.Current.BindValueChanged(changedEvent =>
|
||||
{
|
||||
bool wasIncrease = changedEvent.NewValue > changedEvent.OldValue;
|
||||
bool wasMiss = changedEvent.OldValue > 1 && changedEvent.NewValue == 0;
|
||||
|
||||
if (wasIncrease)
|
||||
{
|
||||
combo1.Text
|
||||
.ScaleTo(new Vector2(1.5f, 1.5f), 10, Easing.OutQuint)
|
||||
.Then()
|
||||
.ScaleTo(Vector2.One, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
if (wasMiss)
|
||||
{
|
||||
combo1.Text.FlashColour(Color4.Red, 500, Easing.OutQuint);
|
||||
}
|
||||
});
|
||||
combo1.Effect.Value = EffectType.None;
|
||||
combo1.NameDropdown.Value = OffsetNumberName.EZ2AC_EVOLVE;
|
||||
}
|
||||
|
||||
var hitErrorMeter = container.OfType<BarHitErrorMeter>().FirstOrDefault();
|
||||
@@ -76,13 +85,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
hitErrorMeter.Position = new Vector2(0, -15);
|
||||
hitErrorMeter.Scale = new Vector2(1.4f, 1.4f);
|
||||
hitErrorMeter.JudgementLineThickness.Value = 2;
|
||||
hitErrorMeter.ShowMovingAverage.Value = true;
|
||||
hitErrorMeter.JudgementFadeOutDuration.Value = hitErrorMeter.JudgementFadeOutDuration.MinValue;
|
||||
hitErrorMeter.ShowMovingAverage.Value = false;
|
||||
hitErrorMeter.ColourBarVisibility.Value = false;
|
||||
hitErrorMeter.CentreMarkerStyle.Value = BarHitErrorMeter.CentreMarkerStyles.Circle;
|
||||
hitErrorMeter.CentreMarkerStyle.Value = BarHitErrorMeter.CentreMarkerStyles.Line;
|
||||
hitErrorMeter.LabelStyle.Value = BarHitErrorMeter.LabelStyles.None;
|
||||
}
|
||||
})
|
||||
{
|
||||
new EzComHitTiming(),
|
||||
new EzComHitTiming(),
|
||||
new EzComComboCounter(),
|
||||
new BarHitErrorMeter(),
|
||||
};
|
||||
@@ -90,11 +102,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
|
||||
return null;
|
||||
|
||||
case SkinComponentLookup<HitResult> resultComponent:
|
||||
case SkinComponentLookup<HitResult>:
|
||||
// if (Skin is SbISkin && resultComponent.Component >= HitResult.Great)
|
||||
// return Drawable.Empty();
|
||||
// return new EzComJudgementTexture(resultComponent.Component);
|
||||
return new SbIJudgementPiece(resultComponent.Component);
|
||||
// return new SbIJudgementPiece(resultComponent.Component);
|
||||
return Drawable.Empty();
|
||||
|
||||
case ManiaSkinComponentLookup maniaComponent:
|
||||
switch (maniaComponent.Component)
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@@ -20,14 +22,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
// private Box background = null!;
|
||||
private Color4 brightColour;
|
||||
private Color4 dimColour;
|
||||
|
||||
private Box backgroundOverlay = null!;
|
||||
// private Box background = null!;
|
||||
// private Box? separator;
|
||||
|
||||
[Resolved]
|
||||
private Column column { get; set; } = null!;
|
||||
|
||||
// private Bindable<Color4> accentColour = null!;
|
||||
// private readonly Bindable<float> overlayHeight = new Bindable<float>(0f);
|
||||
private readonly Bindable<float> overlayHeight = new Bindable<float>(0f);
|
||||
|
||||
public SbIColumnBackground()
|
||||
{
|
||||
@@ -46,19 +52,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
Name = "Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 1,
|
||||
},
|
||||
backgroundOverlay = new Box
|
||||
{
|
||||
Name = "Background Gradient Overlay",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Height = 0.1f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 1,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0,
|
||||
Colour = Color4.White,
|
||||
},
|
||||
};
|
||||
|
||||
// overlayHeight.BindValueChanged(height => backgroundOverlay.Height = height.NewValue, true);
|
||||
overlayHeight.BindValueChanged(height => backgroundOverlay.Height = height.NewValue, true);
|
||||
// accentColour.BindValueChanged(colour =>
|
||||
// {
|
||||
// var newColour = colour.NewValue.Darken(3);
|
||||
@@ -92,20 +99,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
// if (e.Action == column.Action.Value)
|
||||
// {
|
||||
// var noteColour = column.AccentColour.Value;
|
||||
// brightColour = noteColour.Opacity(1f);
|
||||
// dimColour = noteColour.Opacity(0);
|
||||
//
|
||||
// backgroundOverlay.Colour = direction.Value == ScrollingDirection.Up
|
||||
// ? ColourInfo.GradientVertical(brightColour, dimColour)
|
||||
// : ColourInfo.GradientVertical(dimColour, brightColour);
|
||||
//
|
||||
// overlayHeight.Value = 0.75f;
|
||||
//
|
||||
// backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
|
||||
// }
|
||||
if (e.Action == column.Action.Value)
|
||||
{
|
||||
var noteColour = column.AccentColour.Value;
|
||||
brightColour = noteColour.Opacity(1f);
|
||||
dimColour = noteColour.Opacity(0);
|
||||
|
||||
backgroundOverlay.Colour = direction.Value == ScrollingDirection.Up
|
||||
? ColourInfo.GradientVertical(brightColour, dimColour)
|
||||
: ColourInfo.GradientVertical(dimColour, brightColour);
|
||||
|
||||
overlayHeight.Value = 0.1f;
|
||||
|
||||
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,28 @@
|
||||
// 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.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
{
|
||||
public partial class SbIHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||
{
|
||||
// private readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
// private Drawable background = null!;
|
||||
|
||||
public SbIHoldBodyPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
Masking = true;
|
||||
CornerRadius = 0;
|
||||
}
|
||||
@@ -27,23 +31,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(DrawableHitObject? drawableObject)
|
||||
{
|
||||
// InternalChildren = new[]
|
||||
// {
|
||||
// background = new Box
|
||||
// {
|
||||
// RelativeSizeAxes = Axes.Both,
|
||||
// Colour = Colour4.White,
|
||||
// },
|
||||
// };
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.White,
|
||||
},
|
||||
};
|
||||
|
||||
// if (drawableObject != null)
|
||||
// {
|
||||
// var holdNote = (DrawableHoldNote)drawableObject;
|
||||
//
|
||||
// // AccentColour.BindTo(holdNote.AccentColour);
|
||||
// // hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||
// // ((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||
// }
|
||||
if (drawableObject != null)
|
||||
{
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
accentColour.BindTo(holdNote.AccentColour);
|
||||
// hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||
// ((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
// AccentColour.BindValueChanged(colour =>
|
||||
// {
|
||||
|
||||
@@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
ScrollSpeedStyle,
|
||||
|
||||
// [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))]
|
||||
[Description("ms值 通配速度风格")]
|
||||
[Description("ms值 恒定速度")]
|
||||
ScrollTimeStyle,
|
||||
|
||||
[Description("ms值 恒定反应时间")]
|
||||
[Description("ms值 恒定时间")]
|
||||
ScrollTimeStyleFixed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,4 @@
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Skinning\Ez2\Ez2HUD\EzComComboSprite.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -36,6 +37,24 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
};
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
if (!ApproachRate.IsDefault) return format("AR", ApproachRate);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Scoring;
|
||||
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
|
||||
private static readonly object[][] test_cases =
|
||||
private static readonly object[][] no_mod_test_cases =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
@@ -52,30 +53,58 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
new object[] { 7.8f, -64d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
private static readonly object[][] hard_rock_test_cases =
|
||||
{
|
||||
// OD = 5 test cases.
|
||||
// This leads to "effective" OD of 7.
|
||||
// GREAT hit window is (-29ms, 29ms)
|
||||
// OK hit window is (-68ms, 68ms)
|
||||
new object[] { 5f, -27d, HitResult.Great },
|
||||
new object[] { 5f, -28d, HitResult.Great },
|
||||
new object[] { 5f, -29d, HitResult.Ok },
|
||||
new object[] { 5f, -30d, HitResult.Ok },
|
||||
new object[] { 5f, -66d, HitResult.Ok },
|
||||
new object[] { 5f, -67d, HitResult.Ok },
|
||||
new object[] { 5f, -68d, HitResult.Miss },
|
||||
new object[] { 5f, -69d, HitResult.Miss },
|
||||
|
||||
// OD = 7.8 test cases.
|
||||
// This would lead to "effective" OD of 10.92,
|
||||
// but the effects are capped to OD 10.
|
||||
// GREAT hit window is (-20ms, 20ms)
|
||||
// OK hit window is (-50ms, 50ms)
|
||||
new object[] { 7.8f, -18d, HitResult.Great },
|
||||
new object[] { 7.8f, -19d, HitResult.Great },
|
||||
new object[] { 7.8f, -20d, HitResult.Ok },
|
||||
new object[] { 7.8f, -21d, HitResult.Ok },
|
||||
new object[] { 7.8f, -48d, HitResult.Ok },
|
||||
new object[] { 7.8f, -49d, HitResult.Ok },
|
||||
new object[] { 7.8f, -50d, HitResult.Miss },
|
||||
new object[] { 7.8f, -51d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] easy_test_cases =
|
||||
{
|
||||
// OD = 5 test cases.
|
||||
// This leads to "effective" OD of 2.5.
|
||||
// GREAT hit window is ( -42ms, 42ms)
|
||||
// OK hit window is (-100ms, 100ms)
|
||||
new object[] { 5f, -40d, HitResult.Great },
|
||||
new object[] { 5f, -41d, HitResult.Great },
|
||||
new object[] { 5f, -42d, HitResult.Ok },
|
||||
new object[] { 5f, -43d, HitResult.Ok },
|
||||
new object[] { 5f, -98d, HitResult.Ok },
|
||||
new object[] { 5f, -99d, HitResult.Ok },
|
||||
new object[] { 5f, -100d, HitResult.Miss },
|
||||
new object[] { 5f, -101d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private const double hit_time = 100;
|
||||
|
||||
[TestCaseSource(nameof(no_mod_test_cases))]
|
||||
public void TestHitWindowTreatment(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double hit_time = 100;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new TaikoBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = hit_time,
|
||||
Type = HitType.Centre,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
var beatmap = createBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
@@ -98,5 +127,85 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
RunTest($@"single hit @ OD{overallDifficulty}", beatmap, $@"{hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(hard_rock_test_cases))]
|
||||
public void TestHitWindowTreatmentWithHardRock(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time + hitOffset, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(hit_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new TaikoModHardRock()]
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"HR single hit @ OD{overallDifficulty}", beatmap, $@"HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(easy_test_cases))]
|
||||
public void TestHitWindowTreatmentWithEasy(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
var beatmap = createBeatmap(overallDifficulty);
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time + hitOffset, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(hit_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new TaikoModHardRock()]
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"EZ single hit @ OD{overallDifficulty}", beatmap, $@"EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
private static TaikoBeatmap createBeatmap(float overallDifficulty)
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new TaikoBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = hit_time,
|
||||
Type = HitType.Centre,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
@@ -20,6 +21,23 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
ReadCurrentFromDifficulty = _ => 1,
|
||||
};
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -77,33 +77,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerScore()
|
||||
{
|
||||
createLeaderboard();
|
||||
addLocalPlayer();
|
||||
|
||||
var player2Score = new BindableLong(1234567);
|
||||
var player3Score = new BindableLong(1111111);
|
||||
|
||||
AddStep("add player 2", () => createLeaderboardScore(player2Score, new APIUser { Username = "Player 2" }));
|
||||
AddStep("add player 3", () => createLeaderboardScore(player3Score, new APIUser { Username = "Player 3" }));
|
||||
|
||||
AddUntilStep("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1));
|
||||
AddUntilStep("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2));
|
||||
AddUntilStep("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
|
||||
|
||||
AddStep("set score above player 3", () => player2Score.Value = playerScore.Value - 500);
|
||||
AddUntilStep("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
|
||||
AddUntilStep("is player 2 position #2", () => leaderboard.CheckPositionByUsername("Player 2", 2));
|
||||
AddUntilStep("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
|
||||
|
||||
AddStep("set score below players", () => player2Score.Value = playerScore.Value - 123456);
|
||||
AddUntilStep("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
|
||||
AddUntilStep("is player 3 position #2", () => leaderboard.CheckPositionByUsername("Player 3", 2));
|
||||
AddUntilStep("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomScores()
|
||||
{
|
||||
@@ -183,30 +156,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
() => Does.Contain("#FF549A"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTrackedScorePosition([Values] bool partial)
|
||||
{
|
||||
createLeaderboard(partial);
|
||||
|
||||
AddStep("add many scores in one go", () =>
|
||||
{
|
||||
for (int i = 0; i < 49; i++)
|
||||
createRandomScore(new APIUser { Username = $"Player {i + 1}" });
|
||||
|
||||
// Add player at end to force an animation down the whole list.
|
||||
playerScore.Value = 0;
|
||||
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
|
||||
});
|
||||
|
||||
if (partial)
|
||||
AddUntilStep("tracked player has null position", () => leaderboard.TrackedScore?.ScorePosition, () => Is.Null);
|
||||
else
|
||||
AddUntilStep("tracked player is #50", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(50));
|
||||
|
||||
AddStep("move tracked player to top", () => leaderboard.TrackedScore!.TotalScore.Value = 8_000_000);
|
||||
AddUntilStep("all players have non-null position", () => leaderboard.AllScores.Select(s => s.ScorePosition), () => Does.Not.Contain(null));
|
||||
}
|
||||
|
||||
private void addLocalPlayer()
|
||||
{
|
||||
AddStep("add local player", () =>
|
||||
@@ -216,12 +165,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
}
|
||||
|
||||
private void createLeaderboard(bool partial = false)
|
||||
private void createLeaderboard()
|
||||
{
|
||||
AddStep("create leaderboard", () =>
|
||||
{
|
||||
leaderboardProvider.Scores.Clear();
|
||||
leaderboardProvider.IsPartial = partial;
|
||||
Child = leaderboard = new TestDrawableGameplayLeaderboard
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -243,24 +191,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public float Spacing => Flow.Spacing.Y;
|
||||
|
||||
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
||||
{
|
||||
var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username);
|
||||
|
||||
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
||||
}
|
||||
|
||||
public IEnumerable<DrawableGameplayLeaderboardScore> GetAllScoresForUsername(string username)
|
||||
=> Flow.Where(i => i.User?.Username == username);
|
||||
|
||||
public IEnumerable<DrawableGameplayLeaderboardScore> AllScores => Flow;
|
||||
}
|
||||
|
||||
private class TestGameplayLeaderboardProvider : IGameplayLeaderboardProvider
|
||||
{
|
||||
IBindableList<GameplayLeaderboardScore> IGameplayLeaderboardProvider.Scores => Scores;
|
||||
public BindableList<GameplayLeaderboardScore> Scores { get; } = new BindableList<GameplayLeaderboardScore>();
|
||||
public bool IsPartial { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +163,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase(120, 125, null, "120-125 (mostly 120)")]
|
||||
[TestCase(120, 120.6, null, "120-121 (mostly 120)")]
|
||||
[TestCase(120, 120.4, null, "120")]
|
||||
[TestCase(120, 120.6, "DT", "180-182 (mostly 180)")]
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
[TestCase(120, 120.6, "DT", "180-181 (mostly 180)")]
|
||||
[TestCase(120, 120.4, "DT", "180-181 (mostly 180)")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
};
|
||||
|
||||
private Container? resizeContainer;
|
||||
private float relativeWidth;
|
||||
|
||||
protected virtual Anchor ComponentAnchor => Anchor.TopLeft;
|
||||
protected virtual float InitialRelativeWidth => 0.5f;
|
||||
@@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Origin = ComponentAnchor,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = relativeWidth,
|
||||
Width = InitialRelativeWidth,
|
||||
Child = Content
|
||||
}
|
||||
};
|
||||
@@ -49,8 +48,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
if (resizeContainer != null)
|
||||
resizeContainer.Width = v;
|
||||
|
||||
relativeWidth = v;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,63 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3));
|
||||
onlineSet.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
|
||||
onlineSet.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
|
||||
onlineSet.Beatmaps.Single().TopTags = Enumerable.Repeat(onlineSet.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineAvailability()
|
||||
{
|
||||
AddStep("online beatmapset", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("rating wedge visible", () => wedge.RatingsVisible);
|
||||
AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible);
|
||||
AddStep("online beatmapset with local diff", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.ResetOnlineInfo();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
|
||||
AddUntilStep("fail time wedge hidden", () => !wedge.FailRetryVisible);
|
||||
AddStep("local beatmap", () =>
|
||||
{
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = null;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
|
||||
AddAssert("fail time wedge still hidden", () => !wedge.FailRetryVisible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserTags()
|
||||
{
|
||||
AddStep("user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
@@ -164,13 +221,40 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
OnlineID = working.BeatmapInfo.OnlineID,
|
||||
PlayCount = 10000,
|
||||
PassCount = 4567,
|
||||
TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 4, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 2, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 23, VoteCount = 5 },
|
||||
],
|
||||
FailTimes = new APIFailTimes
|
||||
{
|
||||
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
RelatedTags =
|
||||
[
|
||||
new APITag
|
||||
{
|
||||
Id = 2,
|
||||
Name = "song representation/simple",
|
||||
Description = "Accessible and straightforward map design."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 4,
|
||||
Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 23,
|
||||
Name = "aim/aim control",
|
||||
Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
@@ -10,6 +11,9 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -26,6 +30,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
private BeatmapTitleWedge titleWedge = null!;
|
||||
private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single();
|
||||
|
||||
private APIBeatmapSet? currentOnlineSet;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
@@ -36,6 +42,24 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@@ -115,11 +139,45 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("check visibility", () => titleWedge.Alpha > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineAvailability()
|
||||
{
|
||||
AddStep("online beatmapset", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddAssert("play count = 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString() == "10,000");
|
||||
AddAssert("favourites count = 2345", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(1).Text.ToString() == "2,345");
|
||||
AddStep("online beatmapset with local diff", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.ResetOnlineInfo();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddAssert("play count = -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString() == "-");
|
||||
AddAssert("favourites count = 2345", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(1).Text.ToString() == "2,345");
|
||||
AddStep("local beatmapset", () =>
|
||||
{
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = null;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddAssert("play count = -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString() == "-");
|
||||
AddAssert("favourites count = -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(1).Text.ToString() == "-");
|
||||
}
|
||||
|
||||
[TestCase(120, 125, null, "120-125 (mostly 120)")]
|
||||
[TestCase(120, 120.6, null, "120-121 (mostly 120)")]
|
||||
[TestCase(120, 120.4, null, "120")]
|
||||
[TestCase(120, 120.6, "DT", "180-182 (mostly 180)")]
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
[TestCase(120, 120.6, "DT", "180-181 (mostly 180)")]
|
||||
[TestCase(120, 120.4, "DT", "180-181 (mostly 180)")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
IBeatmap beatmap = TestSceneBeatmapInfoWedge.CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
@@ -155,5 +213,29 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
return label.Text == target;
|
||||
});
|
||||
}
|
||||
|
||||
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
|
||||
{
|
||||
var working = CreateWorkingBeatmap(Ruleset.Value);
|
||||
var onlineSet = new APIBeatmapSet
|
||||
{
|
||||
OnlineID = working.BeatmapSetInfo.OnlineID,
|
||||
FavouriteCount = 2345,
|
||||
Beatmaps = new[]
|
||||
{
|
||||
new APIBeatmap
|
||||
{
|
||||
OnlineID = working.BeatmapInfo.OnlineID,
|
||||
PlayCount = 10000,
|
||||
PassCount = 4567,
|
||||
UserPlayCount = 123,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
|
||||
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
|
||||
return (working, onlineSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,81 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneModIcon : OsuTestScene
|
||||
{
|
||||
private FillFlowContainer spreadOutFlow = null!;
|
||||
private ModDisplay modDisplay = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create flows", () =>
|
||||
{
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Relative, 0.5f),
|
||||
new Dimension(GridSizeMode.Relative, 0.5f),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
modDisplay = new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
spreadOutFlow = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void addRange(IEnumerable<IMod> mods)
|
||||
{
|
||||
spreadOutFlow.AddRange(mods.Select(m => new ModIcon(m)));
|
||||
modDisplay.Current.Value = modDisplay.Current.Value.Concat(mods.OfType<Mod>()).ToList();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowAllMods()
|
||||
{
|
||||
AddStep("create mod icons", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)),
|
||||
};
|
||||
if (m is OsuModFlashlight fl)
|
||||
fl.FollowDelay.Value = 1245;
|
||||
|
||||
if (m is OsuModDaycore dc)
|
||||
dc.SpeedChange.Value = 0.74f;
|
||||
|
||||
if (m is OsuModDifficultyAdjust da)
|
||||
da.CircleSize.Value = 8.2f;
|
||||
|
||||
if (m is ModAdaptiveSpeed ad)
|
||||
ad.AdjustPitch.Value = false;
|
||||
|
||||
return m;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("toggle selected", () =>
|
||||
@@ -42,26 +101,22 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("create mod icons", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
var rateAdjustMods = Ruleset.Value.CreateInstance().CreateAllMods()
|
||||
.OfType<ModRateAdjust>();
|
||||
|
||||
addRange(rateAdjustMods.SelectMany(m =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods()
|
||||
.OfType<ModRateAdjust>()
|
||||
.SelectMany(m =>
|
||||
{
|
||||
List<ModIcon> icons = new List<ModIcon> { new ModIcon(m) };
|
||||
List<Mod> mods = new List<Mod> { m };
|
||||
|
||||
for (double i = m.SpeedChange.MinValue; i < m.SpeedChange.MaxValue; i += m.SpeedChange.Precision * 10)
|
||||
{
|
||||
m = (ModRateAdjust)m.DeepClone();
|
||||
m.SpeedChange.Value = i;
|
||||
icons.Add(new ModIcon(m));
|
||||
}
|
||||
for (double i = m.SpeedChange.MinValue; i < m.SpeedChange.MaxValue; i += m.SpeedChange.Precision * 10)
|
||||
{
|
||||
m = (ModRateAdjust)m.DeepClone();
|
||||
m.SpeedChange.Value = i;
|
||||
mods.Add(m);
|
||||
}
|
||||
|
||||
return icons;
|
||||
}),
|
||||
};
|
||||
return mods;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("adjust rates", () =>
|
||||
@@ -81,21 +136,50 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestChangeModType()
|
||||
{
|
||||
ModIcon icon = null!;
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
||||
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
||||
AddStep("create mod icon", () => addRange([new OsuModDoubleTime()]));
|
||||
AddStep("change mod", () =>
|
||||
{
|
||||
foreach (var modIcon in this.ChildrenOfType<ModIcon>())
|
||||
modIcon.Mod = new OsuModEasy();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInterfaceModType()
|
||||
{
|
||||
ModIcon icon = null!;
|
||||
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
|
||||
AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
|
||||
AddStep("create mod icon", () => addRange([ruleset.AllMods.First(m => m.Acronym == "DT")]));
|
||||
AddStep("change mod", () =>
|
||||
{
|
||||
foreach (var modIcon in this.ChildrenOfType<ModIcon>())
|
||||
modIcon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifficultyAdjust()
|
||||
{
|
||||
AddStep("create icons", () =>
|
||||
{
|
||||
addRange([
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 8 }
|
||||
},
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 5.5f }
|
||||
},
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 8 },
|
||||
ApproachRate = { Value = 8 },
|
||||
OverallDifficulty = { Value = 8 },
|
||||
DrainRate = { Value = 8 },
|
||||
}
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@@ -183,32 +182,31 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(2.5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ShearedButton(120)
|
||||
new ShearedButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = "Test",
|
||||
Text = "Button",
|
||||
Action = () => { },
|
||||
Padding = new MarginPadding(),
|
||||
Height = 30,
|
||||
},
|
||||
new ShearedButton(120, 40)
|
||||
new ShearedButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = "Test",
|
||||
Text = "Button",
|
||||
Action = () => { },
|
||||
Padding = new MarginPadding { Left = -1f },
|
||||
Height = 30,
|
||||
},
|
||||
new ShearedButton(120, 70)
|
||||
new ShearedButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = "Test",
|
||||
Text = "Button",
|
||||
Action = () => { },
|
||||
Padding = new MarginPadding { Left = 3f },
|
||||
Height = 30,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@@ -30,16 +32,32 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
(typeof(OverlayColourProvider), colourProvider)
|
||||
},
|
||||
Children = new Drawable[]
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
new ShearedSearchTextBox
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f
|
||||
new ShearedSearchTextBox
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f
|
||||
},
|
||||
new ShearedFilterTextBox
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
FilterText = "12345 matches",
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,38 +3,50 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneShearedSliderBar : OsuManualInputManagerTestScene
|
||||
public partial class TestSceneShearedSliderBar : ThemeComparisonTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
private TestSliderBar slider = null!;
|
||||
|
||||
private ShearedSliderBar<double> slider = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
protected override Drawable CreateContent() => slider = new TestSliderBar
|
||||
{
|
||||
AddStep("create slider", () => Child = slider = new ShearedSliderBar<double>
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = new BindableDouble(5)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = new BindableDouble(5)
|
||||
Precision = 0.1,
|
||||
MinValue = 0,
|
||||
MaxValue = 15
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void TestNubDisplay()
|
||||
{
|
||||
AddSliderStep("nub width", 20, 80, 50, v =>
|
||||
{
|
||||
if (slider.IsNotNull())
|
||||
{
|
||||
Precision = 0.1,
|
||||
MinValue = 0,
|
||||
MaxValue = 15
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f
|
||||
slider.Nub.Width = v;
|
||||
slider.RangePadding = v / 2f;
|
||||
}
|
||||
});
|
||||
AddToggleStep("nub shadow", v =>
|
||||
{
|
||||
if (slider.IsNotNull())
|
||||
slider.NubShadowColour = v ? Color4.Black.Opacity(0.2f) : Color4.Black.Opacity(0f);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,6 +81,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
|
||||
AddAssert("slider is still at 1", () => slider.Current.Value, () => Is.EqualTo(1));
|
||||
AddStep("enable slider", () => slider.Current.Disabled = false);
|
||||
}
|
||||
|
||||
public partial class TestSliderBar : ShearedSliderBar<double>
|
||||
{
|
||||
public new ShearedNub Nub => base.Nub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Extensions
|
||||
/// <param name="maxDecimalDigits">The maximum number of decimals to be considered in the original value.</param>
|
||||
/// <param name="asPercentage">Whether the output should be a percentage. For integer types, 0-100 is mapped to 0-100%; for other types 0-1 is mapped to 0-100%.</param>
|
||||
/// <returns>The formatted output.</returns>
|
||||
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage) where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage = false) where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
double floatValue = double.CreateTruncating(value);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Scheduler.AddOnce(resolvePlayback);
|
||||
}
|
||||
|
||||
private void playClickSample() => sampleClick?.Play();
|
||||
private void playClickSample() => sampleClick.Play();
|
||||
|
||||
private void resolvePlayback()
|
||||
{
|
||||
|
||||
@@ -88,12 +88,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT)
|
||||
{
|
||||
Height = height;
|
||||
Padding = new MarginPadding { Horizontal = OsuGame.SHEAR.X * height };
|
||||
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.Shear = OsuGame.SHEAR;
|
||||
Content.Masking = true;
|
||||
Shear = OsuGame.SHEAR;
|
||||
|
||||
Content.Anchor = Content.Origin = Anchor.Centre;
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
||||
@@ -21,37 +21,54 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public Action? OnDoubleClicked { get; init; }
|
||||
|
||||
protected const float BORDER_WIDTH = 3;
|
||||
|
||||
public const int HEIGHT = 30;
|
||||
public const float EXPANDED_SIZE = 50;
|
||||
public const float CORNER_RADIUS = 5;
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Container main;
|
||||
private readonly Container shadow;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the shape for the nub, allowing for any type of container to be used.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ShearedNub()
|
||||
{
|
||||
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||
InternalChild = main = new Container
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Shear = OsuGame.SHEAR,
|
||||
BorderColour = Colour4.White,
|
||||
BorderThickness = BORDER_WIDTH,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Child = fill = new Box
|
||||
shadow = new Container
|
||||
{
|
||||
Shear = OsuGame.SHEAR,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 20f,
|
||||
},
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
},
|
||||
main = new Container
|
||||
{
|
||||
Shear = OsuGame.SHEAR,
|
||||
BorderColour = Colour4.White,
|
||||
BorderThickness = 8f,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Child = fill = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +93,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private bool glowing;
|
||||
@@ -89,22 +107,22 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return;
|
||||
|
||||
glowing = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
main.FadeColour(GlowingAccentColour.Lighten(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeColour(GlowingAccentColour, 800, Easing.OutQuint);
|
||||
private Color4 shadowColour = Color4.Black.Opacity(0f);
|
||||
|
||||
main.FadeEdgeEffectTo(Color4.White.Opacity(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeEdgeEffectTo(GlowColour.Opacity(0.1f), 800, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
main.FadeEdgeEffectTo(GlowColour.Opacity(0), 800, Easing.OutQuint);
|
||||
main.FadeColour(AccentColour, 800, Easing.OutQuint);
|
||||
}
|
||||
public Color4 ShadowColour
|
||||
{
|
||||
get => shadowColour;
|
||||
set
|
||||
{
|
||||
if (shadowColour == value)
|
||||
return;
|
||||
|
||||
shadowColour = value;
|
||||
shadow.FadeEdgeEffectTo(value, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +148,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
if (!Glowing)
|
||||
main.Colour = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +160,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set
|
||||
{
|
||||
glowingAccentColour = value;
|
||||
if (Glowing)
|
||||
main.Colour = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,10 +172,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set
|
||||
{
|
||||
glowColour = value;
|
||||
|
||||
var effect = main.EdgeEffect;
|
||||
effect.Colour = Glowing ? value : value.Opacity(0);
|
||||
main.EdgeEffect = effect;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +190,26 @@ namespace osu.Game.Graphics.UserInterface
|
||||
else
|
||||
{
|
||||
main.ResizeWidthTo(0.75f, duration, Easing.OutQuint);
|
||||
main.TransformTo(nameof(BorderThickness), BORDER_WIDTH, duration, Easing.OutQuint);
|
||||
main.TransformTo(nameof(BorderThickness), 8f, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
if (Glowing)
|
||||
{
|
||||
main.FadeColour(GlowingAccentColour.Lighten(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeColour(GlowingAccentColour, 800, Easing.OutQuint);
|
||||
|
||||
main.FadeEdgeEffectTo(Color4.White.Opacity(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeEdgeEffectTo(GlowColour.Opacity(0.1f), 800, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
main.FadeEdgeEffectTo(GlowColour.Opacity(0), 800, Easing.OutQuint);
|
||||
main.FadeColour(AccentColour, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,33 +21,33 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private const float corner_radius = 7;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly SearchTextBox textBox;
|
||||
protected readonly InnerSearchTextBox TextBox;
|
||||
|
||||
public Bindable<string> Current
|
||||
{
|
||||
get => textBox.Current;
|
||||
set => textBox.Current = value;
|
||||
get => TextBox.Current;
|
||||
set => TextBox.Current = value;
|
||||
}
|
||||
|
||||
public bool HoldFocus
|
||||
{
|
||||
get => textBox.HoldFocus;
|
||||
set => textBox.HoldFocus = value;
|
||||
get => TextBox.HoldFocus;
|
||||
set => TextBox.HoldFocus = value;
|
||||
}
|
||||
|
||||
public LocalisableString PlaceholderText
|
||||
{
|
||||
get => textBox.PlaceholderText;
|
||||
set => textBox.PlaceholderText = value;
|
||||
get => TextBox.PlaceholderText;
|
||||
set => TextBox.PlaceholderText = value;
|
||||
}
|
||||
|
||||
public new bool HasFocus => textBox.HasFocus;
|
||||
public new bool HasFocus => TextBox.HasFocus;
|
||||
|
||||
public void TakeFocus() => textBox.TakeFocus();
|
||||
public void TakeFocus() => TextBox.TakeFocus();
|
||||
|
||||
public void KillFocus() => textBox.KillFocus();
|
||||
public void KillFocus() => TextBox.KillFocus();
|
||||
|
||||
public bool SelectAll() => textBox.SelectAll();
|
||||
public bool SelectAll() => TextBox.SelectAll();
|
||||
|
||||
public ShearedSearchTextBox()
|
||||
{
|
||||
@@ -69,13 +69,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
textBox = new InnerSearchTextBox
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One
|
||||
},
|
||||
TextBox = CreateInnerTextBox(),
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Search,
|
||||
@@ -101,10 +95,20 @@ namespace osu.Game.Graphics.UserInterface
|
||||
background.Colour = colourProvider.Background3;
|
||||
}
|
||||
|
||||
public override bool HandleNonPositionalInput => textBox.HandleNonPositionalInput;
|
||||
public override bool HandleNonPositionalInput => TextBox.HandleNonPositionalInput;
|
||||
|
||||
private partial class InnerSearchTextBox : SearchTextBox
|
||||
protected virtual InnerSearchTextBox CreateInnerTextBox() => new InnerSearchTextBox();
|
||||
|
||||
protected partial class InnerSearchTextBox : SearchTextBox
|
||||
{
|
||||
public InnerSearchTextBox()
|
||||
{
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Size = Vector2.One;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
using static osu.Game.Graphics.UserInterface.ShearedNub;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@@ -29,6 +28,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private readonly Container mainContent;
|
||||
|
||||
protected virtual bool FocusIndicator => true;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
@@ -56,43 +57,41 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public Color4 NubShadowColour
|
||||
{
|
||||
get => Nub.ShadowColour;
|
||||
set => Nub.ShadowColour = value;
|
||||
}
|
||||
|
||||
public ShearedSliderBar()
|
||||
{
|
||||
Shear = OsuGame.SHEAR;
|
||||
Height = HEIGHT;
|
||||
RangePadding = EXPANDED_SIZE / 2;
|
||||
Height = ShearedNub.HEIGHT;
|
||||
RangePadding = ShearedNub.EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Child = new Container
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Children = new Drawable[]
|
||||
LeftBox = new Box
|
||||
{
|
||||
LeftBox = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
RightBox = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
RightBox = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -102,7 +101,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new ShearedNub
|
||||
{
|
||||
X = -OsuGame.SHEAR.X * HEIGHT / 2f,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true },
|
||||
@@ -146,13 +144,16 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
base.OnFocus(e);
|
||||
|
||||
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||
if (FocusIndicator)
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = AccentColour.Darken(1),
|
||||
Hollow = true,
|
||||
Radius = 2,
|
||||
};
|
||||
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = AccentColour.Darken(1),
|
||||
Hollow = true,
|
||||
Radius = 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFocusLost(FocusLostEvent e)
|
||||
@@ -191,8 +192,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
|
||||
LeftBox.Size = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2f + ShearedNub.CORNER_RADIUS - 0.5f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Size = new Vector2(Math.Clamp(DrawWidth - RangePadding - Nub.DrawPosition.X - Nub.DrawWidth / 2f + ShearedNub.CORNER_RADIUS - 0.5f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
|
||||
@@ -109,15 +109,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString LandscapeTouchOverlay => new TranslatableString(getKey(@"landscape_touch_overlay"), @"Landscape (touch overlay)");
|
||||
|
||||
/// <summary>
|
||||
/// "Scrolling style"
|
||||
/// </summary>
|
||||
public static LocalisableString ScrollingStyle => new TranslatableString(getKey(@"scrolling_style"), @"Scrolling style");
|
||||
|
||||
public static LocalisableString ScrollBaseSpeed => new TranslatableString(getKey(@"scroll_base_speed"), @"Scroll Base MS (when 200 Speed)");
|
||||
|
||||
public static LocalisableString ScrollTimePrecision => new TranslatableString(getKey(@"scrolling_time_precision"), @"Scroll MS per 1 Speed");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RightMouseScroll => new TranslatableString(getKey(@"right_mouse_scroll"), @"Right mouse drag to absolute scroll");
|
||||
|
||||
/// <summary>
|
||||
/// "Show converts"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowConverts => new TranslatableString(getKey(@"show_converts"), @"Show converts");
|
||||
|
||||
/// <summary>
|
||||
/// "Show converted beatmaps"
|
||||
/// </summary>
|
||||
|
||||
@@ -125,7 +125,14 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
var result = LeaderboardScores.Success
|
||||
(
|
||||
response.Scores.Select(s => s.ToScoreInfo(rulesets, newCriteria.Beatmap)).OrderByTotalScore().ToArray(),
|
||||
response.Scores.Select(s => s.ToScoreInfo(rulesets, newCriteria.Beatmap))
|
||||
.OrderByTotalScore()
|
||||
.Select((s, idx) =>
|
||||
{
|
||||
s.Position = idx + 1;
|
||||
return s;
|
||||
})
|
||||
.ToArray(),
|
||||
response.UserScore?.CreateScoreInfo(rulesets, newCriteria.Beatmap)
|
||||
);
|
||||
inFlightOnlineRequest = null;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Height = ModSelectPanel.HEIGHT;
|
||||
|
||||
// shear will be applied at a higher level in `ModPresetColumn`.
|
||||
Content.Shear = Vector2.Zero;
|
||||
Shear = Vector2.Zero;
|
||||
Padding = new MarginPadding();
|
||||
|
||||
Text = "+";
|
||||
|
||||
@@ -40,11 +40,13 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
public AddPresetPopover(AddPresetButton addPresetButton)
|
||||
{
|
||||
const float content_width = 300;
|
||||
|
||||
button = addPresetButton;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Width = 300,
|
||||
Width = content_width,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(7),
|
||||
Children = new Drawable[]
|
||||
@@ -63,12 +65,24 @@ namespace osu.Game.Overlays.Mods
|
||||
Label = CommonStrings.Description,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
createButton = new ShearedButton
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = ModSelectOverlayStrings.AddPreset,
|
||||
Action = createPreset
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(7),
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
createButton = new ShearedButton(content_width)
|
||||
{
|
||||
// todo: for some very odd reason, this needs to be anchored to topright for the fill flow to be correctly sized to the AABB of the sheared button
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = ModSelectOverlayStrings.AddPreset,
|
||||
Action = createPreset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "One or more values are being adjusted by mods that change speed.",
|
||||
Text = "One or more values are being adjusted by mods.",
|
||||
},
|
||||
attributesFillFlow = new FillFlowContainer
|
||||
{
|
||||
|
||||
@@ -52,9 +52,11 @@ namespace osu.Game.Overlays.Mods
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float content_width = 300;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Width = 300,
|
||||
Width = content_width,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(7),
|
||||
Direction = FillDirection.Vertical,
|
||||
@@ -107,25 +109,27 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(7),
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
useCurrentModsButton = new ShearedButton
|
||||
useCurrentModsButton = new ShearedButton(content_width)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
// todo: for some very odd reason, this needs to be anchored to topright for the fill flow to be correctly sized to the AABB of the sheared button
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = ModSelectOverlayStrings.UseCurrentMods,
|
||||
DarkerColour = colours.Blue1,
|
||||
LighterColour = colours.Blue0,
|
||||
TextColour = colourProvider.Background6,
|
||||
Action = useCurrentMods,
|
||||
},
|
||||
saveButton = new ShearedButton
|
||||
saveButton = new ShearedButton(content_width)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
// todo: for some very odd reason, this needs to be anchored to topright for the fill flow to be correctly sized to the AABB of the sheared button
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = Resources.Localisation.Web.CommonStrings.ButtonsSave,
|
||||
DarkerColour = colours.Orange1,
|
||||
LighterColour = colours.Orange0,
|
||||
|
||||
@@ -136,7 +136,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
{
|
||||
LabelText = "Scaling To Game Mode",
|
||||
Current = osuConfig.GetBindable<ScalingGameMode>(OsuSetting.ScalingGameMode),
|
||||
Keywords = new[] { "scale", "letterbox" },
|
||||
},
|
||||
scalingSettings = new FillFlowContainer<SettingsSlider<float>>
|
||||
{
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
[Resolved]
|
||||
private SkinEditorOverlay? skinEditorOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EzSkinEditorOverlay? ezSkinEditorOverlay { get; set; }
|
||||
|
||||
public SkinEditorSceneLibrary()
|
||||
{
|
||||
Height = HEIGHT;
|
||||
@@ -88,6 +91,17 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
Origin = Anchor.CentreLeft,
|
||||
Action = () => skinEditorOverlay?.PresentGameplay(),
|
||||
},
|
||||
new SceneButton
|
||||
{
|
||||
Text = "Skin.ini(Null)",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Action = () =>
|
||||
{
|
||||
skinEditorOverlay?.PresentGameplay();
|
||||
ezSkinEditorOverlay?.PopulateSettings();
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,11 +243,10 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Padding = new MarginPadding { Right = OsuGame.SCREEN_EDGE_MARGIN };
|
||||
|
||||
InternalChild = NextButton = new ShearedButton(0)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Right = 12f },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
Text = FirstRunSetupOverlayStrings.GetStarted,
|
||||
|
||||
@@ -6,9 +6,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO.FileAbstraction;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Utils;
|
||||
using TagLib;
|
||||
using File = TagLib.File;
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
// We use TagLib here for platform invariance; BASS cannot detect audio presence on Linux.
|
||||
using (Stream data = context.WorkingBeatmap.GetStream(storagePath))
|
||||
using (File tagFile = File.Create(new StreamFileAbstraction(filename, data)))
|
||||
using (File tagFile = TagLibUtils.GetTagLibFile(filename, data))
|
||||
{
|
||||
if (tagFile.Properties.AudioChannels == 0)
|
||||
continue;
|
||||
|
||||
@@ -6,9 +6,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO.FileAbstraction;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Utils;
|
||||
using TagLib;
|
||||
using File = TagLib.File;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
try
|
||||
{
|
||||
using (Stream data = context.WorkingBeatmap.GetStream(storagePath))
|
||||
using (File tagFile = File.Create(new StreamFileAbstraction(filename, data)))
|
||||
using (File tagFile = TagLibUtils.GetTagLibFile(filename, data))
|
||||
{
|
||||
int height = tagFile.Properties.VideoHeight;
|
||||
int width = tagFile.Properties.VideoWidth;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@@ -81,5 +83,33 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType())!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether any user adjustable setting attached to this mod has a non-default value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This returns the instantaneous state of this mod. It may change over time.
|
||||
/// For tracking changes on a dynamic display, make sure to setup a <see cref="ModSettingChangeTracker"/>.
|
||||
/// </remarks>
|
||||
bool HasNonDefaultSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
bool hasAdjustments = false;
|
||||
|
||||
foreach (var (_, property) in this.GetSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(this)!;
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
{
|
||||
hasAdjustments = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasAdjustments;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@@ -67,6 +68,22 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
@@ -94,5 +111,26 @@ namespace osu.Game.Rulesets.Mods
|
||||
if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value;
|
||||
if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of settings on this mod instance which have been adjusted by the user from their default values.
|
||||
/// </summary>
|
||||
protected int UserAdjustedSettingsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var (_, property) in this.GetSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(this)!;
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
@@ -81,6 +82,11 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private Container extendedContent = null!;
|
||||
|
||||
private Drawable adjustmentMarker = null!;
|
||||
|
||||
private Circle cogBackground = null!;
|
||||
private SpriteIcon cog = null!;
|
||||
|
||||
private ModSettingChangeTracker? modSettingsChangeTracker;
|
||||
|
||||
/// <summary>
|
||||
@@ -139,7 +145,7 @@ namespace osu.Game.Rulesets.UI
|
||||
Origin = Anchor.CentreLeft,
|
||||
Name = "main content",
|
||||
Size = MOD_ICON_SIZE,
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
background = new Sprite
|
||||
{
|
||||
@@ -165,6 +171,29 @@ namespace osu.Game.Rulesets.UI
|
||||
Size = new Vector2(45),
|
||||
Icon = FontAwesome.Solid.Question
|
||||
},
|
||||
adjustmentMarker = new Container
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
Origin = Anchor.Centre,
|
||||
Position = new Vector2(64, 14),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
cogBackground = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
cog = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Cog,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.6f),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -216,11 +245,18 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
extendedContent.Alpha = showExtended ? 1 : 0;
|
||||
extendedText.Text = mod.ExtendedIconInformation;
|
||||
|
||||
if (mod.HasNonDefaultSettings)
|
||||
adjustmentMarker.Show();
|
||||
else
|
||||
adjustmentMarker.Hide();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
modAcronym.Colour = modIcon.Colour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, backgroundColour, 0, 1);
|
||||
cogBackground.Colour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, backgroundColour, 0, 1);
|
||||
cog.Colour = backgroundColour;
|
||||
|
||||
extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
||||
extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f);
|
||||
|
||||
@@ -25,8 +25,12 @@ namespace osu.Game.Screens.Backgrounds
|
||||
var video = new Video(videoPath)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Loop = true
|
||||
Loop = true,
|
||||
};
|
||||
|
||||
video.FillMode = FillMode.Fill;
|
||||
video.FillAspectRatio = 1.0f * video.DrawSize.X / video.DrawSize.Y;
|
||||
|
||||
AddInternal(video);
|
||||
|
||||
GlobalConfigStore.Config = config;
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private FormTextBox sourceTextBox = null!;
|
||||
private FormTextBox tagsTextBox = null!;
|
||||
|
||||
private bool reloading;
|
||||
private bool dirty;
|
||||
|
||||
public override LocalisableString Title => EditorSetupStrings.MetadataHeader;
|
||||
@@ -105,6 +106,8 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
private void reloadMetadata()
|
||||
{
|
||||
reloading = true;
|
||||
|
||||
var metadata = Beatmap.Metadata;
|
||||
|
||||
RomanisedArtistTextBox.ReadOnly = false;
|
||||
@@ -120,10 +123,15 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
tagsTextBox.Current.Value = metadata.Tags;
|
||||
|
||||
updateReadOnlyState();
|
||||
|
||||
reloading = false;
|
||||
}
|
||||
|
||||
private void applyMetadata()
|
||||
{
|
||||
if (reloading)
|
||||
return;
|
||||
|
||||
Beatmap.Metadata.ArtistUnicode = ArtistTextBox.Current.Value;
|
||||
Beatmap.Metadata.Artist = RomanisedArtistTextBox.Current.Value;
|
||||
Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value;
|
||||
|
||||
@@ -10,9 +10,9 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@@ -98,11 +98,16 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
if (!source.Exists)
|
||||
return false;
|
||||
|
||||
TagLib.File? tagSource;
|
||||
string artist;
|
||||
string title;
|
||||
|
||||
try
|
||||
{
|
||||
tagSource = TagLib.File.Create(source.FullName);
|
||||
using (var tagSource = TagLibUtils.GetTagLibFile(source.FullName))
|
||||
{
|
||||
artist = tagSource.Tag.JoinedAlbumArtists ?? tagSource.Tag.JoinedPerformers;
|
||||
title = tagSource.Tag.Title;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -116,16 +121,12 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
metadata.AudioFile = name;
|
||||
|
||||
string artist = tagSource.Tag.JoinedAlbumArtists;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(artist))
|
||||
{
|
||||
metadata.ArtistUnicode = artist;
|
||||
metadata.Artist = MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode);
|
||||
}
|
||||
|
||||
string title = tagSource.Tag.Title;
|
||||
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
metadata.TitleUnicode = title;
|
||||
|
||||
389
osu.Game/Screens/EzSkinEditorOverlay.cs
Normal file
389
osu.Game/Screens/EzSkinEditorOverlay.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which handles loading a skin editor on user request for a specified target.
|
||||
/// This also handles the scaling / positioning adjustment of the target.
|
||||
/// </summary>
|
||||
public partial class EzSkinEditorOverlay : OverlayContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly ScalingContainer scalingContainer;
|
||||
|
||||
protected override bool BlockNonPositionalInput => true;
|
||||
|
||||
private SkinEditor? skinEditor;
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
[Resolved]
|
||||
private OsuGame game { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
private OsuScreen? lastTargetScreen;
|
||||
private InvokeOnDisposal? nestedInputManagerDisable;
|
||||
|
||||
private readonly LayoutValue drawSizeLayout;
|
||||
|
||||
public EzSkinEditorOverlay(ScalingContainer scalingContainer)
|
||||
{
|
||||
this.scalingContainer = scalingContainer;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
if (skinEditor?.State.Value != Visibility.Visible)
|
||||
break;
|
||||
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
globallyDisableBeatmapSkinSetting();
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
disableNestedInputManagers();
|
||||
skinEditor.Show();
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
PopulateSettings();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var editor = new SkinEditor();
|
||||
|
||||
editor.State.BindValueChanged(_ => updateComponentVisibility());
|
||||
|
||||
skinEditor = editor;
|
||||
|
||||
LoadComponentAsync(editor, _ =>
|
||||
{
|
||||
if (editor != skinEditor)
|
||||
return;
|
||||
|
||||
AddInternal(editor);
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
Debug.Assert(lastTargetScreen != null);
|
||||
|
||||
SetTarget(lastTargetScreen);
|
||||
|
||||
PopulateSettings();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
skinEditor?.Save(false);
|
||||
skinEditor?.Hide();
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
globallyReenableBeatmapSkinSetting();
|
||||
}
|
||||
|
||||
private EditorSidebar settingsSidebar = null!;
|
||||
private EzSkinSettings ezSkinSettings = null!;
|
||||
|
||||
public void PopulateSettings()
|
||||
{
|
||||
settingsSidebar.Clear();
|
||||
|
||||
settingsSidebar.Add(new SkinSettingsToolbox(ezSkinSettings));
|
||||
}
|
||||
|
||||
public void PresentGameplay() => presentGameplay(false);
|
||||
|
||||
private void presentGameplay(bool attemptedBeatmapSwitch)
|
||||
{
|
||||
performer?.PerformFromScreen(screen =>
|
||||
{
|
||||
if (State.Value != Visibility.Visible)
|
||||
return;
|
||||
|
||||
if (beatmap.Value is DummyWorkingBeatmap)
|
||||
{
|
||||
// presume we don't have anything good to play and just bail.
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're playing the intro, switch away to another beatmap.
|
||||
if (beatmap.Value.BeatmapSetInfo.Protected)
|
||||
{
|
||||
if (!attemptedBeatmapSwitch)
|
||||
{
|
||||
music.NextTrack();
|
||||
Schedule(() => presentGameplay(true));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen is Player)
|
||||
return;
|
||||
|
||||
// the validity of the current game-wide beatmap + ruleset combination is enforced by song select.
|
||||
// if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does.
|
||||
if (screen is not PlaySongSelect)
|
||||
ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset;
|
||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
|
||||
IReadOnlyList<Mod> usableMods = mods.Value;
|
||||
|
||||
if (replayGeneratingMod != null)
|
||||
usableMods = usableMods.Append(replayGeneratingMod).ToArray();
|
||||
|
||||
if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid))
|
||||
mods.Value = mods.Value.Except(invalid).ToArray();
|
||||
|
||||
if (replayGeneratingMod != null)
|
||||
screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)));
|
||||
}, new[] { typeof(Player), typeof(PlaySongSelect) });
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!drawSizeLayout.IsValid)
|
||||
{
|
||||
updateScreenSizing();
|
||||
drawSizeLayout.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateScreenSizing()
|
||||
{
|
||||
if (skinEditor?.State.Value != Visibility.Visible) return;
|
||||
|
||||
const float padding = 10;
|
||||
|
||||
float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth;
|
||||
float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight;
|
||||
|
||||
var rect = new RectangleF(
|
||||
relativeSidebarWidth,
|
||||
relativeToolbarHeight,
|
||||
1 - relativeSidebarWidth * 2,
|
||||
1f - relativeToolbarHeight - padding / DrawHeight);
|
||||
|
||||
scalingContainer.SetCustomRect(rect, true);
|
||||
}
|
||||
|
||||
private void updateComponentVisibility()
|
||||
{
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (skinEditor.State.Value == Visibility.Visible)
|
||||
{
|
||||
Scheduler.AddOnce(updateScreenSizing);
|
||||
|
||||
game.Toolbar.Hide();
|
||||
game.CloseAllOverlays();
|
||||
}
|
||||
else
|
||||
{
|
||||
scalingContainer.SetCustomRect(null);
|
||||
|
||||
if (lastTargetScreen?.HideOverlaysOnEnter != true)
|
||||
game.Toolbar.Show();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new target screen which will be used to find skinnable components.
|
||||
/// </summary>
|
||||
public void SetTarget(OsuScreen screen)
|
||||
{
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
lastTargetScreen = screen;
|
||||
|
||||
if (skinEditor == null) return;
|
||||
|
||||
// ensure the toolbar is re-hidden even if a new screen decides to try and show it.
|
||||
updateComponentVisibility();
|
||||
|
||||
// AddOnce with parameter will ensure the newest target is loaded if there is any overlap.
|
||||
Scheduler.AddOnce(setTarget, screen);
|
||||
}
|
||||
|
||||
private void setTarget(OsuScreen? target)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
||||
{
|
||||
Scheduler.AddOnce(setTarget, target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (skinEditor.State.Value == Visibility.Visible)
|
||||
{
|
||||
skinEditor.Save(false);
|
||||
skinEditor.UpdateTargetScreen(target);
|
||||
disableNestedInputManagers();
|
||||
}
|
||||
else
|
||||
{
|
||||
skinEditor.Hide();
|
||||
skinEditor.Expire();
|
||||
skinEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void disableNestedInputManagers()
|
||||
{
|
||||
if (lastTargetScreen == null)
|
||||
return;
|
||||
|
||||
var nestedInputManagers = lastTargetScreen.ChildrenOfType<PassThroughInputManager>().Where(manager => manager.UseParentInput).ToArray();
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = false;
|
||||
nestedInputManagerDisable = new InvokeOnDisposal(() =>
|
||||
{
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
||||
private LeasedBindable<bool>? leasedBeatmapSkins;
|
||||
|
||||
private void globallyDisableBeatmapSkinSetting()
|
||||
{
|
||||
if (beatmapSkins.Disabled)
|
||||
return;
|
||||
|
||||
// The skin editor doesn't work well if beatmap skins are being applied to the player screen.
|
||||
// To keep things simple, disable the setting game-wide while using the skin editor.
|
||||
//
|
||||
// This causes a full reload of the skin, which is pretty ugly.
|
||||
// TODO: Investigate if we can avoid this when a beatmap skin is not being applied by the current beatmap.
|
||||
leasedBeatmapSkins = beatmapSkins.BeginLease(true);
|
||||
leasedBeatmapSkins.Value = false;
|
||||
}
|
||||
|
||||
private void globallyReenableBeatmapSkinSetting()
|
||||
{
|
||||
leasedBeatmapSkins?.Return();
|
||||
leasedBeatmapSkins = null;
|
||||
}
|
||||
|
||||
private partial class EndlessPlayer : ReplayPlayer
|
||||
{
|
||||
protected override UserActivity? InitialActivity => null;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? AllowGlobalTrackControl => false;
|
||||
|
||||
public EndlessPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore)
|
||||
: base(createScore, new PlayerConfiguration
|
||||
{
|
||||
ShowResults = false,
|
||||
AutomaticallySkipIntro = true,
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
Scheduler.AddDelayed(this.Exit, 1000);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
if (GameplayState.HasPassed)
|
||||
GameplayClockContainer.Seek(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
osu.Game/Screens/EzSkinSettings.cs
Normal file
40
osu.Game/Screens/EzSkinSettings.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Screens
|
||||
{
|
||||
public partial class EzSkinSettings : CompositeDrawable
|
||||
{
|
||||
[SettingSource("Column Width", "Column Width")]
|
||||
public BindableNumber<float> ColumnWidth { get; } = new BindableNumber<float>(50f)
|
||||
{
|
||||
MinValue = 9f,
|
||||
MaxValue = 90f,
|
||||
Precision = 1f,
|
||||
};
|
||||
|
||||
[SettingSource("Special Column Width Factor", "Special Column Width Factor")]
|
||||
public BindableNumber<float> SpecialFactor { get; } = new BindableNumber<float>(1f)
|
||||
{
|
||||
MinValue = 0.1f,
|
||||
MaxValue = 2f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace osu.Game.Screens.Footer
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Padding = new MarginPadding { Left = OsuGame.SCREEN_EDGE_MARGIN + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@@ -89,7 +89,7 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Y = 10f,
|
||||
Y = ScreenFooterButton.Y_OFFSET,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(7, 0),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@@ -97,7 +97,7 @@ namespace osu.Game.Screens.Footer
|
||||
footerContentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Y = -15f,
|
||||
Y = -OsuGame.SCREEN_EDGE_MARGIN,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -112,7 +112,7 @@ namespace osu.Game.Screens.Footer
|
||||
hiddenButtonsContainer = new Container<ScreenFooterButton>
|
||||
{
|
||||
Margin = new MarginPadding { Left = OsuGame.SCREEN_EDGE_MARGIN + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Y = 10f,
|
||||
Y = ScreenFooterButton.Y_OFFSET,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected const int CORNER_RADIUS = 10;
|
||||
public const int Y_OFFSET = 10;
|
||||
|
||||
protected const int BUTTON_HEIGHT = 75;
|
||||
protected const int BUTTON_WIDTH = 116;
|
||||
|
||||
@@ -87,7 +88,7 @@ namespace osu.Game.Screens.Footer
|
||||
},
|
||||
Shear = OsuGame.SHEAR,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
CornerRadius = 10,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -134,7 +135,7 @@ namespace osu.Game.Screens.Footer
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -CORNER_RADIUS,
|
||||
Y = -Y_OFFSET,
|
||||
Size = new Vector2(100, 5),
|
||||
Masking = true,
|
||||
CornerRadius = 3,
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@@ -20,8 +19,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class DrawableGameplayLeaderboard : CompositeDrawable
|
||||
{
|
||||
private readonly Cached sorting = new Cached();
|
||||
|
||||
public Bindable<bool> Expanded = new Bindable<bool>();
|
||||
|
||||
protected readonly FillFlowContainer<DrawableGameplayLeaderboardScore> Flow;
|
||||
@@ -87,7 +84,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}, true);
|
||||
}
|
||||
|
||||
Scheduler.AddDelayed(sort, 1000, true);
|
||||
configVisibility.BindValueChanged(_ => this.FadeTo(configVisibility.Value ? 1 : 0, 100, Easing.OutQuint), true);
|
||||
}
|
||||
|
||||
@@ -109,8 +105,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
drawable.Expanded.BindTo(Expanded);
|
||||
|
||||
Flow.Add(drawable);
|
||||
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
|
||||
drawable.DisplayOrder.BindValueChanged(_ => sorting.Invalidate(), true);
|
||||
drawable.ScorePosition.BindValueChanged(_ => Scheduler.AddOnce(sort));
|
||||
drawable.DisplayOrder.BindValueChanged(_ => Scheduler.AddOnce(sort), true);
|
||||
|
||||
int displayCount = Math.Min(Flow.Count, max_panels);
|
||||
Height = displayCount * (DrawableGameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y);
|
||||
@@ -179,22 +175,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private void sort()
|
||||
{
|
||||
if (sorting.IsValid)
|
||||
return;
|
||||
|
||||
var orderedByScore = Flow
|
||||
.OrderByDescending(i => i.TotalScore.Value)
|
||||
.ThenBy(i => i.DisplayOrder.Value)
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < Flow.Count; i++)
|
||||
{
|
||||
var score = orderedByScore[i];
|
||||
Flow.SetLayoutPosition(score, i);
|
||||
score.ScorePosition = i + 1 == Flow.Count && leaderboardProvider?.IsPartial == true && score.Tracked ? null : i + 1;
|
||||
}
|
||||
|
||||
sorting.Validate();
|
||||
foreach (var score in Flow.ToArray())
|
||||
Flow.SetLayoutPosition(score, score.DisplayOrder.Value);
|
||||
}
|
||||
|
||||
private partial class InputDisabledScrollContainer : OsuScrollContainer
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
public BindableDouble Accuracy { get; } = new BindableDouble(1);
|
||||
public BindableInt Combo { get; } = new BindableInt();
|
||||
public BindableBool HasQuit { get; } = new BindableBool();
|
||||
public Bindable<int?> ScorePosition { get; } = new Bindable<int?>();
|
||||
public Bindable<long> DisplayOrder { get; } = new Bindable<long>();
|
||||
|
||||
private Func<ScoringMode, long>? getDisplayScoreFunction;
|
||||
@@ -69,28 +70,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
public Color4? TextColour { get; set; }
|
||||
|
||||
private int? scorePosition;
|
||||
|
||||
private bool scorePositionIsSet;
|
||||
|
||||
public int? ScorePosition
|
||||
{
|
||||
get => scorePosition;
|
||||
set
|
||||
{
|
||||
// We always want to run once, as the incoming value may be null and require a visual update to "-".
|
||||
if (value == scorePosition && scorePositionIsSet)
|
||||
return;
|
||||
|
||||
scorePosition = value;
|
||||
|
||||
positionText.Text = scorePosition.HasValue ? $"#{scorePosition.Value.FormatRank()}" : "-";
|
||||
scorePositionIsSet = true;
|
||||
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
public IUser? User { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +102,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Accuracy.BindTo(score.Accuracy);
|
||||
Combo.BindTo(score.Combo);
|
||||
HasQuit.BindTo(score.HasQuit);
|
||||
ScorePosition.BindTo(score.Position);
|
||||
DisplayOrder.BindTo(score.DisplayOrder);
|
||||
GetDisplayScore = score.GetDisplayScore;
|
||||
|
||||
@@ -334,6 +314,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
updateState();
|
||||
Expanded.BindValueChanged(changeExpandedState, true);
|
||||
ScorePosition.BindValueChanged(_ => updateState(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
@@ -392,7 +373,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
return;
|
||||
}
|
||||
|
||||
if (scorePosition == 1)
|
||||
positionText.Text = ScorePosition.Value.HasValue ? $"#{ScorePosition.Value.Value.FormatRank()}" : "-";
|
||||
|
||||
if (ScorePosition.Value == 1)
|
||||
{
|
||||
widthExtension = true;
|
||||
panelColour = BackgroundColour ?? Color4Extensions.FromHex("7fcc33");
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class SoloResultsScreen : ResultsScreen
|
||||
{
|
||||
private GetScoresRequest? getScoreRequest;
|
||||
private readonly IBindable<LeaderboardScores?> globalScores = new Bindable<LeaderboardScores?>();
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
private LeaderboardManager leaderboardManager { get; set; } = null!;
|
||||
|
||||
public SoloResultsScreen(ScoreInfo score)
|
||||
: base(score)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
globalScores.BindTo(leaderboardManager.Scores);
|
||||
}
|
||||
|
||||
protected override async Task<ScoreInfo[]> FetchScores()
|
||||
{
|
||||
Debug.Assert(Score != null);
|
||||
@@ -39,52 +41,93 @@ namespace osu.Game.Screens.Ranking
|
||||
if (Score.BeatmapInfo!.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
||||
return [];
|
||||
|
||||
var requestTaskSource = new TaskCompletionSource<APIScoresCollection>();
|
||||
|
||||
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);
|
||||
getScoreRequest.Success += requestTaskSource.SetResult;
|
||||
getScoreRequest.Failure += requestTaskSource.SetException;
|
||||
api.Queue(getScoreRequest);
|
||||
|
||||
try
|
||||
var criteria = new LeaderboardCriteria(
|
||||
Score.BeatmapInfo!,
|
||||
Score.Ruleset,
|
||||
leaderboardManager.CurrentCriteria?.Scope ?? BeatmapLeaderboardScope.Global,
|
||||
leaderboardManager.CurrentCriteria?.ExactMods
|
||||
);
|
||||
var requestTaskSource = new TaskCompletionSource<LeaderboardScores>();
|
||||
globalScores.BindValueChanged(_ =>
|
||||
{
|
||||
var scores = await requestTaskSource.Task.ConfigureAwait(false);
|
||||
var toDisplay = new List<ScoreInfo>();
|
||||
if (globalScores.Value != null && leaderboardManager.CurrentCriteria?.Equals(criteria) == true)
|
||||
requestTaskSource.TrySetResult(globalScores.Value);
|
||||
});
|
||||
leaderboardManager.FetchWithCriteria(criteria, forceRefresh: true);
|
||||
|
||||
for (int i = 0; i < scores.Scores.Count; ++i)
|
||||
{
|
||||
var score = scores.Scores[i];
|
||||
int position = i + 1;
|
||||
var result = await requestTaskSource.Task.ConfigureAwait(false);
|
||||
|
||||
if (score.MatchesOnlineID(Score))
|
||||
{
|
||||
// we don't want to add the same score twice, but also setting any properties of `Score` this late will have no visible effect,
|
||||
// so we have to fish out the actual drawable panel and set the position to it directly.
|
||||
var panel = ScorePanelList.GetPanelForScore(Score);
|
||||
Score.Position = panel.ScorePosition.Value = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
var converted = score.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo);
|
||||
converted.Position = position;
|
||||
toDisplay.Add(converted);
|
||||
}
|
||||
}
|
||||
|
||||
return toDisplay.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (result.FailState != null)
|
||||
{
|
||||
Logger.Log($"Failed to fetch scores (beatmap: {Score.BeatmapInfo}, ruleset: {Score.Ruleset}): {ex}");
|
||||
Logger.Log($"Failed to fetch scores (beatmap: {Score.BeatmapInfo}, ruleset: {Score.Ruleset}): {result.FailState}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
var clonedScores = result.AllScores.Select(s => s.DeepClone()).ToArray();
|
||||
|
||||
getScoreRequest?.Cancel();
|
||||
List<ScoreInfo> sortedScores = [];
|
||||
|
||||
foreach (var clonedScore in clonedScores)
|
||||
{
|
||||
// ensure that we do not double up on the score being presented here.
|
||||
// additionally, ensure that the reference that ends up in `sortedScores` is the `Score` reference specifically.
|
||||
// this simplifies handling later.
|
||||
if (clonedScore.Equals(Score) || clonedScore.MatchesOnlineID(Score))
|
||||
{
|
||||
Score.Position = clonedScore.Position;
|
||||
sortedScores.Add(Score);
|
||||
}
|
||||
else
|
||||
sortedScores.Add(clonedScore);
|
||||
}
|
||||
|
||||
// if we haven't encountered a match for the presented score, we still need to attach it.
|
||||
// note that the above block ensuring that the `Score` reference makes it in here makes this valid to write in this way.
|
||||
if (!sortedScores.Contains(Score))
|
||||
sortedScores.Add(Score);
|
||||
|
||||
sortedScores = sortedScores.OrderByTotalScore().ToList();
|
||||
|
||||
int delta = 0;
|
||||
bool isPartialLeaderboard = leaderboardManager.CurrentCriteria?.Scope != BeatmapLeaderboardScope.Local && result.TopScores.Count >= 50;
|
||||
|
||||
for (int i = 0; i < sortedScores.Count; i++)
|
||||
{
|
||||
var sortedScore = sortedScores[i];
|
||||
|
||||
// see `SoloGameplayLeaderboardProvider.sort()` for another place that does the same thing with slight deviations
|
||||
// if this code is changed, that code should probably be changed as well
|
||||
|
||||
if (!isPartialLeaderboard)
|
||||
sortedScore.Position = i + 1;
|
||||
else
|
||||
{
|
||||
if (ReferenceEquals(sortedScore, Score) && sortedScore.Position == null)
|
||||
{
|
||||
int? previousScorePosition = i > 0 ? sortedScores[i - 1].Position : 0;
|
||||
int? nextScorePosition = i < result.TopScores.Count - 1 ? sortedScores[i + 1].Position : null;
|
||||
|
||||
if (previousScorePosition != null && nextScorePosition != null && previousScorePosition + 1 == nextScorePosition)
|
||||
{
|
||||
sortedScore.Position = previousScorePosition + 1;
|
||||
delta += 1;
|
||||
}
|
||||
else
|
||||
sortedScore.Position = null;
|
||||
}
|
||||
else
|
||||
sortedScore.Position += delta;
|
||||
}
|
||||
}
|
||||
|
||||
// there's a non-zero chance that the `Score.Position` was mutated above,
|
||||
// but that is not actually coupled to `ScorePosition` of the relevant score panel in any way,
|
||||
// so ensure that the drawable panel also receives the updated position.
|
||||
// note that this is valid to do precisely because we ensured `Score` was in `sortedScores` earlier.
|
||||
ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = Score.Position;
|
||||
|
||||
sortedScores.Remove(Score);
|
||||
return sortedScores.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,36 +5,22 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
@@ -286,180 +272,6 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => true;
|
||||
|
||||
private partial class DrawableUserTag : OsuAnimatedButton
|
||||
{
|
||||
public readonly UserTag UserTag;
|
||||
|
||||
public Action<UserTag>? OnSelected { get; set; }
|
||||
|
||||
private readonly Bindable<int> voteCount = new Bindable<int>();
|
||||
private readonly BindableBool voted = new BindableBool();
|
||||
private readonly Bindable<bool> confirmed = new BindableBool();
|
||||
private readonly BindableBool updating = new BindableBool();
|
||||
|
||||
protected Box MainBackground { get; private set; } = null!;
|
||||
private Box voteBackground = null!;
|
||||
|
||||
protected OsuSpriteText TagCategoryText { get; private set; } = null!;
|
||||
protected OsuSpriteText TagNameText { get; private set; } = null!;
|
||||
protected VoteCountText VoteCountText { get; private set; } = null!;
|
||||
|
||||
private readonly bool showVoteCount;
|
||||
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public DrawableUserTag(UserTag userTag, bool showVoteCount = true)
|
||||
{
|
||||
UserTag = userTag;
|
||||
this.showVoteCount = showVoteCount;
|
||||
voteCount.BindTo(userTag.VoteCount);
|
||||
updating.BindTo(userTag.Updating);
|
||||
voted.BindTo(userTag.Voted);
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
ScaleOnMouseDown = 0.95f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
CornerRadius = 5;
|
||||
Masking = true;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = colours.Lime1,
|
||||
Radius = 6,
|
||||
Type = EdgeEffectType.Glow,
|
||||
};
|
||||
Content.AddRange(new Drawable[]
|
||||
{
|
||||
MainBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MaxValue,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
TagCategoryText = new OsuSpriteText
|
||||
{
|
||||
Alpha = UserTag.GroupName != null ? 0.6f : 0,
|
||||
Text = UserTag.GroupName ?? default(LocalisableString),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Horizontal = 6 }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.1f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
TagNameText = new OsuSpriteText
|
||||
{
|
||||
Text = UserTag.DisplayName,
|
||||
Font = OsuFont.Default.With(weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Horizontal = 6, Vertical = 3, },
|
||||
},
|
||||
}
|
||||
},
|
||||
showVoteCount
|
||||
? new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
voteBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
VoteCountText = new VoteCountText(voteCount)
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 6 },
|
||||
},
|
||||
}
|
||||
}
|
||||
: Empty(),
|
||||
}
|
||||
},
|
||||
loadingLayer = new LoadingLayer(dimBackground: true),
|
||||
});
|
||||
|
||||
TooltipText = UserTag.Description;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
const double transition_duration = 300;
|
||||
|
||||
updating.BindValueChanged(u => loadingLayer.State.Value = u.NewValue ? Visibility.Visible : Visibility.Hidden);
|
||||
|
||||
if (showVoteCount)
|
||||
{
|
||||
voteCount.BindValueChanged(_ =>
|
||||
{
|
||||
confirmed.Value = voteCount.Value >= 10;
|
||||
}, true);
|
||||
voted.BindValueChanged(v =>
|
||||
{
|
||||
if (v.NewValue)
|
||||
{
|
||||
voteBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint);
|
||||
VoteCountText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
voteBackground.FadeColour(colours.Gray2, transition_duration, Easing.OutQuint);
|
||||
VoteCountText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
}, true);
|
||||
|
||||
confirmed.BindValueChanged(c =>
|
||||
{
|
||||
if (c.NewValue)
|
||||
{
|
||||
MainBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint);
|
||||
TagCategoryText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint);
|
||||
TagNameText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint);
|
||||
FadeEdgeEffectTo(0.3f, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
MainBackground.FadeColour(colours.Gray6, transition_duration, Easing.OutQuint);
|
||||
TagCategoryText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint);
|
||||
TagNameText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint);
|
||||
FadeEdgeEffectTo(0f, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
FinishTransforms(true);
|
||||
|
||||
Action = () => OnSelected?.Invoke(UserTag);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class AddNewTagUserTag : DrawableUserTag, IHasPopover
|
||||
{
|
||||
public BindableDictionary<long, UserTag> AvailableTags { get; } = new BindableDictionary<long, UserTag>();
|
||||
@@ -493,292 +305,5 @@ namespace osu.Game.Screens.Ranking
|
||||
OnSelected = OnTagSelected,
|
||||
};
|
||||
}
|
||||
|
||||
private partial class AddTagsPopover : OsuPopover
|
||||
{
|
||||
private SearchTextBox searchBox = null!;
|
||||
private SearchContainer searchContainer = null!;
|
||||
|
||||
public BindableDictionary<long, UserTag> AvailableTags { get; } = new BindableDictionary<long, UserTag>();
|
||||
|
||||
public Action<UserTag>? OnSelected { get; set; }
|
||||
|
||||
private CancellationTokenSource? loadCancellationTokenSource;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AllowableAnchors = new[]
|
||||
{
|
||||
Anchor.TopCentre,
|
||||
Anchor.BottomCentre,
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(400, 300),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
searchBox = new SearchTextBox
|
||||
{
|
||||
HoldFocus = true,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MinValue,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Y = 40,
|
||||
Height = 260,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = searchContainer = new SearchContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Right = 5, Bottom = 10 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AvailableTags.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
loadCancellationTokenSource?.Cancel();
|
||||
loadCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
LoadComponentsAsync(createItems(AvailableTags.Values), loaded =>
|
||||
{
|
||||
searchContainer.Clear();
|
||||
searchContainer.AddRange(loaded);
|
||||
}, loadCancellationTokenSource.Token);
|
||||
}, true);
|
||||
searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true);
|
||||
}
|
||||
|
||||
private IEnumerable<Drawable> createItems(IEnumerable<UserTag> tags)
|
||||
{
|
||||
var grouped = tags.GroupBy(tag => tag.GroupName).OrderBy(group => group.Key);
|
||||
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
var drawableGroup = new GroupFlow(group.Key);
|
||||
|
||||
foreach (var tag in group.OrderBy(t => t.FullName))
|
||||
drawableGroup.Add(new DrawableAddableTag(tag) { Action = () => OnSelected?.Invoke(tag) });
|
||||
|
||||
yield return drawableGroup;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Action == GlobalAction.Select && !e.Repeat)
|
||||
{
|
||||
attemptSelect();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void attemptSelect()
|
||||
{
|
||||
var visibleItems = searchContainer.ChildrenOfType<DrawableAddableTag>().Where(d => d.IsPresent).ToArray();
|
||||
|
||||
if (visibleItems.Length == 1)
|
||||
OnSelected?.Invoke(visibleItems.Single().Tag);
|
||||
}
|
||||
|
||||
private partial class GroupFlow : FillFlowContainer, IFilterable
|
||||
{
|
||||
public IEnumerable<LocalisableString> FilterTerms { get; }
|
||||
|
||||
public bool MatchingFilter
|
||||
{
|
||||
set => Alpha = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public bool FilteringActive { set { } }
|
||||
|
||||
public GroupFlow(string? name)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
Spacing = new Vector2(5);
|
||||
|
||||
Add(new StatisticItemHeader { Text = name ?? "uncategorised" });
|
||||
|
||||
FilterTerms = name == null ? [] : [name];
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DrawableAddableTag : OsuAnimatedButton, IFilterable
|
||||
{
|
||||
public readonly UserTag Tag;
|
||||
|
||||
private Box votedBackground = null!;
|
||||
private SpriteIcon votedIcon = null!;
|
||||
|
||||
private readonly Bindable<bool> voted = new Bindable<bool>();
|
||||
private readonly BindableBool updating = new BindableBool();
|
||||
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
|
||||
public DrawableAddableTag(UserTag tag)
|
||||
{
|
||||
Tag = tag;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
ScaleOnMouseDown = 0.95f;
|
||||
|
||||
voted.BindTo(Tag.Voted);
|
||||
updating.BindTo(Tag.Updating);
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Content.AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray7,
|
||||
Depth = float.MaxValue,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 30,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Depth = float.MaxValue,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
votedBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
votedIcon = new SpriteIcon
|
||||
{
|
||||
Size = new Vector2(16),
|
||||
Icon = FontAwesome.Solid.ThumbsUp,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2),
|
||||
Padding = new MarginPadding(5) { Right = 35 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold))
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Text = Tag.DisplayName,
|
||||
},
|
||||
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 14))
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Text = Tag.Description,
|
||||
}
|
||||
}
|
||||
},
|
||||
loadingLayer = new LoadingLayer(dimBackground: true),
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => [Tag.FullName, Tag.Description];
|
||||
|
||||
public bool MatchingFilter { set => Alpha = value ? 1 : 0; }
|
||||
public bool FilteringActive { set { } }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
voted.BindValueChanged(_ =>
|
||||
{
|
||||
votedBackground.FadeColour(voted.Value ? colours.Lime2 : colours.Gray2, 250, Easing.OutQuint);
|
||||
votedIcon.FadeColour(voted.Value ? Colour4.Black : Colour4.White, 250, Easing.OutQuint);
|
||||
}, true);
|
||||
FinishTransforms(true);
|
||||
|
||||
updating.BindValueChanged(u => loadingLayer.State.Value = u.NewValue ? Visibility.Visible : Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class VoteCountText : CompositeDrawable
|
||||
{
|
||||
private OsuSpriteText? text;
|
||||
|
||||
private readonly Bindable<int> voteCount;
|
||||
|
||||
public VoteCountText(Bindable<int> voteCount)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
AutoSizeAxes = Axes.X;
|
||||
|
||||
this.voteCount = voteCount.GetBoundCopy();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
voteCount.BindValueChanged(count =>
|
||||
{
|
||||
OsuSpriteText? previousText = text;
|
||||
|
||||
AddInternal(text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
|
||||
Text = voteCount.Value.ToLocalisableString(),
|
||||
});
|
||||
|
||||
if (previousText != null)
|
||||
{
|
||||
const double transition_duration = 500;
|
||||
|
||||
bool isIncrease = count.NewValue > count.OldValue;
|
||||
|
||||
text.MoveToY(isIncrease ? 20 : -20)
|
||||
.MoveToY(0, transition_duration, Easing.OutExpo);
|
||||
|
||||
previousText.BypassAutoSizeAxes = Axes.Both;
|
||||
previousText.MoveToY(isIncrease ? -20 : 20, transition_duration, Easing.OutExpo).Expire();
|
||||
|
||||
AutoSizeDuration = 300;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
// For now, we forcefully refresh to keep things simple.
|
||||
// In the future, removing this requirement may be deemed useful, but will need ample testing of edge case scenarios
|
||||
// (like returning from gameplay after setting a new score, returning to song select after main menu).
|
||||
leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope, filterMods ? mods.Value.ToArray() : null), forceRefresh: true);
|
||||
leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope, filterMods ? mods.Value.Where(m => m.UserPlayable).ToArray() : null), forceRefresh: true);
|
||||
|
||||
if (!initialFetchComplete)
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
/// An optional value to guarantee stable ordering.
|
||||
/// Lower numbers will appear higher in cases of <see cref="TotalScore"/> ties.
|
||||
/// </summary>
|
||||
public Bindable<long> DisplayOrder { get; } = new BindableLong();
|
||||
public long TotalScoreTiebreaker { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// A custom function which handles converting a score to a display score using a provided <see cref="ScoringMode"/>.
|
||||
@@ -68,6 +68,25 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
/// </summary>
|
||||
public Colour4? TeamColour { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The initial position of the score on the leaderboard.
|
||||
/// Mostly used for cases like the local user's best score on the global leaderboard (which will not be contiguous with the other scores).
|
||||
/// </summary>
|
||||
public int? InitialPosition { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The displayed rank of the score on the leaderboard.
|
||||
/// </summary>
|
||||
public Bindable<int?> Position { get; } = new Bindable<int?>();
|
||||
|
||||
/// <summary>
|
||||
/// The index of the score on the leaderboard.
|
||||
/// This differs from <see cref="Position"/> in that it is required (must always be known)
|
||||
/// and that it doesn't represent the score's position on global leaderboards.
|
||||
/// It's a property completely local to and relative to all scores provided by the managing <see cref="IGameplayLeaderboardProvider"/>.
|
||||
/// </summary>
|
||||
public Bindable<long> DisplayOrder { get; } = new BindableLong();
|
||||
|
||||
public GameplayLeaderboardScore(IUser user, ScoreProcessor scoreProcessor, bool tracked)
|
||||
{
|
||||
User = user;
|
||||
@@ -95,8 +114,9 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
TotalScore.Value = scoreInfo.TotalScore;
|
||||
Accuracy.Value = scoreInfo.Accuracy;
|
||||
Combo.Value = scoreInfo.Combo;
|
||||
DisplayOrder.Value = scoreInfo.OnlineID > 0 ? scoreInfo.OnlineID : scoreInfo.Date.ToUnixTimeSeconds();
|
||||
TotalScoreTiebreaker = scoreInfo.OnlineID > 0 ? scoreInfo.OnlineID : scoreInfo.Date.ToUnixTimeSeconds();
|
||||
GetDisplayScore = scoreInfo.GetDisplayScore;
|
||||
InitialPosition = scoreInfo.Position;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
||||
@@ -14,14 +14,5 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
/// List of all scores to display on the leaderboard.
|
||||
/// </summary>
|
||||
public IBindableList<GameplayLeaderboardScore> Scores { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this leaderboard is a partial leaderboard (e.g. contains only the top 50 of all scores),
|
||||
/// or is a full leaderboard (contains all scores that there will ever be).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is <see langword="true"/> and a tracked score is last on the leaderboard, it will show an "unknown" score position.
|
||||
/// </remarks>
|
||||
bool IsPartial { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@@ -55,6 +56,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private readonly Cached sorting = new Cached();
|
||||
|
||||
public MultiplayerLeaderboardProvider(MultiplayerRoomUser[] users)
|
||||
{
|
||||
this.users = users;
|
||||
@@ -101,6 +104,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
HasQuit = { BindTarget = trackedUser.UserQuit },
|
||||
TeamColour = UserScores[user.OnlineID].Team is int team ? getTeamColour(team) : null,
|
||||
};
|
||||
leaderboardScore.TotalScore.BindValueChanged(_ => sorting.Invalidate());
|
||||
leaderboardScore.DisplayOrder.BindValueChanged(_ => sorting.Invalidate(), true);
|
||||
scores.Add(leaderboardScore);
|
||||
}
|
||||
});
|
||||
@@ -124,6 +129,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
// new players are not supported.
|
||||
playingUserIds.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
|
||||
playingUserIds.BindCollectionChanged(playingUsersChanged);
|
||||
|
||||
Scheduler.AddDelayed(sort, 1000, true);
|
||||
}
|
||||
|
||||
private void playingUsersChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -174,6 +181,26 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
}
|
||||
}
|
||||
|
||||
private void sort()
|
||||
{
|
||||
if (sorting.IsValid)
|
||||
return;
|
||||
|
||||
var orderedByScore = scores
|
||||
.OrderByDescending(i => i.TotalScore.Value)
|
||||
.ThenBy(i => i.TotalScoreTiebreaker)
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < orderedByScore.Count; i++)
|
||||
{
|
||||
var score = orderedByScore[i];
|
||||
score.DisplayOrder.Value = i;
|
||||
score.Position.Value = i + 1;
|
||||
}
|
||||
|
||||
sorting.Validate();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Scoring;
|
||||
@@ -12,8 +14,6 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
{
|
||||
public partial class SoloGameplayLeaderboardProvider : Component, IGameplayLeaderboardProvider
|
||||
{
|
||||
public bool IsPartial { get; private set; }
|
||||
|
||||
public IBindableList<GameplayLeaderboardScore> Scores => scores;
|
||||
private readonly BindableList<GameplayLeaderboardScore> scores = new BindableList<GameplayLeaderboardScore>();
|
||||
|
||||
@@ -23,13 +23,16 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
[Resolved]
|
||||
private GameplayState? gameplayState { get; set; }
|
||||
|
||||
private readonly Cached sorting = new Cached();
|
||||
private bool isPartial;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var globalScores = leaderboardManager?.Scores.Value;
|
||||
|
||||
IsPartial = leaderboardManager?.CurrentCriteria?.Scope != BeatmapLeaderboardScope.Local && globalScores?.TopScores.Count >= 50;
|
||||
isPartial = leaderboardManager?.CurrentCriteria?.Scope != BeatmapLeaderboardScope.Local && globalScores?.TopScores.Count >= 50;
|
||||
|
||||
if (globalScores != null)
|
||||
{
|
||||
@@ -39,12 +42,73 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
if (gameplayState != null)
|
||||
{
|
||||
scores.Add(new GameplayLeaderboardScore(gameplayState.Score.ScoreInfo.User, gameplayState.ScoreProcessor, true)
|
||||
var localScore = new GameplayLeaderboardScore(gameplayState.Score.ScoreInfo.User, gameplayState.ScoreProcessor, true)
|
||||
{
|
||||
// Local score should always show lower than any existing scores in cases of ties.
|
||||
DisplayOrder = { Value = long.MaxValue }
|
||||
});
|
||||
TotalScoreTiebreaker = long.MaxValue
|
||||
};
|
||||
localScore.TotalScore.BindValueChanged(_ => sorting.Invalidate());
|
||||
scores.Add(localScore);
|
||||
}
|
||||
|
||||
Scheduler.AddDelayed(sort, 1000, true);
|
||||
}
|
||||
|
||||
private void sort()
|
||||
{
|
||||
if (sorting.IsValid)
|
||||
return;
|
||||
|
||||
var orderedByScore = scores
|
||||
.OrderByDescending(i => i.TotalScore.Value)
|
||||
.ThenBy(i => i.TotalScoreTiebreaker)
|
||||
.ToList();
|
||||
|
||||
int delta = 0;
|
||||
|
||||
for (int i = 0; i < orderedByScore.Count; i++)
|
||||
{
|
||||
var score = orderedByScore[i];
|
||||
|
||||
// see `SoloResultsScreen.FetchScores()` for another place that does the same thing with slight deviations
|
||||
// if this code is changed, that code should probably be changed as well
|
||||
|
||||
score.DisplayOrder.Value = i + 1;
|
||||
|
||||
// if we know we have all scores there can ever be, we can do the simple and obvious thing.
|
||||
if (!isPartial)
|
||||
score.Position.Value = i + 1;
|
||||
else
|
||||
{
|
||||
// we have a partial leaderboard, with potential gaps.
|
||||
// we have initial score positions which were valid at the point of starting play.
|
||||
// the assumption here is that non-tracked scores here cannot move around, only tracked ones can.
|
||||
if (score.Tracked)
|
||||
{
|
||||
int? previousScorePosition = i > 0 ? orderedByScore[i - 1].InitialPosition : 0;
|
||||
int? nextScorePosition = i < orderedByScore.Count - 1 ? orderedByScore[i + 1].InitialPosition : null;
|
||||
|
||||
// if the tracked score is perfectly between two scores which have known neighbouring initial positions,
|
||||
// we can assign it the position of the previous score plus one...
|
||||
if (previousScorePosition != null && nextScorePosition != null && previousScorePosition + 1 == nextScorePosition)
|
||||
{
|
||||
score.Position.Value = previousScorePosition + 1;
|
||||
// but we also need to ensure all subsequent scores get shifted down one position, too.
|
||||
delta++;
|
||||
}
|
||||
// conversely, if the tracked score is not between neighbouring two scores and the leaderboard is partial,
|
||||
// we can't really assign a valid position at all. it could be any number between the two neighbours.
|
||||
else
|
||||
score.Position.Value = null;
|
||||
}
|
||||
// for non-tracked scores, we just need to apply any delta that might have come from the tracked scores
|
||||
// which might have been encountered and assigned a position earlier.
|
||||
else
|
||||
score.Position.Value = score.InitialPosition + delta;
|
||||
}
|
||||
}
|
||||
|
||||
sorting.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -23,7 +24,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
private MetadataDisplay source = null!;
|
||||
private MetadataDisplay genre = null!;
|
||||
private MetadataDisplay language = null!;
|
||||
private MetadataDisplay tag = null!;
|
||||
private MetadataDisplay userTags = null!;
|
||||
private MetadataDisplay mapperTags = null!;
|
||||
private MetadataDisplay submitted = null!;
|
||||
private MetadataDisplay ranked = null!;
|
||||
|
||||
@@ -35,6 +37,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
private Drawable failRetryWedge = null!;
|
||||
private FailRetryDisplay failRetryDisplay = null!;
|
||||
|
||||
public bool RatingsVisible => ratingsWedge.Alpha > 0;
|
||||
public bool FailRetryVisible => failRetryWedge.Alpha > 0;
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
[Resolved]
|
||||
@@ -92,6 +97,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
AutoSizeDuration = (float)transition_duration / 3,
|
||||
AutoSizeEasing = Easing.OutQuint,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
@@ -148,7 +155,11 @@ namespace osu.Game.Screens.SelectV2
|
||||
},
|
||||
},
|
||||
},
|
||||
tag = new MetadataDisplay("Tags"),
|
||||
userTags = new MetadataDisplay("User Tags")
|
||||
{
|
||||
Alpha = 0,
|
||||
},
|
||||
mapperTags = new MetadataDisplay("Mapper Tags"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -250,7 +261,10 @@ namespace osu.Game.Screens.SelectV2
|
||||
// We could consider hiding individual wedges based on zero data in the future.
|
||||
// Needs some experimentation on what looks good.
|
||||
|
||||
if (State.Value == Visibility.Visible && currentOnlineBeatmapSet != null)
|
||||
var beatmapInfo = beatmap.Value.BeatmapInfo;
|
||||
var currentOnlineBeatmap = currentOnlineBeatmapSet?.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmapInfo.OnlineID);
|
||||
|
||||
if (State.Value == Visibility.Visible && currentOnlineBeatmap != null)
|
||||
{
|
||||
ratingsWedge.FadeIn(transition_duration, Easing.OutQuint)
|
||||
.MoveToX(0, transition_duration, Easing.OutQuint);
|
||||
@@ -261,12 +275,12 @@ namespace osu.Game.Screens.SelectV2
|
||||
}
|
||||
else
|
||||
{
|
||||
ratingsWedge.FadeOut(transition_duration, Easing.OutQuint)
|
||||
.MoveToX(-50, transition_duration, Easing.OutQuint);
|
||||
|
||||
failRetryWedge.Delay(100)
|
||||
.FadeOut(transition_duration, Easing.OutQuint)
|
||||
failRetryWedge.FadeOut(transition_duration, Easing.OutQuint)
|
||||
.MoveToX(-50, transition_duration, Easing.OutQuint);
|
||||
|
||||
ratingsWedge.Delay(100)
|
||||
.FadeOut(transition_duration, Easing.OutQuint)
|
||||
.MoveToX(-50, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +296,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
else
|
||||
source.Data = ("-", null);
|
||||
|
||||
tag.Tags = (metadata.Tags.Split(' '), t => songSelect?.Search(t));
|
||||
mapperTags.Tags = (metadata.Tags.Split(' '), t => songSelect?.Search(t));
|
||||
submitted.Date = beatmapSetInfo.DateSubmitted;
|
||||
ranked.Date = beatmapSetInfo.DateRanked;
|
||||
|
||||
@@ -351,7 +365,34 @@ namespace osu.Game.Screens.SelectV2
|
||||
}
|
||||
}
|
||||
|
||||
updateUserTags();
|
||||
updateSubWedgeVisibility();
|
||||
}
|
||||
|
||||
private void updateUserTags()
|
||||
{
|
||||
var beatmapInfo = beatmap.Value.BeatmapInfo;
|
||||
var onlineBeatmapSet = currentOnlineBeatmapSet;
|
||||
var onlineBeatmap = onlineBeatmapSet?.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmapInfo.OnlineID);
|
||||
|
||||
if (onlineBeatmap?.TopTags == null || onlineBeatmap.TopTags.Length == 0 || onlineBeatmapSet?.RelatedTags == null)
|
||||
{
|
||||
userTags.FadeOut(transition_duration, Easing.OutQuint);
|
||||
return;
|
||||
}
|
||||
|
||||
var tagsById = onlineBeatmapSet.RelatedTags.ToDictionary(t => t.Id);
|
||||
string[] userTagsArray = onlineBeatmap.TopTags
|
||||
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
|
||||
.Where(t => t.relatedTag != null)
|
||||
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
|
||||
.OrderByDescending(t => t.topTag.VoteCount)
|
||||
.ThenBy(t => t.relatedTag!.Name)
|
||||
.Select(t => t.relatedTag!.Name)
|
||||
.ToArray();
|
||||
|
||||
userTags.FadeIn(transition_duration, Easing.OutQuint);
|
||||
userTags.Tags = (userTagsArray, t => songSelect?.Search(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Layout;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
@@ -163,7 +164,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
Text = "...",
|
||||
Colour = colourProvider.Background4,
|
||||
Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold),
|
||||
}
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Button),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -308,18 +308,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
var onlineBeatmapSet = currentOnlineBeatmapSet;
|
||||
var onlineBeatmap = currentOnlineBeatmapSet.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmap.Value.BeatmapInfo.OnlineID);
|
||||
|
||||
if (onlineBeatmap != null)
|
||||
{
|
||||
playCount.FadeIn(300, Easing.OutQuint);
|
||||
playCount.Value = new StatisticPlayCount.Data(onlineBeatmap.PlayCount, onlineBeatmap.UserPlayCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
playCount.FadeOut(300, Easing.OutQuint);
|
||||
playCount.Value = null;
|
||||
}
|
||||
|
||||
favouritesStatistic.FadeIn(300, Easing.OutQuint);
|
||||
playCount.Value = new StatisticPlayCount.Data(onlineBeatmap?.PlayCount ?? -1, onlineBeatmap?.UserPlayCount ?? -1);
|
||||
favouritesStatistic.Text = onlineBeatmapSet.FavouriteCount.ToLocalisableString(@"N0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
Depth = float.MaxValue,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
CornerRadius = Y_OFFSET,
|
||||
Size = new Vector2(BUTTON_WIDTH, bar_height),
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
},
|
||||
new Container
|
||||
{
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
CornerRadius = Y_OFFSET,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = mod_display_portion,
|
||||
Masking = true,
|
||||
@@ -264,7 +264,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
CornerRadius = Y_OFFSET;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
@@ -306,7 +306,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
Depth = float.MaxValue;
|
||||
Origin = Anchor.BottomLeft;
|
||||
Shear = OsuGame.SHEAR;
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
CornerRadius = Y_OFFSET;
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = bar_height;
|
||||
Masking = true;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Skinning.Components
|
||||
protected override double RollingDuration => 250;
|
||||
|
||||
[SettingSource("Font", "Font", SettingControlType = typeof(OffsetNumberNameSelector))]
|
||||
public Bindable<string> FontNameDropdown { get; } = new Bindable<string>("NIGHTwhite");
|
||||
public Bindable<OffsetNumberName> FontNameDropdown { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.NIGHTWhite);
|
||||
|
||||
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
|
||||
public Bindable<bool> ShowLabel { get; } = new BindableBool();
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Skinning.Components
|
||||
public partial class EzCounterText : CompositeDrawable, IHasText
|
||||
{
|
||||
public readonly EzGetTexture TextPart;
|
||||
public Bindable<string> FontName { get; } = new Bindable<string>("EZ2DJ-4th");
|
||||
public Bindable<OffsetNumberName> FontName { get; } = new Bindable<OffsetNumberName>(OffsetNumberName.EZ2DJ_4th);
|
||||
|
||||
public FillFlowContainer TextContainer { get; private set; }
|
||||
|
||||
@@ -27,14 +27,20 @@ namespace osu.Game.Skinning.Components
|
||||
|
||||
// public object Spacing { get; set; }
|
||||
|
||||
public EzCounterText(Bindable<string>? externalFontName = null)
|
||||
public EzCounterText(Bindable<OffsetNumberName>? externalFontName = null)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
if (externalFontName is not null)
|
||||
FontName.BindTo(externalFontName);
|
||||
TextPart = new EzGetTexture(textLookup, FontName);
|
||||
|
||||
var fontNameString = new Bindable<string>();
|
||||
FontName.BindValueChanged(e => fontNameString.Value = e.NewValue.ToString(), true);
|
||||
|
||||
TextPart = new EzGetTexture(textLookup, fontNameString);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
TextContainer = new FillFlowContainer
|
||||
@@ -76,12 +82,11 @@ namespace osu.Game.Skinning.Components
|
||||
base.LoadComplete();
|
||||
|
||||
float scale = calculateScale(TextPart.Height);
|
||||
if (scale > 0)
|
||||
TextPart.Scale = new Vector2(scale);
|
||||
TextPart.Scale = new Vector2(scale);
|
||||
|
||||
FontName.BindValueChanged(e =>
|
||||
{
|
||||
TextPart.FontName.Value = e.NewValue;
|
||||
TextPart.FontName.Value = e.NewValue.ToString();
|
||||
// textPart.LoadAsync(); // **强制重新加载字体**
|
||||
TextPart.Invalidate(); // **确保 UI 立即刷新**
|
||||
}, true);
|
||||
@@ -89,6 +94,9 @@ namespace osu.Game.Skinning.Components
|
||||
|
||||
private float calculateScale(float textureHeight, float targetHeight = 25f)
|
||||
{
|
||||
if (textureHeight <= 0)
|
||||
return 1;
|
||||
|
||||
return targetHeight / textureHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,23 +72,28 @@ namespace osu.Game.Skinning.Components
|
||||
string lookup = getLookup(character);
|
||||
TexturedCharacterGlyph? glyph = null;
|
||||
|
||||
string[] possiblePaths = new[]
|
||||
if (textureName != null)
|
||||
{
|
||||
$"Gameplay/Combo/{textureName}/{textureName}-counter-{lookup}",
|
||||
$"Gameplay/Combo/{textureName}/{textureName}-Title-{lookup}",
|
||||
$"Gameplay/EarlyOrLate/{textureName}-{lookup}",
|
||||
$"Gameplay/HitResult/{textureName}-{lookup}"
|
||||
};
|
||||
string textureNameReplace = textureName.Replace(" ", "_");
|
||||
|
||||
foreach (string path in possiblePaths)
|
||||
{
|
||||
var texture = textures.Get(path);
|
||||
|
||||
if (texture != null)
|
||||
string[] possiblePaths = new[]
|
||||
{
|
||||
glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null),
|
||||
texture, 0.125f);
|
||||
break;
|
||||
$"Gameplay/Combo/{textureNameReplace}/{textureNameReplace}-counter-{lookup}",
|
||||
$"Gameplay/Combo/{textureNameReplace}/{textureNameReplace}-Title-{lookup}",
|
||||
$"Gameplay/EarlyOrLate/{textureNameReplace}-{lookup}",
|
||||
$"Gameplay/HitResult/{textureNameReplace}-{lookup}"
|
||||
};
|
||||
|
||||
foreach (string path in possiblePaths)
|
||||
{
|
||||
var texture = textures.Get(path);
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null),
|
||||
texture, 0.125f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using OpenTabletDriver.Plugin;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -61,62 +61,66 @@ namespace osu.Game.Skinning.Components
|
||||
}
|
||||
}
|
||||
|
||||
public partial class OffsetNumberNameSelector : SettingsDropdown<string>
|
||||
public enum OffsetNumberName
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
Air,
|
||||
Aqua,
|
||||
Cricket,
|
||||
D2D,
|
||||
DarkConcert,
|
||||
DJMAX,
|
||||
EMOTIONAL,
|
||||
ENDLESS,
|
||||
EZ2AC_AE,
|
||||
EZ2AC_CV,
|
||||
EZ2AC_EVOLVE,
|
||||
EZ2AC_TT,
|
||||
EZ2DJ_1thSE,
|
||||
EZ2DJ_2nd,
|
||||
EZ2DJ_4th,
|
||||
EZ2DJ_6th,
|
||||
EZ2DJ_7th,
|
||||
EZ2DJ_Platinum,
|
||||
EZ2ON,
|
||||
F3_ANCIENT,
|
||||
F3_CONTEMP,
|
||||
F3_FUTURE,
|
||||
F3_MODERN,
|
||||
FiND_A_WAY,
|
||||
FORTRESS2,
|
||||
GC_TYPE2,
|
||||
Gem,
|
||||
Gem2,
|
||||
Gold,
|
||||
HX_STD,
|
||||
HX_STD_Yellow,
|
||||
HX_1121,
|
||||
Kings,
|
||||
M250,
|
||||
NIGHTFALL,
|
||||
NIGHTWhite,
|
||||
QTZ_02,
|
||||
REBOOT,
|
||||
REBOOT_GOLD,
|
||||
SG_701,
|
||||
SH_512,
|
||||
Star,
|
||||
TCEZ_001,
|
||||
TECHNIKA,
|
||||
Tomato,
|
||||
VariousWays
|
||||
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
public partial class OffsetNumberNameSelector : SettingsDropdown<OffsetNumberName>
|
||||
{
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Items = new List<string>
|
||||
{
|
||||
"Air",
|
||||
"Aqua",
|
||||
"Cricket",
|
||||
"D2D",
|
||||
"DarkConcert",
|
||||
"DJMAX",
|
||||
"EMOTIONAL",
|
||||
"ENDLESS",
|
||||
"EZ2AC-AE",
|
||||
"EZ2AC-CV",
|
||||
"EZ2AC-EVOLVE",
|
||||
"EZ2AC-TT",
|
||||
"EZ2DJ-1thSE",
|
||||
"EZ2DJ-2nd",
|
||||
"EZ2DJ-4th",
|
||||
"EZ2DJ-6th",
|
||||
"EZ2DJ-7th",
|
||||
"EZ2DJ-Platinum",
|
||||
"EZ2ON",
|
||||
"F3-ANCIENT",
|
||||
"F3-CONTEMP",
|
||||
"F3-FUTURE",
|
||||
"F3-MODERN",
|
||||
"FiND A WAY",
|
||||
"FORTRESS2",
|
||||
"GC-TYPE2",
|
||||
"Gem",
|
||||
"Gem2",
|
||||
"Gold",
|
||||
"HX STD",
|
||||
"HX STD黄色",
|
||||
"HX-1121",
|
||||
"Kings",
|
||||
"M250",
|
||||
"NIGHTFALL",
|
||||
"NIGHTwhite",
|
||||
"QTZ-02",
|
||||
"REBOOT",
|
||||
"REBOOT GOLD",
|
||||
"SG-701",
|
||||
"SH-512",
|
||||
"Star",
|
||||
"TCEZ-001",
|
||||
"TECHNIKA",
|
||||
"Tomato",
|
||||
"VariousWays",
|
||||
};
|
||||
Log.Debug("Items", Items.ToString());
|
||||
Items = Enum.GetValues(typeof(OffsetNumberName)).Cast<OffsetNumberName>().ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,6 @@ namespace osu.Game.Skinning
|
||||
switch (lookup)
|
||||
{
|
||||
case GlobalSkinnableContainerLookup containerLookup:
|
||||
// Only handle global level defaults for now.
|
||||
if (containerLookup.Ruleset != null)
|
||||
return null;
|
||||
|
||||
switch (containerLookup.Lookup)
|
||||
{
|
||||
case GlobalSkinnableContainers.SongSelect:
|
||||
@@ -83,6 +79,11 @@ namespace osu.Game.Skinning
|
||||
return songSelectComponents;
|
||||
|
||||
case GlobalSkinnableContainers.MainHUDComponents:
|
||||
if (containerLookup.Ruleset != null)
|
||||
{
|
||||
return new DefaultSkinComponentsContainer(_ => { });
|
||||
}
|
||||
|
||||
var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
|
||||
|
||||
@@ -59,11 +59,8 @@ namespace osu.Game.Utils
|
||||
/// <summary>
|
||||
/// Applies rounding to the given BPM value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Double-rounding is applied intentionally (see https://github.com/ppy/osu/pull/18345#issue-1243311382 for rationale).
|
||||
/// </remarks>
|
||||
/// <param name="baseBpm">The base BPM to round.</param>
|
||||
/// <param name="rate">Rate adjustment, if applicable.</param>
|
||||
public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(Math.Round(baseBpm) * rate);
|
||||
public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(baseBpm * rate);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user